a73x

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"