Lumiera  0.pre.03
»edit your freedom«
test-tracking-test.cpp
Go to the documentation of this file.
1 /*
2  TestTracking(Test) - demonstrate test helpers for tracking automated clean-up
3 
4  Copyright (C)
5  2024, 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"
20 #include "lib/test/test-helper.hpp"
23 #include "lib/allocator-handle.hpp"
24 #include "lib/format-cout.hpp"
25 #include "lib/format-util.hpp"
26 
27 #include <string>
28 
29 using std::string;
30 using util::toString;
31 using util::join;
32 
33 
34 namespace lib {
35 namespace test{
36 namespace test{
37 
38 
39  /***************************************************/
46  class TestTracking_test : public Test
47  {
48  void
49  run (Arg)
50  {
54  }
55 
56 
61  void
63  {
64  auto& log = Tracker::log;
65  log.clear (this);
66 
67  Tracker alpha; // (1) create α
68  auto randomAlpha = toString(alpha.val);
69 
70  log.event("ID",alpha.val); // (2) α has an random ID
71  {
72  Tracker beta{55}; // (3) create β
73  alpha = beta; // (4) assign α ≔ β
74  }
75  log.event ("ID",alpha.val); // (5) thus α now also bears the ID 55 of β
76  Tracker gamma = move(alpha); // (6) create γ by move-defuncting α
77  {
78  Tracker delta(23); // (7) create δ with ID 23
79  delta = move(gamma); // (8) move-assign δ ⟵ γ
80  log.event ("ID",delta.val); // (9) thus δ now bears the ID 55 (moved α ⟶ γ ⟶ δ)
81  CHECK (delta.val == 55);
82  }
83  log.event("ID",alpha.val); // (X) and thus α is now a zombie object
84  CHECK (alpha.val == Tracker::DEFUNCT);
85 
86 
87  cout << "____Tracker-Log_______________\n"
88  << util::join(Tracker::log, "\n")
89  << "\n───╼━━━━━━━━━━━╾──────────────"<<endl;
90 
91  CHECK (log.verify("EventLogHeader").on("TestTracking_test")
92  .beforeCall("ctor").on(&alpha) // (1) create α
93  .beforeEvent("ID", randomAlpha) // (2) α has an random ID
94  .beforeCall("ctor").arg(55) // (3) create β
95  .beforeCall("assign-copy").on(&alpha).arg("Track{55}") // (4) assign α ≔ β
96  .beforeCall("dtor").arg(55)
97  .beforeEvent("ID", "55") // (5) thus α now also bears the ID 55 of β
98  .beforeCall("ctor-move").on(&gamma).arg("Track{55}") // (6) create γ by move-defuncting α
99  .beforeCall("ctor").arg(23) // (7) create δ with ID 23
100  .beforeCall("assign-move").arg("Track{55}") // (8) move-assign δ ⟵ γ
101  .beforeEvent("ID", "55") // (9) thus δ now bears the ID 55 (moved α ⟶ γ ⟶ δ)
102  .beforeCall("dtor").arg(55)
103  .beforeEvent("ID", toString(Tracker::DEFUNCT)) // (X) and thus α is now a zombie object
104  );
105  }
106 
107 
108 
111  void
113  {
114  CHECK (Dummy::checksum() == 0);
115  {
116  Dummy dum1; // picks a random positive int by default...
117  CHECK (0 < dum1.getVal() and dum1.getVal() <= 100'000'000);
118  CHECK (Dummy::checksum() == dum1.getVal());
119 
120  Dummy dum2{55};
121  CHECK (55 == dum2.getVal());
122  CHECK (Dummy::checksum() == dum1.getVal() + 55);
123 
124  Dummy dum3{move (dum2)};
125  CHECK (55 == dum3.getVal());
126  CHECK (0 == dum2.getVal());
127 
128  dum3.setVal (23);
129  CHECK (23 == dum3.getVal());
130 
131  dum1 = move (dum3);
132  CHECK (23 == dum1.getVal());
133  CHECK (0 == dum2.getVal());
134  CHECK (0 == dum3.getVal());
135  CHECK (Dummy::checksum() == 23);
136 
137  Dummy::activateCtorFailure (true);
138  try {
139  Dummy kabooom;
140  }
141  catch (int v)
142  {
143  CHECK (0 < v and v <= 100'000'000);
144  CHECK (Dummy::checksum() == 23 + v);
145  Dummy::checksum() -= v;
146  }
147  Dummy::activateCtorFailure (false);
148  CHECK (23 == dum1.getVal());
149  CHECK (0 == dum2.getVal());
150  CHECK (0 == dum3.getVal());
151  CHECK (Dummy::checksum() == 23);
152  }
153  CHECK (Dummy::checksum() == 0);
154  }
155 
156 
157 
166  void
168  {
169  // setup a common event-log for the tracking objects and -allocator
170  auto& log = TrackingAllocator::log;
171  Tracker::log.clear("Tracking-Allocator-Test");
172  Tracker::log.joinInto(log);
173 
174  // everything is safe and sound initially....
175  CHECK (TrackingAllocator::checksum() == 0, "Testsuite is broken");
176  CHECK (TrackingAllocator::use_count() == 0);
177 
178  { // Test-1 : raw allocations....
179  log.event("Test-1");
180  TrackingAllocator allo;
181  CHECK (TrackingAllocator::use_count() == 1);
182  CHECK (TrackingAllocator::numAlloc() == 0);
183  CHECK (TrackingAllocator::numBytes() == 0);
184 
185  void* mem = allo.allocate (55);
186  CHECK (TrackingAllocator::numAlloc() == 1);
187  CHECK (TrackingAllocator::numBytes() == 55);
188 
189  CHECK (allo.manages (mem));
190  CHECK (allo.getSize (mem) == 55); // individual registration recalls the allocation's size
191  HashVal memID = allo.getID (mem);
192  CHECK (0 < memID);
193  CHECK (TrackingAllocator::checksum() == memID*55);
194 
195  allo.deallocate (mem, 42); // note: passing a wrong number here is marked as ERROR in the log
196  CHECK (not allo.manages (mem));
197  CHECK (allo.getSize (mem) == 0);
198  CHECK (allo.getID (mem) == 0);
199  CHECK (TrackingAllocator::use_count() == 1);
200  CHECK (TrackingAllocator::numAlloc() == 0);
201  CHECK (TrackingAllocator::numBytes() == 0);
202  }
203  CHECK (log.verify("EventLogHeader").on("Tracking-Allocator-Test")
204  .before("logJoin")
205  .beforeEvent("Test-1")
206  .beforeCall("allocate").on(GLOBAL).argPos(0, 55)
207  .beforeEvent("error", "SizeMismatch-42-≠-55")
208  .beforeCall("deallocate").on(GLOBAL).argPos(0, 42)
209  );
210  CHECK (TrackingAllocator::checksum() == 0);
211 
212 
213  { // Test-2 : attach scoped-ownership-front-End
214  log.event("Test-2");
215 
217  CHECK (sizeof(uniFab) == sizeof(TrackingFactory));
218  CHECK (sizeof(uniFab) == sizeof(std::shared_ptr<byte>));
219  CHECK (not allo::is_Stateless_v<decltype(uniFab)>);
220 
221  CHECK (TrackingAllocator::use_count() == 1);
222  CHECK (TrackingAllocator::numAlloc() == 0);
223  CHECK (TrackingAllocator::numBytes() == 0);
224  {
225  log.event("fabricate unique");
226  auto uniqueHandle = uniFab.make_unique<Tracker> (77);
227  CHECK (uniqueHandle);
228  CHECK (uniqueHandle->val == 77);
229  CHECK (TrackingAllocator::use_count() == 2);
230  CHECK (TrackingAllocator::numAlloc() == 1);
231  CHECK (TrackingAllocator::numBytes() == sizeof(Tracker));
232 
233  // all the default tracking allocators indeed attach to the same pool
234  TrackingAllocator allo;
235  void* mem = uniqueHandle.get();
236  CHECK (allo.manages (mem));
237  HashVal memID = allo.getID (mem);
238  CHECK (0 < memID);
239  CHECK (TrackingAllocator::checksum() == memID*sizeof(Tracker));
240 
241  }// and it's gone...
242  CHECK (TrackingAllocator::use_count() == 1);
243  CHECK (TrackingAllocator::numAlloc() == 0);
244  CHECK (TrackingAllocator::numBytes() == 0);
245  }
246 
247  CHECK (log.verifyEvent("Test-2")
248  .beforeEvent("fabricate unique")
249  .beforeCall("allocate").on(GLOBAL).argPos(0, sizeof(Tracker))
250  .beforeCall("create-Tracker").on(GLOBAL).arg(77)
251  .beforeCall("ctor").on("Tracker").arg(77)
252  .beforeCall("destroy-Tracker").on(GLOBAL)
253  .beforeCall("dtor").on("Tracker").arg(77)
254  .beforeCall("deallocate").on(GLOBAL).argPos(0, sizeof(Tracker))
255  );
256  CHECK (TrackingAllocator::checksum() == 0);
257 
258 
259  // define a vector type to use the TrackingAllocator internally
260  using TrackVec = std::vector<Tracker, TrackAlloc<Tracker>>;
261 
262  // the following pointers are used later to identify log entries...
263  Tracker *t1, *t2, *t3, *t4, *t5, *t6;
264 
265 
266  { // Test-3 : use as STL allocator
267  log.event("Test-3");
268 
269  log.event("fill with 3 default instances");
270  TrackVec vec1(3);
271 
272  int v3 = vec1.back().val;
273 
274  TrackVec vec2;
275  log.event("move last instance over into other vector");
276  vec2.emplace_back (move (vec1[2]));
277  CHECK (vec2.back().val == v3);
278  CHECK (vec1.back().val == Tracker::DEFUNCT);
279 
280  // capture object locations for log verification
281  t1 = & vec1[0];
282  t2 = & vec1[1];
283  t3 = & vec1[2];
284  t4 = & vec2.front();
285  log.event("leave scope");
286  }
287  CHECK (log.verifyEvent("Test-3")
288  .beforeEvent("fill with 3 default instances")
289  .beforeCall("allocate").on(GLOBAL)
290  .beforeCall("ctor").on(t1)
291  .beforeCall("ctor").on(t2)
292  .beforeCall("ctor").on(t3)
293  .beforeEvent("move last instance over into other vector")
294  .beforeCall("allocate").on(GLOBAL)
295  .beforeCall("ctor-move").on(t4)
296  .beforeEvent("leave scope")
297  .beforeCall("dtor").on(t4)
298  .beforeCall("deallocate").on(GLOBAL)
299  .beforeCall("dtor").on(t1) // (problematic test? order may be implementation dependent)
300  .beforeCall("dtor").on(t2)
301  .beforeCall("dtor").on(t3)
302  .beforeCall("deallocate").on(GLOBAL)
303  );
304  CHECK (TrackingAllocator::checksum() == 0);
305 
306 
307  { // Test-4 : intermingled use of several pools
308  log.event("Test-4");
309 
310  TrackAlloc<Tracker> allo1{"POOL-1"};
311  TrackAlloc<Tracker> allo2{"POOL-2"};
312  CHECK (allo1 != allo2);
313 
314  CHECK (TrackingAllocator::use_count(GLOBAL) == 0);
315  CHECK (TrackingAllocator::use_count("POOL-1") == 1); // referred by allo1
316  CHECK (TrackingAllocator::use_count("POOL-2") == 1); // referred by allo2
317  CHECK (TrackingAllocator::checksum ("POOL-1") == 0);
318  CHECK (TrackingAllocator::checksum ("POOL-2") == 0);
319 
320  TrackVec vec1{allo1};
321  TrackVec vec2{allo2};
322  CHECK (TrackingAllocator::use_count("POOL-1") == 2); // now also referred by the copy in the vectors
323  CHECK (TrackingAllocator::use_count("POOL-2") == 2);
324 
325  log.event("reserve space in vectors");
326  vec1.reserve(20);
327  vec2.reserve(2);
328  CHECK (TrackingAllocator::numBytes("POOL-1") == 20*sizeof(Tracker));
329  CHECK (TrackingAllocator::numBytes("POOL-2") == 2*sizeof(Tracker));
330 
331  CHECK (TrackingAllocator::numBytes(GLOBAL) == 0);
332  CHECK (TrackingAllocator::numBytes() == 0);
333 
334  log.event("create elements in vec1");
335  vec1.resize(5);
336  vec1.back().val = 11;
337  log.event("add element to vec2");
338  vec2.push_back (Tracker{22});
339 
340  // capture object locations for log verification later
341  t1 = & vec1[0];
342  t2 = & vec1[1];
343  t3 = & vec1[2];
344  t4 = & vec1[3];
345  t5 = & vec1[4];
346  t6 = & vec2.front();
347 
348  log.event ("swap vectors");
349  std::swap (vec1, vec2);
350 
351  CHECK (vec1.back().val == 22);
352  CHECK (vec2.back().val == 11);
353  CHECK (vec1.size() == 1);
354  CHECK (vec2.size() == 5);
355  // the allocators were migrated alongside with the swap
356  CHECK (TrackingAllocator::numBytes("POOL-1") == 20*sizeof(Tracker));
357  CHECK (TrackingAllocator::numBytes("POOL-2") == 2*sizeof(Tracker));
358  // this can be demonstrated....
359  log.event ("clear the elements migrated to vec2");
360  vec2.clear();
361  vec2.shrink_to_fit();
362  CHECK (vec2.capacity() == 0);
363  CHECK (TrackingAllocator::numBytes("POOL-1") == 0 );
364  CHECK (TrackingAllocator::numBytes("POOL-2") == 2*sizeof(Tracker));
365  CHECK (vec1.size() == 1);
366  CHECK (vec1.capacity() == 2); // unaffected
367 
368  log.event ("leave scope");
369  }
370 
371  CHECK (log.verifyEvent("Test-4")
372  .beforeEvent("reserve space in vectors")
373  .beforeCall("allocate").on("POOL-1").argPos(0, 20*sizeof(Tracker))
374  .beforeCall("allocate").on("POOL-2").argPos(0, 2*sizeof(Tracker))
375 
376  .beforeEvent("create elements in vec1")
377  .beforeCall("ctor").on(t1)
378  .beforeCall("ctor").on(t2)
379  .beforeCall("ctor").on(t3)
380  .beforeCall("ctor").on(t4)
381  .beforeCall("ctor").on(t5)
382 
383  .beforeEvent("add element to vec2")
384  .beforeCall("ctor").arg(22)
385  .beforeCall("ctor-move").on(t6).arg("Track{22}")
386  .beforeCall("dtor").arg(Tracker::DEFUNCT)
387 
388  .beforeEvent("swap vectors")
389  .beforeEvent("clear the elements migrated to vec2")
390  .beforeCall("dtor").on(t1)
391  .beforeCall("dtor").on(t2)
392  .beforeCall("dtor").on(t3)
393  .beforeCall("dtor").on(t4)
394  .beforeCall("dtor").on(t5).arg(11)
395  .beforeCall("deallocate").on("POOL-1").argPos(0, 20*sizeof(Tracker))
396 
397  .beforeEvent("leave scope")
398  .beforeCall("dtor").on(t6).arg(22)
399  .beforeCall("deallocate").on("POOL-2").argPos(0, 2*sizeof(Tracker))
400  );
401  // everything clean and all pools empty again...
402  CHECK (TrackingAllocator::use_count(GLOBAL) == 0);
403  CHECK (TrackingAllocator::use_count("POOL-1") == 0);
404  CHECK (TrackingAllocator::use_count("POOL-2") == 0);
405  CHECK (TrackingAllocator::checksum("POOL-1") == 0);
406  CHECK (TrackingAllocator::checksum("POOL-2") == 0);
407  CHECK (TrackingAllocator::checksum(GLOBAL) == 0);
408 
409 
410  cout << "____Tracking-Allo-Log_________\n"
411  << util::join(TrackingAllocator::log,"\n")
412  << "\n───╼━━━━━━━━━━━━━━━━━╾────────"<<endl;
413  }
414  };
415 
416  LAUNCHER (TestTracking_test, "unit common");
417 
418 
419 }}} // namespace lib::test::test
420 
Location allocate(size_t n)
Allot a memory block of given size bytes.
Adapter to use a generic factory FAC for creating managed object instances with unique ownership...
EventLog & event(string text)
log some text as event
Definition: event-log.cpp:676
Automatically use custom string conversion in C++ stream output.
static HashVal checksum(Literal pool=GLOBAL)
get Checksum for specific mem-pool
C++ standard compliant custom allocator adapter backed by the TrackingAllocator and the MemoryPool de...
size_t getSize(Location) const
retrieve the registered size of this allocation, if known.
Definition: run.hpp:40
EventMatch & beforeCall(string match)
find a match for some function invocation after the current point of reference
Definition: event-log.cpp:438
A front-end/concept to allow access to custom memory management.
Generic low-level allocator attached to tracking MemoryPool.
static size_t numBytes(Literal pool=GLOBAL)
calculate currently allotted Bytes for mem-pool
EventMatch & arg(ARGS const &...args)
refine filter to additionally require specific arguments
Definition: event-log.hpp:198
bool manages(Location) const
probe if this allocator pool did allocate the given memory location
EventMatch verifyEvent(string match) const
start a query to match for some event.
Definition: event-log.cpp:770
Implementation namespace for support and library code.
Generic object factory backed by TrackingAllocator.
void deallocate(Location, size_t=0) noexcept
Discard and forget an allocation created through this allocator.
A tracking Dummy object for tests.
EventMatch & beforeEvent(string match)
find a match for an "event" after the current point of reference
Definition: event-log.cpp:417
EventLog & clear()
purge log contents while retaining just the original Header-ID
Definition: event-log.cpp:643
Simplistic test class runner.
unittest helper code: test dummy objects to track instances.
std::string toString(TY const &val) noexcept
get some string representation of any object, reliably.
Definition: format-obj.hpp:191
Unittest helper code: a custom allocator to track memory usage.
EventLog & joinInto(EventLog &otherLog)
Merge this log into another log, forming a combined log.
Definition: event-log.cpp:625
EventMatch & argPos(size_t idx, ARG const &arg)
refine filter to additionally require match on a specific positional argument
Definition: event-log.hpp:227
A collection of frequently used helper functions to support unit testing.
HashVal getID(Location) const
retrieve the internal registration ID for this allocation.
A Dummy object for tests.
size_t HashVal
a STL compatible hash value
Definition: hash-value.h:52
static size_t numAlloc(Literal pool=GLOBAL)
get active allocation count for mem-pool
auto make_unique(ARGS &&...args)
Factory function: generate object with scoped ownership and automated clean-up.
static size_t use_count(Literal pool=GLOBAL)
determine number of active front-end handles
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
EventMatch verify(string match) const
start a query to match for some substring.
Definition: event-log.cpp:752
EventMatch & on(string targetID)
refine filter to additionally match the &#39;this&#39; attribute
Definition: event-log.cpp:583
EventMatch & before(string match)
find a match (substring match) of the given text in an EventLog entry after the current position ...
Definition: event-log.cpp:384