Writing Custom Mutators¶
go-mutesting exposes a public registration API so you can add mutation operators without forking the binary.
The Mutator signature¶
pkg— the package being mutated (may be nil for packages without type information)info— type-checking results fromgo/types(may be nil)node— an AST node; your mutator should type-assert and return nil if the node isn't relevant- Return a
[]Mutationwhere each entry has aChangefunc (apply the mutation) and aResetfunc (undo it)
The Mutation type¶
Both functions close over the AST node and modify it in place. The framework calls Change, prints the mutated file, runs the tests, then calls Reset.
Registration¶
Call mutator.Register from an init() function:
package mypkg
import (
"go/ast"
"go/token"
"go/types"
"github.com/jonbaldie/go-mutesting/v2/mutator"
)
func init() {
mutator.Register("mypkg/flip-sign", flipSign)
}
func flipSign(_ *types.Package, _ *types.Info, node ast.Node) []mutator.Mutation {
n, ok := node.(*ast.UnaryExpr)
if !ok || n.Op != token.SUB {
return nil
}
return []mutator.Mutation{
{Change: func() { n.Op = token.ADD }, Reset: func() { n.Op = token.SUB }},
}
}
Wiring into the binary¶
Because Go's init() functions only run for imported packages, you need a fork of cmd/go-mutesting/main.go that blank-imports your package:
import (
_ "github.com/yourorg/mypkg" // registers mypkg/flip-sign
_ "github.com/jonbaldie/go-mutesting/v2/mutator/arithmetic"
// ... other built-in mutators
)
Build your fork with go build ./cmd/go-mutesting and use it in place of the upstream binary.
Guidelines¶
- Return
nilquickly if the node type isn't one your mutator handles — the mutator is called for every node in every file. - Never mutate zero values to the same zero value (e.g. negating
0.0is a no-op; skip it). - Use
info.TypeOf(expr)when you need type information — it returns nil if the expression has no type. - The name passed to
Registermust be unique; duplicate registration panics at startup.