Expand description
Runtime library for the cargo-rigtest acceptance-testing framework.
This crate provides the attributes, context, and entry point needed to write
tests that run against a live, deployed system — a staging environment, a
real database, a running service. Tests are compiled into a standard Cargo
test binary (with harness = false) and driven by the cargo rigtest CLI,
which runs each test case in its own subprocess for process-level isolation.
§Getting started
Add rigtest to your dev-dependencies and declare a test target with
harness = false:
# Cargo.toml
[dev-dependencies]
rigtest = "0.1"
serde = { version = "1", features = ["derive"] }
[[test]]
name = "acceptance"
path = "tests/acceptance.rs"
harness = falseA minimal test file:
use std::sync::Arc;
use rigtest::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct State { base_url: String }
#[global_setup]
async fn setup() -> State {
State {
base_url: std::env::var("BASE_URL")
.unwrap_or_else(|_| "http://localhost:8080".into()),
}
}
#[global_teardown]
async fn teardown(_state: State) {}
#[testcase]
async fn homepage_is_up(ctx: Arc<TestContext>) -> Result<(), rigtest::Error> {
let state = ctx.global::<State>();
// make assertions against state.base_url…
Ok(())
}
#[rigtest::main]
fn main() {}Run the suite with:
cargo rigtest run§The testing model
cargo-rigtest separates orchestration from execution. The coordinator
(run by cargo rigtest run) calls #[global_setup]
once to produce shared state, then spawns each test case as an independent
subprocess. Each subprocess deserializes the global state, runs its test
function, and exits. When all tests have finished the coordinator calls
#[global_teardown].
Because every test is a separate process:
- A panic, crash, or
process::exitin one test cannot affect others. - Tests run in parallel by default (configurable with
--jobs). - Any resource a test opens lives only for the lifetime of that subprocess.
§Attributes
§#[testcase]
Registers an async function as a test case. The function must accept
Arc<TestContext> and return Result<(), Error>:
#[testcase]
async fn my_test(ctx: Arc<TestContext>) -> Result<(), rigtest::Error> {
Ok(())
}Optional flags can be combined in any order:
#[testcase(serial, timeout = std::time::Duration::from_secs(30), retries = 2)]
async fn careful_test(ctx: Arc<TestContext>) -> Result<(), rigtest::Error> {
Ok(())
}| Flag | Description |
|---|---|
serial | Run this test exclusively — no other test runs concurrently |
timeout = <Duration> | Terminate the subprocess if it runs too long |
retries = <N> | Retry a failing test up to N additional times |
§#[global_setup]
Runs once before any test in the suite. Returns a value that is serialized and passed to every test subprocess as the global state. At most one may be defined.
#[global_setup]
async fn setup() -> MyState {
MyState { db_url: std::env::var("DATABASE_URL").unwrap() }
}The return type must implement serde::Serialize and
serde::de::DeserializeOwned — the value crosses a process boundary
and is deserialized by value, so borrowed Deserialize<'de>
implementations are not sufficient. Store configuration (URLs, ports,
credentials, identifiers) rather than live resources (connection pools,
file descriptors, socket handles).
§#[global_teardown]
Runs once after all tests finish. Receives the deserialized state produced
by #[global_setup]. At most one may be defined.
#[global_teardown]
async fn teardown(state: MyState) {
println!("cleaning up {}", state.db_url);
}Because #[global_teardown] runs in the coordinator process — outside any
test subprocess — it is the right place to clean up resources that must be
released regardless of how individual tests finish, including tests that
time out.
§Test context
Every test receives an Arc<TestContext>. It exposes:
global_data— the deserialized global state from#[global_setup]. Useglobal::<T>()for a typed shorthand that avoids thedowncast_ref/expectboilerplate.setup/teardown— async closures for per-test resource lifecycle. Failures are labelled"setup failed:"or"teardown failed:"in the report so the phase is unambiguous.client— a sharedreqwest::Clientwhen thehttp-clientfeature is enabled.
#[testcase]
async fn creates_record(ctx: Arc<TestContext>) -> Result<(), rigtest::Error> {
let mut conn = ctx.setup(|global| async move {
let state = global.downcast_ref::<State>().unwrap();
db_connect(&state.db_url).await
}).await?;
conn.insert("hello").await?;
assert_eq!(conn.count().await?, 1);
ctx.teardown(|_global| async move {
conn.rollback().await?;
Ok(())
}).await?;
Ok(())
}§Skipping tests
Use skip! to bail out of a test at runtime with an optional reason:
#[testcase]
async fn requires_db(ctx: Arc<TestContext>) -> Result<(), rigtest::Error> {
if std::env::var("DATABASE_URL").is_err() {
rigtest::skip!("DATABASE_URL not set");
}
// …
Ok(())
}Skipped tests appear as SKIP in the summary and do not count as failures.
§Feature flags
| Feature | Description |
|---|---|
http-client | Adds a shared reqwest::Client as ctx.client. Omit this feature if you prefer to construct your own HTTP client. |
ssh-client | Adds ctx.ssh(destination) for running commands over SSH via openssh. Unix only — depends on the system ssh binary; has no effect on non-Unix targets. |
§Entry point
Every test binary needs an entry point that lets the cargo rigtest
coordinator drive the binary as either an orchestrator or a single-test
subprocess depending on how it was invoked. The recommended way is the
#[rigtest::main] attribute:
#[rigtest::main]
fn main() {}run_main remains available for compatibility with the older
fn main() { rigtest::run_main(); } pattern.
Re-exports§
pub use context::TestContext;
Modules§
Macros§
- skip
- Skip the current test with an optional reason.
Structs§
- Skip
- Marker error returned by the
skip!macro to signal that a test should be skipped rather than failed.
Functions§
- run_
main - Entry point for test binaries using cargo-rigtest.
Call this from
main()in a[[test]]target withharness = false.
Type Aliases§
- Error
- Convenience alias for the error type used by test functions, setup, and
teardown closures. Equivalent to
Box<dyn std::error::Error + Send + Sync>.
Attribute Macros§
- global_
setup - Registers an async function as the global setup hook for a test binary.
- global_
teardown - Registers an async function as the global teardown hook for a test binary.
- main
- Marks a
fn main()as the entry point for a cargo-rigtest test binary. - testcase
- Registers an async function as a cargo-rigtest test case.