From 5d7ac495f024649bcb06b3831f78015611e021ec Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Sun, 9 Aug 2026 07:58:05 +0300 Subject: [PATCH] fix: count each coverage block once regardless of cache warmth The gate counted statements straight off the lines of the merged ./... profile, and that profile can carry the same block several times. go test builds the -coverprofile output by concatenating each package's fragment, and cmd/go appends a cached fragment before the checks that decide whether the cached result is usable, once for each of the two keys it consults. Right after go clean -testcache every block lands three times, so the same tree reported 4942 statements warm and 14826 expired. Merge blocks by identity before counting. Counts combine with max rather than the sum go tool cover uses in atomic mode, because the repeats are one run's data re-emitted; the covered/uncovered verdict is the same either way. --- scripts/check-coverage.sh | 53 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/scripts/check-coverage.sh b/scripts/check-coverage.sh index b21ea9f4..25b5df5d 100755 --- a/scripts/check-coverage.sh +++ b/scripts/check-coverage.sh @@ -25,10 +25,59 @@ max_reported=25 go test ./... -timeout 90s -covermode=atomic -coverprofile="$cover_file" # Profile body, one block per line: "/.go: ". +# +# Blocks are merged by identity — the ".go:" field — before anything is +# counted, because the same block can arrive several times over. go test builds the +# -coverprofile output by concatenating each package's fragment, and cmd/go appends a +# cached fragment before the checks that decide whether that cached result is usable, +# once for each of the two keys it tries. Immediately after `go clean -testcache` every +# block therefore lands three times, and a count over the raw lines reports how warm +# the test cache is rather than how large the tree is. Merging is what go tool cover +# does when it reads a profile; an explicit -coverpkg would not help, since the repeats +# are re-emissions of a package's own fragment. +# +# Counts merge by max rather than the sum go tool cover uses in atomic mode, because +# the repeats are one run's data re-emitted: summing would multiply real execution +# counts by however many times a fragment happened to be appended. The verdict is the +# same under either rule, since the gate only asks whether a block ever ran. +# +# One consequence is deliberate: a block that ran in one fragment and not in another +# now counts as covered, the plain meaning of "some test executed this statement". It +# retires a false failure the raw count could produce, where such a block added 2 to +# the total and 1 to the hits. A block no fragment ran still merges to 0, and is still +# reported and still fails. +# # Sorted so the same failure reads the same way on every run. -uncovered="$(tail -n +2 "$cover_file" | awk '$3 == 0' | sort)" +merged="$(awk 'NR > 1 { + if (!($1 in stmts)) { + stmts[$1] = $2 + 0 + count[$1] = $3 + 0 + next + } + # A span identifies one block of one file, so its statement count cannot + # differ between copies. If it does, the profile is not one build and the + # total would depend on line order. + if (stmts[$1] != $2 + 0) { + printf "COVERAGE FAIL: %s reports %d and %d statements\n", $1, stmts[$1], $2 > "/dev/stderr" + conflict = 1 + exit 1 + } + if ($3 + 0 > count[$1]) { + count[$1] = $3 + 0 + } +} +END { + if (conflict) { + exit 1 + } + for (block in stmts) { + print block, stmts[block], count[block] + } +}' "$cover_file" | sort)" + +uncovered="$(printf '%s\n' "$merged" | awk '$3 == 0')" -read -r hit total <<<"$(awk 'NR > 1 { total += $2; if ($3 > 0) hit += $2 } END { print hit + 0, total + 0 }' "$cover_file")" +read -r hit total <<<"$(printf '%s\n' "$merged" | awk '{ total += $2; if ($3 > 0) hit += $2 } END { print hit + 0, total + 0 }')" if [ "$total" -eq 0 ]; then echo "COVERAGE FAIL: the profile records no statements"