feat(forge): mutation testing#13091
Merged
Merged
Conversation
* fix(mutation): key cached results by execution inputs * fix(mutation): include execution inputs in cache key * fix: clean-up
* fix(mutation): key cached results by execution inputs * fix(mutation): include execution inputs in cache key * fix(mutation): honor test filters/isolation, tighten cache key, drop compound assignment mutation, reject incompatible flags - runner: thread FilterArgs and --isolate through MutationRunConfig into compile_and_test_inner; rebuild ProjectPathsAwareFilter against the per-mutant temp config so --match-test/--match-contract/--match-path work and mutants exercise the same test set/execution model as baseline - mod: add runtime_context_digest folded into the per-file cache key so cached results aren't reused when filters, isolation, fork URL/block, sender, or initial_balance change - orchestrator: compute runtime_context_digest once per run and bind it to every MutationHandler - binary_op_mutator: stop mutating compound assignments (a += b would silently be rewritten to a - b dropping the assignment); add regression tests - cmd/test: bail when --mutate is combined with --list/--debug/ --flamegraph/--flamechart/--junit instead of silently mixing modes - fs_permissions guard now narrowed to permissions whose path can reach symlinked dependency trees (lib/node_modules/dependencies) Amp-Thread-ID: https://ampcode.com/threads/T-019e4567-e7ca-717e-bcc0-bb67a3667c4d Co-authored-by: Amp <amp@ampcode.com> * fix(mutation): address fresh oracle review blockers and mediums Blockers - filter parity: pass materialized baseline filter (filter.args().clone()) into MutationRunConfig so positional 'forge test <path>' and --rerun reach mutant runs too; raw self.filter dropped them silently - bail when baseline matched zero tests; without it every compileable mutant was reported as 'Alive' - negative literal mutation: add OwnedLiteral::NegatedNumber(U256) formatted as '-{val}'; the old Number(-*val) wrapped via two's complement and produced wrong-source mutants - mutator test harness: wrap snippets in valid Solidity, panic on parse failure, and check emitted replacement text. Fixed 6 stale test expectations and 1 unmatched delegate-pattern case the old vacuous harness was hiding Mediums - survived-span resume: load retrieve_survived_spans BEFORE mutant generation/cached load and filter cached mutants through should_skip_span so cross-run adaptive skipping actually works - runtime context digest: hash the full serialized EvmOpts (networks, env, gas-limit toggles, fork, sender, balance, ...) instead of a hand-picked 4-field subset that missed several mutation-affecting knobs - deterministic output: BTreeMap for JSON survived_mutants with sorted entries; reporter sorts survived list by (path, line, col, span, mutation); persisted results_vec sorted by span - --mutate-contract: intersect with explicit --mutate <paths> instead of silently overriding them; explicit paths are now respected - workspace: copy 'script/' when present and distinct from src/test so projects that import script-side helpers don't see false Invalid mutants Amp-Thread-ID: https://ampcode.com/threads/T-019e4567-e7ca-717e-bcc0-bb67a3667c4d Co-authored-by: Amp <amp@ampcode.com> * fix: `BTreeMap` import * fix mutation compound assignment handling * test(mutation): compare full mutator output sets * fix: clean-up * fix mutation testing review feedback * test: update require mutator snapshot * fix: mutation cache filtering and resumed skips * fix(mutation): preserve survivors when resuming * fix: test output * fix(forge): harden mutation testing cache and isolation Reject incompatible coverage/showmap mutation runs, make dependency fs_permissions checks path-aware through symlinked ancestors, validate result caches against the current mutant set, and restore live same-span adaptive skipping while keeping resume semantics strict. * fix: mutation compile filters --------- Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com> Co-authored-by: Amp <amp@ampcode.com>
# Conflicts: # crates/forge/src/cmd/test/mod.rs
fix(forge): reject mutation under machine mode
fix(forge): reject empty mutation selections
* fix(forge): preserve mutation workspace config * fix: make clippy happy
fix(forge): use source spans for unary targets
* fix(forge): surface mutator generation errors * fix: surface mutator generation errors with best-effort model
* fix(forge): bound timed-out mutation cleanup * fix: `Default` impl for `SharedMutationState`
* fix(forge): filter no-op mutation outputs * fix: bless * fix require condition inversion
* fix(forge): copy mutation compiler paths * fix: copy mutation compiler paths
* fix(mutation): handle cancellation without progress UI * fix: according review * refactor mutation state constructor
mablr
reviewed
Jun 2, 2026
mablr
left a comment
Member
There was a problem hiding this comment.
Given all previous fixes and thorough local testing, I think it's ready for a final review pass.
Let's consider it "experimental" like symbolic testing.
grandizzy
previously approved these changes
Jun 3, 2026
grandizzy
left a comment
Collaborator
There was a problem hiding this comment.
re reviewed and gtg for the MVP 🚀
Collaborator
|
@mablr could oyu pls fix merge conflicts? ty! |
grandizzy
approved these changes
Jun 3, 2026
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
Implements parallel mutation testing for Foundry, building on PR #11996 from @emo-eth.
Key Features
--mutation-jobsNote: the number of skipped/invalid mutants may vary with worker count since more parallelism means more mutants start testing before any complete and mark spans as survived.
Safety Improvements
../components)catch_unwindprevents single panic from aborting entire runPerformance
~2.5x speedup observed with 4 workers on 150 mutants (Vault.sol test contract):
Usage
Changes
crates/forge/src/mutation/runner.rs(526 lines) - parallel runnercrates/forge/src/cmd/test/mod.rs- CLI integrationCloses #478
Closes OSS-1
Built on the excellent foundation from #11996
Regular run

Run with progress

Final report:
