Lumiera 0.pre.04
»edit your freedom«
Loading...
Searching...
No Matches
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"
24#include "lib/format-cout.hpp"
25#include "lib/format-util.hpp"
26
27#include <string>
28
29using std::string;
30using util::toString;
31using util::join;
32
33
34namespace lib {
35namespace test{
36namespace test{
37
38
39 /***************************************************/
46 class TestTracking_test : public Test
47 {
48 void
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 (dum2.getVal() == 55);
122 CHECK (Dummy::checksum() == dum1.getVal() + 55);
123
124 Dummy dum3{move (dum2)};
125 CHECK (dum3.getVal() == 55);
126 CHECK (dum2.getVal() == Dummy::DEFUNCT);
127
128 dum3.setVal (23);
129 CHECK (dum3.getVal() == 23);
130
131 dum1 = move (dum3);
132 CHECK (dum1.getVal() == 23 );
133 CHECK (dum2.getVal() == Dummy::DEFUNCT);
134 CHECK (dum3.getVal() == Dummy::DEFUNCT);
135 CHECK (Dummy::checksum() == 23 );
136
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 }
148 CHECK (dum1.getVal() == 23 );
149 CHECK (dum2.getVal() == Dummy::DEFUNCT);
150 CHECK (dum3.getVal() == Dummy::DEFUNCT);
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");
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");
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
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}")
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"
412 << "\n───╼━━━━━━━━━━━━━━━━━╾────────"<<endl;
413 }
414 };
415
416 LAUNCHER (TestTracking_test, "unit common");
417
418
419}}} // namespace lib::test::test
420
A front-end/concept to allow access to custom memory management.
Adapter to use a generic factory FAC for creating managed object instances with unique ownership.
auto make_unique(ARGS &&...args)
Factory function: generate object with scoped ownership and automated clean-up.
A Dummy object for tests.
static void activateCtorFailure(bool indeed=true)
static constexpr int DEFUNCT
static long & checksum()
EventLog & event(string text)
log some text as event
EventMatch verify(string match) const
start a query to match for some substring.
EventLog & clear()
purge log contents while retaining just the original Header-ID
EventMatch verifyEvent(string match) const
start a query to match for some event.
EventLog & joinInto(EventLog &otherLog)
Merge this log into another log, forming a combined log.
EventMatch & arg(ARGS const &...args)
refine filter to additionally require specific arguments
EventMatch & before(string match)
find a match (substring match) of the given text in an EventLog entry after the current position
EventMatch & beforeEvent(string match)
find a match for an "event" after the current point of reference
EventMatch & argPos(size_t idx, ARG const &arg)
refine filter to additionally require match on a specific positional argument
EventMatch & on(string targetID)
refine filter to additionally match the ‘'this’` attribute
EventMatch & beforeCall(string match)
find a match for some function invocation after the current point of reference
C++ standard compliant custom allocator adapter backed by the TrackingAllocator and the MemoryPool de...
Generic low-level allocator attached to tracking MemoryPool.
void deallocate(Location, size_t=0) noexcept
Discard and forget an allocation created through this allocator.
static size_t numAlloc(Literal pool=GLOBAL)
get active allocation count for mem-pool
Location allocate(size_t n)
Allot a memory block of given size bytes.
bool manages(Location) const
probe if this allocator pool did allocate the given memory location
HashVal getID(Location) const
retrieve the internal registration ID for this allocation.
static size_t numBytes(Literal pool=GLOBAL)
calculate currently allotted Bytes for mem-pool
static size_t use_count(Literal pool=GLOBAL)
determine number of active front-end handles
size_t getSize(Location) const
retrieve the registered size of this allocation, if known.
static HashVal checksum(Literal pool=GLOBAL)
get Checksum for specific mem-pool
Generic object factory backed by TrackingAllocator.
Automatically use custom string conversion in C++ stream output.
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
Implementation namespace for support and library code.
size_t HashVal
a STL compatible hash value
Definition hash-value.h:52
Test runner and basic definitions for tests.
std::string toString(TY const &val) noexcept
get some string representation of any object, reliably.
string join(COLL &&coll, string const &delim=", ")
enumerate a collection's contents, separated by delimiter.
Simplistic test class runner.
#define LAUNCHER(_TEST_CLASS_, _GROUPS_)
Definition run.hpp:116
A tracking Dummy object for tests.
static lib::test::EventLog log
static constexpr int DEFUNCT
A collection of frequently used helper functions to support unit testing.
Unittest helper code: a custom allocator to track memory usage.
unittest helper code: test dummy objects to track instances.