Documentation
¶
Index ¶
- Constants
- func Modes(results []Result) map[Mode]int
- func MutHarnessSource(testPackageName, mutatedPath string, samePackage bool) []byte
- func RunPattern(tests []string) string
- func SchemataSource(packageName string, helpers []string) []byte
- type Advice
- type Baseline
- type BaselineEntry
- type ExecuteOptions
- type GenerateOptions
- type Kind
- type LineFilter
- type Mode
- type Mutant
- type Outcome
- type Preflight
- type Result
- type Scope
- func (s *Scope) Budget(plan TestPlan) time.Duration
- func (s *Scope) Eligible(absPath string, line int) bool
- func (s *Scope) PackageOrder(plan TestPlan) []string
- func (s *Scope) Packages() []string
- func (s *Scope) TestProfile(pkg, test string) (int, time.Duration, bool)
- func (s *Scope) Tests(absPath string, line int) TestPlan
- type ScopeOptions
- type Score
- type SurvivorGroup
- type TestPlan
- type TestProfile
Constants ¶
const ( // DefaultBaselinePath is committed, unlike the caches: it is curated policy // about which gaps are accepted, not derived data. DefaultBaselinePath = ".assay/mutation-baseline.json" )
const MutHarnessFileName = "assay_mut_harness_test.go"
MutHarnessFileName is the TestMain file injected into a covering test package through -overlay. It never touches the source tree.
const PlanEnvVar = "ASSAY_MUT_PLAN"
PlanEnvVar points the harness at its instruction file. Unset, the injected TestMain is a plain os.Exit(m.Run()) — which is what lets the same binary serve the env-selected fallback path with per-process semantics intact.
const SchemataEnvVar = "ASSAY_MUTANT"
SchemataEnvVar names the mutant to activate. Zero, and anything unparseable, leaves every site behaving exactly as the original — which is what makes the same binary usable as its own control.
const SchemataFileName = "assay_schemata.go"
SchemataFileName is the file injected into a mutated package through -overlay. It never touches the source tree; the overlay makes the compiler see a file that does not exist on disk.
const TrackEnvVar = "ASSAY_MUTANT_TRACK"
TrackEnvVar switches on init-site tracking. Only the mutant harness sets it: tracking costs a sync.Map store per site execution until it is turned off, and the env-selected path never turns it off because it never needs the answer.
Variables ¶
This section is empty.
Functions ¶
func Modes ¶
Modes counts how each mutant was judged, so a run can report how much of it took the fast path.
func MutHarnessSource ¶
MutHarnessSource renders the injected TestMain for a covering test package.
The protocol mirrors the index harness, with one addition: a begin line before each m.Run. The executor needs it to tell two deaths apart — a process that dies after announcing an instruction was killed by that mutant's tests, while a fresh process that dies before its first announcement has a broken harness, and the remaining mutants must fall back rather than be blamed.
func RunPattern ¶
func SchemataSource ¶
SchemataSource renders the injected file for a package, emitting only the helpers the package's sites actually reference.
Types ¶
type Advice ¶
type Advice struct {
// TestPackage and Test name the most focused covering test — the cheapest
// place to add the missing assertion, because it already executes the line.
TestPackage string `json:"testPackage"`
Test string `json:"test"`
// Focus is how many lines that test covers in total. Smaller is closer.
Focus int `json:"focusLines,omitempty"`
// Duration is the test's indexed cost, so the reader knows the price of
// iterating on it.
Duration time.Duration `json:"durationNanos,omitempty"`
Prescription string `json:"prescription"`
}
Advice turns a surviving mutant into a prescription: the covering test whose assertions sit closest to the mutated line, and what input would have told the original from the mutant. A survivor without advice is a metric; with it, it is a to-do item.
func Advise ¶
func Advise(m Mutant, profile TestProfile) *Advice
Advise builds the prescription for one surviving mutant. Nil when nothing covers it — an uncovered site is a coverage gap, not an assertion gap, and the coverage report already owns that story.
type Baseline ¶
type Baseline struct {
Version int `json:"version"`
Survivors []BaselineEntry `json:"survivors"`
// contains filtered or unexported fields
}
func FromResults ¶
FromResults builds a baseline that accepts exactly the survivors observed.
func LoadBaseline ¶
func NewBaseline ¶
func NewBaseline() *Baseline
type BaselineEntry ¶
type ExecuteOptions ¶
type ExecuteOptions struct {
Root string
Tags []string
Env []string
Jobs int
Budget func(plan TestPlan) time.Duration
MinTimeout time.Duration
Progress func(phase string, done, total int)
// PackageDir resolves a test package's directory. Test binaries have to run
// there, the way `go test` does, or a test that opens testdata/ fails for
// reasons that have nothing to do with the mutant.
PackageDir func(importPath string) string
// PackageOrder orders a plan's covering test packages before judging. A
// killed mutant exits at the first package that catches it, so an order
// informed by cost finds the kill sooner. Nil, or an order that is not a
// permutation of the plan, falls back to lexicographic.
PackageOrder func(plan TestPlan) []string
// NoSchemata forces every mutant to compile its own binary. Slow, and the only
// way to cross-check the shared-binary path.
NoSchemata bool
// NoHarness keeps the shared binary but spawns a process per (mutant, test
// package) instead of switching mutants inside one; the only way to
// cross-check the harness path.
NoHarness bool
}
func (ExecuteOptions) PackageBudget ¶
func (o ExecuteOptions) PackageBudget(plan TestPlan, testPkg string) time.Duration
PackageBudget derives the timeout for one test package's run from that package's own tests. Handing every package the whole plan's budget lets a mutant judged by N packages consume N times its allowance.
type GenerateOptions ¶
type LineFilter ¶
LineFilter answers whether a mutation site is eligible. Sites outside it are never generated, so an uncovered or unchanged line costs nothing.
type Mode ¶
type Mode string
Mode records how a mutant was judged. Every mode must reach the same verdict; they differ only in what they pay. Overlay compiles a binary per mutant, schemata shares one compile across a package's mutants but still spawns a process per (mutant, test package), and harness shares the processes too — one per (test package, shard), switching mutants between test runs.
type Mutant ¶
type Mutant struct {
ID string `json:"id"`
Kind Kind `json:"kind"`
Package string `json:"package"`
File string `json:"file"`
Line int `json:"line"`
Function string `json:"function"`
Original string `json:"original"`
Replacement string `json:"replacement"`
// contains filtered or unexported fields
}
func Exclude ¶
Exclude drops known-failing tests from every mutant's plan so the remaining judgements stay meaningful. A mutant left with no tests becomes no-coverage rather than being silently counted as killed.
func (Mutant) Schemata ¶
Schemata reports whether this mutant can be selected at run time inside a shared binary. When it cannot, judging it costs a compile of its own.
type Outcome ¶
type Outcome string
const ( // OutcomeKilled means a covering test noticed the change, which is the result // the suite is supposed to produce. OutcomeKilled Outcome = "killed" // OutcomeSurvived means every covering test still passed. A survivor is a gap. OutcomeSurvived Outcome = "survived" // OutcomeTimeout counts as killed: the mutant changed observable behaviour. OutcomeTimeout Outcome = "timeout" // OutcomeNotBuilt is a defect in assay, not in the suite under test, so it is // excluded from the score and reported separately. OutcomeNotBuilt Outcome = "not-built" // OutcomeNoCoverage means no indexed test executes the line, so there is // nothing to learn. Excluded from the score. OutcomeNoCoverage Outcome = "no-coverage" )
type Preflight ¶
type Preflight struct {
Failing map[string][]string
// Ran lists the tests each package's preflight executed, pass or fail.
// Every verdict here is evidence about test reliability, and callers that
// keep a flake journal want the passes as much as the failures.
Ran map[string][]string
Duration time.Duration
}
Preflight runs the tests that mutants will be judged by, against unmutated code, and reports any that already fail.
This is not optional rigour. A covering test that fails for reasons unrelated to a mutant marks that mutant killed, and every such false kill inflates the mutation score. Silently reporting a flattering number is worse than reporting none, so a red suite has to be surfaced before any mutant is judged.
func RunPreflight ¶
type Result ¶
type Result struct {
Mutant Mutant `json:"mutant"`
Outcome Outcome `json:"outcome"`
Mode Mode `json:"mode,omitempty"`
KilledBy string `json:"killedBy,omitempty"`
Detail string `json:"detail,omitempty"`
// Fallback records why a schemata-capable mutant was judged on a binary of its
// own. A silent fallback costs a compile per mutant, which is exactly the
// slowdown the shared binary exists to remove, so the reason must surface.
Fallback string `json:"fallback,omitempty"`
Tests int `json:"tests"`
Duration time.Duration `json:"durationNanos"`
// Advice is attached to survivors after judging: the covering test closest
// to the mutated line and the input that would have killed the mutant.
Advice *Advice `json:"advice,omitempty"`
}
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
func NewScope ¶
func NewScope(opts ScopeOptions) (*Scope, error)
func (*Scope) Budget ¶
Budget sums the indexed durations of the tests a mutant will run, so its timeout is a multiple of what those tests actually cost rather than a fixed guess.
func (*Scope) Eligible ¶
Eligible restricts mutation to executable statements, and to changed lines when diff-scoped. Coverage is deliberately not consulted here: an uncovered site is still generated so it can be reported as no-coverage, which costs nothing because such mutants are never built.
func (*Scope) PackageOrder ¶
PackageOrder orders a plan's covering packages cheapest-first by their indexed durations. A killed mutant exits at the first package that catches it, and a kill found by the cheap package never pays for the expensive one; lexicographic order was paying that cost by accident of naming. Unindexed packages sort last — their cost is unknown, not zero.
func (*Scope) Packages ¶
Packages lists the packages worth mutating: those owning at least one eligible changed file, or every testable package when not diff-scoped.
func (*Scope) TestProfile ¶
TestProfile resolves a covering test's coverage breadth and indexed duration for survivor advice.
func (*Scope) Tests ¶
Tests finds the tests that execute a line, across every package whose tests can reach the mutated one.
Always-run tests are deliberately excluded. Their attribution is unknown by definition, and a test that was already failing when the index was built would mark every mutant killed and silently inflate the score. A survivor therefore means "no test with known coverage of this line killed it", which is the honest claim.
type ScopeOptions ¶
type Score ¶
type Score struct {
Killed int `json:"killed"`
Survived int `json:"survived"`
Timeout int `json:"timeout"`
NotBuilt int `json:"notBuilt"`
NoCoverage int `json:"noCoverage"`
Accepted int `json:"accepted"`
NewSurvivors []Result `json:"newSurvivors,omitempty"`
}
type SurvivorGroup ¶
SurvivorGroup clusters a report's survivors by the function they mutate.
func GroupSurvivors ¶
func GroupSurvivors(survivors []Result) []SurvivorGroup
GroupSurvivors ranks survivors worst-first: the function with the most survivors is the biggest assertion gap, and scattering its mutants through a flat list hid that. Within a group, source order; groups of equal size in deterministic package/function order.