LUMIERA.clone/src/lib/test/event-log.hpp
Ichthyostega 00abf9f1f9 err: got the naming and the junctor condition backwards
the junctor is called "before" but searches ahead.
And in this case we do not need to *extend* the filter condition,
just replace it with a new one...
2015-12-05 03:37:25 +01:00

344 lines
9.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
EVENT-LOG.hpp - test facility to verify the occurrence of expected events
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.
*/
/** @file event-log.hpp
** Support for verifying the occurrence of events from unit tests.
** Typically used within special rigging and instrumentation for tests,
** the [EventLog] allows to record invocations and similar events. It is
** implemented as a "PImpl" to allow sharing of logs. The front-end 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 deliberately throws an assertion failure, 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
**
*/
#ifndef LIB_TEST_EVENT_LOG_H
#define LIB_TEST_EVENT_LOG_H
#include "lib/error.hpp"
#include "lib/idi/entry-id.hpp"
#include "lib/iter-adapter-stl.hpp"
//#include "lib/time/timevalue.hpp"
#include "lib/format-string.hpp"
#include "lib/format-util.hpp"
#include "lib/diff/record.hpp"
#include "lib/util.hpp"
//#include <boost/lexical_cast.hpp>
#include <vector>
#include <string>
namespace lib {
namespace test{
namespace error = lumiera::error;
// using lib::Literal;
using std::string;
using util::contains;
using util::isnil;
using util::_Fmt;
// using std::rand;
/**
* @internal ongoing evaluation and match within an [EventLog].
* @throws error::Fatal when the expected match fails (error::LUMIERA_ERROR_ASSERTION)
*/
class EventMatch
{
using Entry = lib::diff::Record<string>;
using Log = std::vector<Entry>;
using Iter = lib::RangeIter<Log::const_iterator>;
using Filter = ExtensibleFilterIter<Iter>;
/** match predicate evaluator */
Filter solution_;
/** record last match for diagnostics */
string lastMatch_;
/** core of the evaluation machinery:
* apply a filter predicate and then pull
* through the log to find a acceptable entry
*/
bool
eval()
{
return !isnil (solution_);
}
/** 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 throw an assertion failure as soon as some
* condition can not be satisfied. Rationale is to
* indicate the point where a chained match fails
* @par matchSpec diagnostics description of the predicate just being added
* @par rel indication of the searching relation / direction
* @throws error::Fatal ([assertion failure][error::LUMIERA_ERROR_ASSERTION]
* when the filtering condition built up thus far can not be
* satisfied at all on the current event log contents
*/
void
enforce (string matchSpec, Literal rel = "after")
{
if (!eval())
throw error::Fatal(_Fmt("Failed to %s %s %s")
% matchSpec % rel % lastMatch_
, error::LUMIERA_ERROR_ASSERTION);
lastMatch_ = matchSpec+" @ "+string(*solution_);
}
/** @internal for creating EventLog matchers */
EventMatch(Iter&& srcSeq)
: solution_(srcSeq)
, lastMatch_("HEAD "+ solution_->get("ID"))
{ }
friend class EventLog;
/* == elementary matchers == */
auto
find (string match)
{
return [=](Entry const& entry)
{
return contains (string(entry), match);
};
}
public:
/**
* 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 "β".
*/
EventMatch&
before (string match)
{
solution_.setNewFilter(find(match));
enforce ("match(\""+match+"\")");
return *this;
}
EventMatch&
beforeMatch (string regExp)
{
UNIMPLEMENTED("process combined relational regular expression match");
}
EventMatch&
beforeEvent (string match)
{
UNIMPLEMENTED("process combined relational match");
}
EventMatch&
beforeCall (string match)
{
UNIMPLEMENTED("process combined relational match");
}
EventMatch&
after (string match)
{
UNIMPLEMENTED("process combined relational match backwards");
}
EventMatch&
afterMatch (string regExp)
{
UNIMPLEMENTED("process combined relational regular expression match backwards");
}
EventMatch&
afterEvent (string match)
{
UNIMPLEMENTED("process combined relational match backwards");
}
EventMatch&
afterCall (string match)
{
UNIMPLEMENTED("process combined relational match backwards");
}
EventMatch&
arg ()
{
UNIMPLEMENTED("process match on no-arg call argument list");
}
template<typename... MORE>
EventMatch&
arg (string match, MORE...args)
{
UNIMPLEMENTED("process match on call argument list");
}
template<typename... MORE>
EventMatch&
argMatch (string regExp, MORE...args)
{
UNIMPLEMENTED("process regular expression match on call argument list");
}
EventMatch&
on (string sourceID)
{
UNIMPLEMENTED("process additional filter on source of log entry");
}
};
/**
* Helper to log and verify the occurrence of events.
*/
class EventLog
{
using Entry = lib::diff::Record<string>;
using Log = std::vector<Entry>;
using Iter = lib::RangeIter<Log::const_iterator>;
string logID_;
Log log_;
void
log (std::initializer_list<string> const&& ili)
{
log_.emplace_back(ili);
}
public:
explicit
EventLog (string logID)
: logID_(logID)
{
log({"type=EventLogHeader", "ID="+logID_});
}
template<class X>
explicit
EventLog (const X *const obj)
: EventLog(idi::instanceTypeID (obj))
{ }
// standard copy operations acceptable
EventLog&
join (EventLog& otherLog)
{
UNIMPLEMENTED("log join");
return *this;
}
/* ==== Logging API ==== */
EventLog&
event (string text)
{
log({"type=event", text});
return *this;
}
/* ==== Iteration ==== */
bool
empty() const
{
return 1 >= log_.size();
}
typedef Iter const_iterator;
typedef const Entry value_type;
const_iterator begin() const { return Iter(log_.begin(), log_.end()); }
const_iterator end() const { return Iter(); }
friend const_iterator begin (EventLog const& log) { return log.begin(); }
friend const_iterator end (EventLog const& log) { return log.end(); }
/* ==== Query/Verification API ==== */
EventMatch
verify (string match)
{
EventMatch matcher(this->begin());
matcher.before (match);
return matcher;
}
EventMatch
verifyMatch (string regExp)
{
UNIMPLEMENTED("start matching sequence for regular expression match");
}
EventMatch
verifyEvent (string match)
{
UNIMPLEMENTED("start matching sequence");
}
EventMatch
verifyCall (string match)
{
UNIMPLEMENTED("start matching sequence");
}
EventMatch
ensureNot (string match)
{
UNIMPLEMENTED("start matching sequence");
}
};
}} // namespace lib::test
#endif /*LIB_TEST_EVENT_LOG_H*/