#!/usr/bin/env bash # scripts/check-coverage.sh — enforce Sheaf's test-coverage ratchet. # # Runs `go test -coverprofile`, prints per-file and total coverage, and fails # if any file drops below the per-file floor pinned in coverage-floors.txt. # # Why per-file floors instead of one global percentage: part of the codebase # is not unit-testable. The Wails-bound methods (OpenFileDialog, CopyHTML, # PrintDocument, the BrowserOpenURL line in OpenExternal) and everything in # main.go call Wails runtime functions that log.Fatal outside a live app, and # a few defensive error branches (chroma Tokenise/Format, the os.Getwd # fallback, an unprefixed file: scheme) are unreachable from real input. Each # file's floor is therefore pinned at its *legitimate* ceiling, with the # untestable lines documented in .coverignore. # # The ratchet, per file: # - covered < pinned covered -> FAIL (a covered line regressed; add tests) # - total statements changed -> FAIL (the file's code changed; re-pin after # confirming new lines are covered). This is a drift guard: the pinned # "covered" number is meaningless once the line set shifts. # - no floor entry -> FAIL (new files must be pinned; use # "0 " only with a .coverignore justification) # A file at its pin may still improve silently; the drift guard catches the # next code change, and .coverignore documents why the ceiling is below 100%. # # Usage: # scripts/check-coverage.sh run tests + ratchet (fails on regression) # scripts/check-coverage.sh report print the coverage report, never fail # # Toolchain: go, awk, grep, sort, mktemp. No extra Go tooling to install. set -euo pipefail # Always run from the repo root (parent of this script's directory). ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" MODE="${1:-check}" WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT PROFILE="$WORK/cover.out" FLOORS="coverage-floors.txt" # -count=1: never reuse a cached (stale) coverage result. go test -count=1 -coverprofile="$PROFILE" ./... # Aggregate the per-statement profile into per-file covered/total counts. # Profile lines look like: Sheaf/app.go:70.46,72.2 1 1 # ( file:range ) awk 'NR > 1 { f = $1; sub(/:.*/, "", f) stmts[f] += $(NF-1) if ($NF > 0) cov[f] += $(NF-1) } END { for (f in stmts) printf "%s %d %d\n", f, cov[f], stmts[f] }' "$PROFILE" | sort >"$WORK/pcts" TOTAL=$(awk 'NR > 1 { tot += $(NF-1); if ($NF > 0) cov += $(NF-1) } END { printf "%.1f", (cov / tot) * 100 }' "$PROFILE") echo "=== per-file coverage ===" while read -r f cov stmts; do printf " %-26s %6.1f%% (%d/%d)\n" "$f" "$(awk -v c="$cov" -v s="$stmts" 'BEGIN{print (c/s)*100}')" "$cov" "$stmts" done <"$WORK/pcts" echo " ---------------------------" echo " total ${TOTAL}%" if [ "$MODE" = "report" ]; then exit 0 fi if [ ! -f "$FLOORS" ]; then echo echo "COVERAGE RATCHET: $FLOORS not found; cannot enforce floors." >&2 exit 1 fi # Load pinned floors: "Sheaf/ " per line, # comments # allowed. Floors are exact statement counts, so a single covered/uncovered # statement flips them — no percentage rounding to fight. grep -vE '^[[:space:]]*(#|$)' "$FLOORS" | sort >"$WORK/floors" fail=0 while read -r f cov stmts; do entry=$(awk -v f="$f" '$1 == f { print $2, $3 }' "$WORK/floors") if [ -z "$entry" ]; then echo " !! $f has no pinned floor (currently $cov/$stmts)." >&2 echo " Add it to $FLOORS (use 0 $stmts only with a .coverignore note)." >&2 fail=1 continue fi set -- $entry fcov=$1; ftot=$2 if [ "$cov" -lt "$fcov" ]; then # A previously-covered line lost coverage. This is the regression the # ratchet exists to catch. echo " !! $f regressed: $cov/$stmts covered < pinned floor $fcov/$ftot" >&2 fail=1 elif [ "$ftot" != "$stmts" ]; then # The file's statement count changed, so its code changed. The pin's # "covered" number no longer refers to the same lines, so require a # conscious re-pin (and a check that any new lines are covered). echo " ^^ $f code changed: now $cov/$stmts, pinned at $fcov/$ftot." >&2 echo " Confirm new lines are covered, then re-pin in $FLOORS." >&2 fail=1 fi done <"$WORK/pcts" echo if [ "$fail" -ne 0 ]; then echo "COVERAGE RATCHET FAILED (total ${TOTAL}%)." >&2 exit 1 fi echo "coverage ratchet OK: all files at their pinned floors (total ${TOTAL}%)."