lumiera_/tests/library/test/test-tracking-test.cpp

421 lines
18 KiB
C++
Raw Permalink Normal View History

/*
TestTracking(Test) - demonstrate test helpers for tracking automated clean-up
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright (C)
2024, Hermann Vosseler <Ichthyostega@web.de>
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
  **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.
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
* *****************************************************************/
/** @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