A small, header-only C++20 unit testing framework. Test names are carried as compile-time string literals, assertions come in runtime and compile-time forms, and exceptions thrown by a test are caught and reported rather than aborting the run. One include, no build step.
rt-ut provides assertions and pass/fail reporting — nothing more. There are no
fixtures, no mocks, no test discovery, no registration macros. A test is a name
(a string literal template parameter) and a callable; the framework invokes it,
compares or evaluates the result, prints a line, and returns a bool you can
act on. That is the whole surface.
- A C++20 compiler with support for class-type non-type template parameters (used for the string-literal test names). Developed and tested against Clang 24.0.0, GNU 16.0.1 and MSVC 19.51. Compilers that implement the same feature set should work.
- CMake 3.28+
The library is header-only and has no dependencies.
The following operating systems and compilers are officially supported:
Minimum Requirements:
- C++20 compliant compiler
#include <rt-ut>
#include <cstdint>
int main() {
// Runtime expected value
rt_ut::unit_test<"add-eq">::assert_eq(
42,
[](int a, int b) { return a + b; },
40, 2);
// Compile-time expected value (non-type template parameter)
rt_ut::unit_test<"add-eq-ct">::assert_eq<42>(
[](int a, int b) { return a + b; },
40, 2);
// Inequality
rt_ut::unit_test<"ne">::assert_ne<0>(
[] { return 42; });
// Predicate — passes when the callable returns true
rt_ut::unit_test<"predicate">::run(
[] { return 2 + 2 == 4; });
return 0;
}compile_time_assert lowers to a static_assert, so a failing test is a
compile error and there is no runtime component at all. The callable and its
arguments must be usable in a constant expression:
rt_ut::unit_test<"constexpr-check">::compile_time_assert<true>(
[]() constexpr { return sizeof(int) > 0; });All methods live on
rt_ut::unit_test<name, print_all_results = true, exceptions = true>.
print_all_results: whenfalse, passes are silent and only failures and errors print.exceptions: whentrue(the default), a failed assertion throwsrt_ut::rt_ut_exception, whosewhat()returns the failure message as astd::string_view. Whenfalse, the failure is printed and the call returnsfalse.
run(func, args...) → bool
Invokes func(args...); passes if it returns truthy. Prints [PASSED] <name>
on success. A false return is a failure.
assert_eq<expected>(func, args...) → bool
Compile-time expected value. Passes if func(args...) == expected.
assert_eq(expected, func, args...) → bool
Runtime expected value. Same comparison, value supplied at runtime.
assert_ne<expected>(func, args...) → bool
Passes if func(args...) != expected.
assert_ne(expected, func, args...) → bool
Runtime expected value. Passes if func(args...) != expected.
compile_time_assert<expected>(func, args...)
static_assert that func(args...) == expected. Compile error on failure,
zero runtime footprint. Requires a constant-expression-callable func.
assert_eq and assert_ne accept a comparator type as a template parameter
(defaulting to std::equal_to<> and std::not_equal_to<>), placed after the
function type. Because the trailing arguments are deduced, the function type
must be spelled out when supplying a comparator:
rt_ut::unit_test<"approx">::assert_eq<double, double (*)(double), approx_equal_double>(
1.75, halve, 3.5);Exceptions thrown by the test callable are caught: a std::exception prints
[ERROR] <name> threw exception: <what>, anything else prints
[ERROR] <name> threw unknown exception., and both return false. An
rt_ut_exception from a failed assertion is not swallowed; it propagates to
the caller.
Integer results are promoted before printing (so uint8_t/int8_t show as
numbers, not characters), bool results print as true/false, and
std::tuple values are printable.
[PASSED] add-eq
[FAILED] add-eq | expected: 100 | Got: 42
[FAILED] predicate
[ERROR] alloc-test threw exception: bad allocation
Passes go to stdout and errors go to stderr. With exceptions = true,
failure lines are carried in the thrown rt_ut_exception rather than printed.
With exceptions = false, assert_eq/assert_ne failures print to stderr
and run failures print to stdout.
include(FetchContent)
FetchContent_Declare(
rt-ut
GIT_REPOSITORY https://github.com/nihilai-collective/rt-ut.git
GIT_TAG main
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(rt-ut)
target_link_libraries(your_target PRIVATE rt-ut::rt-ut)git clone https://github.com/nihilai-collective/rt-ut.git
cd rt-ut
cmake -B build -DCMAKE_INSTALL_PREFIX=/path/to/install
cmake --build build --target installThen find_package(rt-ut CONFIG REQUIRED) and link rt-ut::rt-ut.
cmake -S . -B build -DRT_UT_UNIT_TESTS=ON
cmake --build build
./build/unit-tests/rt-ut-unit-tests # .\build\unit-tests\Release\rt-ut-unit-tests.exe on WindowsAddressSanitizer and UndefinedBehaviorSanitizer can be enabled with
-DRT_UT_ASAN=ON and -DRT_UT_UBSAN=ON. UBSan has no effect under MSVC, and
both are disabled for GCC on macOS.
CMakePresets.json also provides windows-* and linux-* presets
(debug, release, test-debug, test-release); the test-* presets enable
RT_UT_UNIT_TESTS. They point at local vcpkg toolchain paths, so adjust those
for your machine.
clang-format with the provided .clang-format. On Windows, run the included
script (skips out/ and build/):
./run-clang-format.ps1Elsewhere:
find . \( -path ./out -o -path ./build \) -prune -o \( -name '*.cpp' -o -name '*.hpp' \) -print | xargs clang-format -iMIT © 2026 Nihilai Collective Corp — see License.
