Lumiera  0.pre.03
»edit your freedom«
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) Lumiera.org
5  2018, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 * *****************************************************/
22 
46 #include "lib/error.hpp"
47 #include "lib/test/event-log.hpp"
48 
49 
50 
51 namespace lib {
52 namespace test{
53  namespace error = lumiera::error;
54 
55  using util::stringify;
56  using util::contains;
57  using util::isnil;
58  using lib::Symbol;
59  using std::string;
60 
61 
62  namespace { /* == elementary matchers == */
63 
64  using Filter = decltype( buildSearchFilter (std::declval<Log const&>()) );
65 
67  using RExSeq = std::vector<std::regex>;
68 
69 
70  inline auto
71  find (string match)
72  {
73  return [=](Entry const& entry)
74  {
75  return contains (string(entry), match);
76  };
77  }
78 
79  inline auto
80  findRegExp (string regExpDef)
81  {
82  std::regex regExp(regExpDef);
83  return [=](Entry const& entry)
84  {
85  return std::regex_search(string(entry), regExp);
86  };
87  }
88 
89  inline auto
90  findEvent (string match)
91  {
92  return [=](Entry const& entry)
93  {
94  return ( entry.getType() == "event"
95  or entry.getType() == "error"
96  or entry.getType() == "create"
97  or entry.getType() == "destroy"
98  or entry.getType() == "logJoin"
99  )
100  and not isnil(entry.scope())
101  and contains (*entry.scope(), match);
102  };
103  }
104 
105  inline auto
106  findEvent (string classifier, string match)
107  {
108  return [=](Entry const& entry)
109  {
110  return ( entry.getType() == classifier
111  or (entry.hasAttribute("ID") and contains (entry.get("ID"), classifier))
112  )
113  and not isnil(entry.scope())
114  and contains (*entry.scope(), match);
115  };
116  }
117 
118  inline auto
119  findCall (string match)
120  {
121  return [=](Entry const& entry)
122  {
123  return entry.getType() == "call"
124  and contains (entry.get("fun"), match);
125  };
126  }
127 
128 
137  inline auto
138  matchArguments (ArgSeq&& argSeq)
139  {
140  return [=](Entry const& entry)
141  {
142  auto scope = entry.scope();
143  for (auto const& match : argSeq)
144  if (isnil (scope) or not contains(*scope, match))
145  return false;
146  else
147  ++scope;
148 
149  return isnil(scope); // must be exhausted by now
150  }; // otherwise the sizes do not match...
151  }
152 
154  inline auto
155  matchArgument (size_t idx, string match)
156  {
157  return [=](Entry const& entry)
158  {
159  return idx < entry.childSize()
160  and contains (entry.child(idx), match);
161  };
162  }
163 
164 
174  inline auto
175  matchArgsRegExp (RExSeq&& regExpSeq)
176  {
177  return [=](Entry const& entry)
178  {
179  auto scope = entry.scope();
180  for (auto const& rex : regExpSeq)
181  {
182  if (isnil (scope)) return false;
183  while (scope and std::regex_search(*scope, rex))
184  ++scope;
185  }
186 
187  return isnil(scope); // must be exhausted by now
188  }; // otherwise we didn't get full coverage...
189  }
190 
191 
193  inline auto
194  matchType (string typeID)
195  {
196  return [=](Entry const& entry)
197  {
198  return contains (entry.getType(), typeID);
199  };
200  }
201 
202 
204  inline auto
205  ensureAttribute (string key)
206  {
207  return [=](Entry const& entry)
208  {
209  return entry.hasAttribute(key);
210  };
211  }
212 
213 
215  inline auto
216  matchAttribute (string key, string valueMatch)
217  {
218  return [=](Entry const& entry)
219  {
220  return entry.hasAttribute(key)
221  and contains (entry.get(key), valueMatch);
222  };
223  }
224 
225 
226 
227  /* === configure the underlying search engine === */
228 
229  enum Direction {
230  FORWARD, BACKWARD, CURRENT
231  };
232 
233  template<typename COND>
234  inline void
235  attachNextSerchStep (Filter& solution, COND&& searchCond, Direction direction)
236  {
237  if (CURRENT == direction)
238  solution.search (forward<COND> (searchCond));
239  else
240  solution.addStep ([predicate{forward<COND> (searchCond)}, direction]
241  (auto& filter)
242  {
243  filter.reverse (BACKWARD == direction);
244  filter.disableFilter(); // deactivate any filtering temporarily
245  ++filter; // move one step in the required direction
246  filter.setNewFilter (predicate);
247  });
248  }
249 
250  template<typename COND>
251  inline void
252  refineSerach (Filter& solution, COND&& additionalCond)
253  {
254  solution.addStep ([predicate{forward<COND> (additionalCond)}]
255  (auto& filter)
256  {
257  filter.andFilter (predicate);
258  });
259  }
260 
261  }//(End) Elementary Matchers and handling of the search engine
262 
263 
264 
265 
266 
267  EventMatch::EventMatch (Log const& srcSeq)
268  : solution_{buildSearchFilter (srcSeq)}
269  , lastMatch_{"HEAD "+ solution_->get("this")}
270  , look_for_match_{true}
271  , violation_{}
272  { }
273 
274 
275 
280  bool
282  {
283  return not isnil (solution_);
284  }
285 
286 
296  void
297  EventMatch::evaluateQuery (string matchSpec, Literal rel)
298  {
299  if (look_for_match_ and not isnil (violation_)) return;
300  // already failed, no further check necessary
301 
302  if (foundSolution()) // NOTE this pulls the filter
303  {
304  lastMatch_ = matchSpec+" @ "+string(*solution_)
305  + (isnil(lastMatch_)? ""
306  : "\n.."+rel+" "+lastMatch_);
307  if (not look_for_match_)
308  violation_ = "FOUND at least "+lastMatch_;
309  }
310  else
311  {
312  if (look_for_match_)
313  violation_ = "FAILED to "+matchSpec
314  + "\n.."+rel
315  + " "+lastMatch_;
316  else
317  violation_ = "";
318  }
319  }
320 
321 
322 
329  EventMatch&
330  EventMatch::locate (string match)
331  {
332  attachNextSerchStep (solution_, find(match), CURRENT);
333  evaluateQuery ("match(\""+match+"\")");
334  return *this;
335  }
336 
340  EventMatch&
341  EventMatch::locateMatch (string regExp)
342  {
343  attachNextSerchStep (solution_, findRegExp(regExp), CURRENT);
344  evaluateQuery ("find-RegExp(\""+regExp+"\")");
345  return *this;
346  }
347 
353  EventMatch&
354  EventMatch::locateEvent (string match)
355  {
356  attachNextSerchStep (solution_, findEvent(match), CURRENT);
357  evaluateQuery ("match-event(\""+match+"\")");
358  return *this;
359  }
360 
361  EventMatch&
362  EventMatch::locateEvent (string classifier, string match)
363  {
364  attachNextSerchStep (solution_, findEvent(classifier,match), CURRENT);
365  evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")");
366  return *this;
367  }
368 
373  EventMatch&
374  EventMatch::locateCall (string match)
375  {
376  attachNextSerchStep (solution_, findCall(match), CURRENT);
377  evaluateQuery ("match-call(\""+match+"\")");
378  return *this;
379  }
380 
381 
382 
383 
392  EventMatch&
393  EventMatch::before (string match)
394  {
395  attachNextSerchStep (solution_, find(match), FORWARD);
396  evaluateQuery ("match(\""+match+"\")");
397  return *this;
398  }
399 
403  EventMatch&
404  EventMatch::beforeMatch (string regExp)
405  {
406  attachNextSerchStep (solution_, findRegExp(regExp), FORWARD);
407  evaluateQuery ("find-RegExp(\""+regExp+"\")");
408  return *this;
409  }
410 
425  EventMatch&
426  EventMatch::beforeEvent (string match)
427  {
428  attachNextSerchStep (solution_, findEvent(match), FORWARD);
429  evaluateQuery ("match-event(\""+match+"\")");
430  return *this;
431  }
432 
433  EventMatch&
434  EventMatch::beforeEvent (string classifier, string match)
435  {
436  attachNextSerchStep (solution_, findEvent(classifier,match), FORWARD);
437  evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")");
438  return *this;
439  }
440 
446  EventMatch&
447  EventMatch::beforeCall (string match)
448  {
449  attachNextSerchStep (solution_, findCall(match), FORWARD);
450  evaluateQuery ("match-call(\""+match+"\")");
451  return *this;
452  }
453 
454 
455 
456 
462  EventMatch&
463  EventMatch::after (string match)
464  {
465  attachNextSerchStep (solution_, find(match), BACKWARD);
466  evaluateQuery ("match(\""+match+"\")", "before");
467  return *this;
468  }
469 
470  EventMatch&
471  EventMatch::afterMatch (string regExp)
472  {
473  attachNextSerchStep (solution_, findRegExp(regExp), BACKWARD);
474  evaluateQuery ("find-RegExp(\""+regExp+"\")", "before");
475  return *this;
476  }
477 
478  EventMatch&
479  EventMatch::afterEvent (string match)
480  {
481  attachNextSerchStep (solution_, findEvent(match), BACKWARD);
482  evaluateQuery ("match-event(\""+match+"\")", "before");
483  return *this;
484  }
485 
486  EventMatch&
487  EventMatch::afterEvent (string classifier, string match)
488  {
489  attachNextSerchStep (solution_, findEvent(classifier,match), BACKWARD);
490  evaluateQuery ("match-event(ID=\""+classifier+"\", \""+match+"\")", "before");
491  return *this;
492  }
493 
497  EventMatch&
498  EventMatch::afterCall (string match)
499  {
500  attachNextSerchStep (solution_, findCall(match), BACKWARD);
501  evaluateQuery ("match-call(\""+match+"\")", "before");
502  return *this;
503  }
504 
505 
506 
510  void
511  EventMatch::refineSerach_matchArgument (size_t idx, string match)
512  {
513  refineSerach (solution_, matchArgument(idx, match));
514  evaluateQuery ("match-argument(["+util::toString(idx)+"]="+match+")");
515  }
516 
520  void
522  {
523  string argList(util::join(argSeq));
524  refineSerach (solution_, matchArguments(move(argSeq)));
525  evaluateQuery ("match-arguments("+argList+")");
526  }
527 
535  void
536  EventMatch::refineSerach_matchArgsRegExp (RExSeq&& regExpSeq, string rendered_regExps)
537  {
538  refineSerach (solution_, matchArgsRegExp (move (regExpSeq)));
539  evaluateQuery ("match-args-RegExp("+rendered_regExps+")");
540  }
541 
542 
543 
547  EventMatch&
548  EventMatch::type (string typeID)
549  {
550  refineSerach (solution_, matchType(typeID));
551  evaluateQuery ("match-type("+typeID+")");
552  return *this;
553  }
554 
558  EventMatch&
560  {
561  refineSerach (solution_, ensureAttribute(key));
562  evaluateQuery ("ensure-attribute("+key+")");
563  return *this;
564  }
565 
569  EventMatch&
570  EventMatch::attrib (string key, string valueMatch)
571  {
572  refineSerach (solution_, matchAttribute(key,valueMatch));
573  evaluateQuery ("match-attribute("+key+"=\""+valueMatch+"\")");
574  return *this;
575  }
576 
580  EventMatch&
581  EventMatch::id (string classifier)
582  {
583  refineSerach (solution_, matchAttribute("ID",classifier));
584  evaluateQuery ("match-ID(\""+classifier+"\")");
585  return *this;
586  }
587 
591  EventMatch&
592  EventMatch::on (string targetID)
593  {
594  refineSerach (solution_, matchAttribute("this",targetID));
595  evaluateQuery ("match-this(\""+targetID+"\")");
596  return *this;
597  }
598 
599  EventMatch&
600  EventMatch::on (const char* targetID)
601  {
602  refineSerach (solution_, matchAttribute("this",targetID));
603  evaluateQuery ("match-this(\""+string(targetID)+"\")");
604  return *this;
605  }
606 
607 
608 
609 
610 
611  /* ==== EventLog implementation ==== */
612 
613  EventLog::EventLog (string logID)
614  : log_(new Log)
615  {
616  log({"type=EventLogHeader", "this="+logID});
617  }
618 
619 
633  EventLog&
635  {
636  Log& target = *otherLog.log_;
637  target.reserve (target.size() + log_->size() + 1);
638  target.emplace_back (log_->front());
639  auto p = log_->begin();
640  while (++p != log_->end()) // move our log's content into the other log
641  target.emplace_back (std::move(*p));
642  this->log_->resize(1);
643  this->log({"type=joined", otherLog.getID()}); // leave a tag to indicate
644  otherLog.log({"type=logJoin", this->getID()}); // where the `joinInto` took place,
645  this->log_ = otherLog.log_; // connect this to the other storage
646  return *this;
647  }
648 
649 
650 
651  EventLog&
653  {
654  string originalLogID = this->getID();
655  return this->clear (originalLogID);
656  }
657 
666  EventLog&
667  EventLog::clear (string alteredLogID)
668  {
669  log_.reset (new Log);
670  log({"type=EventLogHeader", "this="+alteredLogID});
671  return *this;
672  }
673 
674  EventLog&
675  EventLog::clear (const char* alteredLogID)
676  {
677  return clear (string{alteredLogID});
678  }
679 
680 
681 
682  /* ==== Logging API ==== */
683 
684  EventLog&
685  EventLog::event (string text)
686  {
687  log ("event", ArgSeq{}, ArgSeq{text}); // we use this ctor variant to ensure
688  return *this; // that text is not misinterpreted as attribute,
689  } // which might happen when text contains a '='
690 
691  EventLog&
692  EventLog::event (string classifier, string text)
693  {
694  log ("event", ArgSeq{"ID="+classifier}, ArgSeq{text});
695  return *this;
696  }
697 
698  EventLog&
699  EventLog::call (string target, string function)
700  {
701  return call(target, function, ArgSeq());
702  }
703 
704  EventLog&
705  EventLog::call (string target, string function, ArgSeq&& args)
706  {
707  log ("call", ArgSeq{"fun="+function, "this="+target}, std::forward<ArgSeq>(args));
708  return *this;
709  }
710 
711  EventLog&
712  EventLog::call (const char* target, const char* function, ArgSeq&& args)
713  {
714  return call (string(target), string(function), std::forward<ArgSeq>(args));
715  }
716 
717 
718  EventLog&
719  EventLog::warn (string text)
720  {
721  log({"type=warn", text});
722  return *this;
723  }
724 
725  EventLog&
726  EventLog::error (string text)
727  {
728  log({"type=error", text});
729  return *this;
730  }
731 
732  EventLog&
733  EventLog::fatal (string text)
734  {
735  log({"type=fatal", text});
736  return *this;
737  }
738 
739 
740 
741  EventLog&
742  EventLog::create (string text)
743  {
744  log({"type=create", text});
745  return *this;
746  }
747 
748  EventLog&
749  EventLog::destroy (string text)
750  {
751  log({"type=destroy", text});
752  return *this;
753  }
754 
755 
756 
757 
758  /* ==== Query/Verification API ==== */
759 
760  EventMatch
761  EventLog::verify (string match) const
762  {
763  EventMatch matcher(*log_);
764  matcher.locate (match); // new matcher starts linear search from first log element
765  return matcher;
766  }
767 
768 
769  EventMatch
770  EventLog::verifyMatch (string regExp) const
771  {
772  EventMatch matcher(*log_);
773  matcher.locateMatch (regExp);
774  return matcher;
775  }
776 
777 
778  EventMatch
779  EventLog::verifyEvent (string match) const
780  {
781  EventMatch matcher(*log_);
782  matcher.locateEvent (match);
783  return matcher;
784  }
785 
786 
787  EventMatch
788  EventLog::verifyEvent (string classifier, string match) const
789  {
790  EventMatch matcher(*log_);
791  matcher.locateEvent (classifier, match);
792  return matcher;
793  }
794 
795 
796  EventMatch
797  EventLog::verifyCall (string match) const
798  {
799  EventMatch matcher(*log_);
800  matcher.locateCall (match);
801  return matcher;
802  }
803 
804 
813  EventMatch
814  EventLog::ensureNot (string match) const
815  {
816  EventMatch matcher(*log_);
817  matcher.look_for_match_ = false; // flip logic; fail if match succeeds
818  matcher.locate (match);
819  return matcher;
820  }
821 
822 
823 
824 }} // namespace lib::test
void refineSerach_matchArgument(size_t idx, string match)
Definition: event-log.cpp:511
EventMatch & type(string typeID)
refine filter to additionally require a matching log entry type
Definition: event-log.cpp:548
EventLog & event(string text)
log some text as event
Definition: event-log.cpp:685
EventMatch & locateCall(string match)
basic search for some specific function invocation
Definition: event-log.cpp:374
auto ensureAttribute(string key)
refinement filter to ensure a specific attribute is present on the log entry
Definition: event-log.cpp:205
string violation_
record when the underlying query has failed
Definition: event-log.hpp:147
Filter solution_
match predicate evaluator
Definition: event-log.hpp:138
EventLog & warn(string text)
Log a warning entry.
Definition: event-log.cpp:719
Support for verifying the occurrence of events from unit tests.
bool filter(Placement< DummyMO > const &candidate)
a filter predicate to pick some objects from a resultset.
AnyPair entry(Query< TY > const &query, typename WrapReturn< TY >::Wrapper &obj)
helper to simplify creating mock table entries, wrapped correctly
Definition: run.hpp:49
EventMatch & beforeCall(string match)
find a match for some function invocation after the current point of reference
Definition: event-log.cpp:447
inline string literal This is a marker type to indicate that
Definition: symbol.hpp:85
auto stringify(IT &&src)
convert to string as transforming step in a pipeline
Helper to log and verify the occurrence of events.
Definition: event-log.hpp:284
EventMatch & beforeMatch(string regExp)
find a match with the given regular expression
Definition: event-log.cpp:404
EventLog & create(string text)
Log the creation of an object.
Definition: event-log.cpp:742
EventMatch & after(string match)
find a match (substring match) of the given text in an EventLog entry before the current position...
Definition: event-log.cpp:463
EventMatch & locateMatch(string regExp)
basic search like locate() but with the given regular expression
Definition: event-log.cpp:341
EventMatch verifyEvent(string match) const
start a query to match for some event.
Definition: event-log.cpp:779
EventMatch & afterCall(string match)
find a function invocation backwards, before the current point of reference
Definition: event-log.cpp:498
Implementation namespace for support and library code.
auto matchArgsRegExp(RExSeq &&regExpSeq)
refinement filter, to cover all arguments by regular expression(s)
Definition: event-log.cpp:175
void evaluateQuery(string matchSpec, Literal rel="after")
this is actually called after each refinement of the filter and matching conditions.
Definition: event-log.cpp:297
EventMatch & attrib(string key, string valueMatch)
refine filter to additionally match on a specific attribute
Definition: event-log.cpp:570
EventMatch(Log const &srcSeq)
Definition: event-log.cpp:267
auto matchArgument(size_t idx, string match)
refinement filter to match a specific positional argument
Definition: event-log.cpp:155
Token or Atom with distinct identity.
Definition: symbol.hpp:126
EventMatch verifyCall(string match) const
start a query to match especially a function call
Definition: event-log.cpp:797
EventMatch ensureNot(string match) const
start a query to ensure the given expression does not match.
Definition: event-log.cpp:814
auto matchAttribute(string key, string valueMatch)
refinement filter to ensure a specific attribute is present on the log entry
Definition: event-log.cpp:216
bool foundSolution()
core of the evaluation machinery: apply a filter predicate and then pull through the log to find a ac...
Definition: event-log.cpp:281
EventMatch & beforeEvent(string match)
find a match for an "event" after the current point of reference
Definition: event-log.cpp:426
EventLog & clear()
purge log contents while retaining just the original Header-ID
Definition: event-log.cpp:652
auto matchArguments(ArgSeq &&argSeq)
this filter functor is for refinement of an existing filter
Definition: event-log.cpp:138
EventLog & call(string target, string function)
Log occurrence of a function call with no arguments.
Definition: event-log.cpp:699
EventLog & joinInto(EventLog &otherLog)
Merge this log into another log, forming a combined log.
Definition: event-log.cpp:634
bool look_for_match_
support for positive and negative queries.
Definition: event-log.hpp:144
EventMatch & id(string classifier)
refine filter to additionally match on the ID attribute
Definition: event-log.cpp:581
void refineSerach_matchArguments(ArgSeq &&argSeq)
Definition: event-log.cpp:521
Lumiera error handling (C++ interface).
EventMatch & locate(string match)
basic search function: continue linear lookup over the elements of the EventLog to find a match (subs...
Definition: event-log.cpp:330
EventLog & fatal(string text)
Log a fatal failure.
Definition: event-log.cpp:733
EventMatch verify(string match) const
start a query to match for some substring.
Definition: event-log.cpp:761
void refineSerach_matchArgsRegExp(RExSeq &&regExpSeq, string rendered_regExps)
Definition: event-log.cpp:536
EventLog & destroy(string text)
Log the destruction of an object.
Definition: event-log.cpp:749
EventMatch verifyMatch(string regExp) const
start a query to match with a regular expression
Definition: event-log.cpp:770
EventMatch & on(string targetID)
refine filter to additionally match the &#39;this&#39; attribute
Definition: event-log.cpp:592
auto matchType(string typeID)
refinement filter to match on the given typeID
Definition: event-log.cpp:194
EventMatch & key(string key)
refine filter to additionally require the presence an attribute
Definition: event-log.cpp:559
string lastMatch_
record last match for diagnostics
Definition: event-log.hpp:141
EventMatch & before(string match)
find a match (substring match) of the given text in an EventLog entry after the current position ...
Definition: event-log.cpp:393
string getID() const
Definition: event-log.hpp:326
bool contains(SEQ const &cont, typename SEQ::const_reference val)
shortcut for brute-force containment test in any sequential container
Definition: util.hpp:255
EventMatch & locateEvent(string match)
basic search for a matching "event"
Definition: event-log.cpp:354
EventLog & error(string text)
Log an error note.
Definition: event-log.cpp:726