A test system for the agent era: one protocol between test programs and the tools that run them, a C++ test library with no macro and no header, and an engine that runs, stores and explains tests of any language — for people, for CI and for agents alike.
English | 简体中文
Each part is used alone; together they share one data model.
| Part | What it gives | Where |
|---|---|---|
| Protocol | how a test program lists, selects and reports its cases | spec/protocol.md |
| C++ library | cases, expectations, fixtures; bench, cts, e2e modules |
lib/ — mcpplibs.mtest |
| Engine | run, plan, explain, history, analyze, graph, gate, trace, bisect, ci | engine/ — the mtest program |
| Service | the engine's commands as JSON-RPC and MCP tools | mtest serve — docs/service.md |
| Adapters | GoogleTest, Bash, Python | adapters/ — docs/adapters.md |
| C ABI | the protocol for any language with a C FFI | capi/ — include/mtest.h |
| Conformance | one suite that checks every implementation of the protocol | conformance/ |
import mcpplibs.mtest; using namespace mcpplibs::mtest; int main() { "adds"_test = [] { expect(eq(1 + 1, 2)); }; "reads a file"_test[tag{"fs"}] = [] { auto dir = temp_dir(); expect(std::filesystem::is_directory(dir)); }; return report(); }
[dev-dependencies] mcpplibs.mtest = "0.6.3"
A failure names its place and both sides (expected 2, actual 3). Under a runner the same program
speaks the protocol; alone it prints one line per case.
A file may declare its cases as objects and no main; mtest's entry begins it (mcpp 2026.11.8.1
and later; an earlier mcpp is told [test] main = { from = "mtest/main" }). Each object is one
case a runner lists, selects and schedules by the attributes it states (kind by its type,
resources, time limit, isolation, retries, a diagnosis); the cases its body declares are selectable
within it (lib/docs/writing-tests.md):
import mcpplibs.mtest; using namespace mcpplibs::mtest; UTest empty{"an empty line", [] { expect(eq(parse(""), 0)); }}; E2E cli{"cli", resource{"serial:home"}, timeout{std::chrono::minutes{2}}, [] { "prints its version"_test = [] { /* ... */ }; "reads its configuration"_test[retries{1}] = [] { /* ... */ }; }};
mtest run ./build/tests/* # any program: by the protocol, else by its exit status mtest run --set smoke # the sets of mtest.toml: smoke, regression, pr, full, release mtest explain # where the last failure was, why, and the command that repeats it mtest plan --since origin/main # what a change affects, and why each case is in mtest analyze hot # the slowest cases; also durations, path, failures, flaky, bench mtest report --format md # the run as Markdown: failures, metrics, use of the machine, the critical path in mermaid mtest serve # the same commands for an agent (MCP) or an IDE (JSON-RPC)
Every command takes --json, and every answer has a conclusion. See
docs/commands.md and docs/config.md.
| Level | A program does | A runner obtains |
|---|---|---|
| L0 | nothing | one result per program, by its exit status |
| L1 | list, select, report | the case tree, single cases, failure places |
| L2 | metrics | bench verdicts, conformance clauses |
| L3 | resources, artifacts | scheduling, affected selection |
The library, the C ABI and the script adapters are L3; GoogleTest is L1.
mtest is built with mcpp:
mcpp build # every member mcpp test -p mcpplibs.mtest -p mtest.mtest # the library's and the engine's tests mcpp test -p conformance # the protocol, against the library (MTEST_CTS_PROGRAM: another one)