Concepts
muhar's vocabulary is small. Each term below is used with exactly this meaning everywhere on this site and in muhar's output.
Guard
A guard is a piece of behaviour that must hold: a check that refuses a bad input, a limit, an authorisation test, an invariant in a config file. Guards are what you care about; mutations are how muhar asks whether a test protects them.
Mutation
A mutation is one exact text edit that removes or weakens a guard: in file file, replace the first occurrence of old with new. Each mutation has a name, which is how it appears in the report — so name it after the bug it simulates ("zero is accepted as an amount"), not after the edit.
muhar does not generate mutations. You write them. That is the trade: fewer mutations than a generator would produce, each one chosen, named and meaningful, with no equivalent-mutant noise to triage. See Writing good mutants.
Suite
The suite is the spec's test command, run with sh -c in the spec's root (or the current directory). A non-zero exit status means the suite failed, which, with a mutation in place, means the mutation was caught. muhar does not parse test output; the exit status is the whole signal.
Baseline
Before anything is mutated, muhar runs the suite once on pristine source. It must pass. If it fails, any later "caught" could be the pre-existing failure rather than the mutation, so the run is discarded.
Positive control
The control is a mutation the suite must catch: a fault so central that a working harness and a working suite cannot miss it. muhar plants it after the baseline. If it cannot be planted, or the suite stays green with it in place, then either muhar is not mutating what the suite builds or the suite cannot see the source. Either way, no verdict from the run can be trusted, and the run is discarded.
A harness with no failing control is a green light with no bulb: it looks the same whether it works or not. The control is the bulb test, run on every invocation.
The proven-landed guarantee
A mutation counts as applied only when muhar has proven it on disk. Before writing, muhar refuses an edit that cannot change the file: old is empty, old equals new, or old is absent. After writing, it reads the file back and requires the bytes on disk to equal the intended mutated content and to differ from the original. Only then does it run the suite. Every failure along the way becomes COULD_NOT_MUTATE, with the reason, and the suite is never run against that mutation.
The point is that NOT_CAUGHT always means "the fault was in place and the suite still passed". It never means "the edit silently did nothing".
Verdicts
Each mutation gets exactly one of three verdicts. The third is a separate state on purpose: a fault that was never planted has not been tested, and folding it into either of the other two would report something that did not happen.
| Verdict | Condition | Called in the source |
|---|---|---|
CAUGHT | Mutation proven in place; suite exited non-zero. | checked-failed |
NOT_CAUGHT | Mutation proven in place; suite exited zero. A real finding. | checked-clean |
COULD_NOT_MUTATE | Mutation could not be planted or proven; the suite did not run. | could-not-check |
A mutation that does not compile makes go test fail, so it is reported CAUGHT today. That is a known limitation, and the roadmap has the fix.
Harness healthy, harness broken
A run is healthy when the baseline passed and the control was caught. Only a healthy run prints per-guard verdicts and a Totals: line, and exits 0. A broken run prints HARNESS BROKEN — run discarded. and one reason, and exits 2.
A healthy run is not a clean result. Exit 0 says the instrument worked; the verdicts say what it found. A gate needs both: exit status 0, and a Totals: line with 0 NOT CAUGHT, 0 could-not-mutate. See Output and exit codes.
Why exit 2 is never clean
Exit 2 means muhar could not establish that it was measuring anything. Reading it as a pass — "no guards were reported missing" — repeats the failure muhar was built to prevent: a harness that silently tests nothing and reports confidently. A CI step must fail on exit 2. Because Go's flag parser also exits 2 on an unknown flag or a malformed flag value, the safe rule is to fail on any non-zero exit.
Run order
- BaselineRun the suite on pristine source.Red: HARNESS BROKEN, exit 2
- Positive controlPlant, prove on disk, run the suite, restore.Not planted or not caught: HARNESS BROKEN, exit 2
- Each mutation, in spec orderPlant, prove on disk, run the suite, restore.Not planted or not proven: COULD_NOT_MUTATE, suite not run, continue
- ReportOne verdict line per mutation, then
Totals:. Exit 0.
The details of each step, including what happens with -j above 1, are in How a run works.