Lumiera  0.pre.03
»edit your freedom«
activity-detector-test.cpp
Go to the documentation of this file.
1 /*
2  ActivityDetector(Test) - verify diagnostic setup to watch scheduler activities
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 
19 #include "lib/test/run.hpp"
20 #include "lib/test/test-helper.hpp"
21 #include "activity-detector.hpp"
22 #include "vault/real-clock.hpp"
23 #include "lib/time/timevalue.hpp"
24 #include "lib/format-cout.hpp"
25 #include "lib/util.hpp"
26 
27 
28 using lib::time::Time;
29 using lib::time::FSecs;
30 
31 using util::isSameObject;
32 using lib::test::randStr;
34 
35 
36 namespace vault{
37 namespace gear {
38 namespace test {
39 
40 
41  /*****************************************************************/
46  class ActivityDetector_test : public Test
47  {
48 
49  virtual void
50  run (Arg)
51  {
52  seedRand();
53 
54  simpleUsage();
55 
64  watch_gate();
65  }
66 
67 
70  void
72  {
73  ActivityDetector detector("spectre");
74 
75  auto trap = detector.buildDiagnosticFun<int(double,Time)>("trap")
76  .returning(55);
77 
78  CHECK (55 == trap (1.23, Time{FSecs{3,2}}));
79 
80  CHECK (detector == "Rec(EventLogHeader| this = ActivityDetector(spectre) ), "
81  "Rec(call| fun = trap, this = ActivityDetector(spectre), Seq = 0 |{1.23, 0:00:01.500})"_expect);
82  }
83 
84 
85 
92  void
94  {
95  ActivityDetector detector;
96  auto fun = detector.buildDiagnosticFun<void(uint)> ("funny");
97  uint rnd = rani(10000);
98 
99  detector.incrementSeq();
100  CHECK (1 == detector.currSeq());
101  CHECK (detector.ensureNoInvocation ("funny"));
102 
103  detector.incrementSeq();
104  CHECK (2 == detector.currSeq());
105  CHECK (detector.verifySeqIncrement(2));
106 
107  fun (rnd);
108  CHECK (detector.verifyInvocation ("funny"));
109  CHECK (detector.verifyInvocation ("funny").arg(rnd));
110  CHECK (detector.verifyInvocation ("funny").seq(2));
111  CHECK (detector.verifyInvocation ("funny").arg(rnd).seq(2));
112  CHECK (detector.verifyInvocation ("funny").seq(2).arg(rnd));
113  CHECK (detector.ensureNoInvocation ("bunny")); // wrong name
114  CHECK (detector.ensureNoInvocation ("funny").arg()); // fails since empty argument list expected
115  CHECK (detector.ensureNoInvocation ("funny").arg(rnd+5)); // expecting wrong argument
116  CHECK (detector.ensureNoInvocation ("funny").seq(5)); // expecting wrong sequence number
117  CHECK (detector.ensureNoInvocation ("funny").arg(rnd).seq(1)); // expecting correct argument, but wrong sequence
118 
119  detector.incrementSeq();
120  fun (rnd+1);
121  CHECK (detector.verifyInvocation ("funny").seq(2)
123  .beforeInvocation ("funny").seq(3).arg(rnd+1));
124 
125  CHECK (detector == "Rec(EventLogHeader| this = ActivityDetector )"
126  ", Rec(event| ID = IncSeq |{1})"
127  ", Rec(event| ID = IncSeq |{2})"
128  ", Rec(call| fun = funny, this = ActivityDetector, Seq = 2 |{"+util::toString(rnd)+"})"
129  ", Rec(event| ID = IncSeq |{3})"
130  ", Rec(call| fun = funny, this = ActivityDetector, Seq = 3 |{"+util::toString(rnd+1)+"})"_expect);
131  }
132 
133 
134 
138  void
140  {
141  ActivityDetector detector;
142  auto fun = detector.buildDiagnosticFun<int(uint)> ("fakeFun");
143  uint rnd = rani(10000);
144 
145  CHECK (0 == fun (rnd));
146 
147  fun.returning(42);
148  detector.incrementSeq();
149  CHECK (42 == fun (rnd));
150 
151  fun.implementedAs ([](uint i){ return -i; });
152  detector.incrementSeq();
153  CHECK (-int(rnd) == fun (rnd));
154 
155  CHECK (detector.verifyInvocation("fakeFun").seq(0)
156  .beforeInvocation("fakeFun").seq(1)
157  .beforeInvocation("fakeFun").seq(2));
158  }
159 
160 
161 
169  void
171  {
172  ActivityDetector detector;
173  InvocationInstanceID invoKey;
174  Time nominal{FSecs{5,2}};
175  invoKey.part.a = 55;
176 
177  Job dummyJob{detector.buildMockJobFunctor ("mockJob")
178  ,invoKey
179  ,nominal};
180 
181  CHECK (detector.ensureNoInvocation ("mockJob"));
182  dummyJob.triggerJob();
183  CHECK (detector.verifyInvocation ("mockJob"));
184  CHECK (detector.verifyInvocation ("mockJob").arg(nominal, invoKey.part.a));
185  CHECK (detector.verifyInvocation ("mockJob").timeArg(nominal));
186 
187  detector.incrementSeq(); // note: sequence number incremented between invocations
188  dummyJob.parameter.nominalTime += 5 * Time::SCALE; // different job parameter (later nominal time point)
189  dummyJob.triggerJob();
190 
191  CHECK (detector.verifyInvocation ("mockJob").timeArg(nominal).seq(0)
192  .beforeInvocation ("mockJob").timeArg(nominal + Time{FSecs{5}}) // matching first invocation and then second...
193  .afterSeqIncrement(1) // note: searching backwards from the 2nd invocation
194  );
195 // cout << detector.showLog()<<endl; // HINT: use this for investigation...
196  }
197 
198 
199 
205  void
207  {
208  ActivityDetector detector;
209  auto& ctx = detector.executionCtx;
210  // an otherwise opaque object fulfilling the "Concept"
211  activity::_verify_usable_as_ExecutionContext<decltype(detector.executionCtx)>();
212 
213  Time t = randTime();
214  Time td{t+Time(0,1)};
215  size_t x = rani();
216  Activity a;
217 
218  CHECK (detector.ensureNoInvocation(CTX_WORK));
219  CHECK (detector.ensureNoInvocation(CTX_POST));
220  CHECK (detector.ensureNoInvocation(CTX_DONE));
221  CHECK (detector.ensureNoInvocation(CTX_TICK));
222 
223  ctx.work (t,x);
224  CHECK (detector.verifyInvocation(CTX_WORK).arg(t,x));
225 
226  ctx.done (t,x);
227  CHECK (detector.verifyInvocation(CTX_DONE).arg(t,x));
228 
229  CHECK (activity::PASS == ctx.post (t,td, &a, ctx));
230  CHECK (detector.verifyInvocation(CTX_POST).arg(t,td,&a,ctx));
231 
232  CHECK (activity::PASS == ctx.tick(t));
233  CHECK (detector.verifyInvocation(CTX_TICK).arg(t));
234 
235  detector.incrementSeq();
236  ctx.tick.returning(activity::KICK);
237  CHECK (activity::KICK == ctx.tick(t));
238  CHECK (detector.verifyInvocation(CTX_TICK).timeArg(t));
239 
240  CHECK (detector.verifyInvocation(CTX_WORK).timeArg(t)
241  .beforeInvocation(CTX_DONE).timeArg(t)
242  .beforeInvocation(CTX_POST).timeArg(t)
243  .beforeInvocation(CTX_TICK).timeArg(t).seq(0)
244  .beforeInvocation(CTX_TICK).timeArg(t).seq(1));
245  }
246 
247 
248 
251  void
253  {
254  ActivityDetector detector;
255  auto someID = "trap-" + randStr(4);
256  Activity& probe = detector.buildActivationProbe (someID);
257  CHECK (probe.is (Activity::HOOK));
258 
259  CHECK (not detector.wasInvoked (probe));
260 
261  Time realTime = RealClock::now();
262  probe.activate (realTime, detector.executionCtx);
263 
264  CHECK (detector.verifyInvocation(someID).timeArg(realTime));
265 
266  // Probe instance recalls last invocation "now" argument
267  CHECK (realTime == detector.invokeTime (probe));
268  CHECK (detector.wasInvoked (probe));
269  }
270 
271 
272 
275  void
277  {
278  ActivityDetector detector;
279 
280  Time nomTime{99,11};
281  Activity feed{size_t{12},size_t{34}};
282  Activity feed2{size_t{56},size_t{78}};
283  feed.next = &feed2;
284  string jobID = "job-" + randStr(4);
285  Activity invoke{detector.buildMockJobFunctor(jobID), nomTime, feed};
286 
287  Time t1{0,1,1};
288  CHECK (activity::PASS == invoke.activate (t1, detector.executionCtx));
289  CHECK (detector.verifyInvocation (jobID).arg(nomTime, 12));
290 
291  // decorate the INVOKE-Activity with an ActivationTap
292  Activity& tap = detector.buildActivationTap (invoke);
293  CHECK (tap.next == invoke.next);
294 
295  detector.incrementSeq();
296  Time t2{0,2,2};
297  // now activate through the Tap....
298  tap.activate(t2, detector.executionCtx);
299  CHECK (detector.verifySeqIncrement(1) // ==> the ActivationTap "tap-INVOKE" reports and passes activation
300  .beforeInvocation("tap-INVOKE").seq(1).arg("JobFun-ActivityDetector."+jobID)
301  .beforeInvocation(jobID).seq(1).arg(nomTime,12));
302 
303  // WARNING: can still activate the watched subject directly...
304  detector.incrementSeq();
305  Time t3{0,3,3};
306  invoke.activate (t3, detector.executionCtx);
307  CHECK (detector.verifyInvocation(jobID).seq(2)); // subject invoked
308  CHECK (detector.ensureNoInvocation("tap-INVOKE").seq(2) // but invocation not detected by ActivationTap
309  .beforeInvocation(jobID).seq(2));
310  }
311 
312 
313 
316  void
318  {
319  ActivityDetector detector;
320 
321  Activity subject;
322  Activity followUp{size_t(1), size_t(2)};
323  subject.next = &followUp;
324  Activity* wiring = &subject;
325  CHECK (isSameObject (*wiring, subject));
326  CHECK (wiring->verb_ == Activity::TICK);
327 
328  detector.insertActivationTap (wiring);
329  CHECK (not isSameObject (*wiring, subject));
330  CHECK (wiring->verb_ == Activity::HOOK);
331  CHECK (wiring->data_.callback.arg == size_t(&subject));
332  CHECK (wiring->next == subject.next);
333 
334  Time tt{1,1,1};
335  // now activate through the wiring....
336  wiring->activate(tt, detector.executionCtx);
337  CHECK (detector.verifyInvocation("tap-TICK").arg("⧐ Act(TICK")
338  .beforeInvocation("CTX-tick").timeArg(tt));
339  }
340 
341 
342 
353  void
355  {
356  Time tt{11,11}; // start time of the NOTIFY
357  Time ts{22,22}; // start time of the target-chain
358  Time td{33,33}; // deadline for the target-chain
359  ActivityDetector detector;
360 
361  Activity chain;
362  Activity gate{1, td};
363  gate.next = &chain;
364  Activity notification{&gate, ts}; // note: follow-up start time `ts` injected here
365  CHECK (gate.data_.condition.rest == 1);
366 
367  detector.insertActivationTap (notification.data_.notification.target);
368 
369  notification.activate (tt, detector.executionCtx); // dispatch time `tt` (is actually irrelevant here)
370  // activating the NOTIFY causes it to POST its target, thereby setting the deadline from the GATE
371  CHECK (detector.verifyInvocation("CTX-post").arg("22.022","33.033", "tap-GATE", "≺test::CTX≻"));
372 
373  detector.incrementSeq();
374  // to see the effect of the instrumentation, we need to mimic the behaviour of λ-post,
375  // which is to call Activity::dispatch() on the given target
376  notification.data_.notification.target->dispatch (ts, detector.executionCtx); // note: using `ts` for the follow-up chain
377  CHECK (detector.verifyInvocation("tap-GATE").seq(1).arg("22.022 --notify-↯> Act(GATE"));
378  CHECK (gate.data_.condition.rest == 0);
379  }
380 
381 
382 
392  void
394  {
395  ActivityDetector detector;
396 
397  Activity gate{0};
398  Activity followUp;
399  gate.next = &followUp;
400 
401  Activity* wiring = &gate;
402  detector.watchGate (wiring);
403 
404  Time tt{5,5};
405  wiring->activate(tt, detector.executionCtx);
406  detector.incrementSeq();
407  wiring->next->activate(tt, detector.executionCtx);
408 
409  CHECK (detector.verifyInvocation("tap-GATE").seq(0).timeArg(tt)
411  .beforeInvocation("after-GATE").seq(1).timeArg(tt)
412  .beforeInvocation("CTX-tick").seq(1).timeArg(tt));
413  }
414  };
415 
416 
418  LAUNCHER (ActivityDetector_test, "unit engine");
419 
420 
421 
422 }}} // namespace vault::gear::test
Record to describe an Activity, to happen within the Scheduler&#39;s control flow.
Definition: activity.hpp:226
Automatically use custom string conversion in C++ stream output.
Definition: run.hpp:40
Activity & insertActivationTap(Activity *&wiring, string id="")
build ActivationProbe to record each activation before passing it to the subject
static const gavl_time_t SCALE
Number of micro ticks (µs) per second as basic time scale.
Definition: timevalue.hpp:167
int rani(uint bound=_iBOUND())
Definition: random.hpp:135
activity::Proc activate(Time now, EXE &executionCtx)
Core Operation: Activate and perform this Activity.
Definition: activity.hpp:626
string randStr(size_t len)
create garbage string of given length
Definition: test-helper.cpp:61
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:299
auto buildDiagnosticFun(string id)
Generic testing helper: build a λ-mock, logging all invocations.
ActivityMatch & seq(uint seqNr)
qualifier: additionally require the indicated sequence number
Diagnostic context to record and evaluate activations within the Scheduler.
Simplistic test class runner.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
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.
Activity & buildActivationProbe(string id)
build a rigged HOOK-Activity to record each invocation
uint incrementSeq()
increment the internal invocation sequence number
Activity * next
Activities are organised into chains to represent relations based on verbs.
Definition: activity.hpp:249
Diagnostic setup to instrument and observe Activity activations.
ActivityMatch & beforeSeqIncrement(uint seqNr)
special query to match an increment of the sequence number
internal engine »heart beat« for internal maintenance hook(s)
Definition: activity.hpp:240
opaque ID attached to each individual job invocation.
Definition: job.h:103
lib::time::Time randTime()
create a random but not insane Time value between 1s ...
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.
Front-end for simplified access to the current wall clock time.
ActivityMatch & arg(ARGS const &...args)
qualifier: additionally match the function arguments
Vault-Layer implementation namespace root.
bool isSameObject(A const &a, B const &b)
compare plain object identity, based directly on the referee&#39;s memory identities. ...
Definition: util.hpp:421