Skip to main content

Crate rigtest

Crate rigtest 

Source
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 = false

A 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::exit in 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(())
}
FlagDescription
serialRun 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]. Use global::<T>() for a typed shorthand that avoids the downcast_ref / expect boilerplate.
  • 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 shared reqwest::Client when the http-client feature 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

FeatureDescription
http-clientAdds a shared reqwest::Client as ctx.client. Omit this feature if you prefer to construct your own HTTP client.
ssh-clientAdds 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§

context
prelude
Convenient glob import for test files.

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 with harness = 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.