Skip to content
Getting Started

Mutation testing

Run cargo-mutants after kache init and know what the cache saves

Run kache init once, then run cargo mutants as you normally would. There is no Kache command to put in front of it: Cargo invokes Kache as its rustc-wrapper inside every build directory cargo-mutants creates.

kache init
cargo mutants

Why the copies share one cache

cargo-mutants copies the source tree to a temporary directory and builds there, one build directory per --jobs job. Kache maps each build's workspace root to the same <WORKSPACE> sentinel, and applies that rule before the temporary-directory rule, so the copy's path never enters a cache key. The root comes from rustc's own arguments: the parent of the target directory Cargo passes in --out-dir, accepted only when a Cargo.toml sits there and the compiler's working directory is beneath it.

Cargo's -C metadata and -C extra-filename hashes enter the key verbatim. For path packages inside the workspace, Cargo derives them from workspace-relative package identity, so they are the same in every copy and one copy can hit another's entries.

What hits

  • registry crates
  • workspace crates no mutant touched, and their proc-macros
  • test binaries and build-script binaries of those crates (Kache caches executables by default on Linux and macOS; see Configuration)

These hit in every build directory, including all of them under --jobs N, and again on later runs. When several build directories miss the same key at the same time, one compiles it and the others wait for its entry and restore it.

What rebuilds

The mutated crate rebuilds, together with every crate downstream of it and their test binaries. A dependent's key includes the content hash of each --extern artifact, so a changed rlib changes every key above it.

For the unit that misses on every mutant, Kache keeps rustc's incremental compilation instead of caching. Adaptive incremental mode notices a Cargo unit that is built and then misses in the same target with only its sources or dependencies changed, and gives it a private incremental directory while you keep building it. That covers workspace crates and path dependencies whether or not the command selects them, including the local crates downstream of the mutated one. Kache skips the cache for up to eight builds of such a unit. After the eighth, or after 15 minutes without a build of it, Kache checks the cache first, and a miss still compiles incrementally.

To skip the learning step for every crate Cargo builds incrementally, normally your workspace crates, set KACHE_PRESERVE_INCREMENTAL=1 or [cache] preserve_incremental = true. Those units then bypass the artifact cache and keep their own incremental directory, while dependencies still restore from Kache. Unchanged workspace crates compile once in every new build directory instead of restoring, so compare both modes on your project. KACHE_INCREMENTAL_CRATES limits the same treatment to the crates you list:

KACHE_INCREMENTAL_CRATES=my_crate cargo mutants

A mutant that fails to compile keeps the crate's incremental state, so the next mutant still compiles incrementally.

Incremental seeds between worktrees

With rustc 1.98 or later, Kache can start an edited crate from another checkout's finished incremental state. This is enabled by default for managed incremental builds. An unchanged checkout still uses the artifact cache. Registry dependencies and build scripts keep their existing compilation paths.

Kache looks for compatible donors among eight recently built target directories in the local cache index. Each receiver keeps its own compiler state and lock. The import clones files with copy-on-write where the filesystem supports it and uses independent copies otherwise. No imported file shares an inode with the donor, including ThinLTO bitcode that rustc can rewrite in place. A busy donor, incompatible flags, unsupported arguments or unfinished state leaves the receiver compiling without an imported seed. Each candidate import is limited to 1 GiB, 65,536 entries and 256 donor units.

For recognized rustc layouts, the import keeps the newest finalized session per crate, using the timestamp in its name. Tied timestamps and unfamiliar layouts retain their existing copying behavior.

The first managed incremental compile after upgrading starts fresh in each checkout. Kache uses a new state version so it cannot reuse or import older state whose files may already be shared or modified. The old state remains eligible for the usual unused-unit cleanup. Finish builds using the older Kache before upgrading; those versions can still share files with each other.

To disable automatic seeding for one build:

KACHE_SEED_INCREMENTAL=0 cargo build

Or set it in the config file:

[cache]
seed_incremental = false

This keeps target-local adaptive incremental compilation enabled. To choose a donor explicitly, set KACHE_INCREMENTAL_SEED_FROM or [cache] incremental_seed_from to its absolute, canonical Cargo profile path, such as /work/previous/target/debug.

Compiler behavior

Portable private compilations use -Zremap-cwd-prefix=/src on Unix and -Zremap-cwd-prefix=C:\src on Windows. Debug information records that virtual working directory; a debugger started elsewhere may need a source mapping. Relative source operands keep relative file!() values. Absolute source operands retain native path behavior and do not import a seed.

Kache probes compiler support before using the flag. On stable and beta it sets RUSTC_BOOTSTRAP=1 only in the selected rustc child and adds -Zallow-features= so source feature attributes remain forbidden. Cargo and build scripts keep the caller's environment. Existing RUSTC_BOOTSTRAP settings, including an empty value and -1, take precedence. Other unstable compiler flags can prevent Kache from adding bootstrap.

If rustc reports that source read RUSTC_BOOTSTRAP, Kache discards that attempt's outputs and diagnostics, retries with the caller's environment, and remembers not to inject bootstrap for that unit. Genuine compile errors remain errors. A proc macro using untracked environment reads can still observe the child override; disable seeding if that distinction matters to your macro.

An edit to the primary source can select a compatible private graph before cache-key discovery. A previously cached variant may compile during that private run. The existing limit of eight builds or 15 minutes of inactivity still returns the unit to a cache check. These private outputs are never stored or uploaded as ordinary artifact-cache entries.

Use the same relative package layout in both checkouts. Target paths outside the compiler's working directory can prevent compatibility. Older compilers retain target-local incremental behavior. KACHE_UNSTABLE=1 still enables experimental compiler probing independently of the automatic-seeding setting.

What to expect

  • The baseline build in each copy can reuse entries your ordinary cargo test runs stored, as long as the flags match.
  • With cap_lints enabled, cargo-mutants adds --cap-lints=warn to RUSTFLAGS. Cargo folds rustflags into extra-filename, so the first run after enabling it is cold relative to your normal builds. Later runs are warm.
  • Small projects gain little. The per-mutant cost is the mutated crate plus its tests, and nothing caches those. Kache saves the dependency and unchanged-crate builds that each build directory would otherwise repeat.
  • --copy-target decides whether the copies rebuild dependencies at all. With the default, each build directory starts from an empty target and Kache restores dependencies instead of compiling them. With --copy-target=true, the copy carries Cargo's fingerprints, and Cargo does not invoke the wrapper for units it still considers fresh. Kache only helps when a copy's target is empty or dirty.

Crates that read CARGO_MANIFEST_DIR at compile time

A crate that reads CARGO_MANIFEST_DIR during compilation, for example through env!, re-keys in every build directory. Kache keeps that value verbatim in the key because the compiled artifact can embed it. Such a crate compiles once per build directory; if its output embeds the path, its dependents follow.

Check a run

kache stats --full --since 1h

Each build directory is its own root. Add --root <build dir> to look at one copy. See kache stats --full.

Available for:
Apple macOS logomacOSMicrosoft Windows logoWindowsLinux logoLinux
Download Kunobi