From 908c4908c52b99f13f6c88e78ada323eafce7260 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 19 May 2026 08:27:10 -0400 Subject: [PATCH] xo-alloc2: + utest harness for catch2 accept additional commandline arguments --- utest/CMakeLists.txt | 3 +- utest/DArenaIterator.test.cpp | 21 ++++++-- utest/Generation.test.cpp | 3 ++ utest/IAllocator_Any.test.cpp | 3 ++ utest/ResourceVisitor.test.cpp | 3 ++ utest/Role.test.cpp | 3 ++ utest/TestUtil.cpp | 24 +++++++++ utest/TestUtil.hpp | 44 +++++++++++++++ utest/VisitReason.test.cpp | 3 ++ utest/alloc2_utest_main.cpp | 98 +++++++++++++++++++++++++++++++++- utest/arena.test.cpp | 17 ++++++ utest/dp.test.cpp | 7 +++ utest/objectmodel.test.cpp | 21 ++++++++ 13 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 utest/TestUtil.cpp create mode 100644 utest/TestUtil.hpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index a19d1f4..2dec297 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -4,12 +4,11 @@ set(UTEST_EXE utest.alloc2) set(UTEST_SRCS alloc2_utest_main.cpp + TestUtil.cpp objectmodel.test.cpp arena.test.cpp IAllocator_Any.test.cpp DArenaIterator.test.cpp -# Collector.test.cpp -# DX1CollectorIterator.test.cpp Generation.test.cpp Role.test.cpp VisitReason.test.cpp diff --git a/utest/DArenaIterator.test.cpp b/utest/DArenaIterator.test.cpp index 2c7503c..a3e95d9 100644 --- a/utest/DArenaIterator.test.cpp +++ b/utest/DArenaIterator.test.cpp @@ -3,10 +3,11 @@ * @author Roland Conybeare, Dec 2025 **/ -#include "Allocator.hpp" -#include "AllocIterator.hpp" -#include "Arena.hpp" -//#include "arena/IAllocator_DArena.hpp" +#include "TestUtil.hpp" +#include +#include +#include +#include #include "arena/IAllocIterator_DArenaIterator.hpp" #include "padding.hpp" #include @@ -39,6 +40,8 @@ namespace xo { namespace ut { TEST_CASE("IAllocIterator_Xfer_DArenaIterator", "[alloc2]") { + auto log = Utest::ut_scope(); + /* verify IAllocIterator_Xfer is constructible + satisfies concept checks */ IAllocIterator_Xfer xfer; REQUIRE(IAllocIterator_Xfer::_valid); @@ -46,6 +49,8 @@ namespace xo { TEST_CASE("IAllocIterator_Any", "[alloc2]") { + auto log = Utest::ut_scope(); + /* verify IAllocIterator_Any is constructible + satisfies concept checks */ IAllocIterator_Any any; REQUIRE(IAllocIterator_Any::_valid); @@ -53,6 +58,8 @@ namespace xo { TEST_CASE("obj_IAllocIterator", "[alloc2]") { + auto log = Utest::ut_scope(); + /* verify variant obj constructible */ obj obj_any; REQUIRE(obj_any.iface()); @@ -61,6 +68,8 @@ namespace xo { TEST_CASE("IAllocIterator-disabled-1", "[alloc2]") { + auto log = Utest::ut_scope(); + /* verify iteration over empty arena */ /* typed allocator a1o */ ArenaConfig cfg { .name_ = "testarena", @@ -97,6 +106,8 @@ namespace xo { TEST_CASE("IAllocIterator-emptyarena", "[alloc2]") { + auto log = Utest::ut_scope(); + /* verify iteration over empty arena */ /* typed allocator a1o */ ArenaConfig cfg { .name_ = "testarena", @@ -153,7 +164,7 @@ namespace xo { TEST_CASE("IAllocIterator-singlearena", "[alloc2]") { - scope log(XO_DEBUG(false)); + auto log = Utest::ut_scope(); ArenaConfig cfg { .name_ = "testarena", .size_ = 64*1024, diff --git a/utest/Generation.test.cpp b/utest/Generation.test.cpp index e62359e..6507e19 100644 --- a/utest/Generation.test.cpp +++ b/utest/Generation.test.cpp @@ -3,6 +3,7 @@ * @author Roland Conybeare, May 2026 **/ +#include "TestUtil.hpp" #include "Generation.hpp" #include @@ -14,6 +15,8 @@ namespace xo { TEST_CASE("Generation-1", "[Generation]") { + auto log = Utest::ut_scope(); + REQUIRE(Generation::nursery() == 0); REQUIRE(Generation::g0() == 0); REQUIRE(Generation::g1() == 1); diff --git a/utest/IAllocator_Any.test.cpp b/utest/IAllocator_Any.test.cpp index 5d23c6e..68d510e 100644 --- a/utest/IAllocator_Any.test.cpp +++ b/utest/IAllocator_Any.test.cpp @@ -3,6 +3,7 @@ * @author Roland Conybeare, May 2026 **/ +#include "TestUtil.hpp" #include #include #include @@ -16,6 +17,8 @@ namespace xo { TEST_CASE("IAllocator_Any", "[alloc2][death]") { + auto log = Utest::ut_scope(); + // null allocator obj alloc_any; diff --git a/utest/ResourceVisitor.test.cpp b/utest/ResourceVisitor.test.cpp index 0869d38..49c0f55 100644 --- a/utest/ResourceVisitor.test.cpp +++ b/utest/ResourceVisitor.test.cpp @@ -3,6 +3,7 @@ * @author Roland Conybeare, May 2026 **/ +#include "TestUtil.hpp" #include #include @@ -13,6 +14,8 @@ namespace xo { TEST_CASE("ResourceVisitor-1", "[resourcevisitor]") { + auto log = Utest::ut_scope(); + obj v; REQUIRE(v.iface()); diff --git a/utest/Role.test.cpp b/utest/Role.test.cpp index d1de6fe..a4c38d2 100644 --- a/utest/Role.test.cpp +++ b/utest/Role.test.cpp @@ -3,6 +3,7 @@ * @author Roland Conybeare, May 2026 **/ +#include "TestUtil.hpp" #include "role.hpp" #include @@ -13,6 +14,8 @@ namespace xo { TEST_CASE("Role-1", "[Role]") { + auto log = Utest::ut_scope(); + /* 1. there are two distinct valid roles, 'to' and 'from', * 2. valid roles fall in interval [begin, end) */ diff --git a/utest/TestUtil.cpp b/utest/TestUtil.cpp new file mode 100644 index 0000000..c52a5d7 --- /dev/null +++ b/utest/TestUtil.cpp @@ -0,0 +1,24 @@ +/** @file TestUtil.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include "TestUtil.hpp" +#include + +namespace xo { + UtestConfig * + UtestConfig::instance() { + static UtestConfig s_instance; + + return &s_instance; + }; + + scope + Utest::ut_scope() { + return scope(XO_DEBUG(UtestConfig::instance()->debug_flag()), + xtag("name", Catch::getResultCapture().getCurrentTestName())); + } +} + +/* end TestUtil.cpp */ diff --git a/utest/TestUtil.hpp b/utest/TestUtil.hpp new file mode 100644 index 0000000..20253bb --- /dev/null +++ b/utest/TestUtil.hpp @@ -0,0 +1,44 @@ +/** @file TestUtil.hpp + * + * @author Roland Conybeare, May 2026 + **/ + +#pragma once + +#include + +namespace xo { + + /** unit-test configuration here + * + * TODO: promote to its own library, along with UtestListener + **/ + struct UtestConfig { + bool debug_flag() const { return debug_flag_; } + + /** announce each test using catch2's listener api **/ + bool announce_flag_ = false; + /** enable debug output for all (!) tests **/ + bool debug_flag_ = false; + + static UtestConfig * instance(); + }; + + /** RAII logging for catch unit tests + * + * Use: + * TEST_CASE(name, tags, ..) + * { + * scope log = Utest::ut_scope(); + * + * ... + * log && log(xtag("foo", ...)); + * } + **/ + struct Utest { + static scope ut_scope(); + }; + +} /*namespace xo*/ + +/* end TestUtil.hpp */ diff --git a/utest/VisitReason.test.cpp b/utest/VisitReason.test.cpp index 44bfcc3..6fccac8 100644 --- a/utest/VisitReason.test.cpp +++ b/utest/VisitReason.test.cpp @@ -3,6 +3,7 @@ * @author Roland Conybeare, May 2026 **/ +#include "TestUtil.hpp" #include #include @@ -13,6 +14,8 @@ namespace xo { TEST_CASE("VisitReason-1", "[visitreason]") { + auto log = Utest::ut_scope(); + REQUIRE(VisitReason::unspecified() == VisitReason::unspecified()); REQUIRE(VisitReason::unspecified() != VisitReason::forward()); diff --git a/utest/alloc2_utest_main.cpp b/utest/alloc2_utest_main.cpp index 02d64a2..d907b0d 100644 --- a/utest/alloc2_utest_main.cpp +++ b/utest/alloc2_utest_main.cpp @@ -1,6 +1,100 @@ /* file alloc2_utest_main.cpp */ -#define CATCH_CONFIG_MAIN -#include "catch2/catch.hpp" +#include "TestUtil.hpp" +#include +#include +#include + +#define CATCH_CONFIG_RUNNER +#include + +namespace xo { + + struct UtestListener : Catch::TestEventListenerBase { + using TestEventListenerBase::TestEventListenerBase; + + // TestCasweInfo members: .name, .className, .description, .tags, lineInfo {.file, .line} + virtual void testCaseStarting(const Catch::TestCaseInfo & info) override { + using std::cerr; + using std::endl; + + // preamble + + if (UtestConfig::instance()->announce_flag_) { + cerr << "Starting unit test: " + << "[" << info.name << "]" + << " at " + << "[" << info.lineInfo.file << ":" << info.lineInfo.line << "]" + << endl; + } + } + + virtual void testCaseEnded(const Catch::TestCaseStats & stats) override { + // postamble + } + + // also sectionStarting / sectionEnded + + }; + + CATCH_REGISTER_LISTENER(UtestListener); +} + +int +main(int argc, char* argv[]) +{ + using xo::UtestConfig; + using xo::scope; + using xo::xtag; + + using std::cout; + using std::cerr; + using std::endl; + + //cerr << xtag("cli11", CLI11_VERSION) << endl; // version 2.5.0 + + CLI::App app{"utest.alloc2: xo-alloc2 unit tests"}; + app.set_help_flag(); // disable default help impl, see below + { + app.add_flag("--debug", + UtestConfig::instance()->debug_flag_, + "enable debug logging (for all tests)"); + app.add_flag("--announce", + UtestConfig::instance()->announce_flag_, + "announce each test via UtestListener"); + } + bool help_flag = false; + { + app.add_flag("--help,-h,-?", help_flag, "print this help message and exit"); + } + + app.allow_extras(); + CLI11_PARSE(app, argc, argv); + + std::vector argv2 = {argv[0]}; + + if (help_flag) { + // actual help impl, falls through to Session below + + cout << "utest.alloc2 options" << endl; + cout << app.help() << endl; + cout << "catch2 options" << endl; + + argv2.push_back("--help"); + } else { + // keep program name + for (auto & x : app.remaining()) + argv2.push_back(x.c_str()); + + using xo::Subsystem; + Subsystem::initialize_all(); + + } + + scope log(XO_DEBUG(UtestConfig::instance()->debug_flag()), "start catch2 session"); + + // run catch2's test session / help + return Catch::Session().run(argv2.size(), argv2.data()); +} /* end alloc2_utest_main.cpp */ diff --git a/utest/arena.test.cpp b/utest/arena.test.cpp index 95077f6..4f88ffd 100644 --- a/utest/arena.test.cpp +++ b/utest/arena.test.cpp @@ -3,6 +3,7 @@ * @author Roland Conybeare, Dec 2025 **/ +#include "TestUtil.hpp" #include #include #include @@ -33,6 +34,8 @@ namespace xo { namespace ut { TEST_CASE("IAllocator_Xfer_DArena", "[alloc2]") { + auto log = Utest::ut_scope(); + IAllocator_Xfer xfer; REQUIRE(IAllocator_Xfer::_valid); @@ -40,6 +43,8 @@ namespace xo { TEST_CASE("DArena-medium", "[alloc2][DArena]") { + auto log = Utest::ut_scope(); + ArenaConfig cfg { .name_ = "testarena", .size_ = 10*1024*1024 }; DArena arena = DArena::map(cfg); @@ -83,6 +88,8 @@ namespace xo { TEST_CASE("allocator-any-1", "[alloc2][AAllocator]") { + auto log = Utest::ut_scope(); + /* empty allocator alloc1 */ obj alloc1; @@ -122,6 +129,8 @@ namespace xo { TEST_CASE("allocator-expand-1", "[alloc2][AAllocator]") { + auto log = Utest::ut_scope(); + /* typed allocator a1o */ ArenaConfig cfg { .name_ = "testarena", .size_ = 1, @@ -160,6 +169,8 @@ namespace xo { TEST_CASE("allocator-alloc-1", "[alloc2][AAllocator]") { + auto log = Utest::ut_scope(); + /* typed allocator a1o */ ArenaConfig cfg { .name_ = "testarena", .size_ = 64*1024, @@ -207,6 +218,8 @@ namespace xo { TEST_CASE("allocator-alloc-2", "[alloc2][Allocator]") { + auto log = Utest::ut_scope(); + using header_type = AllocHeader; /* typed allocator a1o, with object header */ @@ -293,6 +306,8 @@ namespace xo { TEST_CASE("allocator-alloc-3", "[alloc2][Allocator]") { + auto log = Utest::ut_scope(); + using header_type = AllocHeader; /* typed allocator a1o, with object header + guard bytes */ @@ -363,6 +378,8 @@ namespace xo { TEST_CASE("allocator-fail-1", "[alloc2][AAllocator]") { + auto log = Utest::ut_scope(); + /* typed allocator a1o */ ArenaConfig cfg { .name_ = "testarena", .size_ = 64*1024, diff --git a/utest/dp.test.cpp b/utest/dp.test.cpp index e507b73..c2c91d7 100644 --- a/utest/dp.test.cpp +++ b/utest/dp.test.cpp @@ -3,6 +3,7 @@ * @author Roland Conybeare, May 2026 **/ +#include "TestUtil.hpp" #include "dp.hpp" #include #include @@ -33,6 +34,8 @@ namespace xo { TEST_CASE("dp-1", "[dp]") { + auto log = Utest::ut_scope(); + //ArenaConfig cfg { .name_ = "testarena", .size_ = 1024 }; //DArena arena = DArena::map(cfg); //auto mm = obj(&arena); @@ -55,6 +58,8 @@ namespace xo { TEST_CASE("dp-2", "[dp]") { + auto log = Utest::ut_scope(); + uint32_t counter = 0; Foo foo(&counter); @@ -77,6 +82,8 @@ namespace xo { TEST_CASE("dp-DArena", "[dp][DArena]") { + auto log = Utest::ut_scope(); + ArenaConfig cfg { .name_ = "testarena", .size_ = 1024 }; DArena arena = DArena::map(cfg); //auto mm = obj(&arena); diff --git a/utest/objectmodel.test.cpp b/utest/objectmodel.test.cpp index 1fcc9a8..5f08b2c 100644 --- a/utest/objectmodel.test.cpp +++ b/utest/objectmodel.test.cpp @@ -79,6 +79,7 @@ * Application code will deal with ubox **/ +#include "TestUtil.hpp" #include #include #include @@ -576,6 +577,8 @@ namespace xo { TEST_CASE("objectmodel-specific-1", "[objectmodel]") { + auto log = Utest::ut_scope(); + /* arg=0, mag=1 -> 1+0i */ DPolarCoords polar{0.0, 1.0}; IComplex_Specific polar_iface; @@ -588,6 +591,8 @@ namespace xo { TEST_CASE("objectmodel-specific-2", "[objectmodel]") { + auto log = Utest::ut_scope(); + /* arg=0, mag=1 -> 1+0i */ DRectCoords rect{1.0, 0.0}; IComplex_Specific rect_iface; @@ -600,6 +605,8 @@ namespace xo { TEST_CASE("uniquebox-1", "[objectmodel]") { + auto log = Utest::ut_scope(); + auto tmp = std::make_unique(0.0, 1.0); OUniqueBox box{tmp.release()}; @@ -611,6 +618,8 @@ namespace xo { TEST_CASE("router-1", "[objectmodel]") { + auto log = Utest::ut_scope(); + using Object = OUniqueBox; auto tmp = std::make_unique(0.0, 1.0); @@ -624,6 +633,8 @@ namespace xo { TEST_CASE("routing-type-1", "[objectmodel]") { + auto log = Utest::ut_scope(); + using Object = OUniqueBox; auto tmp = std::make_unique(0.0, 1.0); @@ -637,6 +648,8 @@ namespace xo { TEST_CASE("ubox-1", "[objectmodel]") { + auto log = Utest::ut_scope(); + auto tmp = std::make_unique(0.0, 1.0); ubox box{tmp.release()}; @@ -648,6 +661,8 @@ namespace xo { TEST_CASE("ubox-2", "[objectmodel]") { + auto log = Utest::ut_scope(); + auto tmp = std::make_unique(1.0, 0.0); ubox box{tmp.release()}; @@ -659,12 +674,16 @@ namespace xo { TEST_CASE("ubox-any-1", "[objectmodel]") { + auto log = Utest::ut_scope(); + /* default ctor */ ubox any; } TEST_CASE("ubox-any-2", "[objectmodel]") { + auto log = Utest::ut_scope(); + /* equivalent to ubox, but impl doesn't use std::unique_ptr */ ubox any{new DRectCoords{1.0, 0.0}}; @@ -676,6 +695,8 @@ namespace xo { TEST_CASE("ubox-any-3", "[objectmodel]") { + auto log = Utest::ut_scope(); + /* equivalent to ubox, but impl doesn't use std::unique_ptr */ ubox z1{new DRectCoords{1.0, 0.0}};