Engineering guide
Measure MC/DC in Go without losing the requirement
MC/DC examines whether each condition can independently affect a decision's outcome. It can reveal gaps that statement coverage misses, but a fully exercised decision can still implement the wrong product behavior.
Start with the decision that is actually in the code
Consider a cache helper that serves an entry when it is fresh and either a hit or not stale. This deliberately incomplete teaching fixture was executed on October 9, 2026. The source and results are available below.
type Entry struct {
Fresh, Hit, Stale, Deleted bool
}
func ServeCached(e Entry) bool {
if e.Fresh && (e.Hit || !e.Stale) {
return true
}
return false
}A test that reaches the return-true statement supplies statement coverage for that path. A second test taking the false path can supply both decision outcomes. Neither necessarily shows that hit and stale can each affect the result independently.
Plan independence cases before measuring them
The following input set demonstrates logical independence for this expression. These are the inputs used in the recorded test run. The MC/DC report below also records which conditions were actually evaluated.
| Case | fresh | hit | stale | Expected result |
|---|---|---|---|---|
| A | true | true | true | true |
| B | true | false | true | false |
| C | true | false | false | true |
| D | false | true | true | false |
- A and D change only fresh and reverse the result.
- A and B change only hit and reverse the result.
- B and C change only stale and reverse the result.
Go short-circuits this expression. A field having a planned value does not mean its condition was evaluated in every execution. The measured report must preserve evaluated and skipped conditions and establish eligible witness pairs from actual observations. The report retains skipped conditions alongside the observed values and witness pairs.
Check the product rule that is absent from the predicate
Suppose the approved requirement also says a deleted entry must never be served. The helper never reads deleted. The following regression exposes the omitted rule even if all conditions already present in the helper have independence witnesses.
func TestTombstoneIsNotServed(t *testing.T) {
e := Entry{Fresh: true, Hit: true, Stale: true, Deleted: true}
if ServeCached(e) {
t.Fatal("a deleted entry must not be served")
}
}This test failed in the recorded run with a deleted entry must not be served. Enable it with the requirement build tag in the downloadable example. Keep the requirement and its expected result authoritative; adding the missing condition requires its own implementation and regression review.
Run the example and inspect the evidence
Download the Go example and results
Extract the ZIP and open a terminal in mcdc-cache. The tests need Go 1.22 or later and no third-party modules. The recorded run used Go 1.26.3 on macOS ARM64.
go test -count=1 -v ./cache
# Expected: A, B, C and D pass.
go test -count=1 -tags requirement -run TestTombstoneIsNotServed -v ./cache
# Expected: FAIL, exit 1. A deleted entry is incorrectly served.
If you already have the Proof CLI, measure the same predicate with proof mcdc measure ./cache --engine go --format json. The download includes the example and recorded report, not the CLI. Both Go test commands work without Proof.
Recorded result: one measured decision, three of three conditions covered and four runtime samples. The measurement used proof 0.1.0-dev+geeb5981c. The separate requirement test still fails because the predicate never reads Deleted.
| Condition | Recorded pair | Evaluated / skipped |
|---|---|---|
e.Fresh | A and D | 4 / 0 |
e.Hit | A and B | 3 / 1 |
!e.Stale | B and C | 2 / 2 |
These pairs are reported from instrumented execution, including short-circuit skips. A skipped condition is not an observed false value. The report's sample indexes 0 through 3 correspond to A through D.
Measure the candidate and inspect the artifact
proof mcdc measure ./... --engine go
proof audit --check tests_pass --check code_mcdc_measure
proof audit --check code_mcdc_coverageThe Go engine instruments supported decisions and runs tests. The measurement commands can execute project code. The coverage check reads available suites for the configured targets and evaluates policy; it does not execute test runners itself.
The canonical Go suite is stored at .proof/mcdc/go/latest.json. A measurement narrower than configured targets can be stored as ad hoc evidence, and an interrupted or capped run can be partial. Those channels do not substitute for the canonical audit evidence. Inspect scope, unsupported constructs, missing observations, actual witness pairs, and execution provenance before accepting the result.
Keep code coverage and requirement evidence separate
Code-level MC/DC observes the implemented decisions. Requirement-level MC/DC examines witnesses for the specified formula. Neither denominator automatically includes a product rule that was never modeled. The public jsonparser case illustrates why aggregate coverage claims need to be read alongside the remaining behavioral gaps.
Use measured coverage to challenge the suite, then review the requirement inventory, assertions, interfaces, and failure cases. A passing measurement is bounded evidence about a particular revision and supported scope, not a certificate of complete correctness.
Continue from here
Compare coverage denominators. Build assertions from intended behavior.
Bring a Go decision whose coverage is high but whose product behavior remains uncertain. Bring a change to Proof.