seems more logical than calling the attribute "ID",
especially since we're now able to use the on("xyz") matcher
360 lines
14 KiB
C++
360 lines
14 KiB
C++
/*
|
||
TestEventLog(Test) - helper for event registration and verification
|
||
|
||
Copyright (C) Lumiera.org
|
||
2015, 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.
|
||
|
||
* *****************************************************/
|
||
|
||
|
||
#include "lib/test/run.hpp"
|
||
#include "lib/format-util.hpp"
|
||
#include "lib/test/event-log.hpp"
|
||
#include "lib/util.hpp"
|
||
|
||
#include <string>
|
||
|
||
using util::join;
|
||
using util::isnil;
|
||
|
||
using std::string;
|
||
|
||
|
||
namespace lib {
|
||
namespace test{
|
||
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
|
||
{
|
||
void
|
||
run (Arg)
|
||
{
|
||
verify_simpleUsage();
|
||
verify_backwardMatch();
|
||
verify_negatedMatch();
|
||
verify_logJoining();
|
||
verify_callLogging();
|
||
verify_eventLogging();
|
||
verify_genericLogging();
|
||
verify_regExpMatch();
|
||
}
|
||
|
||
|
||
void
|
||
verify_simpleUsage ()
|
||
{
|
||
EventLog log(this);
|
||
CHECK (isnil (log));
|
||
|
||
log.event("α");
|
||
log.event("β");
|
||
CHECK (!isnil(log));
|
||
|
||
CHECK (log.verify("α"));
|
||
CHECK (log.verify("β"));
|
||
CHECK (not log.verify("γ"));
|
||
|
||
CHECK (log.verify("α").before("β"));
|
||
CHECK (not log.verify("β").before("α"));
|
||
|
||
CHECK (join(log) == "Rec(EventLogHeader| this = "+idi::instanceTypeID(this)+" ), "
|
||
+ "Rec(event|{α}), "
|
||
+ "Rec(event|{β})");
|
||
}
|
||
|
||
|
||
void
|
||
verify_backwardMatch ()
|
||
{
|
||
EventLog log("baked beans");
|
||
log.event("spam");
|
||
log.event("ham");
|
||
|
||
CHECK (log.verify("ham").after("spam").after("beans"));
|
||
CHECK (log.verify("ham").after("beans").before("spam").before("ham"));
|
||
CHECK (not log.verify("spam").after("beans").after("ham"));
|
||
}
|
||
|
||
|
||
void
|
||
verify_negatedMatch ()
|
||
{
|
||
EventLog log("eggs");
|
||
log.event("spam");
|
||
log.event("ham");
|
||
log.event("spam");
|
||
|
||
CHECK (log.ensureNot("baked beans"));
|
||
CHECK (log.ensureNot("ham").before("eggs"));
|
||
CHECK (log.ensureNot("spam").after("spam").before("eggs"));
|
||
CHECK (not log.ensureNot("spam").before("spam").after("eggs").before("ham"));
|
||
}
|
||
|
||
|
||
/** @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 ()
|
||
{
|
||
EventLog log1("spam");
|
||
EventLog log2("ham");
|
||
|
||
log1.event("baked beans");
|
||
log2.event("eggs");
|
||
|
||
CHECK (log1.verify("spam").before("baked beans"));
|
||
CHECK (log2.verify("ham").before("eggs"));
|
||
|
||
CHECK (log1.ensureNot("ham"));
|
||
CHECK (log1.ensureNot("eggs"));
|
||
CHECK (log2.ensureNot("spam"));
|
||
CHECK (log2.ensureNot("baked beans"));
|
||
|
||
EventLog copy(log2);
|
||
copy.event("bacon");
|
||
CHECK (copy.verify("ham").before("eggs").before("bacon"));
|
||
CHECK (log2.verify("ham").before("eggs").before("bacon"));
|
||
CHECK (log1.ensureNot("bacon"));
|
||
|
||
CHECK (log1 != log2);
|
||
CHECK (copy == log2);
|
||
|
||
log2.joinInto(log1);
|
||
|
||
CHECK (log1 == log2);
|
||
CHECK (copy != log2);
|
||
|
||
CHECK (log1.verify("logJoin|{ham}").after("baked beans"));
|
||
CHECK (log1.verify("logJoin|{ham}").after("EventLogHeader| this = ham").before("eggs").before("bacon").before("logJoin"));
|
||
|
||
log2.event("sausage");
|
||
CHECK (log1.verify("sausage").after("logJoin").after("spam"));
|
||
|
||
CHECK (copy.ensureNot("logJoin"));
|
||
CHECK (copy.ensureNot("sausage"));
|
||
CHECK (copy.verify("joined|{spam}").after("EventLogHeader"));
|
||
|
||
copy.event("spam tomato");
|
||
CHECK (log1.ensureNot("spam tomato"));
|
||
CHECK (log2.ensureNot("spam tomato"));
|
||
CHECK (copy.verify("joined|{spam}").before("spam tomato"));
|
||
|
||
|
||
CHECK (join(log1) == string(
|
||
"Rec(EventLogHeader| this = spam ), "
|
||
"Rec(event|{baked beans}), "
|
||
"Rec(EventLogHeader| this = ham ), "
|
||
"Rec(event|{eggs}), "
|
||
"Rec(event|{bacon}), "
|
||
"Rec(logJoin|{ham}), "
|
||
"Rec(event|{sausage})"));
|
||
|
||
CHECK (join(copy) == string(
|
||
"Rec(EventLogHeader| this = ham ), "
|
||
"Rec(joined|{spam}), "
|
||
"Rec(event|{spam tomato})"));
|
||
}
|
||
|
||
|
||
void
|
||
verify_callLogging ()
|
||
{
|
||
EventLog log("funCall");
|
||
log.call (this, "fun1");
|
||
log.call ("some", "fun2");
|
||
log.call ("more", "fun3", "facts", 3.2,1);
|
||
|
||
CHECK(log.verify("fun1").before("fun2").before("fun3"));
|
||
|
||
CHECK (join(log) == string(
|
||
"Rec(EventLogHeader| this = funCall ), "
|
||
"Rec(call| fun = fun1, this = "+idi::instanceTypeID(this)+" ), "
|
||
"Rec(call| fun = fun2, this = some ), "
|
||
"Rec(call| fun = fun3, this = more |{facts, 3.2, 1})"));
|
||
|
||
CHECK (log.verifyCall("fun1"));
|
||
CHECK (log.verifyCall("fun2"));
|
||
CHECK (log.verifyCall("fun3"));
|
||
|
||
CHECK (log.verifyCall("fun"));
|
||
CHECK (log.verifyCall("fun").after("fun").after("fun"));
|
||
CHECK (log.ensureNot("fun").after("fun").after("fun2"));
|
||
|
||
CHECK (log.verifyCall("fun3").arg("facts", 3.2, 1));
|
||
CHECK (log.verifyCall("fun3").arg(string("facts"), 3.2f, int64_t(1)));
|
||
CHECK (log.verifyCall("fun3").arg("facts", "3.2", "1"));
|
||
|
||
CHECK (log.ensureNot("fun").arg(" facts ","3.2", "1"));
|
||
CHECK (log.ensureNot("fun").arg("facts", "3.20","1"));
|
||
CHECK (log.ensureNot("fun").arg("facts", "3.2", "1L"));
|
||
|
||
CHECK (log.verifyCall("fun1").arg());
|
||
CHECK (log.verifyCall("fun2").arg());
|
||
|
||
CHECK (log.verify("fun").arg().before("fun").arg("facts", 3.2, 1));
|
||
|
||
CHECK (log.verify("fun").on(this));
|
||
CHECK (log.verify("fun").on("some"));
|
||
CHECK (log.verify("fun").on("more"));
|
||
CHECK (log.verify("fun").on("more").on("more"));
|
||
CHECK (log.ensureNot("fun").on("some").on("more"));
|
||
|
||
CHECK (log.verify("fun").on("some").arg());
|
||
CHECK (log.ensureNot("fun").arg().on("more"));
|
||
CHECK (log.ensureNot("fun").on("some").arg("facts", "3.2", "1"));
|
||
CHECK (log.verifyCall("fun").arg("facts", "3.2", "1").on("more"));
|
||
}
|
||
|
||
|
||
void
|
||
verify_eventLogging ()
|
||
{
|
||
EventLog log("event trace");
|
||
log.event("no","fun");
|
||
log.call("some","fun");
|
||
|
||
CHECK (log.verify("fun").before("fun"));
|
||
CHECK (log.verify("no").before("some"));
|
||
|
||
CHECK (log.verifyEvent("fun").beforeCall("fun").on("some"));
|
||
CHECK (!log.verifyEvent("fun").after("some"));
|
||
|
||
CHECK (log.verifyEvent("no","fun"));
|
||
CHECK (log.verifyEvent("fun").id("no"));
|
||
CHECK (log.verify("no").arg("fun"));
|
||
|
||
CHECK (join(log) == string(
|
||
"Rec(EventLogHeader| this = event trace ), "
|
||
"Rec(event| ID = no |{fun}), "
|
||
"Rec(call| fun = fun, this = some )"));
|
||
}
|
||
|
||
|
||
void
|
||
verify_genericLogging ()
|
||
{
|
||
EventLog log("theHog");
|
||
log.note ("type=some","ID=weird","stuff");
|
||
log.warn ("danger");
|
||
log.error ("horrible");
|
||
log.fatal ("destiny");
|
||
log.create ("something");
|
||
log.destroy ("everything");
|
||
|
||
CHECK (log.verify("theHog")
|
||
.before("stuff")
|
||
.before("danger")
|
||
.before("horrible")
|
||
.before("destiny")
|
||
.before("something")
|
||
.before("everything"));
|
||
CHECK (log.verify("this").type("EventLogHeader")
|
||
.before("weird").type("some")
|
||
.before("danger").type("warn")
|
||
.before("horrible").type("error")
|
||
.before("destiny").type("fatal")
|
||
.before("something").type("create")
|
||
.before("everything").type("destroy"));
|
||
|
||
CHECK (log.verify("some").attrib("ID","weird").arg("stuff"));
|
||
|
||
// NOTE: there is some built-in leeway in event-matching...
|
||
CHECK (log.verifyEvent("horrible").beforeEvent("something").beforeEvent("everything"));
|
||
CHECK (!log.verifyEvent("stuff")); // not every entry type is an event by default
|
||
CHECK (!log.verifyEvent("danger")); // warning is not an event by default
|
||
CHECK (log.verifyEvent("some","stuff")); // but the classifier-param matches on the type
|
||
CHECK (log.verifyEvent("weird","stuff"));
|
||
CHECK (log.verifyEvent("warn","danger"));
|
||
CHECK (log.verifyEvent("fatal","destiny"));
|
||
CHECK (log.verifyEvent("destroy","everything"));
|
||
|
||
CHECK (join(log) == string(
|
||
"Rec(EventLogHeader| this = theHog ), "
|
||
"Rec(some| ID = weird |{stuff}), "
|
||
"Rec(warn|{danger}), "
|
||
"Rec(error|{horrible}), "
|
||
"Rec(fatal|{destiny}), "
|
||
"Rec(create|{something}), "
|
||
"Rec(destroy|{everything})"));
|
||
}
|
||
|
||
|
||
void
|
||
verify_regExpMatch ()
|
||
{
|
||
EventLog log("Lovely spam!");
|
||
log.note ("type=spam", "egg and bacon"
|
||
, "egg sausage and bacon"
|
||
, "egg and spam"
|
||
, "egg bacon and spam"
|
||
, "egg bacon sausage and spam"
|
||
, "spam bacon sausage and spam"
|
||
, "spam egg spam spam bacon and spam"
|
||
, "spam sausage spam spam bacon spam tomato and spam");
|
||
log.fatal("Lobster Thermidor a Crevette with a mornay sauce served in a Provencale manner "
|
||
"with shallots and aubergines garnished with truffle pate, brandy and with a fried egg on top and spam");
|
||
|
||
CHECK (log.verify("spam").before("(spam|").before("egg on top and spam"));
|
||
CHECK (log.ensureNot("and spam").after("(spam|").after("spam!").after("bacon"));
|
||
|
||
// note: each consecutive match starts with the same element, on which the previous one succeeded
|
||
CHECK (log.verify("spam").before("spam").before("spam").before("spam").before("spam").before("bacon"));
|
||
|
||
// RegExp on full String representation
|
||
CHECK (log.verifyMatch("spam.+spam"));
|
||
CHECK (log.verifyMatch("spam.+spam").beforeMatch("spam(?!spam)"));
|
||
CHECK (log.verifyEvent("fatal","spam").afterMatch("(spam.*){15}"));
|
||
|
||
// 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$"));
|
||
|
||
// argument match must cover all arguments...
|
||
CHECK (log.ensureNot("spam").argMatch("bacon|^spam"));
|
||
}
|
||
};
|
||
|
||
LAUNCHER (TestEventLog_test, "unit common");
|
||
|
||
|
||
}}} // namespace lib::test::test
|
||
|