scripts/coverage.sh
Ref: Size: 6.4 KiB History
#!/usr/bin/env bash
# Per-package coverage ratchet.
#
# Rather than a single flat number (which lets a well-tested package rot while a
# poorly-tested one drags the average), each package has its own floor set a few
# points below today's coverage. CI fails if any package drops below its floor;
# raise a floor whenever you raise the coverage. Generated code (internal/pb) and
# thin main packages (cmd/*) are not gated here; real-VM behaviour is covered by
# the deploy boot-gate (cmd/eitri-smoke) against the live fleet, not by unit
# coverage.
#
# Aspirational targets (not yet enforced): logic/domain packages → 80%,
# host-touching effectful packages → 50%. Ratchet the floors toward those.
set -euo pipefail
cd "$(dirname "$0")/.."
# package (module-relative) -> minimum acceptable coverage %
#
# Each floor sits just under the package's measured coverage — the largest
# integer strictly below it (a 100% package floors at 100). Raise a floor
# whenever you raise the coverage; never lower one to make a drop pass.
#
# The whole table was re-baselined for Go 1.27, which counts statements more
# finely than 1.26 did (a closure body is now its own block rather than being
# folded into the statement that declares it). No test changed: 25 floors rose
# because the finer blocks credit covered code the old accounting merged away.
# Two packages fell — internal/server/boot 33→22 and internal/gateclient 60→56
# — because they are mostly wiring closures that nothing exercises, and 1.27
# stopped hiding that in their denominators. Both cover MORE statements than
# they did under 1.26; only the honest denominator grew.
declare -A FLOOR=(
[internal/agent/reconcile]=91
[internal/agent/state]=67
[internal/agent/statelock]=77
[internal/agent/seed]=90
[internal/agent/ipalloc]=90
[internal/agent/hostinfo]=99
[internal/agent/imagecache]=83
[internal/agent/netenv]=89
# netsnoop's parser, its packet filter and the direction check that decides
# whether a lease is real are all covered here; what is left is opening and
# binding the AF_PACKET socket, which needs CAP_NET_RAW and a live tap and is
# proven on real hardware, so the package number stays in the 60s.
[internal/agent/netsnoop]=63
[internal/agent/cloudhv]=89
[internal/agent/hyperlog]=93
[internal/agent/pidfile]=100
[internal/agent/vfkit]=90
# syncclient has a load-sensitive timing test: 84.9% measured standalone and
# in every all-package run sampled so far, but a loaded machine can cost it a
# branch. The floor keeps a point of margin under that number rather than
# sitting just below it — a floor that only holds on an idle machine fails CI
# at random (collab issue 4cb268b3).
[internal/agent/syncclient]=83
# exposeproxy runs real proxy goroutines; its coverage wobbles run to run
# (measured 91.8–93.0%), so this floor carries a margin the others don't need.
[internal/agent/exposeproxy]=90
[internal/agent/serialpump]=86
[internal/agent/enrollclient]=83
[internal/agent/dhcp]=68
[internal/agent/permanent]=100
[internal/server/api]=84
[internal/server/delegation]=91
[internal/server/seal]=90
[internal/server/sshca]=72
[internal/server/sshgate]=89
[internal/server/mcphttp]=94
[internal/server/vmssh]=100
[internal/server/boot]=22
[internal/server/health]=100
[internal/server/api/client]=93
[internal/server/api/spec]=93
[internal/server/store]=81
[internal/server/registry]=100
[internal/server/release]=93
[internal/agent/selfupdate]=71
[internal/agent/bootstrap]=75
[internal/agent/run]=48
[internal/server/hosttoken]=100
[internal/server/hub]=94
[internal/server/syncsvc]=87
[internal/server/web]=95
[internal/transport]=83
[internal/shape]=92
[internal/site]=87
[internal/smoke]=58
[internal/cli]=72
[internal/mcpserver]=77
[internal/oidcprovider]=81
[internal/server/config]=100
[internal/cloudinit]=77
[internal/covsnap]=77
[internal/joinblob]=96
[internal/gateclient]=56
[internal/names]=74
# internal/version carries the build stamp and the ordering both planes read
# it with; the ordering is pure and exhaustively table-tested.
[internal/version]=100
# internal/random has no tests of its own: it is exercised only through the
# packages that call it, so it reports 0.0% here. A real floor would be a
# lie. The 0 floor keeps it COUNTED — so the renamed/removed-package guard
# still accounts for it — while asserting nothing about a number nothing
# measures. Give it a real floor once it has tests of its own. (internal/pb
# is generated and stays unlisted; internal/guest has no tests AND no
# coverable line to gate yet.)
[internal/random]=0
)
profile="$(mktemp)"
report="$(mktemp)"
trap 'rm -f "$profile" "$report"' EXIT
# The run streams to the terminal and is teed to $report, from which the
# per-package "coverage: NN.N% of statements" lines are read below. A failing
# check must print its evidence: captured into a variable instead, a failing
# `go test` took its whole output down with it under `set -e`, and the gate
# reported a real test failure as silence.
status=0
go test -count=1 -covermode=atomic -coverprofile="$profile" \
./internal/... ./cmd/... 2>&1 | tee "$report" || status=$?
if [ "$status" -ne 0 ]; then
echo "coverage gate: go test failed (exit $status) — see the output above" >&2
exit "$status"
fi
fail=0
checked=0
while IFS= read -r line; do
# Lines look like: ok github.com/a73x/eitri/internal/agent/state 0.004s coverage: 54.2% of statements
case "$line" in
*"coverage:"*"of statements"*) ;;
*) continue ;;
esac
pkg="${line#*github.com/a73x/eitri/}"
pkg="${pkg%%[[:space:]]*}"
floor="${FLOOR[$pkg]:-}"
[ -z "$floor" ] && continue
pct="${line##*coverage: }"
pct="${pct%%% of statements}"
checked=$((checked + 1))
if awk "BEGIN{exit !($pct < $floor)}"; then
printf ' FAIL %-34s %5s%% < floor %s%%\n' "$pkg" "$pct" "$floor"
fail=1
else
printf ' ok %-34s %5s%% (floor %s%%)\n' "$pkg" "$pct" "$floor"
fi
done <"$report"
# Guard against a renamed/removed package silently dropping out of the gate.
if [ "$checked" -ne "${#FLOOR[@]}" ]; then
echo "coverage gate: expected ${#FLOOR[@]} gated packages, saw $checked — a gated package was renamed or removed" >&2
grep -E 'FAIL|cannot|error' "$report" >&2 || true
exit 1
fi
if [ "$fail" -ne 0 ]; then
echo "coverage gate: at least one package fell below its floor" >&2
exit 1
fi
echo "coverage gate: all $checked gated packages meet their floor"