xo-alloc2: + utest harness for catch2

accept additional commandline arguments
This commit is contained in:
Roland Conybeare 2026-05-19 08:27:10 -04:00
commit 908c4908c5
13 changed files with 241 additions and 9 deletions

View file

@ -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

View file

@ -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 <xo/alloc2/Allocator.hpp>
#include <xo/alloc2/AllocIterator.hpp>
#include <xo/alloc2/Arena.hpp>
#include <xo/alloc2/ArenaIterator.hpp>
#include "arena/IAllocIterator_DArenaIterator.hpp"
#include "padding.hpp"
#include <xo/indentlog/scope.hpp>
@ -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<DArenaIterator, IAllocIterator_DArenaIterator> xfer;
REQUIRE(IAllocIterator_Xfer<DArenaIterator, IAllocIterator_DArenaIterator>::_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<AAllocIterator> 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,

View file

@ -3,6 +3,7 @@
* @author Roland Conybeare, May 2026
**/
#include "TestUtil.hpp"
#include "Generation.hpp"
#include <catch2/catch.hpp>
@ -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);

View file

@ -3,6 +3,7 @@
* @author Roland Conybeare, May 2026
**/
#include "TestUtil.hpp"
#include <xo/alloc2/Allocator.hpp>
#include <catch2/catch.hpp>
#include <sys/wait.h>
@ -16,6 +17,8 @@ namespace xo {
TEST_CASE("IAllocator_Any", "[alloc2][death]")
{
auto log = Utest::ut_scope();
// null allocator
obj<AAllocator> alloc_any;

View file

@ -3,6 +3,7 @@
* @author Roland Conybeare, May 2026
**/
#include "TestUtil.hpp"
#include <xo/alloc2/ResourceVisitor.hpp>
#include <catch2/catch.hpp>
@ -13,6 +14,8 @@ namespace xo {
TEST_CASE("ResourceVisitor-1", "[resourcevisitor]")
{
auto log = Utest::ut_scope();
obj<AResourceVisitor> v;
REQUIRE(v.iface());

View file

@ -3,6 +3,7 @@
* @author Roland Conybeare, May 2026
**/
#include "TestUtil.hpp"
#include "role.hpp"
#include <catch2/catch.hpp>
@ -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)
*/

24
utest/TestUtil.cpp Normal file
View file

@ -0,0 +1,24 @@
/** @file TestUtil.cpp
*
* @author Roland Conybeare, May 2026
**/
#include "TestUtil.hpp"
#include <catch2/catch.hpp>
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 */

44
utest/TestUtil.hpp Normal file
View file

@ -0,0 +1,44 @@
/** @file TestUtil.hpp
*
* @author Roland Conybeare, May 2026
**/
#pragma once
#include <xo/indentlog/scope.hpp>
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 */

View file

@ -3,6 +3,7 @@
* @author Roland Conybeare, May 2026
**/
#include "TestUtil.hpp"
#include <xo/alloc2/VisitReason.hpp>
#include <catch2/catch.hpp>
@ -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());

View file

@ -1,6 +1,100 @@
/* file alloc2_utest_main.cpp */
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "TestUtil.hpp"
#include <xo/subsys/Subsystem.hpp>
#include <xo/indentlog/scope.hpp>
#include <CLI/CLI.hpp>
#define CATCH_CONFIG_RUNNER
#include <catch2/catch.hpp>
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<const char *> 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 */

View file

@ -3,6 +3,7 @@
* @author Roland Conybeare, Dec 2025
**/
#include "TestUtil.hpp"
#include <xo/alloc2/Allocator.hpp>
#include <xo/alloc2/Arena.hpp>
#include <xo/arena/print.hpp>
@ -33,6 +34,8 @@ namespace xo {
namespace ut {
TEST_CASE("IAllocator_Xfer_DArena", "[alloc2]")
{
auto log = Utest::ut_scope();
IAllocator_Xfer<DArena, IAllocator_DArena> xfer;
REQUIRE(IAllocator_Xfer<DArena, IAllocator_DArena>::_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<AAllocator> 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,

View file

@ -3,6 +3,7 @@
* @author Roland Conybeare, May 2026
**/
#include "TestUtil.hpp"
#include "dp.hpp"
#include <xo/alloc2/Allocator.hpp>
#include <xo/alloc2/Arena.hpp>
@ -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<AAllocator,DArena>(&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<AAllocator,DArena>(&arena);

View file

@ -79,6 +79,7 @@
* Application code will deal with ubox<AComplex,DPolarCoords>
**/
#include "TestUtil.hpp"
#include <catch2/catch.hpp>
#include <cmath>
#include <cassert>
@ -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<DPolarCoords> 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<DRectCoords> rect_iface;
@ -600,6 +605,8 @@ namespace xo {
TEST_CASE("uniquebox-1", "[objectmodel]")
{
auto log = Utest::ut_scope();
auto tmp = std::make_unique<DPolarCoords>(0.0, 1.0);
OUniqueBox<AComplex, DPolarCoords> box{tmp.release()};
@ -611,6 +618,8 @@ namespace xo {
TEST_CASE("router-1", "[objectmodel]")
{
auto log = Utest::ut_scope();
using Object = OUniqueBox<AComplex, DPolarCoords>;
auto tmp = std::make_unique<DPolarCoords>(0.0, 1.0);
@ -624,6 +633,8 @@ namespace xo {
TEST_CASE("routing-type-1", "[objectmodel]")
{
auto log = Utest::ut_scope();
using Object = OUniqueBox<AComplex, DPolarCoords>;
auto tmp = std::make_unique<DPolarCoords>(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<DPolarCoords>(0.0, 1.0);
ubox<AComplex,DPolarCoords> box{tmp.release()};
@ -648,6 +661,8 @@ namespace xo {
TEST_CASE("ubox-2", "[objectmodel]")
{
auto log = Utest::ut_scope();
auto tmp = std::make_unique<DRectCoords>(1.0, 0.0);
ubox<AComplex,DRectCoords> box{tmp.release()};
@ -659,12 +674,16 @@ namespace xo {
TEST_CASE("ubox-any-1", "[objectmodel]")
{
auto log = Utest::ut_scope();
/* default ctor */
ubox<AComplex> any;
}
TEST_CASE("ubox-any-2", "[objectmodel]")
{
auto log = Utest::ut_scope();
/* equivalent to ubox<AComplex,DRectCoords>, but impl doesn't use std::unique_ptr */
ubox<AComplex,DRectCoords> 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<AComplex,DRectCoords>, but impl doesn't use std::unique_ptr */
ubox<AComplex,DRectCoords> z1{new DRectCoords{1.0, 0.0}};