Lumiera 0.pre.04~rc.1
»edit your freedom«
Loading...
Searching...
No Matches
event-log.cpp
Go to the documentation of this file.
1/*
2 EventLog - test facility to verify the occurrence of expected events
3
4 Copyright (C)
5 2018, Hermann Vosseler <Ichthyostega@web.de>
6
7  **Lumiera** is free software; you can redistribute it and/or modify it
8  under the terms of the GNU General Public License as published by the
9  Free Software Foundation; either version 2 of the License, or (at your
10  option) any later version. See the file COPYING for further details.
11
12* *****************************************************************/
13
37#include "lib/error.hpp"
39
40
41
42namespace lib {
43namespace test{
44 namespace error = lumiera::error;
45
46 using util::stringify;
47 using util::contains;
48 using util::isnil;
49 using lib::Symbol;
50 using std::string;
51
52
53 namespace { /* == elementary matchers == */
54
55 using Filter = decltype( buildSearchFilter (std::declval<Log const&>()) );
56
58 using RExSeq = std::vector<std::regex>;
59
60
61 inline auto
62 find (string match)
63 {
64 return [=](Entry const& entry)
65 {
66 return contains (string(entry), match);
67 };
68 }
69
70 inline auto
71 findRegExp (string regExpDef)
72 {
73 std::regex regExp(regExpDef);
74 return [=](Entry const& entry)
75 {
76 return std::regex_search(string(entry), regExp);
77 };
78 }
79
80 inline auto
81 findEvent (string match)
82 {
83 return [=](Entry const& entry)
84 {
85 return ( entry.getType() == "event"
86 or entry.getType() == "error"
87 or entry.getType() == "create"
88 or entry.getType() == "destroy"
89 or entry.getType() == "logJoin"
90 )
91 and not isnil(entry.scope())
92 and contains (*entry.scope(), match);
93 };
94 }
95
96 inline auto
97 findEvent (string classifier, string match)
98 {
99 return [=](Entry const& entry)
100 {
101 return ( entry.getType() == classifier
102 or (entry.hasAttribute("ID") and contains (entry.get("ID"), classifier))
103 )
104 and not isnil(entry.scope())
105 and contains (*entry.scope(), match);
106 };
107 }
108
109 inline auto
110 findCall (string match)
111 {
112 return [=](Entry const& entry)
113 {
114 return entry.getType() == "call"
115 and contains (entry.get("fun"), match);
116 };
117 }
118
119
128 inline auto
130 {
131 return [=](Entry const& entry)
132 {
133 auto scope = entry.scope();
134 for (auto const& match : argSeq)
135 if (isnil (scope) or not contains(*scope, match))
136 return false;
137 else
138 ++scope;
139
140 return isnil(scope); // must be exhausted by now
141 }; // otherwise the sizes do not match...
142 }
143
145 inline auto
146 matchArgument (size_t idx, string match)
147 {
148 return [=](Entry const& entry)
149 {
150 return idx < entry.childSize()
151 and contains (entry.child(idx), match);
152 };
153 }
154
155
165 inline auto
167 {
168 return [=](Entry const& entry)
169 {
170 auto scope = entry.scope();
171 for (auto const& rex : regExpSeq)
172 {
173 if (isnil (scope)) return false;
174 while (scope and std::regex_search(*scope, rex))
175 ++scope;
176 }
177
178 return isnil(scope); // must be exhausted by now
179 }; // otherwise we didn't get full coverage...
180 }
181
182
184 inline auto
185 matchType (string typeID)
186 {
187 return [=](Entry const& entry)
188 {
189 return contains (entry.getType(), typeID);
190 };
191 }
192
193
195 inline auto
196 ensureAttribute (string key)
197 {
198 return [=](Entry const& entry)
199 {
200 return entry.hasAttribute(key);
201 };
202 }
203
204
206 inline auto
207 matchAttribute (string key, string valueMatch)
208 {
209 return [=](Entry const& entry)
210 {
211 return entry.hasAttribute(key)
212 and contains (entry.get(key), valueMatch);
213 };
214 }
215
216
217
218 /* === configure the underlying search engine === */
219
223
224 template<typename COND>
225 inline void
226 attachNextSerchStep (Filter& solution, COND&& searchCond, Direction direction)
227 {
228 if (CURRENT == direction)
229 solution.search (forward<COND> (searchCond));
230 else
231 solution.addStep ([predicate{forward<COND> (searchCond)}, direction]
232 (auto& filter)
233 {
234 filter.reverse (BACKWARD == direction);
235 filter.disableFilter(); // deactivate any filtering temporarily
236 ++filter; // move one step in the required direction
237 filter.setNewFilter (predicate);
238 });
239 }
240
241 template<typename COND>
242 inline void
243 refineSerach (Filter& solution, COND&& additionalCond)
244 {
245 solution.addStep ([predicate{forward<COND> (additionalCond)}]
246 (auto& filter)
247 {
248 filter.andFilter (predicate);
249 });
250 }
251
252 }//(End) Elementary Matchers and handling of the search engine
253
254
255
256
257
258 EventMatch::EventMatch (Log const& srcSeq)
259 : solution_{buildSearchFilter (srcSeq)}
260 , lastMatch_{"HEAD "+ solution_->get("this")}
261 , look_for_match_{true}
262 , violation_{}
263 { }
264
265
266
271 bool
273 {
274 return not isnil (solution_);
275 }
276
277
287 void
288 EventMatch::evaluateQuery (string matchSpec, Literal rel)
289 {
290 if (look_for_match_ and not isnil (violation_)) return;
291 // already failed, no further check necessary
292
293 if (foundSolution()) // NOTE this pulls the filter
294 {
295 lastMatch_ = matchSpec+" @ "+string(*solution_)
296 + (isnil(lastMatch_)? ""
297 : "\n.."+rel+" "+lastMatch_);
298 if (not look_for_match_)
299 violation_ = "FOUND at least "+lastMatch_;
300 }
301 else
302 {
303 if (look_for_match_)
304 violation_ = "FAILED to "+matchSpec
305 + "\n.."+rel
306 + " "+lastMatch_;
307 else
308 violation_ = "";
309 }
310 }
311
312
313
321 EventMatch::locate (string match)
322 {
323 attachNextSerchStep (solution_, find(match), CURRENT);
324 evaluateQuery ("match(\""+match+"\")");
325 return *this;
326 }
327
333 {
334 attachNextSerchStep (solution_, findRegExp(regExp), CURRENT);
335 evaluateQuery ("find-RegExp(\""+regExp+"\")");
336 return *this;
337 }
338
346 {
347 attachNextSerchStep (solution_, findEvent(match), CURRENT);
348 evaluateQuery ("match-event(\""+match+"\")");
349 return *this;
350 }
351
353 EventMatch::locateEvent (string classifier, string match)
354 {
355 attachNextSerchStep (solution_, findEvent(classifier,match), CURRENT);
356 evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")");
357 return *this;
358 }
359
366 {
367 attachNextSerchStep (solution_, findCall(match), CURRENT);
368 evaluateQuery ("match-call(\""+match+"\")");
369 return *this;
370 }
371
372
373
374
384 EventMatch::before (string match)
385 {
386 attachNextSerchStep (solution_, find(match), FORWARD);
387 evaluateQuery ("match(\""+match+"\")");
388 return *this;
389 }
390
396 {
397 attachNextSerchStep (solution_, findRegExp(regExp), FORWARD);
398 evaluateQuery ("find-RegExp(\""+regExp+"\")");
399 return *this;
400 }
401
418 {
419 attachNextSerchStep (solution_, findEvent(match), FORWARD);
420 evaluateQuery ("match-event(\""+match+"\")");
421 return *this;
422 }
423
425 EventMatch::beforeEvent (string classifier, string match)
426 {
427 attachNextSerchStep (solution_, findEvent(classifier,match), FORWARD);
428 evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")");
429 return *this;
430 }
431
439 {
440 attachNextSerchStep (solution_, findCall(match), FORWARD);
441 evaluateQuery ("match-call(\""+match+"\")");
442 return *this;
443 }
444
445
446
447
454 EventMatch::after (string match)
455 {
456 attachNextSerchStep (solution_, find(match), BACKWARD);
457 evaluateQuery ("match(\""+match+"\")", "before");
458 return *this;
459 }
460
463 {
464 attachNextSerchStep (solution_, findRegExp(regExp), BACKWARD);
465 evaluateQuery ("find-RegExp(\""+regExp+"\")", "before");
466 return *this;
467 }
468
471 {
472 attachNextSerchStep (solution_, findEvent(match), BACKWARD);
473 evaluateQuery ("match-event(\""+match+"\")", "before");
474 return *this;
475 }
476
478 EventMatch::afterEvent (string classifier, string match)
479 {
480 attachNextSerchStep (solution_, findEvent(classifier,match), BACKWARD);
481 evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")", "before");
482 return *this;
483 }
484
490 {
491 attachNextSerchStep (solution_, findCall(match), BACKWARD);
492 evaluateQuery ("match-call(\""+match+"\")", "before");
493 return *this;
494 }
495
496
497
501 void
502 EventMatch::refineSerach_matchArgument (size_t idx, string match)
503 {
504 refineSerach (solution_, matchArgument(idx, match));
505 evaluateQuery ("match-argument(["+util::toString(idx)+"]="+match+")");
506 }
507
511 void
513 {
514 string argList(util::join(argSeq));
515 refineSerach (solution_, matchArguments(move(argSeq)));
516 evaluateQuery ("match-arguments("+argList+")");
517 }
518
526 void
527 EventMatch::refineSerach_matchArgsRegExp (RExSeq&& regExpSeq, string rendered_regExps)
528 {
529 refineSerach (solution_, matchArgsRegExp (move (regExpSeq)));
530 evaluateQuery ("match-args-RegExp("+rendered_regExps+")");
531 }
532
533
534
539 EventMatch::type (string typeID)
540 {
541 refineSerach (solution_, matchType(typeID));
542 evaluateQuery ("match-type("+typeID+")");
543 return *this;
544 }
545
550 EventMatch::key (string key)
551 {
552 refineSerach (solution_, ensureAttribute(key));
553 evaluateQuery ("ensure-attribute("+key+")");
554 return *this;
555 }
556
561 EventMatch::attrib (string key, string valueMatch)
562 {
563 refineSerach (solution_, matchAttribute(key,valueMatch));
564 evaluateQuery ("match-attribute("+key+"=\""+valueMatch+"\")");
565 return *this;
566 }
567
572 EventMatch::id (string classifier)
573 {
574 refineSerach (solution_, matchAttribute("ID",classifier));
575 evaluateQuery ("match-ID(\""+classifier+"\")");
576 return *this;
577 }
578
583 EventMatch::on (string targetID)
584 {
585 refineSerach (solution_, matchAttribute("this",targetID));
586 evaluateQuery ("match-this(\""+targetID+"\")");
587 return *this;
588 }
589
592 {
593 refineSerach (solution_, matchAttribute("this",targetID));
594 evaluateQuery ("match-this(\""+string(targetID)+"\")");
595 return *this;
596 }
597
598
599
600
601
602 /* ==== EventLog implementation ==== */
603
604 EventLog::EventLog (string logID)
605 : log_(new Log)
606 {
607 log({"type=EventLogHeader", "this="+logID});
608 }
609
610
624 EventLog&
626 {
627 Log& target = *otherLog.log_;
628 target.reserve (target.size() + log_->size() + 1);
629 target.emplace_back (log_->front());
630 auto p = log_->begin();
631 while (++p != log_->end()) // move our log's content into the other log
632 target.emplace_back (std::move(*p));
633 this->log_->resize(1);
634 this->log({"type=joined", otherLog.getID()}); // leave a tag to indicate
635 otherLog.log({"type=logJoin", this->getID()}); // where the `joinInto` took place,
636 this->log_ = otherLog.log_; // connect this to the other storage
637 return *this;
638 }
639
640
641
642 EventLog&
644 {
645 string originalLogID = this->getID();
646 return this->clear (originalLogID);
647 }
648
657 EventLog&
658 EventLog::clear (string alteredLogID)
659 {
660 log_.reset (new Log);
661 log({"type=EventLogHeader", "this="+alteredLogID});
662 return *this;
663 }
664
665 EventLog&
666 EventLog::clear (CStr alteredLogID)
667 {
668 return clear (string{alteredLogID});
669 }
670
671
672
673 /* ==== Logging API ==== */
674
675 EventLog&
676 EventLog::event (string text)
677 {
678 log ("event", ArgSeq{}, ArgSeq{text}); // we use this ctor variant to ensure
679 return *this; // that text is not misinterpreted as attribute,
680 } // which might happen when text contains a '='
681
682 EventLog&
683 EventLog::event (string classifier, string text)
684 {
685 log ("event", ArgSeq{"ID="+classifier}, ArgSeq{text});
686 return *this;
687 }
688
689 EventLog&
690 EventLog::call (string target, string function)
691 {
692 return call(target, function, ArgSeq());
693 }
694
695 EventLog&
696 EventLog::call (string target, string function, ArgSeq&& args)
697 {
698 log ("call", ArgSeq{"fun="+function, "this="+target}, std::forward<ArgSeq>(args));
699 return *this;
700 }
701
702 EventLog&
703 EventLog::call (CStr target, CStr function, ArgSeq&& args)
704 {
705 return call (string(target), string(function), std::forward<ArgSeq>(args));
706 }
707
708
709 EventLog&
710 EventLog::warn (string text)
711 {
712 log({"type=warn", text});
713 return *this;
714 }
715
716 EventLog&
717 EventLog::error (string text)
718 {
719 log({"type=error", text});
720 return *this;
721 }
722
723 EventLog&
724 EventLog::fatal (string text)
725 {
726 log({"type=fatal", text});
727 return *this;
728 }
729
730
731
732 EventLog&
733 EventLog::create (string text)
734 {
735 log({"type=create", text});
736 return *this;
737 }
738
739 EventLog&
740 EventLog::destroy (string text)
741 {
742 log({"type=destroy", text});
743 return *this;
744 }
745
746
747
748
749 /* ==== Query/Verification API ==== */
750
752 EventLog::verify (string match) const
753 {
754 EventMatch matcher(*log_);
755 matcher.locate (match); // new matcher starts linear search from first log element
756 return matcher;
757 }
758
759
761 EventLog::verifyMatch (string regExp) const
762 {
763 EventMatch matcher(*log_);
764 matcher.locateMatch (regExp);
765 return matcher;
766 }
767
768
770 EventLog::verifyEvent (string match) const
771 {
772 EventMatch matcher(*log_);
773 matcher.locateEvent (match);
774 return matcher;
775 }
776
777
779 EventLog::verifyEvent (string classifier, string match) const
780 {
781 EventMatch matcher(*log_);
782 matcher.locateEvent (classifier, match);
783 return matcher;
784 }
785
786
788 EventLog::verifyCall (string match) const
789 {
790 EventMatch matcher(*log_);
791 matcher.locateCall (match);
792 return matcher;
793 }
794
795
805 EventLog::ensureNot (string match) const
806 {
807 EventMatch matcher(*log_);
808 matcher.look_for_match_ = false; // flip logic; fail if match succeeds
809 matcher.locate (match);
810 return matcher;
811 }
812
813
814
815}} // namespace lib::test
Inline string literal.
Definition symbol.hpp:78
Token or Atom with distinct identity.
Definition symbol.hpp:120
Helper to log and verify the occurrence of events.
EventLog & warn(string text)
Log a warning entry.
lib::diff::RecordSetup< string >::Storage ArgSeq
string getID() const
EventLog & destroy(string text)
Log the destruction of an object.
EventLog & event(string text)
log some text as event
EventMatch verify(string match) const
start a query to match for some substring.
EventLog & error(string text)
Log an error note.
EventLog & clear()
purge log contents while retaining just the original Header-ID
EventMatch verifyCall(string match) const
start a query to match especially a function call
EventMatch verifyEvent(string match) const
start a query to match for some event.
EventLog & joinInto(EventLog &otherLog)
Merge this log into another log, forming a combined log.
EventMatch verifyMatch(string regExp) const
start a query to match with a regular expression
EventLog(string logID)
std::shared_ptr< Log > log_
EventLog & create(string text)
Log the creation of an object.
EventMatch ensureNot(string match) const
start a query to ensure the given expression does not match.
void log(std::initializer_list< string > const &ili)
EventLog & call(string target, string function)
Log occurrence of a function call with no arguments.
EventLog & fatal(string text)
Log a fatal failure.
bool foundSolution()
core of the evaluation machinery: apply a filter predicate and then pull through the log to find a ac...
EventMatch & type(string typeID)
refine filter to additionally require a matching log entry type
bool look_for_match_
support for positive and negative queries.
EventMatch & beforeMatch(string regExp)
find a match with the given regular expression
void refineSerach_matchArgument(size_t idx, string match)
std::vector< std::regex > RExSeq
EventMatch & afterCall(string match)
find a function invocation backwards, before the current point of reference
string violation_
record when the underlying query has failed
EventMatch(Log const &srcSeq)
lib::diff::RecordSetup< string >::Storage ArgSeq
EventMatch & id(string classifier)
refine filter to additionally match on the ID attribute
EventMatch & locateMatch(string regExp)
basic search like locate() but with the given regular expression
EventMatch & locateEvent(string match)
basic search for a matching "event"
EventMatch & before(string match)
find a match (substring match) of the given text in an EventLog entry after the current position
EventMatch & beforeEvent(string match)
find a match for an "event" after the current point of reference
void refineSerach_matchArgsRegExp(RExSeq &&regExpSeq, string rendered_regExps)
EventMatch & locate(string match)
basic search function: continue linear lookup over the elements of the EventLog to find a match (subs...
void refineSerach_matchArguments(ArgSeq &&argSeq)
Filter solution_
match predicate evaluator
EventMatch & locateCall(string match)
basic search for some specific function invocation
EventMatch & afterEvent(string match)
EventMatch & after(string match)
find a match (substring match) of the given text in an EventLog entry before the current position,...
EventMatch & attrib(string key, string valueMatch)
refine filter to additionally match on a specific attribute
EventMatch & on(string targetID)
refine filter to additionally match the ‘'this’` attribute
EventMatch & key(string key)
refine filter to additionally require the presence an attribute
EventMatch & beforeCall(string match)
find a match for some function invocation after the current point of reference
EventMatch & afterMatch(string regExp)
void evaluateQuery(string matchSpec, Literal rel="after")
this is actually called after each refinement of the filter and matching conditions.
string lastMatch_
record last match for diagnostics
Lumiera error handling (C++ interface).
const char * CStr
Definition error.hpp:42
Support for verifying the occurrence of events from unit tests.
void refineSerach(Filter &solution, COND &&additionalCond)
auto matchArguments(ArgSeq &&argSeq)
this filter functor is for refinement of an existing filter
lib::diff::RecordSetup< string >::Storage ArgSeq
Definition event-log.cpp:57
auto matchArgument(size_t idx, string match)
refinement filter to match a specific positional argument
auto ensureAttribute(string key)
refinement filter to ensure a specific attribute is present on the log entry
auto matchType(string typeID)
refinement filter to match on the given typeID
auto matchArgsRegExp(RExSeq &&regExpSeq)
refinement filter, to cover all arguments by regular expression(s)
auto matchAttribute(string key, string valueMatch)
refinement filter to ensure a specific attribute is present on the log entry
decltype(buildSearchFilter(std::declval< Log const & >())) Filter
Definition event-log.cpp:55
void attachNextSerchStep(Filter &solution, COND &&searchCond, Direction direction)
Implementation namespace for support and library code.
Test runner and basic definitions for tests.
vector< string > stringify(ELMS const &...elms)
standard setup: convert to string into a vector
bool contains(MAP &map, typename MAP::key_type const &key)
shortcut for containment test on a map
Definition util.hpp:230
std::string toString(TY const &val) noexcept
get some string representation of any object, reliably.
string join(COLL &&coll, string const &delim=", ")
enumerate a collection's contents, separated by delimiter.
bool isnil(lib::time::Duration const &dur)