# cargo-affected
Like pytest-testmon for Rust. Maps each test to the source-line ranges it
touches via LLVM coverage, then reruns only the tests whose ranges overlap
`git diff` hunks.
> **Status: extremely early.** Mac & linux only at this stage. The author is
> starting to use this in their own repos; others probably shouldn't yet.
> The schema can change without migration, behavior may break, and there is
> no support promise. CI should still run the full test suite.
## Installation
Not on crates.io yet. Install from source:
```sh
git clone https://github.com/max-sixty/cargo-affected
cd cargo-affected
cargo install --path .
rustup component add llvm-tools
```
## Quick start
```sh
# Build with coverage instrumentation and record what each test touches.
cargo affected collect
# After editing:
cargo affected run # run only tests overlapping the diff
cargo affected status # dry-run: show what would run
cargo affected clean # wipe the coverage cache
```
`run` diffs the working tree against the git sha that was HEAD when
`collect` last ran. Recollect periodically — every committed change since
the last collect adds to the diff and broadens selection.
## How it works
`collect`:
1. `cargo nextest list` enumerates every test.
2. `cargo nextest run` runs them with `-C instrument-coverage` and a
per-test `LLVM_PROFILE_FILE`.
3. For each test, `llvm-profdata` merges its profraw and `llvm-cov export`
lists every hit function with its source-line regions.
4. Per `(test, file, function)`, the min/max line span is stored in
`target/affected/coverage.db` (SQLite), keyed by a fingerprint of
`Cargo.lock`, all workspace `Cargo.toml`s, `rustc -vV`, `RUSTFLAGS`, and
`CARGO_BUILD_TARGET`. The git HEAD sha is recorded alongside.
`run`:
1. `git diff -U0 <collect_sha>` produces OLD-side line ranges for changed
files — same coordinate system as storage.
2. For each changed file, the DB returns stored function ranges overlapping
any hunk; the union of matching tests is run via `cargo nextest run`.
3. If a hunk overlaps no stored range (struct fields, `#[derive]`, `const`,
`use`, `mod`), a per-file backstop selects every test that touched the
file. Crate roots (`lib.rs` / `main.rs` / `tests/*.rs`) are stored with a
sentinel range covering the whole file, so any edit there reselects every
test that covered the crate root.
## Accuracy model
`cargo affected run` is an approximation — it trades correctness for speed.
CI should still run the full suite.
### False positives (tests selected that didn't need to run)
- **Function-level granularity.** A hit function's full line span is treated
as one range, so an edit anywhere inside it reruns every test that touched
the function — even if the edited line is unreachable from those tests.
- **Structural-edit backstop.** Hunks outside any LLVM region (struct
fields, derives, consts, `use`, `mod`) reselect every test that touched
the file.
- **Crate roots.** Any edit to `lib.rs` / `main.rs` / `tests/*.rs` reruns
every test that covered that crate root.
- **Comment- and whitespace-only edits.** Selection diffs lines, not
semantics.
### False negatives (tests skipped that should have run)
- **Non-Rust sources.** `include_str!` / `include_bytes!` targets, SQL
files, migrations, assets, and templates aren't seen by llvm-cov.
- **Build-time inputs not in the fingerprint.** The fingerprint covers
`Cargo.lock`, workspace `Cargo.toml`s, `rustc -vV`, `RUSTFLAGS`, and
`CARGO_BUILD_TARGET`. Changes to `build.rs`, `rust-toolchain.toml`, or
`.cargo/config.toml` don't currently invalidate the cache.
- **Proc-macro crate source.** A proc-macro's own source files compile into
a host dylib, not the test binary, so editing a proc-macro crate won't
reselect its downstream tests.
- **External state.** Tests that read env vars, filesystem state, or the
network can change outcome without any source file changing.
When in doubt, `cargo affected collect` to refresh coverage, or skip
cargo-affected and run the full suite.
## License
Dual-licensed under MIT or Apache-2.0 at your option.