compile-time-unit-testing
Tests that fail the build, not the board
A single-header C++20 library that runs your unit tests while the code
compiles. Write an ordinary constexpr bool test_xxx() with
the expect_* assertions, then
static_assert(test_xxx()). A broken invariant becomes a
compiler error at build time, with the actual mismatched values in the
diagnostic — caught before there is anything to link, flash, or run
separately.
What you get
Six things you can count on, none of which ask you to change how you build.
The build catches it, not a test run later
A broken invariant stops the compile. There is nothing to link, flash, or remember to run afterwards — if the target builds, the checks passed.
The error is already the diagnosis
When an assertion fails, the compiler shows the actual values that did not match, right in the error. You read the discrepancy straight from the build output instead of adding print statements to a generic assertion failure to work out what happened.
Nothing ships to the target
The tests run entirely at compile time, so none of them end up in the image. No test scaffolding consumes flash or RAM on a constrained board, and there is no runtime cost to a check that finished before the binary existed.
No framework to adopt
One header, no test runner, no discovery step, no separate CI job. Write a constexpr function, static_assert it, and it rides the build you already have.
It fails loudly when it should — proven
34 deliberately-broken compile tests regression-test the library's own failure path — checking not just that a bad invariant fails the build, but that the expected values actually appear in the diagnostic text. The tool you trust to catch mistakes is held to that same standard.
Compares what you actually need
The assertions cover single values and whole arrays alike — C arrays, std::array, and std::span — so you compare the thing you meant to compare instead of fighting the tool into shape.
How little there is to it
Every check is a plain consteval assertion evaluated during
compilation. Write a constexpr function that returns whether
the invariants hold, assert the ones you care about, and
static_assert the function — that is the whole integration.
The assertion family is what you would expect:
expect_true, expect_false,
expect_eq, expect_ne, expect_lt,
expect_le, expect_gt, and
expect_ge, plus an epsilon-tolerant
expect_near for floating-point (GCC 11+ / Clang 18+).
Works on the compilers you actually target
Header-only, MIT licensed, nothing to link, C++20 the only requirement.
Tested against GCC 10–15 and Clang 13–21, plus ESP32 (Xtensa), STM32
(Cortex-M4), and RISC-V (rv32imac) bare-metal cross-compilers, and it
works under -fno-exceptions — so it fits the constrained
builds embedded and safety-critical work actually ships.
Applied to a real crash
A real reentrancy crash in ESPHome (issue #18434), the actual decision function extracted and tested this way, and the before-and-after compiler output proving it: the same test fails to compile against the unfixed code and passes against the fix. Not a toy example — a real, public, verifiable case study.
Install it
The repository is the maintained source of truth for install
instructions — CMake's FetchContent, or vendor the single
header directly. Both are documented there.