Lumiera  0.pre.03
»edit your freedom«
activity-detector.hpp
Go to the documentation of this file.
1 /*
2  ACTIVITY-DETECTOR.hpp - test scaffolding to observe activities within the scheduler
3 
4  Copyright (C)
5  2023, 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 
61 #ifndef VAULT_GEAR_TEST_ACTIVITY_DETECTOR_H
62 #define VAULT_GEAR_TEST_ACTIVITY_DETECTOR_H
63 
64 
65 #include "vault/common.hpp"
66 #include "lib/test/test-helper.hpp"
67 #include "lib/test/event-log.hpp"
68 
69 #include "vault/gear/job.h"
70 #include "vault/gear/activity.hpp"
72 #include "lib/time/timevalue.hpp"
74 #include "lib/meta/function.hpp"
75 #include "lib/wrapper.hpp"
76 #include "lib/format-util.hpp"
77 #include "lib/random.hpp"
78 #include "lib/util.hpp"
79 
80 #include <functional>
81 #include <utility>
82 #include <string>
83 #include <deque>
84 
85 
86 namespace vault{
87 namespace gear {
88 namespace test {
89 
90  using std::string;
91  using std::function;
93  using lib::time::Time;
94  using lib::time::FSecs;
95  using lib::time::Offset;
97  using util::unConst;
98  using util::isnil;
99  using lib::rani;
100  using std::forward;
101  using std::move;
102 
103 
104  namespace {// Diagnostic markers
105  const string MARK_INC{"IncSeq"};
106  const string MARK_SEQ{"Seq"};
107 
108  using SIG_JobDiagnostic = void(Time, int32_t);
109  const size_t JOB_ARG_POS_TIME = 0;
110 
111  const string CTX_POST{"CTX-post"};
112  const string CTX_WORK{"CTX-work"};
113  const string CTX_DONE{"CTX-done"};
114  const string CTX_TICK{"CTX-tick"};
115 
117  }
118 
119  class ActivityDetector;
120 
121 
129  : private lib::test::EventMatch
130  {
132 
134  : _Parent{move (matcher)}
135  { }
136 
137  friend class ActivityDetector;
138 
139  public:
140  // standard copy acceptable
141 
146  operator bool() const { return _Parent::operator bool(); }
147 
148 
149  /* query builder(s) to find a match stepping forwards */
150  ActivityMatch& beforeInvocation (string match) { return delegate (&EventMatch::beforeCall, move(match)); }
151  // more here...
152 
153  /* query builders to find a match stepping backwards */
154  ActivityMatch& afterInvocation (string match) { return delegate (&EventMatch::afterCall, move(match)); }
155  // more here...
156 
157 
159  template<typename...ARGS>
161  arg (ARGS const& ...args)
162  {
163  return delegate (&EventMatch::arg<ARGS...>, args...);
164  }
165 
168  seq (uint seqNr)
169  {
170  _Parent::attrib (MARK_SEQ, util::toString (seqNr));
171  return *this;
172  }
173 
176  beforeSeqIncrement (uint seqNr)
177  {
178  _Parent::beforeEvent(MARK_INC, util::toString(seqNr));
179  return *this;
180  }
182  afterSeqIncrement (uint seqNr)
183  {
184  _Parent::afterEvent(MARK_INC, util::toString(seqNr));
185  return *this;
186  }
187 
190  timeArg (Time const& time)
191  {
192  return delegate (&EventMatch::argPos<Time const&>, size_t(JOB_ARG_POS_TIME), time);
193  }
194 
195 
196  private:
202  template<typename...ARGS>
204  delegate (_Parent& (_Parent::*fun) (ARGS...), ARGS&& ...args)
205  {
206  return static_cast<ActivityMatch&> (
207  (this->*fun) (forward<ARGS> (args)...));
208  }
209  };
210 
211 
212 
222  {
224 
225  EventLog eventLog_;
226  uint invocationSeq_;
227 
231  template<typename RET, typename...ARGS>
233  {
235  using ImplFun = std::function<RET(ARGS...)>;
236 
237  string id_;
238  EventLog* log_;
239  uint const* seqNr_;
240  ImplFun implFun_;
241  RetVal retVal_;
242 
243  public:
244  DiagnosticFun (string id, EventLog& masterLog, uint const& invocationSeqNr)
245  : id_{id}
246  , log_{&masterLog}
247  , seqNr_{&invocationSeqNr}
248  , implFun_{}
249  , retVal_{}
250  {
251  retVal_.defaultInit();
252  }
253 
255  template<typename VAL>
256  DiagnosticFun&&
257  returning (VAL&& riggedResponse)
258  {
259  retVal_ = std::forward<VAL> (riggedResponse);
260  return std::move (*this);
261  }
262 
264  template<class FUN>
265  DiagnosticFun&&
266  implementedAs (FUN&& customImpl)
267  {
268  implFun_ = std::forward<FUN> (customImpl);
269  return std::move (*this);
270  }
271 
272  // default copyable
273 
275  RET
276  operator() (ARGS ...args) const
277  {
278  log_->call (log_->getID(), id_, args...)
279  .addAttrib (MARK_SEQ, util::toString(*seqNr_));
280  return implFun_? implFun_(std::forward<ARGS>(args)...)
281  : *retVal_;
282  }
283 
284  operator string() const
285  {
286  return log_->getID()+"."+id_;
287  }
288  };
289 
291  template<typename SIG>
293  {
294  using Ret = typename lib::meta::_Fun<SIG>::Ret;
295  using Args = typename lib::meta::_Fun<SIG>::Args;
296  using ArgsX = typename lib::meta::StripNullType<Args>::Seq;
297  using SigTypes = typename lib::meta::Prepend<Ret, ArgsX>::Seq;
298 
299  using Type = typename RebindVariadic<DiagnosticFun, SigTypes>::Type;
300  };
301 
302  using Logger = _DiagnosticFun<void(string)>::Type;
303 
304 
309  : public NopJobFunctor
310  {
311  using MockOp = typename _DiagnosticFun<SIG_JobDiagnostic>::Type;
312 
313  MockOp mockOperation_;
314 
318  void
319  invokeJobOperation (JobParameter param) override
320  {
321  mockOperation_(Time{TimeValue{param.nominalTime}}, param.invoKey.part.a);
322  }
323 
324  string diagnostic() const override
325  {
326  return "JobFun-"+string{mockOperation_};
327  }
328 
329  JobKind
330  getJobKind() const
331  {
332  return TEST_JOB;
333  }
334 
335  public:
336  MockJobFunctor (MockOp mockedJobOperation)
337  : mockOperation_{move (mockedJobOperation)}
338  { }
339  };
340 
341 
346  : public Activity
347  , public activity::Hook
348  {
349  Logger log_;
350  TimeVar invoked_{Time::ANYTIME};
351 
352  Activity*
353  target()
354  {
355  return reinterpret_cast<Activity*> (data_.callback.arg);
356  }
357 
358  Activity const*
359  target() const
360  {
361  return unConst(this)->target();
362  }
363 
365  activation ( Activity& thisHook
366  , Time now
367  , void* executionCtx) override
368  {
369  REQUIRE (thisHook.is (Activity::HOOK));
370  invoked_ = now;
371  if (not target())
372  {// no adapted target; just record this activation
373  log_(util::toString(now) + " ⧐ ");
374  return activity::PASS;
375  }
376  else
377  {// forward activation to the adapted target Activity
378  auto ctx = *static_cast<FakeExecutionCtx*> (executionCtx);
379  log_(util::toString(now) + " ⧐ " + util::toString (*target()));
380  return target()->activate (now, ctx);
381  }
382  }
383 
385  notify ( Activity& thisHook
386  , Time now
387  , void* executionCtx) override
388  {
389  REQUIRE (thisHook.is (Activity::HOOK));
390  invoked_ = now;
391  if (not target())
392  {// no adapted target; just record this notification
393  log_(util::toString(now) + " --notify-↯• ");
394  return activity::PASS;
395  }
396  else
397  {// forward notification-dispatch to the adapted target Activity
398  auto ctx = *static_cast<FakeExecutionCtx*> (executionCtx);
399  log_(util::toString(now) + " --notify-↯> " + util::toString (*target()));
400  return target()->dispatch (now, ctx);
401  }
402  }
403 
404  Time
405  getDeadline() const override
406  {
407  if (target() and target()->is(Activity::GATE))
408  return target()->data_.condition.getDeadline();
409  else
410  return Time::NEVER;
411  }
412 
413  std::string
414  diagnostic() const override
415  {
416  return "Probe("+string{log_}+")";
417  }
418 
419  public:
420  ActivityProbe (string id, EventLog& masterLog, uint const& invocationSeqNr)
421  : Activity{*this, 0}
422  , log_{id, masterLog, invocationSeqNr}
423  { }
424 
425  ActivityProbe (Activity const& subject, string id, EventLog& masterLog, uint const& invocationSeqNr)
426  : Activity{*this, reinterpret_cast<size_t> (&subject)}
427  , log_{id, masterLog, invocationSeqNr}
428  {
429  next = subject.next;
430  }
431 
432  operator string() const
433  {
434  return diagnostic();
435  }
436 
437 
438  static Time
439  lastInvoked (Activity const* act)
440  {
441  if (act and act->verb_ == HOOK)
442  {
443  ActivityProbe* probe = dynamic_cast<ActivityProbe*> (act->data_.callback.hook);
444  if (probe)
445  return probe->invoked_;
446  }
447  return Time::NEVER;
448  }
449  };
450 
451 
452  /* ===== Maintain throw-away mock instances ===== */
453 
454  std::deque<MockJobFunctor> mockOps_{};
455  std::deque<ActivityProbe> mockActs_{};
456 
457 
458  public:
459  ActivityDetector(string id ="")
460  : eventLog_{"ActivityDetector" + (isnil (id)? string{}: "("+id+")")}
461  , invocationSeq_{0}
462  { }
463 
464  operator string() const
465  {
466  return util::join (eventLog_);
467  }
468 
469  string
470  showLog() const
471  {
472  return "\n____Event-Log___________________________\n"
473  + util::join (eventLog_, "\n")
474  + "\n────╼━━━━━━━━╾──────────────────────────"
475  ;
476  }
477 
478  void
479  clear(string newID)
480  {
481  if (isnil (newID))
482  eventLog_.clear();
483  else
484  eventLog_.clear (newID);
485  }
486 
488  uint
490  {
491  ++invocationSeq_;
492  eventLog_.event (MARK_INC, util::toString(invocationSeq_));
493  return invocationSeq_;
494  }
495 
496  uint
497  currSeq() const
498  {
499  return invocationSeq_;
500  }
501 
502 
509  template<typename SIG>
510  auto
511  buildDiagnosticFun (string id)
512  {
513  using Functor = typename _DiagnosticFun<SIG>::Type;
514  return Functor{id, eventLog_, invocationSeq_};
515  }
516 
517  JobClosure&
518  buildMockJobFunctor (string id)
519  {
520  return mockOps_.emplace_back (
521  buildDiagnosticFun<SIG_JobDiagnostic> (id));
522  }
523 
524  Job
525  buildMockJob (string id =""
526  ,Time nominal = lib::test::randTime()
527  ,size_t extra = rani())
528  {
529  InvocationInstanceID invoKey;
530  invoKey.part.a = extra;
531  invoKey.part.t = _raw(nominal);
532  return Job{buildMockJobFunctor (isnil(id)? "mockJob-"+util::toString(nominal) : id)
533  ,invoKey
534  ,nominal};
535  }
536 
538  Activity&
540  {
541  return mockActs_.emplace_back (id, eventLog_, invocationSeq_);
542  }
543 
545  Activity&
546  buildActivationTap (Activity const& subject, string id ="")
547  {
548  return mockActs_.emplace_back (subject
549  ,isnil(id)? "tap-"+subject.showVerb()+util::showAdr(subject)
550  : id
551  ,eventLog_
552  ,invocationSeq_);
553  }
554 
556  Activity&
557  insertActivationTap (Activity*& wiring, string id ="")
558  {
559  wiring = wiring? & buildActivationTap (*wiring, id)
560  : & buildActivationProbe (isnil(id)? "tail-"+util::showAdr(&wiring) : id);
561  return *wiring;
562  }
563 
564  Activity&
565  buildGateWatcher (Activity& gate, string id ="")
566  {
567  insertActivationTap (gate.next, "after-" + (isnil(id)? gate.showVerb()+util::showAdr(gate) : id));
568  return buildActivationTap (gate, id);
569  }
570 
571  Activity&
572  watchGate (Activity*& wiring, string id ="")
573  {
574  wiring = wiring? & buildGateWatcher (*wiring, id)
575  : & buildActivationProbe (isnil(id)? "tail-"+util::showAdr(&wiring) : id);
576  return *wiring;
577  }
578 
579 
580  Time invokeTime (Activity const* hook) { return ActivityProbe::lastInvoked (hook); }
581  bool wasInvoked (Activity const* hook) { return invokeTime(hook).isRegular(); }
582  Time invokeTime (Activity const& hook) { return invokeTime (&hook); }
583  bool wasInvoked (Activity const& hook) { return wasInvoked (&hook); }
584 
585 
586  struct FakeExecutionCtx;
587  using SIG_post = activity::Proc(Time, Time, Activity*, FakeExecutionCtx&);
588  using SIG_work = void(Time, size_t);
589  using SIG_done = void(Time, size_t);
590  using SIG_tick = activity::Proc(Time);
591 
600  {
605 
606  function<Time()> getSchedTime = [this]{ return SCHED_TIME_MARKER;};
607 
609  : post{detector.buildDiagnosticFun<SIG_post>(CTX_POST).returning(activity::PASS)}
610  , work{detector.buildDiagnosticFun<SIG_work>(CTX_WORK)}
611  , done{detector.buildDiagnosticFun<SIG_done>(CTX_DONE)}
612  , tick{detector.buildDiagnosticFun<SIG_tick>(CTX_TICK).returning(activity::PASS)}
613  { }
614 
615  operator string() const { return "≺test::CTX≻"; }
616  };
617 
618  FakeExecutionCtx executionCtx{*this};
619 
620 
621 
623  verifyInvocation (string fun)
624  {
625  return ActivityMatch{move (eventLog_.verifyCall(fun))};
626  }
627 
629  ensureNoInvocation (string fun)
630  {
631  return ActivityMatch{move (eventLog_.ensureNot(fun).locateCall(fun))};
632  }
633 
635  verifySeqIncrement (uint seqNr)
636  {
637  return ActivityMatch{move (eventLog_.verifyEvent(MARK_INC, util::toString(seqNr)))};
638  }
639 
640 
641  private:
642  };
643 
644 
645 }}} // namespace vault::gear::test
646 #endif /*VAULT_GEAR_TEST_ACTIVITY_DETECTOR_H*/
static const Time ANYTIME
border condition marker value. ANYTIME <= any time value
Definition: timevalue.hpp:313
DiagnosticFun && returning(VAL &&riggedResponse)
prepare a response value to return from the mock invocation
a mutable time value, behaving like a plain number, allowing copy and re-accessing ...
Definition: timevalue.hpp:232
EventLog & event(string text)
log some text as event
Definition: event-log.cpp:676
EventMatch & locateCall(string match)
basic search for some specific function invocation
Definition: event-log.cpp:365
Record to describe an Activity, to happen within the Scheduler&#39;s control flow.
Definition: activity.hpp:226
DiagnosticFun && implementedAs(FUN &&customImpl)
use the given λ to provide (optional) implementation logic
Generic implementation of a JobFunctor to perform no calculations.
void invokeJobOperation(JobParameter param) override
rigged diagnostic implementation of job invocation
Support for verifying the occurrence of events from unit tests.
Metaprogramming helper to transfer variadic arguments.
Definition: run.hpp:40
Any copy and copy construction prohibited.
Definition: nocopy.hpp:37
Activity & insertActivationTap(Activity *&wiring, string id="")
build ActivationProbe to record each activation before passing it to the subject
Helper to log and verify the occurrence of events.
Definition: event-log.hpp:275
int rani(uint bound=_iBOUND())
Definition: random.hpp:135
Helper for uniform access to function signature types.
Definition: function.hpp:99
A rigged CALLBACK-Activity to watch passing of activations.
Helper: prepend a type to an existing type sequence, thus shifting all elements within the sequence t...
activity::Proc activation(Activity &thisHook, Time now, void *executionCtx) override
Callback on activation of the corresponding HOOK-Activity.
EventMatch verifyEvent(string match) const
start a query to match for some event.
Definition: event-log.cpp:770
A Mock functor, logging all invocations into the EventLog.
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:299
ItemWrapper & defaultInit()
implant a default-initialised instance of the payload type
Definition: wrapper.hpp:233
EventMatch & attrib(string key, string valueMatch)
refine filter to additionally match on a specific attribute
Definition: event-log.cpp:561
temporary workaround: strip trailing NullType entries from a type sequence, to make it compatible wit...
auto buildDiagnosticFun(string id)
Generic testing helper: build a λ-mock, logging all invocations.
JobKind
Definition: job.h:62
EventMatch verifyCall(string match) const
start a query to match especially a function call
Definition: event-log.cpp:788
ActivityMatch & seq(uint seqNr)
qualifier: additionally require the indicated sequence number
EventMatch ensureNot(string match) const
start a query to ensure the given expression does not match.
Definition: event-log.cpp:805
Diagnostic context to record and evaluate activations within the Scheduler.
Metaprogramming tools for transforming functor types.
EventMatch & beforeEvent(string match)
find a match for an "event" after the current point of reference
Definition: event-log.cpp:417
EventLog & clear()
purge log contents while retaining just the original Header-ID
Definition: event-log.cpp:643
Extension point to invoke a callback from Activity activation.
Definition: activity.hpp:153
ActivityMatch & delegate(_Parent &(_Parent::*fun)(ARGS...), ARGS &&...args)
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
EventLog & call(string target, string function)
Log occurrence of a function call with no arguments.
Definition: event-log.cpp:690
Activity & buildActivationTap(Activity const &subject, string id="")
build ActivationProbe to record each activation before passing it to the subject
boost::rational< int64_t > FSecs
rational representation of fractional seconds
Definition: timevalue.hpp:220
A collection of frequently used helper functions to support unit testing.
test and diagnostic and research
Definition: job.h:67
Activity & buildActivationProbe(string id)
build a rigged HOOK-Activity to record each invocation
uint incrementSeq()
increment the internal invocation sequence number
probe window + count-down; activate next Activity, else re-schedule
Definition: activity.hpp:236
Activity * next
Activities are organised into chains to represent relations based on verbs.
Definition: activity.hpp:249
EventMatch & id(string classifier)
refine filter to additionally match on the ID attribute
Definition: event-log.cpp:572
ActivityMatch & beforeSeqIncrement(uint seqNr)
special query to match an increment of the sequence number
Definition of a render job.
opaque ID attached to each individual job invocation.
Definition: job.h:103
Basic set of definitions and includes commonly used together (Vault).
static const Time NEVER
border condition marker value. NEVER >= any time value
Definition: timevalue.hpp:314
Interface of the closure for frame rendering jobs.
Definition: job.h:235
Offset measures a distance in time.
Definition: timevalue.hpp:358
EventLog & addAttrib(string const &key, X &&initialiser, ARGS &&...args)
Qualify the latest entry: set further attribute(s)
Definition: event-log.hpp:431
Generating (pseudo) random numbers with controlled seed.
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
A Mocked job operation to detect any actual invocation.
Mock setup of the execution context for Activity activation.
Proc
Result instruction from Activity activation.
Definition: activity.hpp:140
invoke an extension point through the activity::Hook interface
Definition: activity.hpp:239
Individual frame rendering task, forwarding to a closure.
Definition: job.h:268
ActivityMatch & timeArg(Time const &time)
qualifier: additionally match the nominal time argument of JobFunctor invocation
a family of time value like entities and their relationships.
basic constant internal time value.
Definition: timevalue.hpp:133
Time SCHED_TIME_MARKER
marker value for "current scheduler time" used in tests
ActivityMatch & arg(ARGS const &...args)
qualifier: additionally match the function arguments
Vault-Layer implementation namespace root.
Metaprogramming with type sequences based on variadic template parameters.
activity::Proc notify(Activity &thisHook, Time now, void *executionCtx) override
Callback when dispatching a NOTIFY-Activity to thisHook.
Library implementation: smart-pointer variations, wrappers and managing holders.
string getID() const
Definition: event-log.hpp:317
Stub/Test implementation of the JobFunctor interface for a render job to do nothing at all ...
Descriptor for a piece of operational logic performed by the scheduler.