Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions scripts/check-coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,59 @@ max_reported=25
go test ./... -timeout 90s -covermode=atomic -coverprofile="$cover_file"

# Profile body, one block per line: "<import-path>/<file>.go:<span> <stmts> <count>".
#
# Blocks are merged by identity — the "<file>.go:<span>" 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"
Expand Down
Loading