cargo-nextest Advanced Configuration: Filters, Partitioning, and CI Integration

cargo-nextest Advanced Configuration: Filters, Partitioning, and CI Integration

cargo-nextest runs Rust tests faster and with better output than cargo test. Beyond the basics, it has a configuration system that lets you filter which tests run, partition tests across parallel CI jobs, retry flaky tests automatically, and produce JUnit XML for CI dashboards — all without changing test code.

Key Takeaways

  • .config/nextest.toml is the config file — commit it to version control
  • Test filters use Rust-based expressions: binary, test name, tags
  • --partition hash:1/4 splits tests across 4 parallel CI shards
  • Profiles let you define different behavior for local vs CI runs
  • --retries auto-retries flaky tests without failing the build

Configuration file

All nextest configuration lives in .config/nextest.toml at the workspace root. Create it once and commit it:

[profile.default]
# How many tests to run in parallel
test-threads = "num-cpus"

# Retry failed tests up to 2 times before marking as failed
retries = 0

# Fail fast: stop after first test failure
fail-fast = false

# Time limit per test
slow-timeout = { period = "60s", terminate-after = 3 }

Profiles

Profiles let you define different settings for different contexts:

[profile.default]
test-threads = "num-cpus"
retries = 0

[profile.ci]
# More aggressive parallelism on CI (typically more cores available)
test-threads = 8

# Retry flaky tests automatically on CI
retries = 2

# Collect JUnit XML output
[profile.ci.junit]
path = "target/nextest/ci/junit.xml"

[profile.slow]
# Profile for running the full suite including slow integration tests
test-threads = 2
slow-timeout = { period = "300s", terminate-after = 2 }

Run a specific profile:

cargo nextest run --profile ci
cargo nextest run --profile slow

Test filters

nextest's filter expressions are more powerful than substring matching:

# Run tests by name (substring)
cargo nextest run test_auth

<span class="hljs-comment"># Run tests in a specific binary
cargo nextest run --<span class="hljs-built_in">test integration_tests

<span class="hljs-comment"># Run tests matching a regex
cargo nextest run -E <span class="hljs-string">'test(~"auth")'

<span class="hljs-comment"># Run tests NOT matching a pattern
cargo nextest run -E <span class="hljs-string">'not test(~"slow")'

<span class="hljs-comment"># Run tests in a specific package
cargo nextest run -p my-crate

<span class="hljs-comment"># Combine: tests in my-crate that contain "user"
cargo nextest run -E <span class="hljs-string">'package(my-crate) and test(~"user")'

The -E flag takes a filter expression. Available predicates:

Predicate Meaning
test(name) Test name matches (substring)
test(~"regex") Test name matches regex
binary(name) Test binary matches
package(name) Package matches
kind(lib) Test kind: lib, bin, test, bench, example
not X Negation
X and Y Both must match
X or Y Either matches

Partitioning for parallel CI

Partition splits your test suite into N equal shards, each running independently on a separate CI job:

# Shard 1 of 4
cargo nextest run --partition <span class="hljs-built_in">hash:1/4

<span class="hljs-comment"># Shard 2 of 4
cargo nextest run --partition <span class="hljs-built_in">hash:2/4

<span class="hljs-comment"># Shard 3 of 4
cargo nextest run --partition <span class="hljs-built_in">hash:3/4

<span class="hljs-comment"># Shard 4 of 4
cargo nextest run --partition <span class="hljs-built_in">hash:4/4

The hash strategy distributes tests by name hash — reproducible across runs, no coordination required. Each CI job gets a different shard index.

GitHub Actions matrix example:

jobs:
  test:
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: taiki-e/install-action@cargo-nextest
      - run: cargo nextest run --profile ci --partition hash:${{ matrix.shard }}/4

Four jobs run in parallel, cutting total CI time by up to 75%.

Retry configuration for flaky tests

Configure retries globally or per-test in the config file:

[profile.ci]
# Retry all failing tests up to 2 times
retries = 2

