Lumiera 0.pre.04~rc.1
»edit your freedom«
Loading...
Searching...
No Matches
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"
21#include "activity-detector.hpp"
22#include "vault/real-clock.hpp"
24#include "lib/format-cout.hpp"
25#include "lib/util.hpp"
26
27
28using lib::time::Time;
30
34
35
36namespace vault{
37namespace gear {
38namespace test {
39
40
41 /*****************************************************************/
46 class ActivityDetector_test : public Test
47 {
48
49 virtual void
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"
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
419
420
421
422}}} // namespace vault::gear::test
Diagnostic setup to instrument and observe Activity activations.
static const raw_time_64 SCALE
Number of micro ticks (µs) per second as basic time scale.
Lumiera's internal time value datatype.
static Time now()
Record to describe an Activity, to happen within the Scheduler's control flow.
Definition activity.hpp:227
bool is(Activity::Verb expectedVerb) const
Definition activity.hpp:422
activity::Proc activate(Time now, EXE &executionCtx)
Core Operation: Activate and perform this Activity.
Definition activity.hpp:626
Activity * next
Activities are organised into chains to represent relations based on verbs.
Definition activity.hpp:249
@ HOOK
invoke an extension point through the activity::Hook interface
Definition activity.hpp:239
@ TICK
internal engine »heart beat« for internal maintenance hook(s)
Definition activity.hpp:240
Individual frame rendering task, forwarding to a closure.
Definition job.h:276
Diagnostic context to record and evaluate activations within the Scheduler.
ActivityMatch verifySeqIncrement(uint seqNr)
bool wasInvoked(Activity const *hook)
auto buildDiagnosticFun(string id)
Generic testing helper: build a λ-mock, logging all invocations.
JobClosure & buildMockJobFunctor(string id)
ActivityMatch ensureNoInvocation(string fun)
Activity & buildActivationProbe(string id)
build a rigged HOOK-Activity to record each invocation
Activity & insertActivationTap(Activity *&wiring, string id="")
build ActivationProbe to record each activation before passing it to the subject
Time invokeTime(Activity const *hook)
Activity & watchGate(Activity *&wiring, string id="")
Activity & buildActivationTap(Activity const &subject, string id="")
build ActivationProbe to record each activation before passing it to the subject
ActivityMatch verifyInvocation(string fun)
uint incrementSeq()
increment the internal invocation sequence number
ActivityMatch & arg(ARGS const &...args)
qualifier: additionally match the function arguments
ActivityMatch & beforeInvocation(string match)
ActivityMatch & beforeSeqIncrement(uint seqNr)
special query to match an increment of the sequence number
ActivityMatch & seq(uint seqNr)
qualifier: additionally require the indicated sequence number
ActivityMatch & timeArg(Time const &time)
qualifier: additionally match the nominal time argument of JobFunctor invocation
Automatically use custom string conversion in C++ stream output.
unsigned int uint
Definition integral.hpp:29
struct InvocationInstanceID::@62 part
opaque ID attached to each individual job invocation.
Definition job.h:105
lib::time::Time randTime()
create a random but not insane Time value between 1s ... 10min + 500ms
string randStr(size_t len)
create garbage string of given length
boost::rational< int64_t > FSecs
rational representation of fractional seconds
Test runner and basic definitions for tests.
bool isSameObject(A const &a, B const &b)
compare plain object identity, based directly on the referee's memory identities.
Definition util.hpp:421
std::string toString(TY const &val) noexcept
get some string representation of any object, reliably.
constexpr void _verify_usable_as_ExecutionContext()
Definition to emulate a Concept for the Execution Context.
Definition activity.hpp:203
@ PASS
pass on the activation down the chain
Definition activity.hpp:140
@ KICK
back pressure; get out of the way but be back soon
Definition activity.hpp:143
Vault-Layer implementation namespace root.
Front-end for simplified access to the current wall clock time.
Simplistic test class runner.
#define LAUNCHER(_TEST_CLASS_, _GROUPS_)
Definition run.hpp:116
A collection of frequently used helper functions to support unit testing.
a family of time value like entities and their relationships.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...