Lumiera 0.pre.04
»edit your freedom«
Loading...
Searching...
No Matches
lazy-init-test.cpp
Go to the documentation of this file.
1/*
2 LazyInit(Test) - verify a mechanism to install a self-initialising functor
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
20#include "lib/test/run.hpp"
22#include "lib/lazy-init.hpp"
23#include "lib/meta/util.hpp"
24#include "lib/util.hpp"
25
26#include <memory>
27
28
29
30namespace lib {
31namespace test{
32
36 using err::LUMIERA_ERROR_LIFECYCLE;
37 using std::make_unique;
38
39
40
41
42 /***********************************************************************************/
51 : public Test
52 {
53
54 void
66
67
68
78 void
80 {
81 size_t beacon;
82 auto fun = [&](uint challenge){ return beacon+challenge; };
83
84 using Sig = size_t(uint);
85 CHECK (isFunMember<Sig> (&fun));
86
87 beacon = rani();
88 uint c = beacon % 42;
89 // verify we can invoke the target function
90 CHECK (beacon+c == fun(c));
91
92 // verify we can also invoke the target function through a reference
93 using FunType = decltype(fun);
94 FunType& funRef = fun;
95 CHECK (beacon+c == funRef(c));
96
97 // construct delegate function exposing the expected behaviour;
98 // additionally this function captures the passed-in address.
99 RawAddr location{nullptr};
100 auto delegate = [&](RawAddr adr) -> FunType&
101 {
102 location = adr;
103 return fun;
104 };
105 using Delegate = decltype(delegate);
106 auto delP = make_unique<Delegate> (delegate);
107
108 // verify the heap-allocated copy of the delegate behaves as expected
109 location = nullptr;
110 CHECK (beacon+c == (*delP)(this)(c));
111 CHECK (location == this);
112
113 // now (finally) build the »trap function«...
114 auto trojanLambda = TrojanFun<Sig>::generateTrap (delP.get());
115 CHECK (sizeof(trojanLambda) == sizeof(size_t));
116
117 // on invocation...
118 // - it captures its current location
119 // - passes this to the delegate
120 // - invokes the target function returned from the delegate
121 CHECK (beacon+c == trojanLambda(c));
122 CHECK (location == &trojanLambda);
123
124 // repeat same with a copy, and changed beacon value
125 auto trojanClone = trojanLambda;
126 beacon = rani();
127 c = beacon % 55;
128 CHECK (beacon+c == trojanClone(c));
129 CHECK (location == &trojanClone);
130 CHECK (beacon+c == trojanLambda(c));
131 CHECK (location == &trojanLambda);
132 }
133
134
135
151 void
153 {
154// char payload[24];// ◁─────────────────────────────── use this to make the test fail....
155 const char* payload = "I am innocent as a lamb";
156 auto lambda = [payload]{ return RawAddr(&payload); };
157
158 RawAddr location = lambda();
159 CHECK (location == &lambda);
160
161 std::function funWrap{lambda};
162 CHECK (funWrap);
163 CHECK (not isSameObject (funWrap, lambda));
164
165 location = funWrap();
166 CHECK (util::isCloseBy (location, funWrap));
167 // if »small object optimisation« was used,
168 // the lambda will be copied directly into the std:function;
169 // otherwise it will be heap allocated and this test fails.
170
171 // for context: these are considered "close by",
172 // since both are sitting right here in the same stack frame
173 CHECK (util::isCloseBy (funWrap, lambda));
174 }
175
176
177
184 void
186 {
187 struct Nested
188 {
189 int unrelated{rani()};
190 int anchor{rani()};
191 };
192 struct Demo
193 {
194 Nested nested;
195 virtual ~Demo(){ };
196 virtual RawAddr peek()
197 {
198 return &nested.anchor;
199 }
200 };
201
202 // find out generic offset...
203 const ptrdiff_t offNested = []{
204 Nested probe;
205 return captureRawAddrOffset(&probe, &probe.anchor);
206 }();
207 Demo here;
208 // find out actual offset in existing object
209 const ptrdiff_t offBase = captureRawAddrOffset(&here, &here.nested);
210
211 CHECK (offBase > 0);
212 CHECK (offNested > 0);
213
214 // create a copy far far away...
215 auto farAway = make_unique<Demo> (here);
216
217 // reconstruct base address from starting point
218 RawAddr startPoint = farAway->peek();
219 Nested* farNested = relocate<Nested>(startPoint, -offNested);
220 CHECK (here.nested.unrelated == farNested->unrelated);
221
222 Demo* farSelf = relocate<Demo> (farNested, -offBase);
223 CHECK (here.nested.anchor == farSelf->nested.anchor);
224 CHECK (isSameObject (*farSelf, *farAway));
225 }
226
227
228
231 void
233 {
234 using Fun = std::function<float(int)>;
235 Fun theFun;
236 CHECK (not theFun);
237
238 int report{0};
239 auto delegate = [&report](RawAddr insideFun) -> Fun&
240 {
241 auto realFun = [&report](int num)
242 {
243 report += num;
244 return num + 23.55f;
245 };
246 Fun& target = *relocate<Fun>(insideFun, -FUNCTOR_PAYLOAD_OFFSET);
247 report = -42; // as proof that the init-delegate was invoked
248 target = realFun;
249 return target;
250 };
251 CHECK (not theFun);
252 // install the init-»trap«
253 theFun = TrojanFun<float(int)>::generateTrap (&delegate);
254 CHECK (theFun);
255 CHECK (0 == report);
256
257 // invoke function
258 int feed{1 + rani (100)};
259 float res = theFun (feed);
260
261 // delegate *and* realFun were invoked
262 CHECK (feed == report + 42);
263 CHECK (res = feed -42 +23.55f);
264
265 // again...
266 report = 0;
267 feed = -1-rani(20);
268 res = theFun (feed);
269
270 // this time the delegate was *not* invoked,
271 // only the installed realFun
272 CHECK (feed == report);
273 CHECK (res = feed + 23.55f);
274 }
275
276
277
280 void
282 {
283 using Fun = std::function<float(int)>;
284 using Lazy = LazyInit<Fun>;
285
286 bool init{false};
287 uint invoked{0};
288 Lazy funny{funny, [&](Lazy* self)
289 {
290 Fun& thisFun = static_cast<Fun&> (*self);
291
292 thisFun = [&invoked](int num)
293 {
294 ++invoked;
295 return num * 0.555f;
296 };
297 init = true;
298 }};
299 CHECK (not invoked);
300 CHECK (not init);
301 CHECK (funny);
302
303 int feed = 1 + rani(99);
304 CHECK (feed*0.555f == funny(feed));
305 CHECK (1 == invoked);
306 CHECK (init);
307 }
308
309
310
312 struct LazyDemo
313 : LazyInit<>
314 {
315 using Fun = std::function<int(int)>;
316
317 int seed{0};
318 Fun fun; // ◁────────────────────────────────── this will be initialised lazily....
319
320 template<typename FUN>
321 auto
322 buildInit (FUN&& fun2install)
323 {
324 return [theFun = forward<FUN> (fun2install)]
325 (LazyDemo* self)
326 { // this runs when init is actually performed....
327 CHECK (self);
328 if (self->fun)
329 // chain-up behind existing function
330 self->fun = [self, prevFun=self->fun, nextFun=theFun]
331 (int i)
332 {
333 return nextFun (prevFun (i));
334 };
335 else
336 // build new function chain, inject seed from object
337 self->fun = [self, newFun=theFun]
338 (int i)
339 {
340 return newFun (i + self->seed); // Note: binding to actual instance location
341 };
342 };
343 }
344
345
348 , fun{}
349 {
350 installInitialiser(fun, buildInit([](int){ return 0; }));
351 }
352 // prevent this ctor from shadowing the copy ctors //////TICKET #963
353 template<typename FUN, typename =disable_if_self<LazyDemo, FUN>>
354 LazyDemo (FUN&& someFun)
356 , fun{}
357 {
358 installInitialiser(fun, buildInit (forward<FUN> (someFun)));
359 }
360
361 template<typename FUN>
362 LazyDemo&&
363 attach (FUN&& someFun)
364 {
365 installInitialiser(fun, buildInit (forward<FUN> (someFun)));
366 return move(*this);
367 }
368 };
369
385 void
387 {
388 LazyDemo dd;
389 CHECK (not dd.isInit()); // not initialised, since function was not invoked yet
390 CHECK (dd.fun); // the functor is not empty anymore, since the »trap« was installed
391
392 dd.seed = 2;
393 CHECK (0 == dd.fun(22)); // d1 was default initialised and thus got the "return 0" function
394 CHECK (dd.isInit()); // first invocation also triggered the init-routine
395
396 // is »engaged« after init and rejects move / copy
397 VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} );
398
399
400 dd = LazyDemo{[](int i) // assign a fresh copy (discarding any state in d1)
401 {
402 return i + 1; // using a "return i+1" function
403 }};
404 CHECK (not dd.isInit());
405 CHECK (dd.seed == 0); // assignment indeed erased any existing settings (seed≔2)
406 CHECK (dd.fun);
407
408 CHECK (23 == dd.fun(22)); // new function was tied in (while also referring to self->seed)
409 CHECK (dd.isInit());
410 dd.seed = 3; // set the seed
411 CHECK (26 == dd.fun(22)); // seed value is picked up dynamically
412
413 VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} );
414
415 // attach a further function, to be chained-up
416 dd.attach([](int i)
417 {
418 return i / 2;
419 });
420 CHECK (dd.isInit());
421 CHECK (dd.seed == 3);
422 CHECK (12 == dd.fun(21)); // 21+3+1=25 / 2
423 CHECK (13 == dd.fun(22));
424 CHECK (13 == dd.fun(23));
425 dd.seed++;
426 CHECK (14 == dd.fun(23)); // 23+4+1=28 / 2
427 CHECK (14 == dd.fun(24));
428 CHECK (15 == dd.fun(25));
429
430 // ...use exactly the same configuration,
431 // but applied in one shot -> chained lazy-Init
432 dd = LazyDemo{[](int i){return i+1; }}
433 .attach([](int i){return i/2; });
434 dd.seed = 3;
435 CHECK (not dd.isInit());
436 CHECK (dd.seed == 3);
437 CHECK (dd.fun);
438
439 CHECK (12 == dd.fun(21));
440 CHECK (13 == dd.fun(22));
441 CHECK (13 == dd.fun(23));
442 dd.seed++;
443 CHECK (14 == dd.fun(23));
444 CHECK (14 == dd.fun(24));
445 CHECK (15 == dd.fun(25));
446
447 // create a nested graph of chained pending init
448 dd = LazyDemo{[](int i){return i+1; }};
449 LazyDemo d1{dd};
450 LazyDemo d2{move(dd)};
451 d2.seed = 3;
452 d2.attach ([](int i){return i/2; });
453 LazyDemo d3{d2};
454 d2.attach ([](int i){return i-1; });
455
456 // dd was left in defunct state by the move, and thus is locked
457 CHECK (not dd.fun);
458 CHECK (dd.isInit());
459 VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} );
460 // this can be amended by assigning another instance not yet engaged
461 dd = d2;
462 d2.seed = 5;
463 std::swap (d2,d3);
464 std::swap (d3,d1);
465 // confused?? ;-)
466 CHECK (not dd.isInit() and dd.seed == 3); // Seed≡3 {i+1} ⟶ {i/2} ⟶ {i-1}
467 CHECK (not d1.isInit() and d1.seed == 5); // Seed≡5 {i+1} ⟶ {i/2} ⟶ {i-1}
468 CHECK (not d2.isInit() and d2.seed == 3); // Seed≡3 {i+1} ⟶ {i/2}
469 CHECK (not d3.isInit() and d3.seed == 0); // Seed≡0 {i+1}
470
471 CHECK (12 == dd.fun(23)); // 23+3 +1 = 27/2 = 13 -1 = 12
472 CHECK (13 == d1.fun(23)); // 23+5 +1 = 29/2 = 14 -1 = 13
473 CHECK (13 == d2.fun(23)); // 23+3 +1 = 27/2 = 13 = 13
474 CHECK (24 == d3.fun(23)); // 23+0 +1 = 24
475 }
476 };
477
478
480 LAUNCHER (LazyInit_test, "unit common");
481
482
483}} // namespace lib::test
Mix-in for lazy/delayed initialisation of an embedded functor.
void installInitialiser(std::function< SIG > &targetFunctor, INI &&initialiser)
bool isInit() const
»Trojan Function« builder.
static auto generateTrap(DEL *delegate)
Invocation: build a Lambda to activate the »Trap« and then to forward the invocation to the actual fu...
unsigned int uint
Definition integral.hpp:29
Building block to allow delayed initialisation of infrastructure tied to a functor.
Simple and lightweight helpers for metaprogramming and type detection.
disable_if< std::is_same< std::remove_cv_t< std::remove_reference_t< extractFirst_t< ARGS... > > >, SELF > > disable_if_self
helper to prevent a template constructor from shadowing inherited copy ctors
constexpr auto isFunMember(FUN)
Helper to pick up a member field for verification.
Definition function.hpp:332
Implementation namespace for support and library code.
void const * RawAddr
Definition lazy-init.hpp:92
int rani(uint bound=_iBOUND())
Definition random.hpp:135
Test runner and basic definitions for tests.
bool isCloseBy(A &&a, B &&b, size_t consideredNearby=50)
determine heuristically if two objects are located „close to each other“ in memory.
Definition util.hpp:434
bool isSameObject(A const &a, B const &b)
compare plain object identity, based directly on the referee's memory identities.
Definition util.hpp:421
Simplistic test class runner.
#define LAUNCHER(_TEST_CLASS_, _GROUPS_)
Definition run.hpp:116
elaborate setup used for integration test
LazyDemo && attach(FUN &&someFun)
A collection of frequently used helper functions to support unit testing.
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises an exception.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...