Skip to content

/flags/rust/rustc-test

rustc --test: build a test runner from a single Rust file

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · rustc 1.98.1 (48a229cea 2026-09-01)updated
tool
Rust
command
rustc
flag
--test
default
off (rustc builds the crate's normal binary or library)

rustc --test file.rs compiles the file into a test runner instead of a normal program: code under #[cfg(test)] is included, every #[test] function is registered, and rustc generates a main that runs them with the standard libtest harness. Running the resulting binary prints the familiar running N tests ... test result: report and exits with 101 if any test fails. cargo test does exactly this for each crate, plus integration tests and doc tests.

Build and run

bash
cat > calc.rs <<'EOF'
pub fn add(a: i32, b: i32) -> i32 { a + b }

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn adds() { assert_eq!(add(2, 2), 4); }
    #[test]
    fn fails() { assert_eq!(add(2, 2), 5); }
    #[test]
    #[ignore]
    fn slow() {}
}

fn main() { println!("main: {}", add(1, 2)); }
EOF
clean() { sed -E "s/^thread '([^']+)' \([0-9]+\)/thread '\1'/; s/finished in [0-9.]+s/finished in <time>/"; }
rustc --test calc.rs -o calc-tests
./calc-tests --test-threads=1 2>&1 | clean; echo "exit: ${PIPESTATUS[0]}"
echo "== one test by name"
./calc-tests adds 2>&1 | clean
echo "== list"
./calc-tests --list
echo "== normal build"
rustc calc.rs -o calc && ./calc

Output

running 3 tests
test tests::adds ... ok
test tests::fails ... FAILED
test tests::slow ... ignored

failures:

---- tests::fails stdout ----

thread 'tests::fails' panicked at calc.rs:9:18:
assertion `left == right` failed
  left: 4
 right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    tests::fails

test result: FAILED. 1 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in <time>

exit: 101
== one test by name

running 1 test
test tests::adds ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in <time>

== list
tests::adds: test
tests::fails: test
tests::slow: test

3 tests, 0 benchmarks
== normal build
main: 3

The failing assertion shows the left and right values; the ignored test is reported but not run. A name given to the test binary is a filter (substring match), and --list prints the registered tests. Without --test the same file builds the regular program, whose main runs and the tests are left out. The clean function only replaces the run time and the thread number, which change on every run.

Options of the test binary

OptionEffect
NAMERun only tests whose path contains NAME (several filters allowed)
--exactFilters must match the full test path, e.g. tests::adds
--ignored / --include-ignoredRun only the #[ignore] tests / run them as well
--test-threads=NNumber of tests run in parallel; 1 gives a stable order
--nocaptureDo not capture stdout and stderr of tests
--show-outputPrint the captured output of passing tests too
--listList tests without running them
--skip NAMESkip tests whose path contains NAME

These are arguments of the generated binary, not of rustc. With Cargo they go after --: cargo test -- --test-threads=1 --nocapture, and a filter can go before it (cargo test adds, see run a single test).

  • --cfg test without --test compiles the #[cfg(test)] code but no harness, which is rarely what you want.
  • -C opt-level=3 --test builds optimized tests, the equivalent of cargo test --release; overflow checks are then off (see rustc -C opt-level).
  • --crate-type lib --test works the same way for library files that have no main.

Pitfalls

  • A file that only contains a library (no main) builds fine with --test, but without --test rustc expects a binary and fails with error E0601 (main function not found in crate); pass --crate-type lib in that case.
  • Tests run in parallel by default, so their output can interleave and tests sharing a file or environment variable can interfere. Use --test-threads=1 while you debug.
  • Exit code 101 is the Rust panic code; CI treats any non-zero code as failure, so no extra flag is needed.

FAQ

Why does rustc --test not run my main function?

The test harness provides its own main, which runs the #[test] functions. Your main is compiled but not called. Build without --test to get the normal program.

How do I see println! output from a passing test?

Pass --nocapture to the test binary (./tests --nocapture), or --show-output to print captured output of passing tests after the run. With Cargo, put the options after a double dash: cargo test -- --nocapture.

Can I run the tests of a single file without Cargo?

Yes, that is what rustc --test file.rs is for: it needs no Cargo.toml. As soon as the code uses crates from crates.io, switch to Cargo, which passes the --extern flags for you.

Sources