Configuration and skipping
Some mutants are never worth a test. mutastic skips some on its own, and a project can say which others to skip, or bring back, always with a reason.
What mutastic skips
Section titled “What mutastic skips”mutastic skips these on its own, and gives the reason with each. Skipped mutants don’t count towards the score.
- Equivalent changes, which can’t be told apart from the original:
return 0becoming “return the default”. - Code the compiler wrote: a
switchon aString, a record’sequals(). - Calls that only write output: logging,
System.out. - Trivial accessors: a getter that only returns one of its class’s fields, or a setter that only stores its argument in one, named for that field. An accessor with any logic (a copy, a check, lazy set-up, a notification) is mutated like any code.
toString()andhashCode(), with the lambdas they define and the helpers only they call.toString()is a debug representation, andhashCode()may return any value consistent withequals().
@DoMutate on a method or class brings back what mutastic would skip there as trivial: its
accessors, or a toString() or hashCode() that’s a contract.
Annotations
Section titled “Annotations”@DoNotMutateor@CoverageIgnoreskips a method or class, with itsvalue()orreason()as the reason.@DoMutatebrings back trivial code, as above.@Generatedmarks code a tool wrote.
Annotations are matched by simple name, so your own work. Or depend on
io.mutastic:mutastic-annotations, a small jar with @DoNotMutate and @DoMutate and nothing
else. mutastic reads them from the class files, so the jar is never needed at run time.
mutastic.yaml
Section titled “mutastic.yaml”A project’s own say goes in mutastic.yaml, in its directory:
rules: - skip: com.acme.generated.** # ** any depth, * one name, Class#method one method reason: protobuf output - mutate: com.acme.Money#toString # brings back what mutastic would skip as arid reason: shown to customersplugins: - build/libs/acme-filters.jar # a jar: filters and operators of your own - mutiny.yaml # a rules file: call operators, as datafilters: - acme:no-metrics # a plugin's filter runs only when named hereoperators: - defaults # which operators run, as --operators chooses - -stream/swap-matchplugins: lists jars and rules files, relative to the project: see Plugins.
operators: chooses which operators run, the same way as --operators,
which replaces it for one run; see Choosing operators.
An annotation on the code comes first, then a rule, then mutastic’s own filters. A mutate: rule, like @DoMutate, brings back only what mutastic would call arid, never generated or equivalent code. Every rule needs a reason, which is shown with each mutant it skips.
Ignoring one mutant
Section titled “Ignoring one mutant”For one mutant no test should catch, mutastic ignore ID [dir] [:module] --reason="…" records the decision in .mutastic/ignores.yaml, with where the mutant is, what it changes, the line it’s on and the reason. Commit it: each entry is a decision someone reviews. An ignore holds until its line changes; then the mutant runs again, and the run says the ignore no longer holds (mutastic ignore --prune removes those). Each says where it wrote and how to undo it: mutastic ignore --remove ID takes one back. Every missed mutant’s card offers the command (or ignore), and an ignore never hides code the compiler wrote or a change that can’t be told apart from the original.
A coding agent proposes an ignore instead of making one: mutastic ignore ID --propose --as=unreachable --reason="…" records it in .mutastic/proposals.yaml, and until a person accepts it the mutant still counts as missed. mutastic ignore --accept ID makes it an ignore, with its reason and sort as proposed, and --remove withdraws it. Commit the proposals too: reviewing a pull request is where a person decides them. The agent format offers or propose where a person’s card offers or ignore, since whether a case can happen is a person’s decision.
--as says what sort of decision it is: equivalent (no test could tell the change apart), unreachable (its case can’t happen in practice), telemetry (it only feeds logs or metrics), deprecated (the code is on its way out) or accepted-risk. The summary counts ignores by sort (3 ignored as unreachable), so what they hide can be reviewed by sort. Three or more ignores in one method are one decision said again and again: the run suggests the skip: rule that says it once.
To see what the arid skips hide, MUTASTIC_ARID=audit runs them anyway and adds an Arid audit row: how many ran, how many were caught and how many of those by a test’s own assertion (a check on what the code returns, which may make it a contract worth @DoMutate), and where.