Test Matrix
A comprehensive test matrix validating code across multiple platforms, Rust versions, and feature combinations.
Reference
Section titled “Reference”| Field | Value |
|---|---|
| Workflow | .github/workflows/ci-test-matrix.yml |
| Platforms | Linux, macOS, Windows |
| Rust versions | Stable, Beta, Nightly, MSRV (1.92) |
| Triggers | Pull requests (via pipeline.yml) |
Operating systems
Section titled “Operating systems”- ubuntu-latest — Linux (primary platform).
- macos-latest — macOS (Apple Silicon + Intel).
- windows-latest — Windows (x64).
Rust toolchains
Section titled “Rust toolchains”- stable — latest stable Rust.
- beta — beta channel (upcoming stable).
- nightly — nightly builds (experimental features).
- 1.92 — MSRV (Minimum Supported Rust Version).
Feature combinations
Section titled “Feature combinations”--all-features— all features enabled.--no-default-features— minimal build.- Default — standard feature set.
Total jobs: ~12-15 (optimized to skip redundant combinations).
What gets tested
Section titled “What gets tested”| Scope | Checks |
|---|---|
| All combinations | cargo build, cargo test, cargo test --doc |
| Stable + Ubuntu only | cargo fmt --check, cargo clippy -- -D warnings, cargo doc --no-deps |
| Separate jobs | Integration tests (cargo test --test '*'), Miri (nightly) |
Result interpretation
Section titled “Result interpretation”| Result | Meaning | Action |
|---|---|---|
| Success ✅ | All platforms and versions pass | Code is portable and compatible |
| Partial failure ⚠️ | One OS/version fails | Fix the platform-specific issue (path, filesystem, or API difference) |
| MSRV failure ❌ | MSRV job fails, stable passes | Code uses features newer than MSRV — bump MSRV, remove the feature, or gate it |
Example partial failure:
ubuntu-latest / stable: ✅macos-latest / stable: ❌windows-latest / stable: ✅Example MSRV failure:
ubuntu-latest / 1.92: ❌ubuntu-latest / stable: ✅Miri coverage
Section titled “Miri coverage”Miri (nightly only, on a separate job) detects use-after-free, out-of-bounds memory access, data races in unsafe code, invalid pointer arithmetic, and uninitialized memory reads. It is 100-1000x slower than normal tests and does not support file/network I/O.
How-to
Section titled “How-to”Check MSRV locally
Section titled “Check MSRV locally”Current MSRV: 1.92.
# Install the MSRV toolchainrustup install 1.92
# Test with MSRVcargo +1.92 checkcargo +1.92 testVerify: both commands succeed under the pinned toolchain.
Test feature combinations locally
Section titled “Test feature combinations locally”# Individual featurescargo test --no-default-features --features feature1cargo test --no-default-features --features feature2cargo test --features feature1,feature2
# Maximum and minimal buildscargo test --all-featurescargo test --no-default-features--all-features ensures no feature conflicts; --no-default-features ensures optional features don’t leak into the default set. Verify: every combination compiles and passes.
Run integration tests
Section titled “Run integration tests”cargo test --test '*' --verboseIntegration tests live in tests/*.rs and exercise the public API, not internal units. Verify: all integration tests pass.
Run Miri locally
Section titled “Run Miri locally”# Install the miri componentrustup +nightly component add miri
# Run miri testscargo +nightly miri test
# Run a specific testcargo +nightly miri test test_nameSkip a test that does I/O (unsupported under Miri):
#[test]#[cfg_attr(miri, ignore)] // Skip in mirifn test_file_io() { // File I/O test}Verify: cargo +nightly miri test completes with no UB reports.
Test multiple platforms locally
Section titled “Test multiple platforms locally”# Cross-compilation with crosscargo install crosscross test --target x86_64-pc-windows-gnucross test --target x86_64-apple-darwin
# Linux containerdocker run --rm -v $(pwd):/app -w /app rust:latest cargo testVerify: tests pass for each target you exercise.
Write portable code
Section titled “Write portable code”Avoid the common cross-platform pitfalls:
// Path separators — use PathBuf, not string formattinguse std::path::PathBuf;let path = PathBuf::from(dir).join("file.txt");
// Line endings — compare structurally, not byte-for-byteassert_eq!(content.lines().count(), 2);
// Filesystem case sensitivity — match exact caselet file = File::open("config.toml")?;
// Process signals — use a cross-platform crate instead of `kill`use subprocess::Popen;Verify: the test matrix passes on all three operating systems.
Resolve MSRV failures
Section titled “Resolve MSRV failures”Common causes and fixes:
# Edition vs MSRV conflict[package]edition = "2024" # Requires a recent toolchainrust-version = "1.92" # Keep these consistent// Newer std APIs may exceed MSRV — use an older API or bump MSRVlet mut buf = Vec::new();reader.read_buf(&mut buf)?;# A dependency may require a newer Rust — pin a compatible version or bump MSRV[dependencies]serde = "1.0"Diagnostic steps:
- Check dependencies:
cargo tree --edges no-devfor version conflicts. - Check APIs: search for recently stabilized features.
- Update or lower MSRV based on the requirement.
Verify: cargo +1.92 check succeeds.
Optimize CI time
Section titled “Optimize CI time”Skip redundant combinations:
matrix: exclude: - os: macos-latest rust: beta - os: windows-latest rust: betaCache dependencies:
- uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-${{ matrix.rust }}-cargo-${{ hashFiles('**/Cargo.lock') }}See all failures rather than stopping at the first:
strategy: fail-fast: falseVerify: total matrix runtime drops while still covering each platform.
Troubleshooting
Section titled “Troubleshooting”Tests fail only on Windows — usually path separators (/ vs \), case-insensitive filesystem, a different temp directory, or CRLF vs LF line endings:
#[cfg(windows)]#[test]fn test_windows_specific() { println!("Temp dir: {:?}", std::env::temp_dir());}Tests fail only on macOS — usually the case-insensitive but case-preserving filesystem, different system APIs, or Apple-specific security restrictions.
MSRV test fails — check dependency versions, check for recently stabilized APIs, then update or lower MSRV.
Best practices
Section titled “Best practices”- Test locally first — use
crossor Docker before pushing. - Fix MSRV issues early — don’t let them accumulate.
- Use
PathBuf— always for cross-platform file paths. - Gate platform-specific tests —
#[cfg(target_os = "linux")]. - Monitor CI time — optimize the matrix if jobs run long.
Why this matters
Section titled “Why this matters”“It works on my machine” is the failure mode this matrix exists to prevent. Rust compiles per target, and behavior diverges on path separators, filesystem case sensitivity, line endings, and platform APIs — bugs that only surface on an OS the author doesn’t run. Pinning the MSRV job alongside stable catches the silent dependency on a too-new language feature before downstream users on the supported floor hit it, and the beta/nightly lanes give early warning of upcoming compiler changes while Miri probes for undefined behavior that ordinary tests pass right over.