From e82dd86b39f128ba63251261595f29c502ded2ed Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 15 Jun 2024 18:14:00 +0200 Subject: [PATCH] Library: reorganise test helpers and cover logging tracker object ...these features are now used quite regularly, and so a dedicated documentation test seems indicated. Actually my intention is to add a tracking allocator to these test helpers (and then to use that to verify the custom allocator usage of `lib::Several`) --- src/lib/allocator-handle.hpp | 1 + src/lib/test/test-helper.cpp | 2 +- src/lib/test/tracking-allocator.hpp | 255 ++++++++++++++++++ .../{testdummy.hpp => tracking-dummy.hpp} | 10 +- tests/00support.tests | 4 + .../engine/buffer-provider-protocol-test.cpp | 2 +- tests/library/linked-elements-test.cpp | 2 +- .../library/meta/late-bind-instance-test.cpp | 2 +- tests/library/scoped-collection-test.cpp | 2 +- tests/library/scoped-holder-test.cpp | 2 +- tests/library/scoped-holder-transfer-test.cpp | 2 +- tests/library/scoped-ptrvect-test.cpp | 2 +- tests/library/several-builder-test.cpp | 2 +- tests/library/test/test-tracking-test.cpp | 133 +++++++++ .../thread-wrapper-autonomous-test.cpp | 2 +- .../library/thread-wrapper-lifecycle-test.cpp | 2 +- tests/library/vector-transfer-test.cpp | 2 +- tests/vault/gear/special-job-fun-test.cpp | 2 +- wiki/thinkPad.ichthyo.mm | 65 +++-- 19 files changed, 460 insertions(+), 34 deletions(-) create mode 100644 src/lib/test/tracking-allocator.hpp rename src/lib/test/{testdummy.hpp => tracking-dummy.hpp} (96%) create mode 100644 tests/library/test/test-tracking-test.cpp diff --git a/src/lib/allocator-handle.hpp b/src/lib/allocator-handle.hpp index 6bbfa3d79..55b6a2d37 100644 --- a/src/lib/allocator-handle.hpp +++ b/src/lib/allocator-handle.hpp @@ -49,6 +49,7 @@ ** @see allocation-cluster.hpp ** @see steam::fixture::Segment ** @see steam::engine::JobTicket + ** @see tracking-allocator.hpp */ diff --git a/src/lib/test/test-helper.cpp b/src/lib/test/test-helper.cpp index ac1009dd1..adf0b79e1 100644 --- a/src/lib/test/test-helper.cpp +++ b/src/lib/test/test-helper.cpp @@ -31,7 +31,7 @@ #include "lib/test/test-helper.hpp" -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" #include "lib/format-string.hpp" #include "lib/format-cout.hpp" #include "lib/unique-malloc-owner.hpp" diff --git a/src/lib/test/tracking-allocator.hpp b/src/lib/test/tracking-allocator.hpp new file mode 100644 index 000000000..af016784b --- /dev/null +++ b/src/lib/test/tracking-allocator.hpp @@ -0,0 +1,255 @@ +/* + TRACKING-ALLOCATOR.hpp - test dummy objects for tracking ctor/dtor calls + + Copyright (C) Lumiera.org + 2023, Hermann Vosseler + + This program 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +/** @file tracking-allocator.hpp + ** unittest helper code: a custom allocator to track memory usage. + ** By registering each allocation and deallocation, correct memory handling + ** can be verified and memory usage can be investigated in practice. + */ + + +#ifndef LIB_TEST_TRACKING_ALLOCATOR_H +#define LIB_TEST_TRACKING_ALLOCATOR_H + +#include "lib/error.hpp" + +#include +#include +#include + + + +namespace lib { + namespace allo {///< Concepts and Adaptors for custom memory management + + /////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1366 : define Allocator Concepts here + /// TODO the following Concepts can be expected here (with C++20) + /// - Allocator : for the bare memory allocation + /// - Factory : for object fabrication and disposal + /// - Handle : a functor front-end to be dependency-injected + + + /** + * Adapter to implement the *Factory* concept based on a `std::allocator` + * @tparam ALO a std::allocator instance or anything compliant to [Allocator] + * [Allocator]: https://en.cppreference.com/w/cpp/named_req/Allocator + * @note in addition to the abilities defined by the standard, this adapter + * strives to provide some kind of _lateral leeway,_ attempting to + * create dedicated allocators for other types than the BaseType + * implied by the given \a ALO (standard-allocator). + * - this is possible if the rebound allocator can be constructed + * from the given base allocator + * - alternatively, an attempt will be made to default-construct + * the rebound allocator for the other type requested. + * @warning Both avenues for adaptation may fail, + * which could lead to compilation or runtime failure. + * @remark deliberately this class inherits from the allocator, + * allowing to exploit empty-base-optimisation, since + * usage of monostate allocators is quite common. + */ + template + class StdFactory + : private ALO + { + using Allo = ALO; + using AlloT = std::allocator_traits; + using BaseType = typename Allo::value_type; + + Allo& baseAllocator() { return *this; } + + template + auto + adaptAllocator() + { + using XAllo = typename AlloT::template rebind_alloc; + if constexpr (std::is_constructible_v) + return XAllo{baseAllocator()}; + else + return XAllo{}; + } + + template + typename ALOT::pointer + construct (typename ALOT::allocator_type& allo, ARGS&& ...args) + { + auto loc = ALOT::allocate (allo, 1); + try { ALOT::construct (allo, loc, std::forward(args)...); } + catch(...) + { + ALOT::deallocate (allo, loc, 1); + throw; + } + return loc; + } + + template + void + destroy (typename ALOT::allocator_type& allo, typename ALOT::pointer elm) + { + ALOT::destroy (allo, elm); + ALOT::deallocate (allo, elm, 1); + } + + + public: + /** + * Create an instance of the adapter factory, + * forwarding to the embedded standard conforming allocator + * for object creation and destruction and memory management. + * @param allo (optional) instance of the C++ standard allocator + * used for delegation, will be default constructed if omitted. + * @remark the adapted standard allocator is assumed to be either a copyable + * value object, or even a mono-state; in both cases, a dedicated + * manager instance residing »elsewhere« is referred, rendering + * all those front-end instances exchangeable. + */ + StdFactory (Allo allo = Allo{}) + : Allo{std::move (allo)} + { } + + template + bool constexpr operator== (StdFactory const& o) const + { + return baseAllocator() == o.baseAllocator(); + } + template + bool constexpr operator!= (StdFactory const& o) const + { + return not (*this == o); + } + + + /** create new element using the embedded allocator */ + template + TY* + create (ARGS&& ...args) + { + if constexpr (std::is_same_v) + { + return construct (baseAllocator(), std::forward(args)...); + } + else + { + using XAlloT = typename AlloT::template rebind_traits; + auto xAllo = adaptAllocator(); + return construct (xAllo, std::forward(args)...); + } + } + + /** destroy the given element and discard the associated memory */ + template + void + dispose (TY* elm) + { + if constexpr (std::is_same_v) + { + destroy (baseAllocator(), elm); + } + else + { + using XAlloT = typename AlloT::template rebind_traits; + auto xAllo = adaptAllocator(); + destroy (xAllo, elm); + } + } + }; + } + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1366 : the following code becomes obsolete in the long term + + /** + * Placeholder implementation for a custom allocator + * @todo shall be replaced by an AllocationCluster eventually + * @todo 5/2024 to be reworked and aligned with a prospective C++20 Allocator Concept /////////////////////TICKET #1366 + * @remark using `std::list` container, since re-entrant allocation calls are possible, + * meaning that further allocations will be requested recursively from a ctor. + * Moreover, for the same reason we separate the allocation from the ctor call, + * so we can capture the address of the new allocation prior to any possible + * re-entrant call, and handle clean-up of allocation without requiring any + * additional state flags..... + */ + template + class AllocatorHandle + { + struct Allocation + { + alignas(TY) + std::byte buf_[sizeof(TY)]; + + template + TY& + create (ARGS&& ...args) + { + return *new(&buf_) TY {std::forward (args)...}; + } + + TY& + access() + { + return * std::launder (reinterpret_cast (&buf_)); + } + void + discard() /// @warning strong assumption made here: Payload was created + { + access().~TY(); + } + }; + + std::list storage_; + + public: + template + TY& + operator() (ARGS&& ...args) + { // EX_STRONG + auto pos = storage_.emplace (storage_.end()); ////////////////////////////////////////////////////TICKET #230 : real implementation should care for concurrency here + try { + return pos->create (std::forward (args)...); + } + catch(...) + { + storage_.erase (pos); // EX_FREE + + const char* errID = lumiera_error(); + ERROR (memory, "Allocation failed with unknown exception. " + "Lumiera errorID=%s", errID?errID:"??"); + throw; + } + } + + /** @note need to do explicit clean-up, since a ctor-call might have been failed, + * and we have no simple way to record this fact internally in Allocation, + * short of wasting additional memory for a flag to mark this situation */ + ~AllocatorHandle() + try { + for (auto& alloc : storage_) + alloc.discard(); + } + ERROR_LOG_AND_IGNORE (memory, "clean-up of custom AllocatorHandle") + }; + + + +} // namespace lib +#endif /*LIB_TEST_TRACKING_ALLOCATOR_H*/ diff --git a/src/lib/test/testdummy.hpp b/src/lib/test/tracking-dummy.hpp similarity index 96% rename from src/lib/test/testdummy.hpp rename to src/lib/test/tracking-dummy.hpp index b55466e33..e22a44d76 100644 --- a/src/lib/test/testdummy.hpp +++ b/src/lib/test/tracking-dummy.hpp @@ -1,5 +1,5 @@ /* - TESTDUMMY.hpp - yet another test dummy for tracking ctor/dtor calls + TRACKING-DUMMY.hpp - test dummy objects for tracking ctor/dtor calls Copyright (C) Lumiera.org 2008, Hermann Vosseler @@ -21,7 +21,7 @@ * *****************************************************/ -/** @file testdummy.hpp +/** @file tracking-dummy.hpp ** unittest helper code: test dummy objects to track instances. ** These can be used to verify proper allocation handling, either by ** watching the checksum of \ref Dummy, or by matching on the \ref EventLog @@ -29,6 +29,10 @@ */ +#ifndef LIB_TEST_TRACKING_DUMMY_H +#define LIB_TEST_TRACKING_DUMMY_H + + #include "lib/nocopy.hpp" #include "lib/test/event-log.hpp" #include "lib/format-string.hpp" @@ -229,4 +233,4 @@ namespace test{ }} // namespace lib::test - +#endif /*LIB_TEST_TRACKING_DUMMY_H*/ diff --git a/tests/00support.tests b/tests/00support.tests index 796a510a4..2d55fb421 100644 --- a/tests/00support.tests +++ b/tests/00support.tests @@ -101,3 +101,7 @@ return: 0 END +TEST "Object and allocation tracking" TestTracking_test < diff --git a/tests/library/meta/late-bind-instance-test.cpp b/tests/library/meta/late-bind-instance-test.cpp index afbd496dd..4ef327e9f 100644 --- a/tests/library/meta/late-bind-instance-test.cpp +++ b/tests/library/meta/late-bind-instance-test.cpp @@ -29,7 +29,7 @@ #include "lib/meta/function.hpp" #include "lib/meta/tuple-helper.hpp" #include "lib/test/test-helper.hpp" -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" #include "lib/format-cout.hpp" #include "lib/format-util.hpp" #include "lib/util.hpp" diff --git a/tests/library/scoped-collection-test.cpp b/tests/library/scoped-collection-test.cpp index 3ee222783..17a170eb7 100644 --- a/tests/library/scoped-collection-test.cpp +++ b/tests/library/scoped-collection-test.cpp @@ -31,7 +31,7 @@ #include "lib/util.hpp" #include "lib/scoped-collection.hpp" -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" #include diff --git a/tests/library/scoped-holder-test.cpp b/tests/library/scoped-holder-test.cpp index f5ae27699..7e8b77639 100644 --- a/tests/library/scoped-holder-test.cpp +++ b/tests/library/scoped-holder-test.cpp @@ -33,7 +33,7 @@ #include "lib/error.hpp" #include "lib/scoped-holder.hpp" -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" #include diff --git a/tests/library/scoped-holder-transfer-test.cpp b/tests/library/scoped-holder-transfer-test.cpp index 438584833..eeec33a43 100644 --- a/tests/library/scoped-holder-transfer-test.cpp +++ b/tests/library/scoped-holder-transfer-test.cpp @@ -31,7 +31,7 @@ #include "lib/scoped-holder.hpp" #include "lib/scoped-holder-transfer.hpp" -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" #include #include diff --git a/tests/library/scoped-ptrvect-test.cpp b/tests/library/scoped-ptrvect-test.cpp index 004a6d754..eff9fde74 100644 --- a/tests/library/scoped-ptrvect-test.cpp +++ b/tests/library/scoped-ptrvect-test.cpp @@ -31,7 +31,7 @@ #include "lib/util.hpp" #include "lib/scoped-ptrvect.hpp" -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" namespace lib { diff --git a/tests/library/several-builder-test.cpp b/tests/library/several-builder-test.cpp index 75779bd29..9cab3780d 100644 --- a/tests/library/several-builder-test.cpp +++ b/tests/library/several-builder-test.cpp @@ -27,7 +27,7 @@ #include "lib/test/run.hpp" -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" #include "lib/test/test-coll.hpp" #include "lib/test/test-helper.hpp" #include "lib/test/diagnostic-output.hpp"////////////////TODO diff --git a/tests/library/test/test-tracking-test.cpp b/tests/library/test/test-tracking-test.cpp new file mode 100644 index 000000000..fce10b7d5 --- /dev/null +++ b/tests/library/test/test-tracking-test.cpp @@ -0,0 +1,133 @@ +/* + TestTracking(Test) - demonstrate test helpers for tracking automated clean-up + + Copyright (C) Lumiera.org + 2024, Hermann Vosseler + + This program 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @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/format-cout.hpp" + +#include + +using std::string; +using util::toString; + + +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.note("type=ID",alpha.val); // (2) α has an random ID + { + Tracker beta{55}; // (3) create β + alpha = beta; // (4) assign α ≔ β + } + log.note("type=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.note("type=ID",delta.val); // (9) thus δ now bears the ID 55 (moved α ⟶ γ ⟶ δ) + } + log.note("type=ID",alpha.val); // (X) and thus α is now a zombie object + + cout << "____Tracker-Log_______________\n" + << util::join(Tracker::log, "\n") + << "\n───╼━━━━━━━━━━━╾──────────────"< diff --git a/tests/library/thread-wrapper-lifecycle-test.cpp b/tests/library/thread-wrapper-lifecycle-test.cpp index f4c72ce4c..68ab5be46 100644 --- a/tests/library/thread-wrapper-lifecycle-test.cpp +++ b/tests/library/thread-wrapper-lifecycle-test.cpp @@ -27,7 +27,7 @@ #include "lib/test/run.hpp" #include "lib/thread.hpp" -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" #include #include diff --git a/tests/library/vector-transfer-test.cpp b/tests/library/vector-transfer-test.cpp index d46dc81fe..16905c0de 100644 --- a/tests/library/vector-transfer-test.cpp +++ b/tests/library/vector-transfer-test.cpp @@ -29,7 +29,7 @@ #include "lib/test/run.hpp" #include "lib/scoped-holder-transfer.hpp" -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" #include #include diff --git a/tests/vault/gear/special-job-fun-test.cpp b/tests/vault/gear/special-job-fun-test.cpp index 91c39234b..f480d87e8 100644 --- a/tests/vault/gear/special-job-fun-test.cpp +++ b/tests/vault/gear/special-job-fun-test.cpp @@ -32,7 +32,7 @@ #include "vault/gear/special-job-fun.hpp" #include "lib/format-cout.hpp" ////////////////////////////////////TODO Moo-oh #include "lib/test/diagnostic-output.hpp"//////////////////////////TODO TOD-oh -#include "lib/test/testdummy.hpp" +#include "lib/test/tracking-dummy.hpp" //#include "lib/util.hpp" //#include diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 19334915f..b76f885a5 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -83354,8 +83354,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
enthält ein nested template / typedef: Policy