For per-test overrides (some tests are known-flaky):

[[profile.ci.overrides]]
filter = 'test(~"integration")'
retries = 3
slow-timeout = { period = "120s", terminate-after = 2 }

[[profile.ci.overrides]]
filter = 'test(~"snapshot")'
retries = 0  # snapshot tests should never be flaky

Overrides stack from most specific to least specific. The first matching override wins.

Thread limits for resource-intensive tests

Some tests (database, network) can't run fully in parallel:

[[profile.default.overrides]]
# Limit database tests to 4 parallel executions
filter = 'test(~"db_")'
test-threads = 4

[[profile.default.overrides]]
# Run end-to-end tests serially
filter = 'test(~"e2e_")'
test-threads = 1

Timeout configuration

nextest distinguishes between slow (warn) and termination timeout:

[profile.ci]
# Warn after 60 seconds, kill after 3 warnings (180 seconds total)
slow-timeout = { period = "60s", terminate-after = 3 }

[[profile.ci.overrides]]
# Integration tests get more time
filter = 'test(~"integration")'
slow-timeout = { period = "120s", terminate-after = 2 }

When a test exceeds the period, nextest marks it as "slow" in output. When terminate-after is exceeded, it kills the test process.

JUnit XML output

CI systems (GitHub Actions, GitLab, CircleCI) can display test results from JUnit XML:

[profile.ci.junit]
path = "target/nextest/ci/junit.xml"
# Report each retry as a separate test case
report-skipped = true

GitHub Actions example using the XML for annotations:

- run: cargo nextest run --profile ci
  
- name: Upload test results
  uses: EnricoMi/publish-unit-test-result-action@v2
  if: always()
  with:
    files: target/nextest/ci/junit.xml

Listing tests without running them

Useful for debugging filter expressions:

# List all tests
cargo nextest list

<span class="hljs-comment"># List tests matching a filter
cargo nextest list -E <span class="hljs-string">'test(~"auth")'

<span class="hljs-comment"># List tests in JSON format (for tooling)
cargo nextest list --message-format json

Complete CI configuration example

A .config/nextest.toml for a typical Rust web service:

[profile.default]
test-threads = "num-cpus"
retries = 0
fail-fast = false
slow-timeout = { period = "30s", terminate-after = 2 }

[profile.ci]
test-threads = 8
retries = 2
fail-fast = false
slow-timeout = { period = "60s", terminate-after = 2 }

[profile.ci.junit]
path = "target/nextest/ci/junit.xml"
report-skipped = true

# Database tests run serially to avoid connection pool exhaustion
[[profile.ci.overrides]]
filter = 'test(~"db_") or test(~"_database") or test(~"migration")'
test-threads = 1
retries = 1

# Integration tests get more time
[[profile.ci.overrides]]
filter = 'kind(test) and not test(~"unit_")'
slow-timeout = { period = "120s", terminate-after = 3 }

Archive and reuse test binaries

nextest can archive compiled test binaries to run them later (e.g., after cross-compilation):

# Compile and archive
cargo nextest archive --archive-file tests.tar.zst

<span class="hljs-comment"># Run from archive (on the same or another machine)
cargo nextest run --archive-file tests.tar.zst

This is useful in Docker-based CI where you want to compile once and run tests in a different environment.

Installation

# Via cargo-binstall (fast)
cargo binstall cargo-nextest

<span class="hljs-comment"># Via cargo (slower compile)
cargo install cargo-nextest

<span class="hljs-comment"># In CI with taiki-e/install-action
- uses: taiki-e/install-action@cargo-nextest

Summary

cargo-nextest's configuration system covers the common friction points in Rust CI: flaky test retries, parallel shard distribution, per-test thread limits, and JUnit XML output. All of this is declared in .config/nextest.toml without touching test code. Start with the basic profile setup, add partitioning when your suite grows past a few minutes, and layer in per-test overrides as you encounter specific timing or resource constraints.

Read more