chore(mutants): keep the mutation scratch dir out of the OS temp sweep - #500
Merged
Conversation
cargo-mutants copies the workspace into $TMPDIR and mutates the copy, and the OS temp cleaner deletes that copy by age — three days on macOS — so a pass that runs for days loses source files out of its own copy and dies partway through. Point the scratch at target/mutants-scratch on every task that invokes cargo-mutants, and register the shipped pass as `cargo make mutants` so the redirect is not something to remember. That task forwards extra flags, keeping it a drop-in for the bare command; the scoped passes do not, since a repeated --file/--re widens their fixed scope instead of narrowing it. Under target/ the scratch needs copy_target pinned false — otherwise each build directory would copy the scratch into itself — and `cargo clean` now reaches it, reclaiming a killed run's leftovers and destroying a live pass's copies alike. Also gitignore mutants.out, which was excluded only locally.
There was a problem hiding this comment.
Pull request overview
This PR makes long-running cargo mutants passes resilient to OS temp-directory cleanup by redirecting cargo-mutants’ scratch workspace from $TMPDIR to a stable, repository-local location under target/mutants-scratch via cargo make tasks.
Changes:
- Introduces a workspace-wide
MUTANTS_SCRATCHenv var and a privatemutants-scratchprerequisite task, then runs all mutation passes withTMPDIRset to that directory. - Adds a new
mutantscargo-make task for the shipped/default mutation pass and updates the existingmutants-arpack/mutants-hptttasks to use the same scratch redirect. - Pins
copy_target = falsein.cargo/mutants.tomland ignores root-levelmutants.outartifacts; updates CONTRIBUTING docs accordingly.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| Makefile.toml | Defines MUTANTS_SCRATCH, adds mutants-scratch + mutants tasks, and ensures all mutation tasks export TMPDIR and create the scratch directory first. |
| CONTRIBUTING.md | Switches guidance from bare cargo mutants to cargo make mutants and documents why (OS temp sweeping) and where scratch is stored. |
| .gitignore | Ignores mutants.out / mutants.out.old written at repo root by the shipped pass. |
| .cargo/mutants.toml | Pins copy_target = false to prevent recursive copying when scratch lives under target/. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cargo mutantscopies the workspace into$TMPDIRand mutates the copy, and the OS temp cleaner deletes files there by age. On macOS 26.5.2/System/Library/LaunchDaemons/com.apple.bsd.dirhelper.plistschedules that sweep daily and setsCLEAN_FILES_OLDER_THAN_DAYSto 3. The full pass is 3345 mutants ate08cf9b, each needing its own build and test cycle: a run here reached 31% after 3.3 days, then lost source files out of its own copy and died withdoes not exist, refusing to create itnaming a file that is present in the working tree.cargo make mutantsnow points the scratch attarget/mutants-scratch, which no cleaner touches, and the two pre-existing scoped tasksmutants-arpackandmutants-hpttdo the same. Everything below describes cargo-mutants 27.1.0.Changes
Makefile.toml—MUTANTS_SCRATCHnames the scratch directory, a privatemutants-scratchtask creates it, and all three passes export it asTMPDIR. Without the directory cargo-mutants exits withError: create temp dir/Caused by: No such file or directory (os error 2), so creating it is a task dependency rather than an assumption.Makefile.toml— a newmutantstask runs the full pass, forwarding extra flags through${@}so it stays a drop-in for a barecargo mutants.mutants-arpackandmutants-hpttdeliberately do not forward: cargo-mutants unions a repeated--file/--rerather than replacing it, so forwarding into their fixed scope would widen it instead of narrowing it.Makefile.toml— thecleantask's description records that cleaning also deletes a running pass's scratch copies..cargo/mutants.toml—copy_targetis pinned tofalse. With the scratch undertarget/, copyingtarget/into each build directory would copy the scratch into itself..gitignore—mutants.outandmutants.out.old, which the full pass writes at the repo root and whichmainneither tracks nor ignores.Impact
Only the mutation tasks change.
MUTANTS_SCRATCHis defined workspace-wide but nothing outside those tasks reads it, andTMPDIRis set per task rather than globally, sobuild/test/clippyare unaffected. The repository has no CI workflow, and no script underscripts/invokes the mutation passes.Test plan
Against a fresh
git worktreecarrying this change, with notarget/and nomutants.out:cargo make mutantsruns itsmutants-scratchdependency and lands its build copy undertarget/mutants-scratch;cargo make --print-stepsshows all three passes carrying that dependency andTMPDIR;cargo make mutants --listlists those 3345 and exits without starting a pass;copy_targetleaves that list unchanged and still excludes the top-levelalgorithms-fixtures/andcrates/ariadnetor-tensor/src/test_fixtures/, so the existingexclude_globsstill apply;git check-ignore -vmatches both new.gitignoreentries.The union behaviour behind the forwarding asymmetry is reproducible at
e08cf9b:--file crates/ariadnetor-algorithms/src/krylov/arpack.rslists 34 mutants,--file crates/ariadnetor-algorithms/src/dmrg/sweep.rslists 61, and passing both lists 95.The recursion the
copy_targetpin prevents was reproduced in a scratch crate outside this repository, withTMPDIRpointed at a directory inside it andcopy_target = true: each copy nests one level deeper until the path is too long to create (ENAMETOOLONG). A--copy-target trueon the command line still overrides the pinned value, so the pin guards against a change of default rather than a deliberate override.Notes
cargo cleannow reaches the scratch. That reclaims what a killed run leaves behind and destroys a live pass's copies just as readily, which is what thecleantask description warns about. Neither is in play as the repository stands —.cargo/config.tomlsets notarget-dir— but settingCARGO_TARGET_DIRorbuild.target-dirbreaks both halves of that: the scratch stays at the literal repotarget/whilecargo cleanoperates on the relocated directory, so it neither reclaims the leftovers nor endangers a live pass.