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:
Fischlurch 2015-12-13 05:03:36 +01:00
parent d0cdae2cee
commit c8068496d1
4 changed files with 117 additions and 42 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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"));
}
};