Skip to content

How it works

Mutato injects mutants by rewriting source at compile time, then runs the tests once per mutant in a pool of warm, coverage-filtered hosts. There is one recompile total — never a recompile per mutant.

MSBuild’s Csc task honours CscToolPath / CscToolExe. Mutato points them at a bundled wrapper, csc-mutato, which receives the exact argv the real compiler would get, parses the response file with Roslyn’s own command-line parser, and builds a real CSharpCompilation (with a full semantic model) from it — no project-graph reconstruction.

Each mutation point becomes a runtime selection on MutantContext.ActiveId (Sel(id) == id ? mutant : original). Operators are chosen against the semantic model, so only type-valid mutants are emitted, each with an exact source span and replacement text recorded in the catalog. Every mutant is woven into one instrumented copy of the target assembly, and the active mutant is flipped by writing a single static field (~0 µs). Because the rewrite happens on source with a semantic model, the resulting IL is valid by construction.

The mutated compilation is verified in-process — under the real assembly name and strong-name key, so InternalsVisibleTo still holds — and any mutant that doesn’t compile is dropped before the real csc ever runs.

Loop and recursion guards are woven only into scopes that contain a mutation. Infinite loops and broken base cases throw catchable exceptions in-process instead of hanging the run.

The static surface the instrumented code calls into (MutantContext) is injected as source into the target, so the instrumented assembly is self-contained — no DLL to deploy, no deps.json patching. If several copies of the type coexist, all state lives in corlib-typed cells shared through a process-global rendezvous, so every copy aliases the same state.

Mutato runs one coverage pass to build a covering-test map, shards the covered mutants across N long-lived worker processes, then for each mutant flips the field and reruns only its covering tests. Timeouts adapt per mutant, and a worker that crashes is respawned. A cold subprocess handles static-constructor mutants that can’t be re-flipped in a warm host.

Survivors are rendered as exact text or HTML diffs, spliced from the rewriter’s own replacement text, and the run drives Stryker’s standard schema — so you can open the report in Stryker’s viewer or dashboard.

The approach was proven across three generations: a Harmony runtime-detour prototype, a Mono.Cecil static IL weave, and finally this csc-wrapper source rewrite — which replaced the IL weave entirely. Rewriting source with a semantic model makes the invalid-IL bug class structurally impossible, maps every mutant to an exact source span, and works for Release builds too.