- - + @@ -83391,8 +83390,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Name: SetupSeveral

- - + @@ -83412,8 +83410,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Schema: Policy<I,E>

- - +
@@ -83436,8 +83433,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Denn auch den Allocator verwendet man typischerweise an vielen Stellen, und nicht überall möchte man dann auch several-builder.hpp mit reinziehen; wäre also gut wenn es einfacher Template-Code ist, der mit entspr. Forward-Deklarationen auch »blank« vom Compiler akzeptiert wird

- - +
@@ -83461,8 +83457,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
struct AllocationPolicy;

- - +
@@ -83477,8 +83472,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
struct SetupSeveral;

- - +
@@ -83491,8 +83485,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
SetupSeveral<std::void_t, lib::AllocationCluster&>

- - + @@ -83502,8 +83495,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Die Definition der Builder-Methode withAllocator<ALO>(args...)  sollte dazu führen, daß das Konfigurations-Template SetupSeveral  genau mit diesen Argumenten instantiiert wird...

- -
+
@@ -83522,6 +83514,44 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + +

+ diese erweiterten Event-Log-Matches sind wohl etwas ad hoc +

+ + +
+
+
+
+ + + + +
@@ -83955,8 +83985,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
braucht ein API zum Übernehmen eines bereits konstruierten Objekts

- - +