|
TTS v4.0.0
The Tiny Test System
|
The library is header-only, and a test suite holds dozens or hundreds of small translation units, each including <tts/tts.hpp> on its own. No separately compiled object amortizes the parsing, so each header included by TTS is parsed once per test file and per build, in each project using the library. The CI matrix of the library alone rebuilds the suite on more than a dozen combinations of compiler, system, and standard library per pull request, so a header added to a widely included file adds its parsing time to each of those builds.
The library therefore holds small replacements for the facilities it needs, listed in TTS replacements, rather than including the standard headers.
The cost of a header is measured before the header is included, since the name of a header does not predict its cost. The times below are what a header adds to a minimal TTS_MAIN translation unit, with g++ 13 and clang++ 18, -std=c++20, best of eight runs.
| Header | g++ | clang++ | Replacement |
|---|---|---|---|
| <chrono> | +460ms | +680ms | now_ns() in tools/clock.hpp |
| <regex> | +270ms | +350ms | none, not used |
| <thread> | +200ms | +240ms | none, not used |
| <memory> | +170ms | +210ms | file_guard in tools/file.hpp |
| <sstream> | +140ms | +190ms | tts::text in tools/text.hpp |
| <iostream> | +140ms | +170ms | fputs on a FILE*, in place of std::cout |
| <functional> | +100ms | +100ms | callable in tools/erased.hpp |
| <string> | +90ms | +110ms | tts::text in tools/text.hpp |
| <unordered_map> | +60ms | +60ms | none, not used |
| <vector> | +50ms | +60ms | tts::buffer in tools/buffer.hpp |
| <map> | +50ms | +50ms | none, not used |
| <array> | +20ms | +20ms | a C array sized by a constant |
A standard header is allowed once measured as free:
<windows.h> is included only on the Windows branch of tools/clock.hpp, guarded by WIN32_LEAN_AND_MEAN and NOMINMAX. Measured on cl.exe, it costs nothing relative to an empty translation unit, and less than declaring by hand the three functions it provides. Platform-specific code is measured on that platform.
engine/deps.hpp, included by nearly every TTS header, already includes the acceptable headers, so using one of them costs nothing more.
Each file below replaces a standard header listed in the table above, and is a model for a new replacement:
Each publication of this documentation builds test executables with TTS, doctest, Catch2, GoogleTest, Boost.Test, and ut, and measures the CPU time of their compilation and link with -std=c++20. Each executable runs and passes before its time is kept, and each point is the median of several builds. Each publication runs on a new CI machine, named below, and its times compare only with one another.
Measured on 2026-10-14 on AMD EPYC 7763 64-Core Processor, 4 cores, with clang++ 22.1.2, g++ 15.2.0 and -O0: the median of 3 builds per point, made in rounds that alternate the libraries, 2 at a time.
Every test is the same trivial case, three checks on two integers. Each library is set up as its documentation sets it up:
A test file holding neither runner nor test, compiled with -c. The unit of ut holds its runner.
An executable built from one file holding the runner and one test.
An executable built from one file holding the runner and a growing number of tests. Each curve is fitted by least squares to a fixed cost plus a cost per test.
Cost of an executable from one file holding \(n\) tests, fitted as a fixed part and a part per test:
| Library | Cost |
|---|---|
| TTS | \(0.34\,\mathrm{s} + 2.1\,\mathrm{ms} \cdot n\) |
| Boost.Test | \(6.17\,\mathrm{s} + 12.4\,\mathrm{ms} \cdot n\) |
| Catch2 | \(1.02\,\mathrm{s} + 4.8\,\mathrm{ms} \cdot n\) |
| GoogleTest | \(0.92\,\mathrm{s} + 8.0\,\mathrm{ms} \cdot n\) |
| doctest | \(1.81\,\mathrm{s} + 5.4\,\mathrm{ms} \cdot n\) |
| ut | \(1.76\,\mathrm{s} + 2.8\,\mathrm{ms} \cdot n\) |
Cost of an executable from one file holding \(n\) tests, fitted as a fixed part and a part per test:
| Library | Cost |
|---|---|
| TTS | \(0.34\,\mathrm{s} + 1.2\,\mathrm{ms} \cdot n\) |
| Boost.Test | \(5.01\,\mathrm{s} + 9.8\,\mathrm{ms} \cdot n\) |
| Catch2 | \(2.20\,\mathrm{s} + 5.0\,\mathrm{ms} \cdot n\) |
| GoogleTest | \(1.63\,\mathrm{s} + 5.5\,\mathrm{ms} \cdot n\) |
| doctest | \(2.05\,\mathrm{s} + 4.9\,\mathrm{ms} \cdot n\) |
| ut | \(2.97\,\mathrm{s} + 1.2\,\mathrm{ms} \cdot n\) |
A test file without runner holding 200 tests, compiled with -c. The cost of one test is its time minus that of the test file with no test, divided by the number of tests. The runner stays out: for some libraries, it varies more between two builds than 50 tests add to the curve above.
An executable built from a runner and a growing number of files of one test each, linked together. TTS, doctest, and Boost.Test compile their runner once, in its own file; ut compiles it in each file.