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:   "???"
This commit is contained in:
Roland Conybeare 2026-06-06 21:50:26 -04:00
commit 5a06bced3b
13 changed files with 360 additions and 0 deletions

12
xo-testutil/.gitrepo Normal file
View file

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

View file

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

3
xo-testutil/README.md Normal file
View file

@ -0,0 +1,3 @@
# xo-testutil
unit test harness for catch2

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,52 @@
/** @file UtestListener.hpp
*
* @author Roland Conybeare, May 2026
**/
#pragma once
#include "UtestConfig.hpp"
// caller must define CATCH_CONFIG_RUNNER
#include <catch2/catch.hpp>
namespace xo {
/** @brief listener for catch2 unit tests.
* catch2 invokes this at the beginning of each unit test
*
* Enable with:
* @begin_code
* #include <catch2/catch.hpp>
* 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 */

View file

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

View file

@ -0,0 +1,18 @@
/** @file Utest.cpp
*
* @author Roland Conybeare, May 2026
**/
#include "Utest.hpp"
#include "UtestConfig.hpp"
#include <catch2/catch.hpp>
namespace xo {
scope
Utest::ut_scope() {
return scope(XO_DEBUG(UtestConfig::instance()->debug_flag()),
xtag("name", Catch::getResultCapture().getCurrentTestName()));
}
}
/* end Utest.cpp */

View file

@ -0,0 +1,75 @@
/** @file UtestAppStart.cpp
*
* @author Roland Conybeare, May 2026
**/
#include "UtestAppStart.hpp"
#include "UtestConfig.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 {
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<const char *> 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 */

View file

@ -0,0 +1,18 @@
/** @file UtestConfig.cpp
*
* @author Roland Conybeare, May 2026
**/
#include "UtestConfig.hpp"
#include <catch2/catch.hpp>
namespace xo {
UtestConfig *
UtestConfig::instance() {
static UtestConfig s_instance;
return &s_instance;
}
}
/* end UtestConfig.cpp */