A plain-text test runner, Go-style benchmarks, and compile-time test fixtures for Zig 0.16 and 0.17.
Zig's built-in test runner draws a progress display and speaks a binary protocol to the build runner, which is why output piped to a file is unreadable and why writing to stdout in a test can deadlock. testingx replaces it with plain text on stderr. The benchmark half follows Go's testing.B and prints the Go benchmark format, so benchstat compares two runs with no glue. The fixture helpers keep binary test data as base64 or hex text and organize fixtures in txtar archives, all read at compile time. One dependency, no dependencies of its own, Zig 0.16 and 0.17.
- Your first benchmark: a suite is a file with
pub fn benchmarkMain(b: *testingx.B) !void.addBenchbuilds it as a ReleaseFast executable. - Test runner flags:
--format,--filter,--timeout,--count,--rerun-fails,--shuffle, and--slowest. A wrong filter fails instead of passing silently;--allow-empty-filterrelaxes that for multi-binary builds. - Writing benchmarks:
b.loop(),defer b.keepAlive(sink),b.setBytes,b.reportMetric, andb.runfor sub-benchmarks. Names use benchfmt parts, so benchstat groups a matrix. - Embedded fixtures:
testingx.b64andtestingx.hexdecode one embedded file, andtestingx.fixturesreads a txtar archive by name at compile time. - Artifacts:
--artifacts=DIRgives each test a directory of files worth keeping when it fails, and deletes it when the test passes. - For library authors:
.lazy = trueplusif (b.pkg_hash.len != 0) return;keeps the runner and benchmarks out of a consumer's build. - Limits: what the compiler does not allow, such as parallel tests and subtests.
Not implemented:
- Fuzz mode uses the built-in runner, which speaks the server protocol that
.mode = .simpleskips. Select the runner with a-Dfuzzoption and leave.test_runnernull for that build. - Tests run one at a time.
std.testingkeeps its allocator and I/O in globals, so there is nothing to parallelize safely. - A skip cannot carry a reason.
error.SkipZigTestis the whole vocabulary.
zig fetch --save https://baskingspot.dev/testingx/v0.4.0.tar.gzPoint the test runner at the dependency:
const testingx = b.dependency("testingx", .{ .target = target });
const tests = b.addTest(.{
.root_module = mod,
.test_runner = .{ .path = testingx.path("src/runner.zig"), .mode = .simple },
});
const run_tests = b.addRunArtifact(tests);
run_tests.addPassthruArgs();On Zig 0.16, Run.addPassthruArgs does not exist yet. Forward the flags with
if (b.args) |args| run_tests.addArgs(args); instead.
Benchmarks take one more step. testingx.addBench builds the suite and returns the run step. The guide has the wiring, including the .imports list the suite needs.
testingx builds with Zig 0.16 and 0.17.
- The guide covers adoption, flags, benchmark authoring, fixtures, and the library-author setup, with examples that compile.
- The API reference documents every symbol.
just cinix develop provides the toolchain. just lists the recipes, and just ci runs the tests, the lint, and the docs build.
MIT. See LICENSE. The logo files in docs/static/ are baskingspot marks and are not covered by it. See docs/static/LICENSE.
Built with LLM assistance. How, in detail.