Lumiera  0.pre.03
»edit your freedom«
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"
21 #include "lib/test/test-helper.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 
30 namespace lib {
31 namespace test{
32 
33  using util::isSameObject;
36  using err::LUMIERA_ERROR_LIFECYCLE;
37  using std::make_unique;
38 
39 
40 
41 
42  /***********************************************************************************/
51  : public Test
52  {
53 
54  void
55  run (Arg)
56  {
57  seedRand();
58 
65  }
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 = "please look elsewhere";
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 
346  LazyDemo()
347  : LazyInit{MarkDisabled()}
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)
355  : LazyInit{MarkDisabled()}
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
elaborate setup used for integration test
»Trojan Function« builder.
Definition: lazy-init.hpp:149
Definition: run.hpp:40
Simple and lightweight helpers for metaprogramming and type detection.
Mix-in for lazy/delayed initialisation of an embedded functor.
Definition: lazy-init.hpp:206
static auto generateTrap(DEL *delegate)
Invocation: build a Lambda to activate the »Trap« and then to forward the invocation to the actual fu...
Definition: lazy-init.hpp:179
int rani(uint bound=_iBOUND())
Definition: random.hpp:135
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises an exception.
constexpr auto isFunMember(FUN)
Helper to pick up a member field for verification.
Definition: function.hpp:263
Implementation namespace for support and library code.
Building block to allow delayed initialisation of infrastructure tied to a functor.
Simplistic test class runner.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
A collection of frequently used helper functions to support unit testing.
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
Definition: meta/util.hpp:151
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