Mutation

Advanced overview

Vary's mutation engine goes beyond kill/survive counting. Six pillars work together to tell you why mutants survive, what to fix, and how strong your test suite actually is.

For the basics, see Introduction. For a worked example, see Golden path.

The six pillars

PillarWhat it doesDeep dive
1. ContractsRuntime oracles that kill mutants automatically; contract clauses are also mutated to measure specification adequacyContracts
2. ObservabilityRecords reads, writes, assertions, and branches per test to classify why survivors escapedObservability
3. Oracle analysisBuilds a graph of observe statements → values → identifiers; validates oracle quality per testOracle analysis
4. Effect classificationTags functions with side-effect categories; skips nondeterministic mutants; seals randomness at test timeEffects
5. Signatures & manifestGives every mutant a stable, refactor-proof identity; emits a machine-readable manifest for CI and toolingSignatures
6. Semantic mutationOperators that understand program meaning (field access, nullability, loop boundaries, constructor bindings) and catch bugs that classic syntax-level mutation missesOperators: Semantic

End-to-end flow

When you run vary mutate, the engine performs these steps in order:

StepPhaseWhat happens
1DiscoveryScans source for mutation sites, assigns expression IDs and stable signatures
2Effect analysisClassifies each function's effects, skips nondeterministic mutants (unless --unstable)
3MutationApplies operators to generate mutants at AST and bytecode levels
4ExecutionRuns tests against each mutant; contract violations count as kills
5ObservationIf --observe is enabled, records trace data per test
6Oracle validationClassifies each test's oracle strength (STRONG / CONSTANT / NONE)
7ScoringComputes mutation score, contract adequacy, oracle coverage, effect stability
8IntegrityCombines the four scores into a weighted composite grade (A to F)

Language features that strengthen mutation testing

Contracts serve two roles

Contracts (in {}, out (r) {}, post {}) serve two roles in mutation testing:

RoleHow it works
Runtime oracleA contract violation during a mutant run counts as a kill, without a specific test for it
Specification adequacyContract clauses are mutated with dedicated operators (contract_precondition, contract_postcondition, contract_remove) to measure whether tests exercise the boundaries contracts define

Pure functions simplify diagnosis

A pure def function has no side effects. When a mutant survives in a pure function, the cause is always a missing assertion, never hidden state. The --why output skips side-effect explanations and points directly at what to test.

Invariants catch construction mutations

Classes with invariant {} blocks kill mutants that corrupt construction arguments, without any test asserting on fields directly.

Symptom → feature guide

You notice...Use this
Mutants survive but you don't know why--why + --observe (Observability)
Tests pass but assertions are tautologicalLie detection
Contract exists but nothing tests its boundaryContract adequacy score (Contracts)
Flaky mutation resultsEffect classification (Effects)
Score changes after a refactorStable signatures (Signatures)
No observe in a testOracle validation (Oracle analysis)
Need a CI quality gateIntegrity score + min_integrity in vary.toml (Signatures)
Want to know which survivors are worth investigatingObservability score (see below)

Integrity score

The integrity score combines four metrics into a single grade:

ComponentWeightSource
Mutation score40%Fraction of mutants killed
Contract adequacy20%Fraction of contract obligations defended
Oracle coverage20%Fraction of tests with strong oracles
Effect stability20%Fraction of functions with stable effects

Grades: A (90 to 100), B (75 to 89), C (60 to 74), D (40 to 59), F (0 to 39). Set a minimum with min_integrity in vary.toml.

Observability score

The observability score tracks whether behavioural changes reach an oracle, not just whether the oracle catches them. It reports three metrics:

MetricDefinition
Kill ratekilled / total
Observability(killed + weak-oracle survivors) / total
Actionable survivor rate(all survivors − equivalent-likely) / total

Observability is always greater than or equal to kill rate. The gap between them shows how many survivors were seen by a test but not caught. These are the easiest to fix (strengthen the assertion). Survivors classified as equivalent-likely reduce the actionable count but do not affect observability.

Set CI gates in vary.toml:

[mutation]
min_observability = 70.0       # Minimum observability percentage
max_unobserved_survivors = 5   # Maximum unobserved survivors allowed

Next steps

PageTopic
ContractsHow contracts kill mutants and how contract adequacy works
ObservabilityRuntime tracing, differential detection, assertion groups
Oracle analysisOracle graph structure, determinism tags, oracle validation
EffectsEffect tags, nondeterministic filtering, runtime sealing
SignaturesStable identities, expression IDs, mutation manifest
InfrastructureCaching, quarantine, policy gates, certificates, CLI reference
← Observability
Oracle analysis →