Introduction

The cargo test --no-run flag compiles test binaries without executing them, which is useful for CI pipelines that separate build and test stages. However, finding and running the resulting test binary is non-trivial because cargo generates complex filenames that include the test target name, hash, and platform information.

Symptoms

  • cargo test --no-run succeeds but test binary cannot be found
  • CI pipeline builds tests but test stage cannot locate the binary
  • Running the binary directly produces no tests to run or unexpected behavior
  • Test binary names change between builds, breaking cached paths
  • Integration tests and unit tests produce separate binaries

Example workflow that fails: ```bash # Build works cargo test --no-run --release

# But finding the binary is unclear ./target/release/deps/my_test-* # Glob may match multiple files ```

Common Causes

  • Test binary names include content hashes that change each build
  • Multiple test targets produce multiple binaries
  • --release and debug binaries go to different directories
  • Cargo workspace layout changes binary output paths
  • CARGO_TARGET_DIR environment variable redirects output

Step-by-Step Fix

  1. 1.**Use cargo test --no-run --message-format=json to find binaries**:
  2. 2.```bash
  3. 3.# Get machine-readable output with binary paths
  4. 4.cargo test --no-run --message-format=json 2>&1 | \
  5. 5.jq -r 'select(.reason == "compiler-artifact" and .profile.test == true) | .executable'
  6. 6.`
  7. 7.List test binaries with cargo metadata:
  8. 8.```bash
  9. 9.# Get test executable paths
  10. 10.cargo test --no-run -v 2>&1 | grep "Running"

# Or use cargo's built-in listing cargo test --no-run -- --list 2>/dev/null || true ```

  1. 1.Run specific test binary directly:
  2. 2.```bash
  3. 3.# Find and run the test binary
  4. 4.TEST_BIN=$(cargo test --no-run --message-format=json 2>/dev/null | \
  5. 5.jq -r 'select(.reason == "compiler-artifact" and .target.name == "my_test") | .executable')

# Run with filters $TEST_BIN --test-threads=1 specific_test_name $TEST_BIN --list # List all tests $TEST_BIN --show-output # Show stdout from tests ```

  1. 1.**Use a stable output path with CARGO_TARGET_DIR**:
  2. 2.```bash
  3. 3.# Set a known target directory
  4. 4.export CARGO_TARGET_DIR=$(pwd)/target

cargo test --no-run

# Find binaries in a predictable location ls target/debug/deps/*-*.exe # Windows ls target/debug/deps/*-* # Unix ```

  1. 1.CI-friendly build and test separation:
  2. 2.```bash
  3. 3.#!/bin/bash
  4. 4.# build.sh
  5. 5.cargo test --no-run --message-format=json > /tmp/build-artifacts.json

# test.sh (runs later) while read -r binary; do echo "Running: $binary" $binary --test-threads=$(nproc) done < <(jq -r 'select(.reason == "compiler-artifact" and .profile.test == true and .executable != null) | .executable' /tmp/build-artifacts.json) ```

Prevention

  • Use --message-format=json in CI for reliable binary path discovery
  • Cache the target/ directory in CI to avoid recompilation between stages
  • Pin test binary paths using cargo metadata in your CI scripts
  • Consider using cargo-nextest for better test execution: cargo nextest run
  • Use --test flag to build specific test targets: cargo test --no-run --test integration
  • Document the test binary discovery process in your CI pipeline README