Documentation
¶
Overview ¶
Package mutator defines the contract every mutation operator implements and the name-keyed plugin registry the mutation engine discovers operators through.
Interface shape ¶
The original go-turango used a channel-based generator: a mutator returned a channel of "revert" funcs and the engine advanced it one mutation at a time. That shape leaks a goroutine whenever the engine abandons a walk early (timeout, SIGINT, a NotViable mutant short-circuit), and it makes a mutator impossible to unit-test without draining the channel. This package keeps the same conceptual model — a mutator yields a sequence of individually applicable, individually reversible edits to a live AST — but materialises it as a slice:
Applies(node) bool // cheap type/shape check, no allocation Mutate(node) []Mutation // the edits this mutator would make to node
Each Mutation carries an Apply/Revert pair that closes over the node it edits. The engine's contract is strictly nested: apply one mutation, print and test the file, revert it, then move to the next. Mutations are not required to compose with each other, and a Revert must restore the exact prior value (not merely a semantically equivalent one) so the AST can be reused for the whole walk instead of being reparsed per mutant.
Mutators are registered from package init functions, so an operator package is enabled purely by being imported.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Register ¶
Register makes a mutator constructor available under name.
It is intended to be called from an operator package's init function. Register panics if name is empty, if constructor is nil, or if name is already registered — all three are programmer errors detectable at process start, following the precedent of database/sql.Register and http.Handle.
Types ¶
type Mutation ¶
type Mutation struct {
// Description is a short human-readable summary of the edit, used in
// reports to show a user what was changed, e.g. "== -> !=" or
// "remove if body".
Description string
// Apply performs the mutation on the AST.
Apply func()
// Revert undoes the mutation, restoring the original AST.
Revert func()
// Node is the specific AST node this mutation edits, used only for
// reporting the mutated source text before and after Apply (see
// [MutantResult.Before]/[MutantResult.After] in package mutate). Most
// operators mutate exactly the node their Applies/Mutate were called
// on and can leave this nil — the engine falls back to that node — but
// an operator called on a *container* that edits one specific element
// of it (statement/remover is called on the block holding the
// statement, not the statement itself) should set Node to the actual
// element, so the reported snippet matches Description's granularity
// rather than rendering the whole container.
Node ast.Node
}
Mutation is a single reversible edit to a node in a live AST.
Apply performs the edit; Revert undoes it, restoring the node to exactly the state it had before Apply was called. Both must be safe to call once, in that order. Applying two Mutations concurrently against the same file is not supported — the engine serialises them per AST.
type Mutator ¶
type Mutator interface {
// Name reports the operator's registry name. It must equal the name the
// constructor was registered under, since [All] returns Mutators without
// their keys and reports identify operators by this value.
Name() string
// Applies reports whether this mutator can produce any mutation for node.
// It is a cheap pre-filter run for every node of every walk and must not
// modify the AST or allocate on the common (non-matching) path.
Applies(node ast.Node) bool
// Mutate returns the mutations this mutator would make to node, or nil if
// there are none. Calling Mutate does not modify the AST; only the returned
// [Mutation.Apply] funcs do. Mutate must return nil whenever Applies
// reports false.
Mutate(node ast.Node) []Mutation
}
Mutator produces mutations for the AST nodes it recognises.
Implementations must be stateless with respect to the AST: all per-node state belongs in the closures held by the returned Mutation values, so a single Mutator instance can be walked over many files.
type TypedMutator ¶
type TypedMutator interface {
// WithScope returns a Mutator bound to one package's type information.
// The returned value is used for every file of that package; it is never
// the same value WithScope was called on, so the original, registry-held
// instance stays stateless and safe to reuse — including concurrently
// across the other packages in the same run — exactly as Mutator's
// contract already requires.
WithScope(info *types.Info, pkg *types.Package) Mutator
}
TypedMutator is implemented by an operator whose eligibility depends on static type information the plain Mutator interface has no way to receive. It is checked once per run (engine.needsTypes) and, when present, invoked once per package.
This is deliberately a second, optional interface rather than a change to Mutator itself: Mutator is implemented by every operator, most of them purely syntactic, and threading type information through Applies/Mutate's signatures would force all of them to accept a parameter they ignore. See identifier/constswap, the first (and, as of this writing, only) operator that implements it.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package control implements the body-removal mutation operators.
|
Package control implements the body-removal mutation operators. |
|
Package expression implements the mutation operators that rewrite Go expressions.
|
Package expression implements the mutation operators that rewrite Go expressions. |
|
Package identifier implements mutation operators whose eligibility depends on static type information, answering questions purely syntactic operators (control, expression, literal, operator, statement) cannot: "is this identifier interchangeable with that one?"
|
Package identifier implements mutation operators whose eligibility depends on static type information, answering questions purely syntactic operators (control, expression, literal, operator, statement) cannot: "is this identifier interchangeable with that one?" |
|
Package literal implements mutation operators that change a literal value in source rather than swapping an operator or removing a statement: a numeric constant shifted — an integer by one in each direction (the classic off-by-one — `x < 0` becomes `x < 1`), a float by a small relative nudge in each direction (`x < 0.95` becomes `x < 0.95095`) — or a boolean literal flipped (`true` becomes `false`).
|
Package literal implements mutation operators that change a literal value in source rather than swapping an operator or removing a statement: a numeric constant shifted — an integer by one in each direction (the classic off-by-one — `x < 0` becomes `x < 1`), a float by a small relative nudge in each direction (`x < 0.95` becomes `x < 0.95095`) — or a boolean literal flipped (`true` becomes `false`). |
|
Package operator implements the token-level mutation operators: the mutators that change a program's meaning by substituting one Go operator for another (`+` for `-`, `==` for `!=`, `++` for `--`) or by stripping a unary operator off the expression it negates.
|
Package operator implements the token-level mutation operators: the mutators that change a program's meaning by substituting one Go operator for another (`+` for `-`, `==` for `!=`, `++` for `--`) or by stripping a unary operator off the expression it negates. |
|
Package statement implements mutation operators that delete whole statements from a function body, answering the question "does the test suite notice when this line never runs?".
|
Package statement implements mutation operators that delete whole statements from a function body, answering the question "does the test suite notice when this line never runs?". |