From 5a06bced3b11dd0bb3a0aeaef81b50670aebca8a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 6 Jun 2026 21:50:26 -0400 Subject: [PATCH] git subrepo clone git@github.com:rconybea/xo-testutil.git xo-testutil subrepo: subdir: "xo-testutil" merged: "281418da" upstream: origin: "git@github.com:rconybea/xo-testutil.git" branch: "main" commit: "281418da" git-subrepo: version: "0.4.9" origin: "???" commit: "???" --- xo-testutil/.gitrepo | 12 +++ xo-testutil/CMakeLists.txt | 30 ++++++++ xo-testutil/README.md | 3 + xo-testutil/cmake/xo-bootstrap-macros.cmake | 33 ++++++++ xo-testutil/cmake/xo_testutilConfig.cmake.in | 14 ++++ xo-testutil/include/xo/testutil/Utest.hpp | 35 +++++++++ .../include/xo/testutil/UtestAppStart.hpp | 30 ++++++++ .../include/xo/testutil/UtestConfig.hpp | 25 +++++++ .../include/xo/testutil/UtestListener.hpp | 52 +++++++++++++ xo-testutil/src/testutil/CMakeLists.txt | 15 ++++ xo-testutil/src/testutil/Utest.cpp | 18 +++++ xo-testutil/src/testutil/UtestAppStart.cpp | 75 +++++++++++++++++++ xo-testutil/src/testutil/UtestConfig.cpp | 18 +++++ 13 files changed, 360 insertions(+) create mode 100644 xo-testutil/.gitrepo create mode 100644 xo-testutil/CMakeLists.txt create mode 100644 xo-testutil/README.md create mode 100644 xo-testutil/cmake/xo-bootstrap-macros.cmake create mode 100644 xo-testutil/cmake/xo_testutilConfig.cmake.in create mode 100644 xo-testutil/include/xo/testutil/Utest.hpp create mode 100644 xo-testutil/include/xo/testutil/UtestAppStart.hpp create mode 100644 xo-testutil/include/xo/testutil/UtestConfig.hpp create mode 100644 xo-testutil/include/xo/testutil/UtestListener.hpp create mode 100644 xo-testutil/src/testutil/CMakeLists.txt create mode 100644 xo-testutil/src/testutil/Utest.cpp create mode 100644 xo-testutil/src/testutil/UtestAppStart.cpp create mode 100644 xo-testutil/src/testutil/UtestConfig.cpp diff --git a/xo-testutil/.gitrepo b/xo-testutil/.gitrepo new file mode 100644 index 00000000..691e43e7 --- /dev/null +++ b/xo-testutil/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme +; +[subrepo] + remote = git@github.com:rconybea/xo-testutil.git + branch = main + commit = 281418da1c5fc6311aa66485f5f883b597c3c467 + parent = e0acb52c389319202b9860f3c42149e56b84f3e5 + method = merge + cmdver = 0.4.9 diff --git a/xo-testutil/CMakeLists.txt b/xo-testutil/CMakeLists.txt new file mode 100644 index 00000000..6a912ee8 --- /dev/null +++ b/xo-testutil/CMakeLists.txt @@ -0,0 +1,30 @@ +# xo-testutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_testutil VERSION 1.0) +enable_language(CXX) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- +# output targets + +add_subdirectory(src/testutil) + +# ---------------------------------------------------------------- +# cmake export + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/xo-testutil/README.md b/xo-testutil/README.md new file mode 100644 index 00000000..b016e595 --- /dev/null +++ b/xo-testutil/README.md @@ -0,0 +1,3 @@ +# xo-testutil + +unit test harness for catch2 \ No newline at end of file diff --git a/xo-testutil/cmake/xo-bootstrap-macros.cmake b/xo-testutil/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..2cf387e5 --- /dev/null +++ b/xo-testutil/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,33 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + message(FATAL "could not find xo-cmake-config executable") +endif() + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/xo-testutil/cmake/xo_testutilConfig.cmake.in b/xo-testutil/cmake/xo_testutilConfig.cmake.in new file mode 100644 index 00000000..e7ffa898 --- /dev/null +++ b/xo-testutil/cmake/xo_testutilConfig.cmake.in @@ -0,0 +1,14 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in CMakeLists.txt +# +find_dependency(subsys) +find_dependency(indentlog) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Share.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/xo-testutil/include/xo/testutil/Utest.hpp b/xo-testutil/include/xo/testutil/Utest.hpp new file mode 100644 index 00000000..e8516b36 --- /dev/null +++ b/xo-testutil/include/xo/testutil/Utest.hpp @@ -0,0 +1,35 @@ +/** @file Utest.hpp + * + * @author Roland Conybeare, May 2026 + **/ + +#pragma once + +#include + +namespace xo { + + /** RAII logging for catch2 unit tests + * + * Use: + * TEST_CASE(name, tags, ..) + * { + * scope log = Utest::ut_scope(); + * + * ... + * log && log(xtag("foo", ...)); + * } + * + * Honors: + * UtestConfig::instance()->debug_flag_ + **/ + struct Utest { + /** Toplevel logging scope for unit tests. + * Integrates with UtestConfig + **/ + static scope ut_scope(); + }; + +} /*namespace xo*/ + +/* end Utest.hpp */ diff --git a/xo-testutil/include/xo/testutil/UtestAppStart.hpp b/xo-testutil/include/xo/testutil/UtestAppStart.hpp new file mode 100644 index 00000000..3cbe683d --- /dev/null +++ b/xo-testutil/include/xo/testutil/UtestAppStart.hpp @@ -0,0 +1,30 @@ +/** @file UtestAppStart.hpp + * + * @author Roland Conybeare, May 2026 + **/ + +#pragma once + +namespace xo { + + /** @brief Startup sequence for a unit test + * + * Standard unit test startup sequence + **/ + class UtestAppStart { + public: + explicit UtestAppStart(const char * app_name) : app_name_{app_name} {} + + /** + * Parse program arguments; recognize XO test arguments, + * sending remainder to catch2; do subsystem initialization + **/ + int run(int argc, char * argv[]); + + private: + const char * app_name_ = ""; + }; + +} /*namespace xo*/ + +/* end UtestAppStart.hpp */ diff --git a/xo-testutil/include/xo/testutil/UtestConfig.hpp b/xo-testutil/include/xo/testutil/UtestConfig.hpp new file mode 100644 index 00000000..9c8179ec --- /dev/null +++ b/xo-testutil/include/xo/testutil/UtestConfig.hpp @@ -0,0 +1,25 @@ +/** @file UtestConfig.hpp + * + * @author Roland Conybeare, May 2026 + **/ + +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(); + }; + +} + +/* end UtestConfig.hpp */ diff --git a/xo-testutil/include/xo/testutil/UtestListener.hpp b/xo-testutil/include/xo/testutil/UtestListener.hpp new file mode 100644 index 00000000..a76d50fb --- /dev/null +++ b/xo-testutil/include/xo/testutil/UtestListener.hpp @@ -0,0 +1,52 @@ +/** @file UtestListener.hpp + * + * @author Roland Conybeare, May 2026 + **/ + +#pragma once + +#include "UtestConfig.hpp" + +// caller must define CATCH_CONFIG_RUNNER +#include + +namespace xo { + + /** @brief listener for catch2 unit tests. + * catch2 invokes this at the beginning of each unit test + * + * Enable with: + * @begin_code + * #include + * CATCH_REGISTER_LISTENER(UtestListener); + * @end_code + **/ + 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 + + }; +} + +/* end UtestListener.hpp */ diff --git a/xo-testutil/src/testutil/CMakeLists.txt b/xo-testutil/src/testutil/CMakeLists.txt new file mode 100644 index 00000000..b43b6144 --- /dev/null +++ b/xo-testutil/src/testutil/CMakeLists.txt @@ -0,0 +1,15 @@ +# testutil/CMakeLists.txt + +set(SELF_LIB xo_testutil) +set(SELF_SRCS + UtestAppStart.cpp + UtestConfig.cpp + Utest.cpp +) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_install_include_tree3(include/xo/testutil) +# note: deps here must also appear in cmake/xo_testutilConfig.cmake.in +xo_dependency(${SELF_LIB} subsys) +xo_dependency(${SELF_LIB} indentlog) +xo_external_target_dependency(${SELF_LIB} Catch2 Catch2::Catch2) diff --git a/xo-testutil/src/testutil/Utest.cpp b/xo-testutil/src/testutil/Utest.cpp new file mode 100644 index 00000000..c0632819 --- /dev/null +++ b/xo-testutil/src/testutil/Utest.cpp @@ -0,0 +1,18 @@ +/** @file Utest.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include "Utest.hpp" +#include "UtestConfig.hpp" +#include + +namespace xo { + scope + Utest::ut_scope() { + return scope(XO_DEBUG(UtestConfig::instance()->debug_flag()), + xtag("name", Catch::getResultCapture().getCurrentTestName())); + } +} + +/* end Utest.cpp */ diff --git a/xo-testutil/src/testutil/UtestAppStart.cpp b/xo-testutil/src/testutil/UtestAppStart.cpp new file mode 100644 index 00000000..433c76a1 --- /dev/null +++ b/xo-testutil/src/testutil/UtestAppStart.cpp @@ -0,0 +1,75 @@ +/** @file UtestAppStart.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include "UtestAppStart.hpp" +#include "UtestConfig.hpp" +#include +#include +#include + +#define CATCH_CONFIG_RUNNER +#include + +namespace xo { + using xo::UtestConfig; + using xo::scope; + using xo::xtag; + + using std::cout; + using std::cerr; + using std::endl; + + int + UtestAppStart::run(int argc, char * argv[]) + { + CLI::App app{app_name_}; + + 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 << app_name_ << " 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()); + } +} /*namespace xo*/ + +/* end UtestAppStart.cpp */ diff --git a/xo-testutil/src/testutil/UtestConfig.cpp b/xo-testutil/src/testutil/UtestConfig.cpp new file mode 100644 index 00000000..49bd2c4c --- /dev/null +++ b/xo-testutil/src/testutil/UtestConfig.cpp @@ -0,0 +1,18 @@ +/** @file UtestConfig.cpp + * + * @author Roland Conybeare, May 2026 + **/ + +#include "UtestConfig.hpp" +#include + +namespace xo { + UtestConfig * + UtestConfig::instance() { + static UtestConfig s_instance; + + return &s_instance; + } +} + +/* end UtestConfig.cpp */