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) 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 
28 #include "lib/test/run.hpp"
29 #include "lib/test/test-helper.hpp"
30 #include "activity-detector.hpp"
31 #include "vault/real-clock.hpp"
32 #include "lib/time/timevalue.hpp"
33 #include "lib/format-cout.hpp"
34 #include "lib/util.hpp"
35 
36 
37 using lib::time::Time;
38 using lib::time::FSecs;
39 
40 using util::isSameObject;
41 using lib::test::randStr;
43 
44 
45 namespace vault{
46 namespace gear {
47 namespace test {
48 
49 
50  /*****************************************************************/
55  class ActivityDetector_test : public Test
56  {
57 
58  virtual void
59  run (Arg)
60  {
61  simpleUsage();
62 
71  watch_gate();
72  }
73 
74 
77  void
79  {
80  ActivityDetector detector("spectre");
81 
82  auto trap = detector.buildDiagnosticFun<int(double,Time)>("trap")
83  .returning(55);
84 
85  CHECK (55 == trap (1.23, Time{FSecs{3,2}}));
86 
87  CHECK (detector == "Rec(EventLogHeader| this = ActivityDetector(spectre) ), "
88  "Rec(call| fun = trap, this = ActivityDetector(spectre), Seq = 0 |{1.23, 0:00:01.500})"_expect);
89  }
90 
91 
92 
99  void
101  {
102  ActivityDetector detector;
103  auto fun = detector.buildDiagnosticFun<void(uint)> ("funny");
104  uint rnd = rand() % 10000;
105 
106  detector.incrementSeq();
107  CHECK (1 == detector.currSeq());
108  CHECK (detector.ensureNoInvocation ("funny"));
109 
110  detector.incrementSeq();
111  CHECK (2 == detector.currSeq());
112  CHECK (detector.verifySeqIncrement(2));
113 
114  fun (rnd);
115  CHECK (detector.verifyInvocation ("funny"));
116  CHECK (detector.verifyInvocation ("funny").arg(rnd));
117  CHECK (detector.verifyInvocation ("funny").seq(2));
118  CHECK (detector.verifyInvocation ("funny").arg(rnd).seq(2));
119  CHECK (detector.verifyInvocation ("funny").seq(2).arg(rnd));
120  CHECK (detector.ensureNoInvocation ("bunny")); // wrong name
121  CHECK (detector.ensureNoInvocation ("funny").arg()); // fails since empty argument list expected
122  CHECK (detector.ensureNoInvocation ("funny").arg(rnd+5)); // expecting wrong argument
123  CHECK (detector.ensureNoInvocation ("funny").seq(5)); // expecting wrong sequence number
124  CHECK (detector.ensureNoInvocation ("funny").arg(rnd).seq(1)); // expecting correct argument, but wrong sequence
125 
126  detector.incrementSeq();
127  fun (rnd+1);
128  CHECK (detector.verifyInvocation ("funny").seq(2)
130  .beforeInvocation ("funny").seq(3).arg(rnd+1));
131 
132  CHECK (detector == "Rec(EventLogHeader| this = ActivityDetector )"
133  ", Rec(event| ID = IncSeq |{1})"
134  ", Rec(event| ID = IncSeq |{2})"
135  ", Rec(call| fun = funny, this = ActivityDetector, Seq = 2 |{"+util::toString(rnd)+"})"
136  ", Rec(event| ID = IncSeq |{3})"
137  ", Rec(call| fun = funny, this = ActivityDetector, Seq = 3 |{"+util::toString(rnd+1)+"})"_expect);
138  }
139 
140 
141 
145  void
147  {
148  ActivityDetector detector;
149  auto fun = detector.buildDiagnosticFun<int(uint)> ("fakeFun");
150  uint rnd = rand() % 10000;
151 
152  CHECK (0 == fun (rnd));
153 
154  fun.returning(42);
155  detector.incrementSeq();
156  CHECK (42 == fun (rnd));
157 
158  fun.implementedAs ([](uint i){ return -i; });
159  detector.incrementSeq();
160  CHECK (-int(rnd) == fun (rnd));
161 
162  CHECK (detector.verifyInvocation("fakeFun").seq(0)
163  .beforeInvocation("fakeFun").seq(1)
164  .beforeInvocation("fakeFun").seq(2));
165  }
166 
167 
168 
176  void
178  {
179  ActivityDetector detector;
180  InvocationInstanceID invoKey;
181  Time nominal{FSecs{5,2}};
182  invoKey.part.a = 55;
183 
184  Job dummyJob{detector.buildMockJobFunctor ("mockJob")
185  ,invoKey
186  ,nominal};
187 
188  CHECK (detector.ensureNoInvocation ("mockJob"));
189  dummyJob.triggerJob();
190  CHECK (detector.verifyInvocation ("mockJob"));
191  CHECK (detector.verifyInvocation ("mockJob").arg(nominal, invoKey.part.a));
192  CHECK (detector.verifyInvocation ("mockJob").timeArg(nominal));
193 
194  detector.incrementSeq(); // note: sequence number incremented between invocations
195  dummyJob.parameter.nominalTime += 5 * Time::SCALE; // different job parameter (later nominal time point)
196  dummyJob.triggerJob();
197 
198  CHECK (detector.verifyInvocation ("mockJob").timeArg(nominal).seq(0)
199  .beforeInvocation ("mockJob").timeArg(nominal + Time{FSecs{5}}) // matching first invocation and then second...
200  .afterSeqIncrement(1) // note: searching backwards from the 2nd invocation
201  );
202 // cout << detector.showLog()<<endl; // HINT: use this for investigation...
203  }
204 
205 
206 
212  void
214  {
215  ActivityDetector detector;
216  auto& ctx = detector.executionCtx;
217  // an otherwise opaque object fulfilling the "Concept"
218  activity::_verify_usable_as_ExecutionContext<decltype(detector.executionCtx)>();
219 
220  Time t = randTime();
221  Time td{t+Time(0,1)};
222  size_t x = rand();
223  Activity a;
224 
225  CHECK (detector.ensureNoInvocation(CTX_WORK));
226  CHECK (detector.ensureNoInvocation(CTX_POST));
227  CHECK (detector.ensureNoInvocation(CTX_DONE));
228  CHECK (detector.ensureNoInvocation(CTX_TICK));
229 
230  ctx.work (t,x);
231  CHECK (detector.verifyInvocation(CTX_WORK).arg(t,x));
232 
233  ctx.done (t,x);
234  CHECK (detector.verifyInvocation(CTX_DONE).arg(t,x));
235 
236  CHECK (activity::PASS == ctx.post (t,td, &a, ctx));
237  CHECK (detector.verifyInvocation(CTX_POST).arg(t,td,&a,ctx));
238 
239  CHECK (activity::PASS == ctx.tick(t));
240  CHECK (detector.verifyInvocation(CTX_TICK).arg(t));
241 
242  detector.incrementSeq();
243  ctx.tick.returning(activity::KICK);
244  CHECK (activity::KICK == ctx.tick(t));
245  CHECK (detector.verifyInvocation(CTX_TICK).timeArg(t));
246 
247  CHECK (detector.verifyInvocation(CTX_WORK).timeArg(t)
248  .beforeInvocation(CTX_DONE).timeArg(t)
249  .beforeInvocation(CTX_POST).timeArg(t)
250  .beforeInvocation(CTX_TICK).timeArg(t).seq(0)
251  .beforeInvocation(CTX_TICK).timeArg(t).seq(1));
252  }
253 
254 
255 
258  void
260  {
261  ActivityDetector detector;
262  auto someID = "trap-" + randStr(4);
263  Activity& probe = detector.buildActivationProbe (someID);
264  CHECK (probe.is (Activity::HOOK));
265 
266  CHECK (not detector.wasInvoked (probe));
267 
268  Time realTime = RealClock::now();
269  probe.activate (realTime, detector.executionCtx);
270 
271  CHECK (detector.verifyInvocation(someID).timeArg(realTime));
272 
273  // Probe instance recalls last invocation "now" argument
274  CHECK (realTime == detector.invokeTime (probe));
275  CHECK (detector.wasInvoked (probe));
276  }
277 
278 
279 
282  void
284  {
285  ActivityDetector detector;
286 
287  Time nomTime{99,11};
288  Activity feed{size_t{12},size_t{34}};
289  Activity feed2{size_t{56},size_t{78}};
290  feed.next = &feed2;
291  string jobID = "job-" + randStr(4);
292  Activity invoke{detector.buildMockJobFunctor(jobID), nomTime, feed};
293 
294  Time t1{0,1,1};
295  CHECK (activity::PASS == invoke.activate (t1, detector.executionCtx));
296  CHECK (detector.verifyInvocation (jobID).arg(nomTime, 12));
297 
298  // decorate the INVOKE-Activity with an ActivationTap
299  Activity& tap = detector.buildActivationTap (invoke);
300  CHECK (tap.next == invoke.next);
301 
302  detector.incrementSeq();
303  Time t2{0,2,2};
304  // now activate through the Tap....
305  tap.activate(t2, detector.executionCtx);
306  CHECK (detector.verifySeqIncrement(1) // ==> the ActivationTap "tap-INVOKE" reports and passes activation
307  .beforeInvocation("tap-INVOKE").seq(1).arg("JobFun-ActivityDetector."+jobID)
308  .beforeInvocation(jobID).seq(1).arg(nomTime,12));
309 
310  // WARNING: can still activate the watched subject directly...
311  detector.incrementSeq();
312  Time t3{0,3,3};
313  invoke.activate (t3, detector.executionCtx);
314  CHECK (detector.verifyInvocation(jobID).seq(2)); // subject invoked
315  CHECK (detector.ensureNoInvocation("tap-INVOKE").seq(2) // but invocation not detected by ActivationTap
316  .beforeInvocation(jobID).seq(2));
317  }
318 
319 
320 
323  void
325  {
326  ActivityDetector detector;
327 
328  Activity subject;
329  Activity followUp{size_t(1), size_t(2)};
330  subject.next = &followUp;
331  Activity* wiring = &subject;
332  CHECK (isSameObject (*wiring, subject));
333  CHECK (wiring->verb_ == Activity::TICK);
334 
335  detector.insertActivationTap (wiring);
336  CHECK (not isSameObject (*wiring, subject));
337  CHECK (wiring->verb_ == Activity::HOOK);
338  CHECK (wiring->data_.callback.arg == size_t(&subject));
339  CHECK (wiring->next == subject.next);
340 
341  Time tt{1,1,1};
342  // now activate through the wiring....
343  wiring->activate(tt, detector.executionCtx);
344  CHECK (detector.verifyInvocation("tap-TICK").arg("⧐ Act(TICK")
345  .beforeInvocation("CTX-tick").timeArg(tt));
346  }
347 
348 
349 
360  void
362  {
363  Time tt{11,11}; // start time of the NOTIFY
364  Time ts{22,22}; // start time of the target-chain
365  Time td{33,33}; // deadline for the target-chain
366  ActivityDetector detector;
367 
368  Activity chain;
369  Activity gate{1, td};
370  gate.next = &chain;
371  Activity notification{&gate, ts}; // note: follow-up start time `ts` injected here
372  CHECK (gate.data_.condition.rest == 1);
373 
374  detector.insertActivationTap (notification.data_.notification.target);
375 
376  notification.activate (tt, detector.executionCtx); // dispatch time `tt` (is actually irrelevant here)
377  // activating the NOTIFY causes it to POST its target, thereby setting the deadline from the GATE
378  CHECK (detector.verifyInvocation("CTX-post").arg("22.022","33.033", "tap-GATE", "≺test::CTX≻"));
379 
380  detector.incrementSeq();
381  // to see the effect of the instrumentation, we need to mimic the behaviour of λ-post,
382  // which is to call Activity::dispatch() on the given target
383  notification.data_.notification.target->dispatch (ts, detector.executionCtx); // note: using `ts` for the follow-up chain
384  CHECK (detector.verifyInvocation("tap-GATE").seq(1).arg("22.022 --notify-↯> Act(GATE"));
385  CHECK (gate.data_.condition.rest == 0);
386  }
387 
388 
389 
399  void
401  {
402  ActivityDetector detector;
403 
404  Activity gate{0};
405  Activity followUp;
406  gate.next = &followUp;
407 
408  Activity* wiring = &gate;
409  detector.watchGate (wiring);
410 
411  Time tt{5,5};
412  wiring->activate(tt, detector.executionCtx);
413  detector.incrementSeq();
414  wiring->next->activate(tt, detector.executionCtx);
415 
416  CHECK (detector.verifyInvocation("tap-GATE").seq(0).timeArg(tt)
418  .beforeInvocation("after-GATE").seq(1).timeArg(tt)
419  .beforeInvocation("CTX-tick").seq(1).timeArg(tt));
420  }
421  };
422 
423 
425  LAUNCHER (ActivityDetector_test, "unit engine");
426 
427 
428 
429 }}} // namespace vault::gear::test
Record to describe an Activity, to happen within the Scheduler&#39;s control flow.
Definition: activity.hpp:235
Automatically use custom string conversion in C++ stream output.
Definition: run.hpp:49
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:176
activity::Proc activate(Time now, EXE &executionCtx)
Core Operation: Activate and perform this Activity.
Definition: activity.hpp:635
string randStr(size_t len)
create garbage string of given length
Definition: test-helper.cpp:69
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:308
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.
Simple 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:229
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:258
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:249
opaque ID attached to each individual job invocation.
Definition: job.h:112
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: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.
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, bypassing any custom comparison operators.
Definition: util.hpp:372