2018-09-20 02:19:10 +02:00
|
|
|
|
/*
|
|
|
|
|
|
EventLog - test facility to verify the occurrence of expected events
|
|
|
|
|
|
|
|
|
|
|
|
Copyright (C) Lumiera.org
|
|
|
|
|
|
2018, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
|
|
modify it under the terms of the GNU General Public License as
|
|
|
|
|
|
published by the Free Software Foundation; either version 2 of
|
|
|
|
|
|
the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
|
|
|
|
|
|
|
|
* *****************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
/** @file event-log.cpp
|
|
|
|
|
|
** Implementation details of event logging and verification for unit tests.
|
|
|
|
|
|
** The EventLog is a helper for writing tests or for special rigging within
|
|
|
|
|
|
** the test or debugging or mock variant of some common services. It allows
|
|
|
|
|
|
** to log some events to memory and then to build match expressions to verify
|
|
|
|
|
|
** those captured information.
|
|
|
|
|
|
**
|
|
|
|
|
|
** The machinery for matching on the occurrence of some events can be quite
|
|
|
|
|
|
** intricate at implementation level; we use an implementation based on a
|
|
|
|
|
|
** filtering iterator pipeline. This kind of code is template heavy and
|
|
|
|
|
|
** can cause a significant increase of generated code size -- especially
|
|
|
|
|
|
** on debug builds. For that reason, we prefer to call through regular
|
|
|
|
|
|
** (non-inline) functions and package all the actual implementation
|
2018-09-20 04:11:00 +02:00
|
|
|
|
** code into this dedicated translation unit.
|
2018-09-20 02:19:10 +02:00
|
|
|
|
**
|
2023-08-14 19:22:18 +02:00
|
|
|
|
** @see EventLog_test
|
2018-09-20 02:19:10 +02:00
|
|
|
|
** @see IterChainSearch_test
|
2023-08-13 20:49:30 +02:00
|
|
|
|
** @see iter-explorer.hpp
|
2018-09-20 02:19:10 +02:00
|
|
|
|
**
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "lib/error.hpp"
|
|
|
|
|
|
#include "lib/test/event-log.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace lib {
|
|
|
|
|
|
namespace test{
|
|
|
|
|
|
namespace error = lumiera::error;
|
|
|
|
|
|
|
|
|
|
|
|
using util::stringify;
|
|
|
|
|
|
using util::contains;
|
|
|
|
|
|
using util::isnil;
|
|
|
|
|
|
using lib::Symbol;
|
|
|
|
|
|
using std::string;
|
|
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
|
|
|
|
|
|
namespace { /* == elementary matchers == */
|
|
|
|
|
|
|
|
|
|
|
|
using Filter = decltype( buildSearchFilter (std::declval<Log const&>()) );
|
|
|
|
|
|
|
|
|
|
|
|
using ArgSeq = lib::diff::RecordSetup<string>::Storage;
|
|
|
|
|
|
using RExSeq = std::vector<std::regex>;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
find (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
return contains (string(entry), match);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
findRegExp (string regExpDef)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::regex regExp(regExpDef);
|
|
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
return std::regex_search(string(entry), regExp);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
findEvent (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ( entry.getType() == "event"
|
|
|
|
|
|
or entry.getType() == "error"
|
|
|
|
|
|
or entry.getType() == "create"
|
|
|
|
|
|
or entry.getType() == "destroy"
|
|
|
|
|
|
or entry.getType() == "logJoin"
|
|
|
|
|
|
)
|
2023-08-13 20:49:30 +02:00
|
|
|
|
and not isnil(entry.scope())
|
2018-09-20 04:11:00 +02:00
|
|
|
|
and contains (*entry.scope(), match);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
findEvent (string classifier, string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ( entry.getType() == classifier
|
|
|
|
|
|
or (entry.hasAttribute("ID") and contains (entry.get("ID"), classifier))
|
|
|
|
|
|
)
|
2023-08-13 20:49:30 +02:00
|
|
|
|
and not isnil(entry.scope())
|
2018-09-20 04:11:00 +02:00
|
|
|
|
and contains (*entry.scope(), match);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
findCall (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
return entry.getType() == "call"
|
|
|
|
|
|
and contains (entry.get("fun"), match);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** this filter functor is for refinement of an existing filter
|
|
|
|
|
|
* @param argSeq perform a substring match consecutively
|
|
|
|
|
|
* for each of the log entry's arguments
|
|
|
|
|
|
* @note the match also fails, when the given log entry
|
|
|
|
|
|
* has more or less arguments, than the number of
|
|
|
|
|
|
* given match expressions in `argSeq`
|
|
|
|
|
|
* @see ExtensibleFilterIter::andFilter()
|
|
|
|
|
|
*/
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
matchArguments (ArgSeq&& argSeq)
|
|
|
|
|
|
{
|
|
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto scope = entry.scope();
|
|
|
|
|
|
for (auto const& match : argSeq)
|
|
|
|
|
|
if (isnil (scope) or not contains(*scope, match))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
else
|
|
|
|
|
|
++scope;
|
|
|
|
|
|
|
|
|
|
|
|
return isnil(scope); // must be exhausted by now
|
|
|
|
|
|
}; // otherwise the sizes do not match...
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** refinement filter, to cover all arguments by regular expression(s)
|
|
|
|
|
|
* @param regExpSeq several regular expressions, which, when applied
|
|
|
|
|
|
* consecutively until exhaustion, must cover and verify _all_
|
|
|
|
|
|
* arguments of the log entry.
|
|
|
|
|
|
* @remarks to explain, we "consume" arguments with a regExp from the list,
|
|
|
|
|
|
* and when this one doesn't match anymore, we try the next one.
|
|
|
|
|
|
* When we'ver tried all regular expressions, we must have also
|
|
|
|
|
|
* consumed all arguments, otherwise we fail.
|
|
|
|
|
|
*/
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
matchArgsRegExp (RExSeq&& regExpSeq)
|
|
|
|
|
|
{
|
|
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto scope = entry.scope();
|
|
|
|
|
|
for (auto const& rex : regExpSeq)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isnil (scope)) return false;
|
|
|
|
|
|
while (scope and std::regex_search(*scope, rex))
|
|
|
|
|
|
++scope;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return isnil(scope); // must be exhausted by now
|
|
|
|
|
|
}; // otherwise we didn't get full coverage...
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
/** refinement filter to match on the given typeID */
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
matchType (string typeID)
|
2018-09-20 02:19:10 +02:00
|
|
|
|
{
|
2018-09-20 04:11:00 +02:00
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
return contains (entry.getType(), typeID);
|
|
|
|
|
|
};
|
2018-09-20 02:19:10 +02:00
|
|
|
|
}
|
2018-09-20 04:11:00 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** refinement filter to ensure a specific attribute is present on the log entry */
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
ensureAttribute (string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
return entry.hasAttribute(key);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** refinement filter to ensure a specific attribute is present on the log entry */
|
|
|
|
|
|
inline auto
|
|
|
|
|
|
matchAttribute (string key, string valueMatch)
|
|
|
|
|
|
{
|
|
|
|
|
|
return [=](Entry const& entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
return entry.hasAttribute(key)
|
|
|
|
|
|
and contains (entry.get(key), valueMatch);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* === configure the underlying search engine === */
|
|
|
|
|
|
|
|
|
|
|
|
enum Direction {
|
|
|
|
|
|
FORWARD, BACKWARD, CURRENT
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template<typename COND>
|
|
|
|
|
|
inline void
|
2023-08-13 20:49:30 +02:00
|
|
|
|
attachNextSerchStep (Filter& solution, COND&& searchCond, Direction direction)
|
2018-09-20 04:11:00 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (CURRENT == direction)
|
2023-08-13 20:49:30 +02:00
|
|
|
|
solution.search (forward<COND> (searchCond));
|
2018-09-20 04:11:00 +02:00
|
|
|
|
else
|
2023-08-13 20:49:30 +02:00
|
|
|
|
solution.addStep ([predicate{forward<COND> (searchCond)}, direction]
|
2018-09-20 04:11:00 +02:00
|
|
|
|
(auto& filter)
|
|
|
|
|
|
{
|
|
|
|
|
|
filter.reverse (BACKWARD == direction);
|
|
|
|
|
|
filter.disableFilter(); // deactivate any filtering temporarily
|
|
|
|
|
|
++filter; // move one step in the required direction
|
|
|
|
|
|
filter.setNewFilter (predicate);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename COND>
|
|
|
|
|
|
inline void
|
2023-08-13 20:49:30 +02:00
|
|
|
|
refineSerach (Filter& solution, COND&& additionalCond)
|
2018-09-20 04:11:00 +02:00
|
|
|
|
{
|
2023-08-13 20:49:30 +02:00
|
|
|
|
solution.addStep ([predicate{forward<COND> (additionalCond)}]
|
2018-09-20 04:11:00 +02:00
|
|
|
|
(auto& filter)
|
|
|
|
|
|
{
|
|
|
|
|
|
filter.andFilter (predicate);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}//(End) Elementary Matchers and handling of the search engine
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch::EventMatch (Log const& srcSeq)
|
|
|
|
|
|
: solution_{buildSearchFilter (srcSeq)}
|
|
|
|
|
|
, lastMatch_{"HEAD "+ solution_->get("this")}
|
|
|
|
|
|
, look_for_match_{true}
|
|
|
|
|
|
, violation_{}
|
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** core of the evaluation machinery:
|
|
|
|
|
|
* apply a filter predicate and then pull
|
|
|
|
|
|
* through the log to find a acceptable entry
|
|
|
|
|
|
*/
|
|
|
|
|
|
bool
|
|
|
|
|
|
EventMatch::foundSolution()
|
|
|
|
|
|
{
|
|
|
|
|
|
return not isnil (solution_);
|
2018-09-20 02:19:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
/** this is actually called after each refinement of
|
|
|
|
|
|
* the filter and matching conditions. The effect is to search
|
|
|
|
|
|
* for an (intermediary) solution right away and to mark failure
|
|
|
|
|
|
* as soon as some condition can not be satisfied. Rationale is to
|
|
|
|
|
|
* indicate the point where a chained match fails
|
|
|
|
|
|
* @see ::operator bool() for extracting the result
|
|
|
|
|
|
* @param matchSpec diagnostics description of the predicate just being added
|
|
|
|
|
|
* @param rel indication of the searching relation / direction
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
EventMatch::evaluateQuery (string matchSpec, Literal rel)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (look_for_match_ and not isnil (violation_)) return;
|
|
|
|
|
|
// already failed, no further check necessary
|
|
|
|
|
|
|
|
|
|
|
|
if (foundSolution()) // NOTE this pulls the filter
|
|
|
|
|
|
{
|
|
|
|
|
|
lastMatch_ = matchSpec+" @ "+string(*solution_)
|
|
|
|
|
|
+ (isnil(lastMatch_)? ""
|
|
|
|
|
|
: "\n.."+rel+" "+lastMatch_);
|
|
|
|
|
|
if (not look_for_match_)
|
|
|
|
|
|
violation_ = "FOUND at least "+lastMatch_;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (look_for_match_)
|
|
|
|
|
|
violation_ = "FAILED to "+matchSpec
|
|
|
|
|
|
+ "\n.."+rel
|
|
|
|
|
|
+ " "+lastMatch_;
|
|
|
|
|
|
else
|
|
|
|
|
|
violation_ = "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-09-20 02:19:10 +02:00
|
|
|
|
/**
|
2018-09-20 04:11:00 +02:00
|
|
|
|
* basic search function: continue linear lookup over the elements of the
|
|
|
|
|
|
* EventLog to find a match (substring match) of the given text. The search begins
|
|
|
|
|
|
* at the current position and proceeds in the currently configured direction.
|
|
|
|
|
|
* Initially the search starts at the first record and proceeds forward.
|
2018-09-20 02:19:10 +02:00
|
|
|
|
*/
|
2018-09-20 04:11:00 +02:00
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::locate (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, find(match), CURRENT);
|
|
|
|
|
|
evaluateQuery ("match(\""+match+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* basic search like locate() but with the given regular expression
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::locateMatch (string regExp)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findRegExp(regExp), CURRENT);
|
|
|
|
|
|
evaluateQuery ("find-RegExp(\""+regExp+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* basic search for a matching "event"
|
|
|
|
|
|
* @param match perform a substring match against the arguments of the event
|
|
|
|
|
|
* @see beforeEvent() for a description of possible "events"
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::locateEvent (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findEvent(match), CURRENT);
|
|
|
|
|
|
evaluateQuery ("match-event(\""+match+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::locateEvent (string classifier, string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findEvent(classifier,match), CURRENT);
|
|
|
|
|
|
evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* basic search for some specific function invocation
|
|
|
|
|
|
* @param match perform a substring match against the name of the function invoked
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::locateCall (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findCall(match), CURRENT);
|
|
|
|
|
|
evaluateQuery ("match-call(\""+match+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* find a match (substring match) of the given text
|
|
|
|
|
|
* in an EventLog entry after the current position
|
|
|
|
|
|
* @remarks the name of this junctor might seem counter intuitive;
|
|
|
|
|
|
* it was chosen due to expected DSL usage: `log.verify("α").before("β")`.
|
|
|
|
|
|
* Operationally this means first to find a Record matching the substring "α"
|
|
|
|
|
|
* and then to forward from this point until hitting a record to match "β".
|
2018-09-20 02:19:10 +02:00
|
|
|
|
*/
|
2018-09-20 04:11:00 +02:00
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::before (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, find(match), FORWARD);
|
|
|
|
|
|
evaluateQuery ("match(\""+match+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* find a match with the given regular expression
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::beforeMatch (string regExp)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findRegExp(regExp), FORWARD);
|
|
|
|
|
|
evaluateQuery ("find-RegExp(\""+regExp+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* find a match for an "event" _after_ the current point of reference
|
|
|
|
|
|
* @remarks the term "event" designates several types of entries, which
|
|
|
|
|
|
* typically capture something happening within the observed entity.
|
|
|
|
|
|
* Especially, the following [record types](\ref lib::Record::getType())
|
|
|
|
|
|
* qualify as event:
|
|
|
|
|
|
* - `event`
|
|
|
|
|
|
* - `error`
|
|
|
|
|
|
* - `create`
|
|
|
|
|
|
* - `destroy`
|
|
|
|
|
|
* - `logJoin`
|
|
|
|
|
|
* @param match perform a substring match against the arguments of the event
|
|
|
|
|
|
* @see ::findEvent
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::beforeEvent (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findEvent(match), FORWARD);
|
|
|
|
|
|
evaluateQuery ("match-event(\""+match+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::beforeEvent (string classifier, string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findEvent(classifier,match), FORWARD);
|
|
|
|
|
|
evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* find a match for some function invocation _after_ the current point of reference
|
|
|
|
|
|
* @param match perform a substring match against the name of the function invoked
|
|
|
|
|
|
* @see ::findCall
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::beforeCall (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findCall(match), FORWARD);
|
|
|
|
|
|
evaluateQuery ("match-call(\""+match+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* find a match (substring match) of the given text
|
|
|
|
|
|
* in an EventLog entry _before_ the current position,
|
|
|
|
|
|
* switching to backwards search direction
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::after (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, find(match), BACKWARD);
|
|
|
|
|
|
evaluateQuery ("match(\""+match+"\")", "before");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::afterMatch (string regExp)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findRegExp(regExp), BACKWARD);
|
|
|
|
|
|
evaluateQuery ("find-RegExp(\""+regExp+"\")", "before");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::afterEvent (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findEvent(match), BACKWARD);
|
|
|
|
|
|
evaluateQuery ("match-event(\""+match+"\")", "before");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::afterEvent (string classifier, string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findEvent(classifier,match), BACKWARD);
|
|
|
|
|
|
evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")", "before");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* find a function invocation backwards, before the current point of reference
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::afterCall (string match)
|
|
|
|
|
|
{
|
|
|
|
|
|
attachNextSerchStep (solution_, findCall(match), BACKWARD);
|
|
|
|
|
|
evaluateQuery ("match-call(\""+match+"\")", "before");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @internal refine filter condition additionally to match the call arguments.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
EventMatch::refineSerach_matchArguments (ArgSeq&& argSeq)
|
|
|
|
|
|
{
|
|
|
|
|
|
string argList(util::join(argSeq));
|
|
|
|
|
|
refineSerach (solution_, matchArguments(move(argSeq)));
|
|
|
|
|
|
evaluateQuery ("match-arguments("+argList+")");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @internal refine filter condition additionally to cover call arguments by
|
|
|
|
|
|
* a sequence of regular expressions. Each regExp is used consecutively,
|
|
|
|
|
|
* until it fails to match; at that point, the next regExp is tried.
|
|
|
|
|
|
* When reaching the end of the argument sequence, we also expect
|
|
|
|
|
|
* to have used up all available regular expressions.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
EventMatch::refineSerach_matchArgsRegExp (RExSeq&& regExpSeq, string rendered_regExps)
|
|
|
|
|
|
{
|
|
|
|
|
|
refineSerach (solution_, matchArgsRegExp (move (regExpSeq)));
|
|
|
|
|
|
evaluateQuery ("match-args-RegExp("+rendered_regExps+")");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* refine filter to additionally require a matching log entry type
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::type (string typeID)
|
|
|
|
|
|
{
|
|
|
|
|
|
refineSerach (solution_, matchType(typeID));
|
|
|
|
|
|
evaluateQuery ("match-type("+typeID+")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* refine filter to additionally require the presence an attribute
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::key (string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
refineSerach (solution_, ensureAttribute(key));
|
|
|
|
|
|
evaluateQuery ("ensure-attribute("+key+")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* refine filter to additionally match on a specific attribute
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::attrib (string key, string valueMatch)
|
|
|
|
|
|
{
|
|
|
|
|
|
refineSerach (solution_, matchAttribute(key,valueMatch));
|
|
|
|
|
|
evaluateQuery ("match-attribute("+key+"=\""+valueMatch+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* refine filter to additionally match on the ID attribute
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::id (string classifier)
|
|
|
|
|
|
{
|
|
|
|
|
|
refineSerach (solution_, matchAttribute("ID",classifier));
|
|
|
|
|
|
evaluateQuery ("match-ID(\""+classifier+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* refine filter to additionally match the `'this'` attribute
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::on (string targetID)
|
|
|
|
|
|
{
|
|
|
|
|
|
refineSerach (solution_, matchAttribute("this",targetID));
|
|
|
|
|
|
evaluateQuery ("match-this(\""+targetID+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch&
|
|
|
|
|
|
EventMatch::on (const char* targetID)
|
|
|
|
|
|
{
|
|
|
|
|
|
refineSerach (solution_, matchAttribute("this",targetID));
|
|
|
|
|
|
evaluateQuery ("match-this(\""+string(targetID)+"\")");
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ==== EventLog implementation ==== */
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
EventLog::EventLog (string logID)
|
|
|
|
|
|
: log_(new Log)
|
|
|
|
|
|
{
|
|
|
|
|
|
log({"type=EventLogHeader", "this="+logID});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @remarks EventLog uses a heap based, sharable log storage,
|
|
|
|
|
|
* where the EventLog object is just a front-end (shared ptr).
|
|
|
|
|
|
* The `joinInto` operation both integrates this logs contents
|
|
|
|
|
|
* into the other log, and then disconnects from the old storage
|
|
|
|
|
|
* and connects to the storage of the combined log.
|
|
|
|
|
|
* @warning beware of clone copies. Since copying EventLog is always a
|
|
|
|
|
|
* shallow copy, all copied handles actually refer to the same
|
|
|
|
|
|
* log storage. If you invoke `joinInto` in such a situation,
|
|
|
|
|
|
* only the current EventLog front-end handle will be rewritten
|
|
|
|
|
|
* to point to the combined log, while any other clone will
|
|
|
|
|
|
* continue to point to the original log storage.
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::joinInto (EventLog& otherLog)
|
|
|
|
|
|
{
|
|
|
|
|
|
Log& target = *otherLog.log_;
|
|
|
|
|
|
target.reserve (target.size() + log_->size() + 1);
|
|
|
|
|
|
target.emplace_back (log_->front());
|
|
|
|
|
|
auto p = log_->begin();
|
|
|
|
|
|
while (++p != log_->end()) // move our log's content into the other log
|
|
|
|
|
|
target.emplace_back (std::move(*p));
|
|
|
|
|
|
this->log_->resize(1);
|
|
|
|
|
|
this->log({"type=joined", otherLog.getID()}); // leave a tag to indicate
|
|
|
|
|
|
otherLog.log({"type=logJoin", this->getID()}); // where the `joinInto` took place,
|
|
|
|
|
|
this->log_ = otherLog.log_; // connect this to the other storage
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::clear()
|
|
|
|
|
|
{
|
|
|
|
|
|
string originalLogID = this->getID();
|
|
|
|
|
|
return this->clear (originalLogID);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** purge log contents and also reset Header-ID
|
|
|
|
|
|
* @note actually we're starting a new log
|
|
|
|
|
|
* and let the previous one go away.
|
|
|
|
|
|
* @warning while this also unties any joined logs,
|
|
|
|
|
|
* other log front-ends might still hold onto
|
|
|
|
|
|
* the existing, combined log. Just we are
|
|
|
|
|
|
* detached and writing to a pristine log.
|
|
|
|
|
|
*/
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::clear (string alteredLogID)
|
|
|
|
|
|
{
|
|
|
|
|
|
log_.reset (new Log);
|
|
|
|
|
|
log({"type=EventLogHeader", "this="+alteredLogID});
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::clear (const char* alteredLogID)
|
|
|
|
|
|
{
|
|
|
|
|
|
return clear (string{alteredLogID});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ==== Logging API ==== */
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::event (string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
log ("event", ArgSeq{}, ArgSeq{text}); // we use this ctor variant to ensure
|
|
|
|
|
|
return *this; // that text is not misinterpreted as attribute,
|
|
|
|
|
|
} // which might happen when text contains a '='
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::event (string classifier, string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
log ("event", ArgSeq{"ID="+classifier}, ArgSeq{text});
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::call (string target, string function)
|
|
|
|
|
|
{
|
|
|
|
|
|
return call(target, function, ArgSeq());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::call (string target, string function, ArgSeq&& args)
|
|
|
|
|
|
{
|
|
|
|
|
|
log ("call", ArgSeq{"fun="+function, "this="+target}, std::forward<ArgSeq>(args));
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::call (const char* target, const char* function, ArgSeq&& args)
|
|
|
|
|
|
{
|
|
|
|
|
|
return call (string(target), string(function), std::forward<ArgSeq>(args));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::warn (string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
log({"type=warn", text});
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::error (string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
log({"type=error", text});
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::fatal (string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
log({"type=fatal", text});
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
2018-09-20 04:11:00 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::create (string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
log({"type=create", text});
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EventLog&
|
|
|
|
|
|
EventLog::destroy (string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
log({"type=destroy", text});
|
|
|
|
|
|
return *this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ==== Query/Verification API ==== */
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch
|
|
|
|
|
|
EventLog::verify (string match) const
|
|
|
|
|
|
{
|
|
|
|
|
|
EventMatch matcher(*log_);
|
|
|
|
|
|
matcher.locate (match); // new matcher starts linear search from first log element
|
|
|
|
|
|
return matcher;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch
|
|
|
|
|
|
EventLog::verifyMatch (string regExp) const
|
|
|
|
|
|
{
|
|
|
|
|
|
EventMatch matcher(*log_);
|
|
|
|
|
|
matcher.locateMatch (regExp);
|
|
|
|
|
|
return matcher;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch
|
|
|
|
|
|
EventLog::verifyEvent (string match) const
|
|
|
|
|
|
{
|
|
|
|
|
|
EventMatch matcher(*log_);
|
|
|
|
|
|
matcher.locateEvent (match);
|
|
|
|
|
|
return matcher;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch
|
|
|
|
|
|
EventLog::verifyEvent (string classifier, string match) const
|
|
|
|
|
|
{
|
|
|
|
|
|
EventMatch matcher(*log_);
|
|
|
|
|
|
matcher.locateEvent (classifier, match);
|
|
|
|
|
|
return matcher;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
EventMatch
|
|
|
|
|
|
EventLog::verifyCall (string match) const
|
|
|
|
|
|
{
|
|
|
|
|
|
EventMatch matcher(*log_);
|
|
|
|
|
|
matcher.locateCall (match);
|
|
|
|
|
|
return matcher;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @remarks the query expression is built similar to the other queries,
|
|
|
|
|
|
* but the logic of evaluation is flipped: whenever we find any match
|
|
|
|
|
|
* the overall result (when evaluating to `bool`) will be `false`.
|
2023-08-14 19:22:18 +02:00
|
|
|
|
* @note since the chained search involves backtracking, this should perform
|
|
|
|
|
|
* an exhaustive negation, searching for a possible match until no
|
|
|
|
|
|
* further search options are left.
|
2018-09-20 04:11:00 +02:00
|
|
|
|
*/
|
|
|
|
|
|
EventMatch
|
|
|
|
|
|
EventLog::ensureNot (string match) const
|
|
|
|
|
|
{
|
|
|
|
|
|
EventMatch matcher(*log_);
|
|
|
|
|
|
matcher.look_for_match_ = false; // flip logic; fail if match succeeds
|
|
|
|
|
|
matcher.locate (match);
|
|
|
|
|
|
return matcher;
|
|
|
|
|
|
}
|
2018-09-20 02:19:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}} // namespace lib::test
|