cargo-rigtest
A Cargo plugin for infrastructure and acceptance testing in Rust.
cargo-rigtest runs each test in its own subprocess, giving you process-level isolation, parallel execution, structured output, and first-class support for shared infrastructure setup — without the overhead of spinning up a full test harness.
Overview
cargo-rigtest is designed for the layer of tests that sit above unit tests: API integration tests, service smoke tests, environment verification, end-to-end workflows. It is not a replacement for #[test] — it is a complement to it.
Key properties:
- Each test runs in its own subprocess — a panic or crash in one test cannot affect others
- Tests run in parallel by default, with a configurable concurrency limit
- A single
#[global_setup]/#[global_teardown]pair provisions and tears down shared infrastructure once per suite - Per-test setup and teardown hooks are available via
TestContext - Tests can be marked
serial, given atimeout, or configured toretryon failure - Output is captured per-test and printed only on failure, nextest-style
- A
rigtest::skip!macro lets tests opt out at runtime with a reason
Installation
Install the Cargo plugin:
cargo install cargo-rigtest
Add the runtime library to your project:
[]
= "0.1"
Optional features
| Feature | Description |
|---|---|
http-client |
Adds a pre-built reqwest::Client as ctx.client, available to every test. Enable it if your tests make HTTP calls and you want a shared client without constructing one manually. |
[]
= { = "0.1", = ["http-client"] }
Quick start
1. Add a test target
In your Cargo.toml, add a [[test]] section with harness = false:
[[]]
= "acceptance"
= "tests/acceptance.rs"
= false
2. Write the test file
use Arc;
use *;
use ;
async
async
async
3. Run
cargo rigtest run
Test attributes
#[testcase]
Registers an async function as a test case. The function must have this signature:
async
Optional flags can be combined in any order:
| Flag | Description |
|---|---|
serial |
Run this test exclusively — no other test runs concurrently with it |
timeout = <Duration> |
Hard-kill the test subprocess and report failure if it exceeds the duration |
retries = <N> |
Retry a failing test up to N additional times before reporting failure |
Note on timeout and teardown: when a timeout fires the subprocess is hard-killed. Any teardown registered with
ctx.teardown(...)will not run. Resources that must be released regardless of outcome should use RAII guards or#[global_teardown].
#[global_setup]
Runs once before any test in the suite. The return value is serialized and passed to each test subprocess via a temporary environment variable. At most one may be defined.
async
The return type must implement serde::Serialize and serde::Deserialize — it is serialized to JSON and forwarded to each test subprocess via a temporary environment variable. Store configuration values (URLs, ports, credentials) rather than live handles.
#[global_teardown]
Runs once after all tests finish. Receives the value produced by #[global_setup]. At most one may be defined.
async
Note: The global state type must implement
serde::Serializeandserde::Deserializebecause it is serialized to JSON and passed to each test subprocess via a temporary environment variable. Live resources such as connection pools, file descriptors, and socket handles cannot be serialized — store the configuration needed to recreate them instead (a URL, a path, a port number).
Per-test setup and teardown
TestContext provides setup and teardown hooks for resources with a clear per-test lifecycle.
The global argument passed to both closures is the deserialized global state — the same value produced by #[global_setup] and subject to the same constraint: it contains only serializable configuration (URLs, ports, credentials), not live handles. Use it to create a live resource; the resource itself lives entirely within the test subprocess and is not subject to any serialization requirement.
async
Skipping tests
Use rigtest::skip! to skip a test at runtime with an optional reason:
async
Skipped tests appear in the summary as SKIP and do not count as failures.
Running tests
cargo rigtest run [OPTIONS]
| Flag | Description |
|---|---|
--jobs <N> |
Maximum parallel test jobs (default: number of CPUs) |
--seed <N> |
Fix the random order seed for reproducible runs |
--filter <STRING> |
Only run tests whose name contains STRING |
--test <NAME> |
Only run the named test target (repeatable: --test a --test b) |
--package <NAME> |
Package containing the test targets |
--no-capture |
Print test output in real time instead of capturing it (implies --jobs 1) |
The seed is printed at the start of every run so a failing order can be reproduced exactly:
cargo rigtest run --seed 12345678
Output
cargo-rigtest produces nextest-style output. In a TTY, running tests show live spinners; results are printed as they complete:
── global setup
PASS [0.142s] homepage_returns_200
SKIP [0.031s] requires_live_database: DATABASE_URL not set
FAIL [0.089s] creates_a_record: assertion failed at tests/acceptance.rs:42
── stdout
created record with id 99
expected count 1, got 2
────────────────────────────────────────────────────────────
Summary [0.21s] 3 tests run: 1 passed, 1 skipped, 1 failed
── global teardown
In CI or piped output, spinners are replaced with plain lines so no output is lost.
Multiple test targets
If a package has more than one rigtest test target, all of them are discovered and run in sequence automatically:
cargo rigtest run # run all rigtest targets
cargo rigtest run --test smoke # run one
cargo rigtest run --test smoke --test e2e # run two
cargo-rigtest identifies rigtest test targets automatically and ignores any other harness = false binaries in the package.
Crate layout
| Crate | Description |
|---|---|
cargo-rigtest |
The cargo rigtest CLI plugin |
rigtest |
Runtime library — add this to [dev-dependencies] |
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.