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) Lumiera.org
5  2023, 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 
70 #ifndef VAULT_GEAR_TEST_ACTIVITY_DETECTOR_H
71 #define VAULT_GEAR_TEST_ACTIVITY_DETECTOR_H
72 
73 
74 #include "vault/common.hpp"
75 #include "lib/test/test-helper.hpp"
76 #include "lib/test/event-log.hpp"
77 
78 #include "vault/gear/job.h"
79 #include "vault/gear/activity.hpp"
81 #include "lib/time/timevalue.hpp"
83 #include "lib/meta/function.hpp"
84 #include "lib/wrapper.hpp"
85 #include "lib/format-util.hpp"
86 #include "lib/util.hpp"
87 
88 #include <functional>
89 #include <utility>
90 #include <string>
91 #include <deque>
92 
93 
94 namespace vault{
95 namespace gear {
96 namespace test {
97 
98  using std::string;
99  using std::function;
100  using lib::time::TimeValue;
101  using lib::time::Time;
102  using lib::time::FSecs;
103  using lib::time::Offset;
105  using util::unConst;
106  using util::isnil;
107  using std::forward;
108  using std::move;
109 
110 
111  namespace {// Diagnostic markers
112  const string MARK_INC{"IncSeq"};
113  const string MARK_SEQ{"Seq"};
114 
115  using SIG_JobDiagnostic = void(Time, int32_t);
116  const size_t JOB_ARG_POS_TIME = 0;
117 
118  const string CTX_POST{"CTX-post"};
119  const string CTX_WORK{"CTX-work"};
120  const string CTX_DONE{"CTX-done"};
121  const string CTX_TICK{"CTX-tick"};
122 
124  }
125 
126  class ActivityDetector;
127 
128 
136  : private lib::test::EventMatch
137  {
139 
141  : _Parent{move (matcher)}
142  { }
143 
144  friend class ActivityDetector;
145 
146  public:
147  // standard copy acceptable
148 
153  operator bool() const { return _Parent::operator bool(); }
154 
155 
156  /* query builder(s) to find a match stepping forwards */
157  ActivityMatch& beforeInvocation (string match) { return delegate (&EventMatch::beforeCall, move(match)); }
158  // more here...
159 
160  /* query builders to find a match stepping backwards */
161  ActivityMatch& afterInvocation (string match) { return delegate (&EventMatch::afterCall, move(match)); }
162  // more here...
163 
164 
166  template<typename...ARGS>
168  arg (ARGS const& ...args)
169  {
170  return delegate (&EventMatch::arg<ARGS...>, args...);
171  }
172 
175  seq (uint seqNr)
176  {
177  _Parent::attrib (MARK_SEQ, util::toString (seqNr));
178  return *this;
179  }
180 
183  beforeSeqIncrement (uint seqNr)
184  {
185  _Parent::beforeEvent(MARK_INC, util::toString(seqNr));
186  return *this;
187  }
189  afterSeqIncrement (uint seqNr)
190  {
191  _Parent::afterEvent(MARK_INC, util::toString(seqNr));
192  return *this;
193  }
194 
197  timeArg (Time const& time)
198  {
199  return delegate (&EventMatch::argPos<Time const&>, size_t(JOB_ARG_POS_TIME), time);
200  }
201 
202 
203  private:
209  template<typename...ARGS>
211  delegate (_Parent& (_Parent::*fun) (ARGS...), ARGS&& ...args)
212  {
213  return static_cast<ActivityMatch&> (
214  (this->*fun) (forward<ARGS> (args)...));
215  }
216  };
217 
218 
219 
229  {
231 
232  EventLog eventLog_;
233  uint invocationSeq_;
234 
238  template<typename RET, typename...ARGS>
240  {
242  using ImplFun = std::function<RET(ARGS...)>;
243 
244  string id_;
245  EventLog* log_;
246  uint const* seqNr_;
247  ImplFun implFun_;
248  RetVal retVal_;
249 
250  public:
251  DiagnosticFun (string id, EventLog& masterLog, uint const& invocationSeqNr)
252  : id_{id}
253  , log_{&masterLog}
254  , seqNr_{&invocationSeqNr}
255  , implFun_{}
256  , retVal_{}
257  {
258  retVal_.defaultInit();
259  }
260 
262  template<typename VAL>
263  DiagnosticFun&&
264  returning (VAL&& riggedResponse)
265  {
266  retVal_ = std::forward<VAL> (riggedResponse);
267  return std::move (*this);
268  }
269 
271  template<class FUN>
272  DiagnosticFun&&
273  implementedAs (FUN&& customImpl)
274  {
275  implFun_ = std::forward<FUN> (customImpl);
276  return std::move (*this);
277  }
278 
279  // default copyable
280 
282  RET
283  operator() (ARGS ...args) const
284  {
285  log_->call (log_->getID(), id_, args...)
286  .addAttrib (MARK_SEQ, util::toString(*seqNr_));
287  return implFun_? implFun_(std::forward<ARGS>(args)...)
288  : *retVal_;
289  }
290 
291  operator string() const
292  {
293  return log_->getID()+"."+id_;
294  }
295  };
296 
298  template<typename SIG>
300  {
301  using Ret = typename lib::meta::_Fun<SIG>::Ret;
302  using Args = typename lib::meta::_Fun<SIG>::Args;
303  using ArgsX = typename lib::meta::StripNullType<Args>::Seq;
304  using SigTypes = typename lib::meta::Prepend<Ret, ArgsX>::Seq;
305 
306  using Type = typename RebindVariadic<DiagnosticFun, SigTypes>::Type;
307  };
308 
309  using Logger = _DiagnosticFun<void(string)>::Type;
310 
311 
316  : public NopJobFunctor
317  {
318  using MockOp = typename _DiagnosticFun<SIG_JobDiagnostic>::Type;
319 
320  MockOp mockOperation_;
321 
325  void
326  invokeJobOperation (JobParameter param) override
327  {
328  mockOperation_(Time{TimeValue{param.nominalTime}}, param.invoKey.part.a);
329  }
330 
331  string diagnostic() const override
332  {
333  return "JobFun-"+string{mockOperation_};
334  }
335 
336  JobKind
337  getJobKind() const
338  {
339  return TEST_JOB;
340  }
341 
342  public:
343  MockJobFunctor (MockOp mockedJobOperation)
344  : mockOperation_{move (mockedJobOperation)}
345  { }
346  };
347 
348 
353  : public Activity
354  , public activity::Hook
355  {
356  Logger log_;
357  TimeVar invoked_{Time::ANYTIME};
358 
359  Activity*
360  target()
361  {
362  return reinterpret_cast<Activity*> (data_.callback.arg);
363  }
364 
365  Activity const*
366  target() const
367  {
368  return unConst(this)->target();
369  }
370 
372  activation ( Activity& thisHook
373  , Time now
374  , void* executionCtx) override
375  {
376  REQUIRE (thisHook.is (Activity::HOOK));
377  invoked_ = now;
378  if (not target())
379  {// no adapted target; just record this activation
380  log_(util::toString(now) + " ⧐ ");
381  return activity::PASS;
382  }
383  else
384  {// forward activation to the adapted target Activity
385  auto ctx = *static_cast<FakeExecutionCtx*> (executionCtx);
386  log_(util::toString(now) + " ⧐ " + util::toString (*target()));
387  return target()->activate (now, ctx);
388  }
389  }
390 
392  notify ( Activity& thisHook
393  , Time now
394  , void* executionCtx) override
395  {
396  REQUIRE (thisHook.is (Activity::HOOK));
397  invoked_ = now;
398  if (not target())
399  {// no adapted target; just record this notification
400  log_(util::toString(now) + " --notify-↯• ");
401  return activity::PASS;
402  }
403  else
404  {// forward notification-dispatch to the adapted target Activity
405  auto ctx = *static_cast<FakeExecutionCtx*> (executionCtx);
406  log_(util::toString(now) + " --notify-↯> " + util::toString (*target()));
407  return target()->dispatch (now, ctx);
408  }
409  }
410 
411  Time
412  getDeadline() const override
413  {
414  if (target() and target()->is(Activity::GATE))
415  return target()->data_.condition.getDeadline();
416  else
417  return Time::NEVER;
418  }
419 
420  std::string
421  diagnostic() const override
422  {
423  return "Probe("+string{log_}+")";
424  }
425 
426  public:
427  ActivityProbe (string id, EventLog& masterLog, uint const& invocationSeqNr)
428  : Activity{*this, 0}
429  , log_{id, masterLog, invocationSeqNr}
430  { }
431 
432  ActivityProbe (Activity const& subject, string id, EventLog& masterLog, uint const& invocationSeqNr)
433  : Activity{*this, reinterpret_cast<size_t> (&subject)}
434  , log_{id, masterLog, invocationSeqNr}
435  {
436  next = subject.next;
437  }
438 
439  operator string() const
440  {
441  return diagnostic();
442  }
443 
444 
445  static Time
446  lastInvoked (Activity const* act)
447  {
448  if (act and act->verb_ == HOOK)
449  {
450  ActivityProbe* probe = dynamic_cast<ActivityProbe*> (act->data_.callback.hook);
451  if (probe)
452  return probe->invoked_;
453  }
454  return Time::NEVER;
455  }
456  };
457 
458 
459  /* ===== Maintain throw-away mock instances ===== */
460 
461  std::deque<MockJobFunctor> mockOps_{};
462  std::deque<ActivityProbe> mockActs_{};
463 
464 
465  public:
466  ActivityDetector(string id ="")
467  : eventLog_{"ActivityDetector" + (isnil (id)? string{}: "("+id+")")}
468  , invocationSeq_{0}
469  { }
470 
471  operator string() const
472  {
473  return util::join (eventLog_);
474  }
475 
476  string
477  showLog() const
478  {
479  return "\n____Event-Log___________________________\n"
480  + util::join (eventLog_, "\n")
481  + "\n────╼━━━━━━━━╾──────────────────────────"
482  ;
483  }
484 
485  void
486  clear(string newID)
487  {
488  if (isnil (newID))
489  eventLog_.clear();
490  else
491  eventLog_.clear (newID);
492  }
493 
495  uint
497  {
498  ++invocationSeq_;
499  eventLog_.event (MARK_INC, util::toString(invocationSeq_));
500  return invocationSeq_;
501  }
502 
503  uint
504  currSeq() const
505  {
506  return invocationSeq_;
507  }
508 
509 
516  template<typename SIG>
517  auto
518  buildDiagnosticFun (string id)
519  {
520  using Functor = typename _DiagnosticFun<SIG>::Type;
521  return Functor{id, eventLog_, invocationSeq_};
522  }
523 
524  JobClosure&
525  buildMockJobFunctor (string id)
526  {
527  return mockOps_.emplace_back (
528  buildDiagnosticFun<SIG_JobDiagnostic> (id));
529  }
530 
531  Job
532  buildMockJob (string id =""
533  ,Time nominal = lib::test::randTime()
534  ,size_t extra = rand())
535  {
536  InvocationInstanceID invoKey;
537  invoKey.part.a = extra;
538  invoKey.part.t = _raw(nominal);
539  return Job{buildMockJobFunctor (isnil(id)? "mockJob-"+util::toString(nominal) : id)
540  ,invoKey
541  ,nominal};
542  }
543 
545  Activity&
547  {
548  return mockActs_.emplace_back (id, eventLog_, invocationSeq_);
549  }
550 
552  Activity&
553  buildActivationTap (Activity const& subject, string id ="")
554  {
555  return mockActs_.emplace_back (subject
556  ,isnil(id)? "tap-"+subject.showVerb()+util::showAddr(subject)
557  : id
558  ,eventLog_
559  ,invocationSeq_);
560  }
561 
563  Activity&
564  insertActivationTap (Activity*& wiring, string id ="")
565  {
566  wiring = wiring? & buildActivationTap (*wiring, id)
567  : & buildActivationProbe (isnil(id)? "tail-"+util::showAddr(&wiring) : id);
568  return *wiring;
569  }
570 
571  Activity&
572  buildGateWatcher (Activity& gate, string id ="")
573  {
574  insertActivationTap (gate.next, "after-" + (isnil(id)? gate.showVerb()+util::showAddr(gate) : id));
575  return buildActivationTap (gate, id);
576  }
577 
578  Activity&
579  watchGate (Activity*& wiring, string id ="")
580  {
581  wiring = wiring? & buildGateWatcher (*wiring, id)
582  : & buildActivationProbe (isnil(id)? "tail-"+util::showAddr(&wiring) : id);
583  return *wiring;
584  }
585 
586 
587  Time invokeTime (Activity const* hook) { return ActivityProbe::lastInvoked (hook); }
588  bool wasInvoked (Activity const* hook) { return invokeTime(hook).isRegular(); }
589  Time invokeTime (Activity const& hook) { return invokeTime (&hook); }
590  bool wasInvoked (Activity const& hook) { return wasInvoked (&hook); }
591 
592 
593  struct FakeExecutionCtx;
594  using SIG_post = activity::Proc(Time, Time, Activity*, FakeExecutionCtx&);
595  using SIG_work = void(Time, size_t);
596  using SIG_done = void(Time, size_t);
597  using SIG_tick = activity::Proc(Time);
598 
607  {
612 
613  function<Time()> getSchedTime = [this]{ return SCHED_TIME_MARKER;};
614 
616  : post{detector.buildDiagnosticFun<SIG_post>(CTX_POST).returning(activity::PASS)}
617  , work{detector.buildDiagnosticFun<SIG_work>(CTX_WORK)}
618  , done{detector.buildDiagnosticFun<SIG_done>(CTX_DONE)}
619  , tick{detector.buildDiagnosticFun<SIG_tick>(CTX_TICK).returning(activity::PASS)}
620  { }
621 
622  operator string() const { return "≺test::CTX≻"; }
623  };
624 
625  FakeExecutionCtx executionCtx{*this};
626 
627 
628 
630  verifyInvocation (string fun)
631  {
632  return ActivityMatch{move (eventLog_.verifyCall(fun))};
633  }
634 
636  ensureNoInvocation (string fun)
637  {
638  return ActivityMatch{move (eventLog_.ensureNot(fun).locateCall(fun))};
639  }
640 
642  verifySeqIncrement (uint seqNr)
643  {
644  return ActivityMatch{move (eventLog_.verifyEvent(MARK_INC, util::toString(seqNr)))};
645  }
646 
647 
648  private:
649  };
650 
651 
652 }}} // namespace vault::gear::test
653 #endif /*VAULT_GEAR_TEST_ACTIVITY_DETECTOR_H*/
static const Time ANYTIME
border condition marker value. ANYTIME <= any time value
Definition: timevalue.hpp:322
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:241
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
Record to describe an Activity, to happen within the Scheduler&#39;s control flow.
Definition: activity.hpp:235
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:49
Any copy and copy construction prohibited.
Definition: nocopy.hpp:46
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:284
Helper for uniform access to function signature types.
Definition: function.hpp:108
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:779
A Mock functor, logging all invocations into the EventLog.
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:308
ItemWrapper & defaultInit()
implant a default-initialised instance of the payload type
Definition: wrapper.hpp:242
EventMatch & attrib(string key, string valueMatch)
refine filter to additionally match on a specific attribute
Definition: event-log.cpp:570
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:71
EventMatch verifyCall(string match) const
start a query to match especially a function call
Definition: event-log.cpp:797
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:814
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:426
EventLog & clear()
purge log contents while retaining just the original Header-ID
Definition: event-log.cpp:652
Extension point to invoke a callback from Activity activation.
Definition: activity.hpp:162
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:699
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:229
A collection of frequently used helper functions to support unit testing.
test and diagnostic and research
Definition: job.h:76
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:245
Activity * next
Activities are organised into chains to represent relations based on verbs.
Definition: activity.hpp:258
EventMatch & id(string classifier)
refine filter to additionally match on the ID attribute
Definition: event-log.cpp:581
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:112
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:323
Interface of the closure for frame rendering jobs.
Definition: job.h:244
Offset measures a distance in time.
Definition: timevalue.hpp:367
EventLog & addAttrib(string const &key, X &&initialiser, ARGS &&...args)
Qualify the latest entry: set further attribute(s)
Definition: event-log.hpp:432
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:149
invoke an extension point through the activity::Hook interface
Definition: activity.hpp:248
Individual frame rendering task, forwarding to a closure.
Definition: job.h:277
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:142
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:326
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.