EventLog unit test PASS
so this turned out to be rather expensive, while actually not difficult to implement. On the way, I've learned - how to build a backtracking matcher, based on a filtering (monadic) structure and chained lambdas - learned the hard way how (not) to return a container by move-reference - made first contact with the regular expressions now available from the standard library
This commit is contained in:
parent
d0cdae2cee
commit
c8068496d1
4 changed files with 117 additions and 42 deletions
|
|
@ -30,12 +30,11 @@
|
|||
** used for access offers a query facility, so the test code may express some
|
||||
** expected patterns of incidence and verify match or non-match.
|
||||
**
|
||||
** Failure of match prints a detailed trace message to `STDERR`, in order to
|
||||
** deliver a precise indication what part of the condition failed.
|
||||
** Failure of match prints a detailed trace message to `STDERR`, in order
|
||||
** to deliver a precise indication what part of the condition failed.
|
||||
**
|
||||
** @todo as of 11/2015 this is complete WIP-WIP-WIP
|
||||
**
|
||||
** @see ////TODO_test usage example
|
||||
** @see TestEventLog_test
|
||||
** @see [usage example][AbstractTangible_test]
|
||||
**
|
||||
*/
|
||||
|
||||
|
|
@ -48,13 +47,11 @@
|
|||
#include "lib/idi/entry-id.hpp"
|
||||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/iter-cursor.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
#include "lib/diff/record.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
//#include <boost/lexical_cast.hpp>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
|
@ -67,20 +64,22 @@ namespace lib {
|
|||
namespace test{
|
||||
namespace error = lumiera::error;
|
||||
|
||||
// using lib::Literal;
|
||||
using std::string;
|
||||
using util::stringify;
|
||||
using util::contains;
|
||||
using util::isnil;
|
||||
using util::_Fmt;
|
||||
using lib::Symbol;
|
||||
// using std::rand;
|
||||
using std::string;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @internal ongoing evaluation and match within an [EventLog].
|
||||
* @throws error::Fatal when the expected match fails (error::LUMIERA_ERROR_ASSERTION)
|
||||
* @remarks an EventMatch object is returned when building query expression
|
||||
* to verify the occurrence of some events within the EventLog.
|
||||
* This "matcher" object implements the query logic with backtracking.
|
||||
* The query expressions are added as filter functors to a filtering
|
||||
* iterator; when all of the log's contents are filtered away,
|
||||
* the evaluation counts as failed.
|
||||
*/
|
||||
class EventMatch
|
||||
{
|
||||
|
|
@ -90,6 +89,7 @@ namespace test{
|
|||
using Filter = ExtensibleFilterIter<Iter>;
|
||||
|
||||
using ArgSeq = lib::diff::RecordSetup<string>::Storage;
|
||||
using RExSeq = std::vector<std::regex>;
|
||||
|
||||
|
||||
/** match predicate evaluator */
|
||||
|
|
@ -98,10 +98,10 @@ namespace test{
|
|||
/** record last match for diagnostics */
|
||||
string lastMatch_;
|
||||
|
||||
/** support for positive and negative queries.
|
||||
* @note negative queries enforced in dtor */
|
||||
/** support for positive and negative queries. */
|
||||
bool look_for_match_;
|
||||
|
||||
/** record when the underlying query has failed */
|
||||
string violation_;
|
||||
|
||||
/** core of the evaluation machinery:
|
||||
|
|
@ -114,14 +114,14 @@ namespace test{
|
|||
return !isnil (solution_);
|
||||
}
|
||||
|
||||
/** this is actually called after each refinement
|
||||
* of the filter and matching conditions. The effect is to search
|
||||
/** 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
|
||||
* @par matchSpec diagnostics description of the predicate just being added
|
||||
* @par rel indication of the searching relation / direction
|
||||
* @param matchSpec diagnostics description of the predicate just being added
|
||||
* @param rel indication of the searching relation / direction
|
||||
*/
|
||||
void
|
||||
evaluateQuery (string matchSpec, Literal rel = "after")
|
||||
|
|
@ -160,6 +160,7 @@ namespace test{
|
|||
friend class EventLog;
|
||||
|
||||
|
||||
|
||||
/* == elementary matchers == */
|
||||
|
||||
auto
|
||||
|
|
@ -233,7 +234,7 @@ namespace test{
|
|||
* @see ExtensibleFilterIter::andFilter()
|
||||
*/
|
||||
auto
|
||||
matchArguments(ArgSeq&& argSeq)
|
||||
matchArguments (ArgSeq&& argSeq)
|
||||
{
|
||||
return [=](Entry const& entry)
|
||||
{
|
||||
|
|
@ -249,6 +250,33 @@ namespace test{
|
|||
}
|
||||
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
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...
|
||||
}
|
||||
|
||||
|
||||
/** refinement filter to match on the given typeID */
|
||||
auto
|
||||
matchType (string typeID)
|
||||
|
|
@ -282,6 +310,7 @@ namespace test{
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
/** final evaluation of the match query,
|
||||
* usually triggered from the unit test `CHECK()`.
|
||||
|
|
@ -433,13 +462,23 @@ namespace test{
|
|||
return *this;
|
||||
}
|
||||
|
||||
/** refine filter to additionally cover all arguments
|
||||
* with a series of regular expressions.
|
||||
* @note For this match to succeed, any arguments of the log entry
|
||||
* must be covered by applying the given regular expressions
|
||||
* consecutively. Each regular expression is applied to further
|
||||
* arguments, as long as it matches. It is possible to have
|
||||
* just one RegExp to "rule them all", but as soon as there
|
||||
* is one argument, that can not be covered by the next
|
||||
* RegExp, the whole match counts as failed.
|
||||
*/
|
||||
template<typename...ARGS>
|
||||
EventMatch&
|
||||
argMatch (ARGS const& ...regExps)
|
||||
{
|
||||
stringify<ArgSeq> (regExps...);
|
||||
|
||||
UNIMPLEMENTED("process regular expression match on call argument list");
|
||||
solution_.andFilter (matchArgsRegExp (stringify<RExSeq> (regExps...)));
|
||||
evaluateQuery ("match-args-RegExp("+util::join(stringify<ArgSeq>(regExps...))+")");
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** refine filter to additionally require a matching log entry type */
|
||||
|
|
@ -508,7 +547,9 @@ namespace test{
|
|||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
|
||||
/****************************************************************//**
|
||||
* Helper to log and verify the occurrence of events.
|
||||
* The EventLog object is a front-end handle, logging flexible
|
||||
* [information records][lib::Record] into a possibly shared (vector)
|
||||
|
|
@ -524,6 +565,7 @@ namespace test{
|
|||
|
||||
std::shared_ptr<Log> log_;
|
||||
|
||||
|
||||
void
|
||||
log (std::initializer_list<string> const& ili)
|
||||
{
|
||||
|
|
@ -544,6 +586,7 @@ namespace test{
|
|||
return log_->front().get("ID");
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
explicit
|
||||
EventLog (string logID)
|
||||
|
|
@ -726,7 +769,7 @@ namespace test{
|
|||
bool
|
||||
empty() const
|
||||
{
|
||||
return 1 >= log_->size();
|
||||
return 1 >= log_->size(); // do not count the log header
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,34 @@ END
|
|||
|
||||
|
||||
TEST "Helper for event registration and verification" TestEventLog_test <<END
|
||||
err-lit: __Log_condition_violated__
|
||||
err-lit: FAILED to match("γ")
|
||||
err: ..after HEAD TestEventLog_test.+
|
||||
err-lit: __Log_condition_violated__
|
||||
err-lit: FAILED to match("α")
|
||||
err-lit: ..after match("β") @ Rec(event|{β})
|
||||
err: ..after HEAD TestEventLog_test.+
|
||||
err-lit: __Log_condition_violated__
|
||||
err-lit: FAILED to match("ham")
|
||||
err-lit: ..before match("beans") @ Rec(EventLogHeader| ID = baked beans )
|
||||
err-lit: ..before match("spam") @ Rec(event|{spam})
|
||||
err-lit: ..after HEAD baked beans
|
||||
err-lit: __Log_condition_violated__
|
||||
err-lit: FOUND at least match("ham") @ Rec(event|{ham})
|
||||
err-lit: ..after match("eggs") @ Rec(EventLogHeader| ID = eggs )
|
||||
err-lit: ..before match("spam") @ Rec(event|{spam})
|
||||
err-lit: ..after match("spam") @ Rec(event|{spam})
|
||||
err-lit: ..after HEAD eggs
|
||||
err-lit: __Log_condition_violated__
|
||||
err-lit: FAILED to match("some")
|
||||
err-lit: ..before match-event("fun") @ Rec(event| ID = no |{fun})
|
||||
err-lit: ..after HEAD event trace
|
||||
err-lit: __Log_condition_violated__
|
||||
err-lit: FAILED to match-event("stuff")
|
||||
err-lit: ..after HEAD theHog
|
||||
err-lit: __Log_condition_violated__
|
||||
err-lit: FAILED to match-event("danger")
|
||||
err-lit: ..after HEAD theHog
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ out: ^he says: hey Joe!
|
|||
out: ^the truth: 0
|
||||
out: ^just a number: 1.234.*e\+56
|
||||
out: ^12345X
|
||||
out: 0.+1.1.+2.2.+3.3.+4.4.+5.5.+6.6.+7.7.+8.8.+\-\-\+\-\-9.9
|
||||
out-lit: 0--+--1.1--+--2.2--+--3.3--+--4.4--+--5.5--+--6.6--+--7.7--+--8.8--+--9.9
|
||||
out-lit: Nr.01(0.0), Nr.02(2.2), Nr.03(4.4), Nr.04(6.6), Nr.05(8.8), Nr.06(11.0), Nr.07(13.2), Nr.08(15.4), Nr.09(17.6), Nr.10(19.8)
|
||||
return: 0
|
||||
END
|
||||
|
|
@ -260,7 +260,7 @@ out: GenNode.+"baked beans".+Rec...hasSpam = DataCap.«bool».+«char».+«std::
|
|||
out: GenNode.+"hasSpam".+«bool».1
|
||||
out: GenNode.+_CHILD_char.+«char».\*
|
||||
out: GenNode.+_CHILD_string.+«std::string».★
|
||||
out: GenNode.+_CHILD_double.+«double».3.1415926535897931
|
||||
out: GenNode.+_CHILD_double.+«double».3.1415927
|
||||
out: GenNode.+"spam".+«lib::diff::Record<lib::diff::GenNode>».Rec.ham.+eggs.+spam.+spam
|
||||
out: GenNode.+_CHILD_string.+«std::string».eggs
|
||||
out: GenNode.+_CHILD_string.+«std::string».spam
|
||||
|
|
|
|||
|
|
@ -22,35 +22,22 @@
|
|||
|
||||
|
||||
#include "lib/test/run.hpp"
|
||||
//#include "lib/test/test-helper.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
#include "lib/test/event-log.hpp"
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using util::join;
|
||||
using util::isnil;
|
||||
using lumiera::error::LUMIERA_ERROR_ASSERTION;
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace test{
|
||||
namespace test{
|
||||
|
||||
template<class T>
|
||||
class Wrmrmpft
|
||||
{
|
||||
T tt_;
|
||||
};
|
||||
|
||||
struct Murpf { };
|
||||
|
||||
|
||||
|
||||
|
|
@ -58,7 +45,13 @@ namespace test{
|
|||
/***********************************************************//**
|
||||
* @test verify a logging facility, which can be used to ensure
|
||||
* some events happened while running test code.
|
||||
*
|
||||
* - various kinds of events or function calls
|
||||
* are logged via the logging API.
|
||||
* - within the test code, a match is performed against
|
||||
* the contents of the log, using a DSL to represent
|
||||
* matches relative to other matches
|
||||
* - when a match fails, additional diagnostics are
|
||||
* printed to STDERR
|
||||
* @see event-log.hpp
|
||||
*/
|
||||
class TestEventLog_test : public Test
|
||||
|
|
@ -128,6 +121,17 @@ namespace test{
|
|||
}
|
||||
|
||||
|
||||
/** @test combining several logs
|
||||
* The EventLog objects are actually just lightweight front-end handles,
|
||||
* while the actual log lives on the Heap. This allows to have several handles
|
||||
* hold onto the same actual log; this way, we can access and verify logs
|
||||
* even after the managing object is destroyed.
|
||||
*
|
||||
* The "log joining" functionality covered here is just an obvious extension
|
||||
* to this setup: it allows to attach one log to another log after the fact;
|
||||
* the contents of the joined log are integrated into the target log.
|
||||
* @remarks this functionality is "low hanging fruit" -- not sure if it's useful.
|
||||
*/
|
||||
void
|
||||
verify_logJoining ()
|
||||
{
|
||||
|
|
@ -342,10 +346,10 @@ namespace test{
|
|||
|
||||
// Cover all arguments with sequence of regular expressions
|
||||
CHECK (log.verify("spam").argMatch("^egg ", "^spam .+spam$"));
|
||||
CHECK (log.verifyMatch("Rec.+fatal").afterMatch("{.+}").argMatch("bacon$","and spam$"));
|
||||
CHECK (log.verifyMatch("Rec.+fatal").afterMatch("\\{.+\\}").argMatch("bacon$","and spam$"));
|
||||
|
||||
// argument match must cover all arguments...
|
||||
CHECK (log.ensureNot("spam").arg("sausage|egg"));
|
||||
CHECK (log.ensureNot("spam").argMatch("bacon|^spam"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue