lumiera_/tests/library/test/test-tracking-test.cpp
Ichthyostega fbf7a792a8 Library: a step towards variadic type sequences (see #987)
Likely the most widely used facility, which enters into meta-programming
with type sequences, is our function-signature-detector `_Fun<X>`,
which returns a argument type-sequence.

After adding some bridges for cross-compatibility,
function-arguments are now extracted as a new-style,
''variadic sequence'' without trailing `NullType`.

Doing so required to augment some of the most widely used
sequence-processing helpers to work seamlessly also with the
new-style variadic sequences with a definition variant based
on variadics, which typically will later-on obsolete the original
solution, which at that time needed to be tediously coded as a
series of explicit specialisations for N arguments.
2025-02-16 21:10:06 +01:00

420 lines
18 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
TestTracking(Test) - demonstrate test helpers for tracking automated clean-up
Copyright (C)
2024, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
* *****************************************************************/
/** @file test-tracking-test.cpp
** unit test \ref TestTracking_test
*/
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/test/tracking-dummy.hpp"
#include "lib/test/tracking-allocator.hpp"
#include "lib/allocator-handle.hpp"
#include "lib/format-cout.hpp"
#include "lib/format-util.hpp"
#include <string>
using std::string;
using util::toString;
using util::join;
namespace lib {
namespace test{
namespace test{
/***************************************************//**
* @test verify proper working of test helpers to track
* automated clean-up and memory deallocation.
* @see TestHelper_test
* @see tracking-dummy.hpp
* @see tracking-allocator.hpp
*/
class TestTracking_test : public Test
{
void
run (Arg)
{
demonstrate_logObject();
demonstrate_checkObject();
demonstrate_checkAllocator();
}
/** @test capture object lifecycle events in the EventLog.
* @see EventLog_test
* @see LateBindInstance_test
*/
void
demonstrate_logObject ()
{
auto& log = Tracker::log;
log.clear (this);
Tracker alpha; // (1) create α
auto randomAlpha = toString(alpha.val);
log.event("ID",alpha.val); // (2) α has an random ID
{
Tracker beta{55}; // (3) create β
alpha = beta; // (4) assign α ≔ β
}
log.event ("ID",alpha.val); // (5) thus α now also bears the ID 55 of β
Tracker gamma = move(alpha); // (6) create γ by move-defuncting α
{
Tracker delta(23); // (7) create δ with ID 23
delta = move(gamma); // (8) move-assign δ ⟵ γ
log.event ("ID",delta.val); // (9) thus δ now bears the ID 55 (moved αγ ⟶ δ)
CHECK (delta.val == 55);
}
log.event("ID",alpha.val); // (X) and thus α is now a zombie object
CHECK (alpha.val == Tracker::DEFUNCT);
cout << "____Tracker-Log_______________\n"
<< util::join(Tracker::log, "\n")
<< "\n───╼━━━━━━━━━━━╾──────────────"<<endl;
CHECK (log.verify("EventLogHeader").on("TestTracking_test")
.beforeCall("ctor").on(&alpha) // (1) create α
.beforeEvent("ID", randomAlpha) // (2) α has an random ID
.beforeCall("ctor").arg(55) // (3) create β
.beforeCall("assign-copy").on(&alpha).arg("Track{55}") // (4) assign α ≔ β
.beforeCall("dtor").arg(55)
.beforeEvent("ID", "55") // (5) thus α now also bears the ID 55 of β
.beforeCall("ctor-move").on(&gamma).arg("Track{55}") // (6) create γ by move-defuncting α
.beforeCall("ctor").arg(23) // (7) create δ with ID 23
.beforeCall("assign-move").arg("Track{55}") // (8) move-assign δ ⟵ γ
.beforeEvent("ID", "55") // (9) thus δ now bears the ID 55 (moved αγ ⟶ δ)
.beforeCall("dtor").arg(55)
.beforeEvent("ID", toString(Tracker::DEFUNCT)) // (X) and thus α is now a zombie object
);
}
/** @test dummy object with a tracking checksum.
*/
void
demonstrate_checkObject ()
{
CHECK (Dummy::checksum() == 0);
{
Dummy dum1; // picks a random positive int by default...
CHECK (0 < dum1.getVal() and dum1.getVal() <= 100'000'000);
CHECK (Dummy::checksum() == dum1.getVal());
Dummy dum2{55};
CHECK (dum2.getVal() == 55);
CHECK (Dummy::checksum() == dum1.getVal() + 55);
Dummy dum3{move (dum2)};
CHECK (dum3.getVal() == 55);
CHECK (dum2.getVal() == Dummy::DEFUNCT);
dum3.setVal (23);
CHECK (dum3.getVal() == 23);
dum1 = move (dum3);
CHECK (dum1.getVal() == 23 );
CHECK (dum2.getVal() == Dummy::DEFUNCT);
CHECK (dum3.getVal() == Dummy::DEFUNCT);
CHECK (Dummy::checksum() == 23 );
Dummy::activateCtorFailure (true);
try {
Dummy kabooom;
}
catch (int v)
{
CHECK (0 < v and v <= 100'000'000);
CHECK (Dummy::checksum() == 23 + v);
Dummy::checksum() -= v;
}
Dummy::activateCtorFailure (false);
CHECK (dum1.getVal() == 23 );
CHECK (dum2.getVal() == Dummy::DEFUNCT);
CHECK (dum3.getVal() == Dummy::DEFUNCT);
CHECK (Dummy::checksum() == 23 );
}
CHECK (Dummy::checksum() == 0);
}
/** @test custom allocator to track memory handling
* - use the base allocator to perform raw memory allocation
* - demonstrate checksum and diagnostic functions
* - use a standard adapter to create objects with `unique_ptr`
* - use as _custom allocator_ within STL containers
* - can use several distinct pools
* - swapping containers will move allocators alongside
*/
void
demonstrate_checkAllocator()
{
// setup a common event-log for the tracking objects and -allocator
auto& log = TrackingAllocator::log;
Tracker::log.clear("Tracking-Allocator-Test");
Tracker::log.joinInto(log);
// everything is safe and sound initially....
CHECK (TrackingAllocator::checksum() == 0, "Testsuite is broken");
CHECK (TrackingAllocator::use_count() == 0);
{ // Test-1 : raw allocations....
log.event("Test-1");
TrackingAllocator allo;
CHECK (TrackingAllocator::use_count() == 1);
CHECK (TrackingAllocator::numAlloc() == 0);
CHECK (TrackingAllocator::numBytes() == 0);
void* mem = allo.allocate (55);
CHECK (TrackingAllocator::numAlloc() == 1);
CHECK (TrackingAllocator::numBytes() == 55);
CHECK (allo.manages (mem));
CHECK (allo.getSize (mem) == 55); // individual registration recalls the allocation's size
HashVal memID = allo.getID (mem);
CHECK (0 < memID);
CHECK (TrackingAllocator::checksum() == memID*55);
allo.deallocate (mem, 42); // note: passing a wrong number here is marked as ERROR in the log
CHECK (not allo.manages (mem));
CHECK (allo.getSize (mem) == 0);
CHECK (allo.getID (mem) == 0);
CHECK (TrackingAllocator::use_count() == 1);
CHECK (TrackingAllocator::numAlloc() == 0);
CHECK (TrackingAllocator::numBytes() == 0);
}
CHECK (log.verify("EventLogHeader").on("Tracking-Allocator-Test")
.before("logJoin")
.beforeEvent("Test-1")
.beforeCall("allocate").on(GLOBAL).argPos(0, 55)
.beforeEvent("error", "SizeMismatch-42-≠-55")
.beforeCall("deallocate").on(GLOBAL).argPos(0, 42)
);
CHECK (TrackingAllocator::checksum() == 0);
{ // Test-2 : attach scoped-ownership-front-End
log.event("Test-2");
allo::OwnUniqueAdapter<TrackingFactory> uniFab;
CHECK (sizeof(uniFab) == sizeof(TrackingFactory));
CHECK (sizeof(uniFab) == sizeof(std::shared_ptr<byte>));
CHECK (not allo::is_Stateless_v<decltype(uniFab)>);
CHECK (TrackingAllocator::use_count() == 1);
CHECK (TrackingAllocator::numAlloc() == 0);
CHECK (TrackingAllocator::numBytes() == 0);
{
log.event("fabricate unique");
auto uniqueHandle = uniFab.make_unique<Tracker> (77);
CHECK (uniqueHandle);
CHECK (uniqueHandle->val == 77);
CHECK (TrackingAllocator::use_count() == 2);
CHECK (TrackingAllocator::numAlloc() == 1);
CHECK (TrackingAllocator::numBytes() == sizeof(Tracker));
// all the default tracking allocators indeed attach to the same pool
TrackingAllocator allo;
void* mem = uniqueHandle.get();
CHECK (allo.manages (mem));
HashVal memID = allo.getID (mem);
CHECK (0 < memID);
CHECK (TrackingAllocator::checksum() == memID*sizeof(Tracker));
}// and it's gone...
CHECK (TrackingAllocator::use_count() == 1);
CHECK (TrackingAllocator::numAlloc() == 0);
CHECK (TrackingAllocator::numBytes() == 0);
}
CHECK (log.verifyEvent("Test-2")
.beforeEvent("fabricate unique")
.beforeCall("allocate").on(GLOBAL).argPos(0, sizeof(Tracker))
.beforeCall("create-Tracker").on(GLOBAL).arg(77)
.beforeCall("ctor").on("Tracker").arg(77)
.beforeCall("destroy-Tracker").on(GLOBAL)
.beforeCall("dtor").on("Tracker").arg(77)
.beforeCall("deallocate").on(GLOBAL).argPos(0, sizeof(Tracker))
);
CHECK (TrackingAllocator::checksum() == 0);
// define a vector type to use the TrackingAllocator internally
using TrackVec = std::vector<Tracker, TrackAlloc<Tracker>>;
// the following pointers are used later to identify log entries...
Tracker *t1, *t2, *t3, *t4, *t5, *t6;
{ // Test-3 : use as STL allocator
log.event("Test-3");
log.event("fill with 3 default instances");
TrackVec vec1(3);
int v3 = vec1.back().val;
TrackVec vec2;
log.event("move last instance over into other vector");
vec2.emplace_back (move (vec1[2]));
CHECK (vec2.back().val == v3);
CHECK (vec1.back().val == Tracker::DEFUNCT);
// capture object locations for log verification
t1 = & vec1[0];
t2 = & vec1[1];
t3 = & vec1[2];
t4 = & vec2.front();
log.event("leave scope");
}
CHECK (log.verifyEvent("Test-3")
.beforeEvent("fill with 3 default instances")
.beforeCall("allocate").on(GLOBAL)
.beforeCall("ctor").on(t1)
.beforeCall("ctor").on(t2)
.beforeCall("ctor").on(t3)
.beforeEvent("move last instance over into other vector")
.beforeCall("allocate").on(GLOBAL)
.beforeCall("ctor-move").on(t4)
.beforeEvent("leave scope")
.beforeCall("dtor").on(t4)
.beforeCall("deallocate").on(GLOBAL)
.beforeCall("dtor").on(t1) // (problematic test? order may be implementation dependent)
.beforeCall("dtor").on(t2)
.beforeCall("dtor").on(t3)
.beforeCall("deallocate").on(GLOBAL)
);
CHECK (TrackingAllocator::checksum() == 0);
{ // Test-4 : intermingled use of several pools
log.event("Test-4");
TrackAlloc<Tracker> allo1{"POOL-1"};
TrackAlloc<Tracker> allo2{"POOL-2"};
CHECK (allo1 != allo2);
CHECK (TrackingAllocator::use_count(GLOBAL) == 0);
CHECK (TrackingAllocator::use_count("POOL-1") == 1); // referred by allo1
CHECK (TrackingAllocator::use_count("POOL-2") == 1); // referred by allo2
CHECK (TrackingAllocator::checksum ("POOL-1") == 0);
CHECK (TrackingAllocator::checksum ("POOL-2") == 0);
TrackVec vec1{allo1};
TrackVec vec2{allo2};
CHECK (TrackingAllocator::use_count("POOL-1") == 2); // now also referred by the copy in the vectors
CHECK (TrackingAllocator::use_count("POOL-2") == 2);
log.event("reserve space in vectors");
vec1.reserve(20);
vec2.reserve(2);
CHECK (TrackingAllocator::numBytes("POOL-1") == 20*sizeof(Tracker));
CHECK (TrackingAllocator::numBytes("POOL-2") == 2*sizeof(Tracker));
CHECK (TrackingAllocator::numBytes(GLOBAL) == 0);
CHECK (TrackingAllocator::numBytes() == 0);
log.event("create elements in vec1");
vec1.resize(5);
vec1.back().val = 11;
log.event("add element to vec2");
vec2.push_back (Tracker{22});
// capture object locations for log verification later
t1 = & vec1[0];
t2 = & vec1[1];
t3 = & vec1[2];
t4 = & vec1[3];
t5 = & vec1[4];
t6 = & vec2.front();
log.event ("swap vectors");
std::swap (vec1, vec2);
CHECK (vec1.back().val == 22);
CHECK (vec2.back().val == 11);
CHECK (vec1.size() == 1);
CHECK (vec2.size() == 5);
// the allocators were migrated alongside with the swap
CHECK (TrackingAllocator::numBytes("POOL-1") == 20*sizeof(Tracker));
CHECK (TrackingAllocator::numBytes("POOL-2") == 2*sizeof(Tracker));
// this can be demonstrated....
log.event ("clear the elements migrated to vec2");
vec2.clear();
vec2.shrink_to_fit();
CHECK (vec2.capacity() == 0);
CHECK (TrackingAllocator::numBytes("POOL-1") == 0 );
CHECK (TrackingAllocator::numBytes("POOL-2") == 2*sizeof(Tracker));
CHECK (vec1.size() == 1);
CHECK (vec1.capacity() == 2); // unaffected
log.event ("leave scope");
}
CHECK (log.verifyEvent("Test-4")
.beforeEvent("reserve space in vectors")
.beforeCall("allocate").on("POOL-1").argPos(0, 20*sizeof(Tracker))
.beforeCall("allocate").on("POOL-2").argPos(0, 2*sizeof(Tracker))
.beforeEvent("create elements in vec1")
.beforeCall("ctor").on(t1)
.beforeCall("ctor").on(t2)
.beforeCall("ctor").on(t3)
.beforeCall("ctor").on(t4)
.beforeCall("ctor").on(t5)
.beforeEvent("add element to vec2")
.beforeCall("ctor").arg(22)
.beforeCall("ctor-move").on(t6).arg("Track{22}")
.beforeCall("dtor").arg(Tracker::DEFUNCT)
.beforeEvent("swap vectors")
.beforeEvent("clear the elements migrated to vec2")
.beforeCall("dtor").on(t1)
.beforeCall("dtor").on(t2)
.beforeCall("dtor").on(t3)
.beforeCall("dtor").on(t4)
.beforeCall("dtor").on(t5).arg(11)
.beforeCall("deallocate").on("POOL-1").argPos(0, 20*sizeof(Tracker))
.beforeEvent("leave scope")
.beforeCall("dtor").on(t6).arg(22)
.beforeCall("deallocate").on("POOL-2").argPos(0, 2*sizeof(Tracker))
);
// everything clean and all pools empty again...
CHECK (TrackingAllocator::use_count(GLOBAL) == 0);
CHECK (TrackingAllocator::use_count("POOL-1") == 0);
CHECK (TrackingAllocator::use_count("POOL-2") == 0);
CHECK (TrackingAllocator::checksum("POOL-1") == 0);
CHECK (TrackingAllocator::checksum("POOL-2") == 0);
CHECK (TrackingAllocator::checksum(GLOBAL) == 0);
cout << "____Tracking-Allo-Log_________\n"
<< util::join(TrackingAllocator::log,"\n")
<< "\n───╼━━━━━━━━━━━━━━━━━╾────────"<<endl;
}
};
LAUNCHER (TestTracking_test, "unit common");
}}} // namespace lib::test::test