Mutations

A mutation is a tiny change in your code. Gooze applies one mutation and runs tests to see if tests notice.

Statuses

Each mutation ends in a status. Simple meaning:

  • Killed: tests failed for the mutation (usually good)
  • Survived: tests still passed (usually means weak/missing assertion)
  • Skipped: Gooze did not run it (example: no matching test)
  • Not covered: the mutated line is not covered by the coverage profile passed with --coverage-profile, so Gooze skipped its tests. It counts as a survivor (it lowers the score) but is tallied separately.
  • Error: Gooze had an unexpected error

About timeouts: different Gooze versions can show timeouts differently. If you see many suspicious kills, check logs and try --verbose.

Mutation types

These six types run by default. Each one makes a single, mechanical change to one spot in your code, then your tests run against it. If the tests still pass, the mutation survived — a gap in coverage. Each example below is shown as a diff: the - line is your code, the + line is what Gooze runs instead.

Quick reference:

arithmetic   + - * / %         each -> every other operator
comparison   < > <= >= == !=   each -> every other operator
logical       && ||            && <-> ||
boolean       true false       true <-> false
numbers       5, 2.5, ...      literal -> 0 and 1
unary         - + ! ^          - <-> + ;  ! and ^ removed

arithmetic + - * / %

Swaps a binary math operator. Catches off-by-operator bugs and results that are computed but never asserted.

Rule: each operator becomes every other arithmetic operator (- shown; also *, /, %)

- sum := a + b
+ sum := a - b

If it survives: no test checks the numeric result of this expression.

comparison < > <= >= == !=

Swaps a relational operator. Great at exposing untested boundaries (off-by-one) and branches that are never taken in tests.

Rule: each operator becomes every other comparison operator (>= shown; also >, <=, ==, !=)

- if a < b {
+ if a >= b {

If it survives: a boundary or the two branches of this condition aren't both exercised.

logical && ||

Flips AND/OR. Reveals conditions where one operand never actually decides the outcome in your tests.

Rule: && becomes ||, and || becomes &&

- if a && b {
+ if a || b {

If it survives: one side of the condition is never the deciding factor in any test.

boolean true false

Inverts a boolean literal. Surfaces feature flags and defaults whose effect is never asserted.

Rule: true becomes false, and false becomes true

- enabled := true
+ enabled := false

If it survives: the value of this flag doesn't change any tested behavior.

numbers int & float literals

Replaces a numeric constant with 0 and 1. Finds magic numbers (limits, sizes, rates) that no test pins down.

Rule: a numeric literal becomes 0 and 1 (0 shown)

- limit := 5
+ limit := 0

If it survives: the exact value of this constant doesn't matter to any test.

unary - + ! ^

Flips a sign or drops a negation. Catches missing checks on negation, sign, and bitwise complement.

Rule: -x becomes +x (and vice versa); ! and ^ are removed

- ok := !flag
+ ok := flag

If it survives: the negation/sign here isn't checked by a test.

The engine also contains branch (replace a condition with true/false), statement (delete a statement), and loop (loop boundaries and body removal) mutators; these are not part of the default set.

Skip a line or block with the bypass annotation.

What to do when you see a survivor

A survivor is a hint. It usually means: your tests do not check this behavior.

  1. Open the diff in gooze report view.
  2. Ask: what behavior changed?
  3. Add a test that would fail if this behavior changes.
  4. Run Gooze again and see if it is killed now.
gooze report view