Skip to content

Error-path mutants

Error-handling code is the least tested (Lima et al., “Assessing exception handling testing practices in open-source libraries”, 2021). The errpath operator, on by default, mutates it: fmt.Errorf("...: %w", err) stops wrapping (%w -> %v), and with a lone %w returns err itself; errors.Is / errors.As are forced to true and false; a panic(x) statement is removed unless the function needs it as its terminating statement; and recover() returns nil while it still stops the panic. A return result replaced by nil (return x, err -> return x, nil, a nil pointer, map, slice or func) and a forced nil check (if err != nil -> if true / if false) are the return and condition operators’ mutants, tagged with the same class.

Every mutant carries a class in mutants.json and report.json: errpath, concurrency or default. totals.classes scores each class on its own, next to the overall score, so “error paths: 40% killed” is visible:

"classes": {
"default": { "mutants": 120, "killed": 96, "survived": 14, "score": 0.87 },
"errpath": { "mutants": 25, "killed": 8, "survived": 12, "score": 0.4 }
}

totals.covered_score is the score over the covered mutants only, NO_COVERAGE left out (PIT’s test strength): how well the tests check the code they do reach.