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) 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 
29 #include "lib/test/run.hpp"
30 #include "lib/test/test-helper.hpp"
31 #include "lib/lazy-init.hpp"
32 #include "lib/meta/util.hpp"
33 #include "lib/util.hpp"
34 
35 #include <memory>
36 
37 
38 
39 namespace lib {
40 namespace test{
41 
42  using util::isSameObject;
45  using err::LUMIERA_ERROR_LIFECYCLE;
46  using std::make_unique;
47 
48 
49 
50 
51  /***********************************************************************************/
60  : public Test
61  {
62 
63  void
64  run (Arg)
65  {
72  }
73 
74 
75 
85  void
87  {
88  size_t beacon;
89  auto fun = [&](uint challenge){ return beacon+challenge; };
90 
91  using Sig = size_t(uint);
92  CHECK (isFunMember<Sig> (&fun));
93 
94  beacon = rand();
95  uint c = beacon % 42;
96  // verify we can invoke the target function
97  CHECK (beacon+c == fun(c));
98 
99  // verify we can also invoke the target function through a reference
100  using FunType = decltype(fun);
101  FunType& funRef = fun;
102  CHECK (beacon+c == funRef(c));
103 
104  // construct delegate function exposing the expected behaviour;
105  // additionally this function captures the passed-in address.
106  RawAddr location{nullptr};
107  auto delegate = [&](RawAddr adr) -> FunType&
108  {
109  location = adr;
110  return fun;
111  };
112  using Delegate = decltype(delegate);
113  auto delP = make_unique<Delegate> (delegate);
114 
115  // verify the heap-allocated copy of the delegate behaves as expected
116  location = nullptr;
117  CHECK (beacon+c == (*delP)(this)(c));
118  CHECK (location == this);
119 
120  // now (finally) build the »trap function«...
121  auto trojanLambda = TrojanFun<Sig>::generateTrap (delP.get());
122  CHECK (sizeof(trojanLambda) == sizeof(size_t));
123 
124  // on invocation...
125  // - it captures its current location
126  // - passes this to the delegate
127  // - invokes the target function returned from the delegate
128  CHECK (beacon+c == trojanLambda(c));
129  CHECK (location == &trojanLambda);
130 
131  // repeat same with a copy, and changed beacon value
132  auto trojanClone = trojanLambda;
133  beacon = rand();
134  c = beacon % 55;
135  CHECK (beacon+c == trojanClone(c));
136  CHECK (location == &trojanClone);
137  CHECK (beacon+c == trojanLambda(c));
138  CHECK (location == &trojanLambda);
139  }
140 
141 
142 
158  void
160  {
161 // char payload[24];// ◁─────────────────────────────── use this to make the test fail....
162  const char* payload = "please look elsewhere";
163  auto lambda = [payload]{ return RawAddr(&payload); };
164 
165  RawAddr location = lambda();
166  CHECK (location == &lambda);
167 
168  std::function funWrap{lambda};
169  CHECK (funWrap);
170  CHECK (not isSameObject (funWrap, lambda));
171 
172  location = funWrap();
173  CHECK (util::isCloseBy (location, funWrap));
174  // if »small object optimisation« was used,
175  // the lambda will be copied directly into the std:function;
176  // otherwise it will be heap allocated and this test fails.
177 
178  // for context: these are considered "close by",
179  // since both are sitting right here in the same stack frame
180  CHECK (util::isCloseBy (funWrap, lambda));
181  }
182 
183 
184 
191  void
193  {
194  struct Nested
195  {
196  int unrelated{rand()};
197  int anchor{rand()};
198  };
199  struct Demo
200  {
201  Nested nested;
202  virtual ~Demo(){ };
203  virtual RawAddr peek()
204  {
205  return &nested.anchor;
206  }
207  };
208 
209  // find out generic offset...
210  const ptrdiff_t offNested = []{
211  Nested probe;
212  return captureRawAddrOffset(&probe, &probe.anchor);
213  }();
214  Demo here;
215  // find out actual offset in existing object
216  const ptrdiff_t offBase = captureRawAddrOffset(&here, &here.nested);
217 
218  CHECK (offBase > 0);
219  CHECK (offNested > 0);
220 
221  // create a copy far far away...
222  auto farAway = make_unique<Demo> (here);
223 
224  // reconstruct base address from starting point
225  RawAddr startPoint = farAway->peek();
226  Nested* farNested = relocate<Nested>(startPoint, -offNested);
227  CHECK (here.nested.unrelated == farNested->unrelated);
228 
229  Demo* farSelf = relocate<Demo> (farNested, -offBase);
230  CHECK (here.nested.anchor == farSelf->nested.anchor);
231  CHECK (isSameObject (*farSelf, *farAway));
232  }
233 
234 
235 
238  void
240  {
241  using Fun = std::function<float(int)>;
242  Fun theFun;
243  CHECK (not theFun);
244 
245  int report{0};
246  auto delegate = [&report](RawAddr insideFun) -> Fun&
247  {
248  auto realFun = [&report](int num)
249  {
250  report += num;
251  return num + 23.55f;
252  };
253  Fun& target = *relocate<Fun>(insideFun, -FUNCTOR_PAYLOAD_OFFSET);
254  report = -42; // as proof that the init-delegate was invoked
255  target = realFun;
256  return target;
257  };
258  CHECK (not theFun);
259  // install the init-»trap«
260  theFun = TrojanFun<float(int)>::generateTrap (&delegate);
261  CHECK (theFun);
262  CHECK (0 == report);
263 
264  // invoke function
265  int feed{1+rand()%100};
266  float res = theFun (feed);
267 
268  // delegate *and* realFun were invoked
269  CHECK (feed == report + 42);
270  CHECK (res = feed -42 +23.55f);
271 
272  // again...
273  report = 0;
274  feed = -1-rand()%20;
275  res = theFun (feed);
276 
277  // this time the delegate was *not* invoked,
278  // only the installed realFun
279  CHECK (feed == report);
280  CHECK (res = feed + 23.55f);
281  }
282 
283 
284 
287  void
289  {
290  using Fun = std::function<float(int)>;
291  using Lazy = LazyInit<Fun>;
292 
293  bool init{false};
294  uint invoked{0};
295  Lazy funny{funny, [&](Lazy* self)
296  {
297  Fun& thisFun = static_cast<Fun&> (*self);
298 
299  thisFun = [&invoked](int num)
300  {
301  ++invoked;
302  return num * 0.555f;
303  };
304  init = true;
305  }};
306  CHECK (not invoked);
307  CHECK (not init);
308  CHECK (funny);
309 
310  int feed = 1 + rand()%99;
311  CHECK (feed*0.555f == funny(feed));
312  CHECK (1 == invoked);
313  CHECK (init);
314  }
315 
316 
317 
319  struct LazyDemo
320  : LazyInit<>
321  {
322  using Fun = std::function<int(int)>;
323 
324  int seed{0};
325  Fun fun; // ◁────────────────────────────────── this will be initialised lazily....
326 
327  template<typename FUN>
328  auto
329  buildInit (FUN&& fun2install)
330  {
331  return [theFun = forward<FUN> (fun2install)]
332  (LazyDemo* self)
333  { // this runs when init is actually performed....
334  CHECK (self);
335  if (self->fun)
336  // chain-up behind existing function
337  self->fun = [self, prevFun=self->fun, nextFun=theFun]
338  (int i)
339  {
340  return nextFun (prevFun (i));
341  };
342  else
343  // build new function chain, inject seed from object
344  self->fun = [self, newFun=theFun]
345  (int i)
346  {
347  return newFun (i + self->seed); // Note: binding to actual instance location
348  };
349  };
350  }
351 
352 
353  LazyDemo()
354  : LazyInit{MarkDisabled()}
355  , fun{}
356  {
357  installInitialiser(fun, buildInit([](int){ return 0; }));
358  }
359  // prevent this ctor from shadowing the copy ctors //////TICKET #963
360  template<typename FUN, typename =disable_if_self<LazyDemo, FUN>>
361  LazyDemo (FUN&& someFun)
362  : LazyInit{MarkDisabled()}
363  , fun{}
364  {
365  installInitialiser(fun, buildInit (forward<FUN> (someFun)));
366  }
367 
368  template<typename FUN>
369  LazyDemo&&
370  attach (FUN&& someFun)
371  {
372  installInitialiser(fun, buildInit (forward<FUN> (someFun)));
373  return move(*this);
374  }
375  };
376 
392  void
394  {
395  LazyDemo dd;
396  CHECK (not dd.isInit()); // not initialised, since function was not invoked yet
397  CHECK (dd.fun); // the functor is not empty anymore, since the »trap« was installed
398 
399  dd.seed = 2;
400  CHECK (0 == dd.fun(22)); // d1 was default initialised and thus got the "return 0" function
401  CHECK (dd.isInit()); // first invocation also triggered the init-routine
402 
403  // is »engaged« after init and rejects move / copy
404  VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} );
405 
406 
407  dd = LazyDemo{[](int i) // assign a fresh copy (discarding any state in d1)
408  {
409  return i + 1; // using a "return i+1" function
410  }};
411  CHECK (not dd.isInit());
412  CHECK (dd.seed == 0); // assignment indeed erased any existing settings (seed≔2)
413  CHECK (dd.fun);
414 
415  CHECK (23 == dd.fun(22)); // new function was tied in (while also referring to self->seed)
416  CHECK (dd.isInit());
417  dd.seed = 3; // set the seed
418  CHECK (26 == dd.fun(22)); // seed value is picked up dynamically
419 
420  VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} );
421 
422  // attach a further function, to be chained-up
423  dd.attach([](int i)
424  {
425  return i / 2;
426  });
427  CHECK (dd.isInit());
428  CHECK (dd.seed == 3);
429  CHECK (12 == dd.fun(21)); // 21+3+1=25 / 2
430  CHECK (13 == dd.fun(22));
431  CHECK (13 == dd.fun(23));
432  dd.seed++;
433  CHECK (14 == dd.fun(23)); // 23+4+1=28 / 2
434  CHECK (14 == dd.fun(24));
435  CHECK (15 == dd.fun(25));
436 
437  // ...use exactly the same configuration,
438  // but applied in one shot -> chained lazy-Init
439  dd = LazyDemo{[](int i){return i+1; }}
440  .attach([](int i){return i/2; });
441  dd.seed = 3;
442  CHECK (not dd.isInit());
443  CHECK (dd.seed == 3);
444  CHECK (dd.fun);
445 
446  CHECK (12 == dd.fun(21));
447  CHECK (13 == dd.fun(22));
448  CHECK (13 == dd.fun(23));
449  dd.seed++;
450  CHECK (14 == dd.fun(23));
451  CHECK (14 == dd.fun(24));
452  CHECK (15 == dd.fun(25));
453 
454  // create a nested graph of chained pending init
455  dd = LazyDemo{[](int i){return i+1; }};
456  LazyDemo d1{dd};
457  LazyDemo d2{move(dd)};
458  d2.seed = 3;
459  d2.attach ([](int i){return i/2; });
460  LazyDemo d3{d2};
461  d2.attach ([](int i){return i-1; });
462 
463  // dd was left in defunct state by the move, and thus is locked
464  CHECK (not dd.fun);
465  CHECK (dd.isInit());
466  VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} );
467  // this can be amended by assigning another instance not yet engaged
468  dd = d2;
469  d2.seed = 5;
470  std::swap (d2,d3);
471  std::swap (d3,d1);
472  // confused?? ;-)
473  CHECK (not dd.isInit() and dd.seed == 3); // Seed≡3 {i+1} ⟶ {i/2} ⟶ {i-1}
474  CHECK (not d1.isInit() and d1.seed == 5); // Seed≡5 {i+1} ⟶ {i/2} ⟶ {i-1}
475  CHECK (not d2.isInit() and d2.seed == 3); // Seed≡3 {i+1} ⟶ {i/2}
476  CHECK (not d3.isInit() and d3.seed == 0); // Seed≡0 {i+1}
477 
478  CHECK (12 == dd.fun(23)); // 23+3 +1 = 27/2 = 13 -1 = 12
479  CHECK (13 == d1.fun(23)); // 23+5 +1 = 29/2 = 14 -1 = 13
480  CHECK (13 == d2.fun(23)); // 23+3 +1 = 27/2 = 13 = 13
481  CHECK (24 == d3.fun(23)); // 23+0 +1 = 24
482  }
483  };
484 
485 
487  LAUNCHER (LazyInit_test, "unit common");
488 
489 
490 }} // namespace lib::test
elaborate setup used for integration test
»Trojan Function« builder.
Definition: lazy-init.hpp:158
Definition: run.hpp:49
Simple and lightweight helpers for metaprogramming and type detection.
Mix-in for lazy/delayed initialisation of an embedded functor.
Definition: lazy-init.hpp:215
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:188
#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:272
Implementation namespace for support and library code.
Building block to allow delayed initialisation of infrastructure tied to a functor.
Simple 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:160
bool isSameObject(A const &a, B const &b)
compare plain object identity, bypassing any custom comparison operators.
Definition: util.hpp:372