#!/bin/sh
# scripts/zig-coverage — line-coverage measurement for the Zig broker.
#
# WHY this exists: until the 2026-05 review wave, `server.zig` was an
# executable (not a `zig build test` target), so its formatters +
# admission control had ZERO measurable coverage. Part 1 decomposed it
# into `admission.zig` / `format*.zig` / `dispatch.zig` test targets;
# this script turns "is it covered?" from a guess into a number.
#
# HOW: Zig has no native coverage, but every test file is also a
# standalone test runner (`zig test src/X.zig --test-no-exec` emits the
# binary without running it). kcov then collects DWARF-based line
# coverage from those binaries with zero instrumentation, and merges all
# runs into one report. The test-file list is extracted from
# `hrb-code/build.zig` at runtime, so this never drifts from the suite
# that `zig build test` actually runs.
#
# Usage:
#   scripts/zig-coverage                 # build + measure, print summary
#   scripts/zig-coverage --fail-under=N  # exit 1 if line coverage < N%
#   scripts/zig-coverage --open          # also print the HTML report path
#
# kcov is NOT in Debian/Ubuntu apt as of this writing. When it is
# absent this script prints install guidance and exits 0 (a SKIP, not a
# failure) so it is safe to wire into `scripts/gates` without breaking
# contributors who lack the tool. Pass --require to make absence fatal.
#
# Exit codes: 0 measured-and-passed (or skipped); 1 below threshold or
# (with --require) kcov missing; 2 usage error.

set -eu

REPO_ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
cd "$REPO_ROOT"

ZIG="${ZIG:-.venv/lib/python3.13/site-packages/ziglang/zig}"
PY="${PY:-.venv/bin/python}"
KCOV="${KCOV:-kcov}"
SRC_DIR="$REPO_ROOT/hrb-code/src"
BUILD_FILE="$REPO_ROOT/hrb-code/build.zig"
COV_DIR="$REPO_ROOT/hrb-code/.zig-cache/coverage"

fail_under=""
require_kcov=0
show_open=0
for arg in "$@"; do
    case "$arg" in
        --fail-under=*) fail_under="${arg#--fail-under=}" ;;
        --require)      require_kcov=1 ;;
        --open)         show_open=1 ;;
        -h|--help)
            sed -n '1,/^set/p' "$0" | sed 's/^# \{0,1\}//'
            exit 0
            ;;
        *)
            printf 'scripts/zig-coverage: unknown flag: %s\n' "$arg" >&2
            exit 2
            ;;
    esac
done

if ! command -v "$KCOV" >/dev/null 2>&1; then
    printf 'zig-coverage: kcov not found.\n' >&2
    printf '  kcov is the DWARF-based coverage tool this script drives.\n' >&2
    printf '  It is not in apt; install from https://github.com/SimonKagstrom/kcov\n' >&2
    printf '  (build: cmake + binutils-dev libcurl4-openssl-dev libdw-dev libelf-dev).\n' >&2
    if [ "$require_kcov" -eq 1 ]; then
        printf '  (--require given: treating absence as failure)\n' >&2
        exit 1
    fi
    printf '  Skipping coverage (exit 0). Pass --require to make this fatal.\n' >&2
    exit 0
fi

# Test-file list — extracted from build.zig's `inline for` tuple so it
# tracks the real suite. Production source is what we want the % to
# reflect, so the pure test harnesses (*_test.zig) are collected (to
# drive the code under test) but excluded from the coverage denominator.
tests="$(sed -n '/const test_step/,/}) |src|/p' "$BUILD_FILE" \
    | grep -oE '"src/[^"]+\.zig"' | tr -d '"' | sort -u)"

if [ -z "$tests" ]; then
    printf 'zig-coverage: no test targets found in %s\n' "$BUILD_FILE" >&2
    exit 1
fi

rm -rf "$COV_DIR"
mkdir -p "$COV_DIR/bin" "$COV_DIR/runs"

merge_dirs=""
for src in $tests; do
    name="$(basename "$src" .zig)"
    bin="$COV_DIR/bin/$name"
    # Emit the test runner without executing it. Root the build at
    # hrb-code/ so sibling `@import("model.zig")` resolves exactly as in
    # `zig build test`.
    ( cd "$REPO_ROOT/hrb-code" \
        && "$REPO_ROOT/$ZIG" test "$src" --test-no-exec \
            -femit-bin="$bin" --cache-dir .zig-cache )
    # Collect coverage for this binary. --include-path keeps the report
    # to our own source; --exclude-pattern drops the test harnesses from
    # the numerator/denominator so the % is production-code coverage.
    "$KCOV" --include-path="$SRC_DIR" --exclude-pattern=_test.zig \
        "$COV_DIR/runs/$name" "$bin" >/dev/null 2>&1 || true
    merge_dirs="$merge_dirs $COV_DIR/runs/$name"
done

# Merge every per-binary run into one report.
# shellcheck disable=SC2086
"$KCOV" --merge "$COV_DIR/merged" $merge_dirs >/dev/null 2>&1

report="$COV_DIR/merged/kcov-merged"
cobertura="$report/cobertura.xml"
if [ ! -f "$cobertura" ]; then
    printf 'zig-coverage: merged report not found at %s\n' "$cobertura" >&2
    exit 1
fi

# cobertura's root <coverage line-rate="0.87" ...> is the merged ratio.
pct="$("$PY" - "$cobertura" <<'PYEOF'
import sys, xml.etree.ElementTree as ET
root = ET.parse(sys.argv[1]).getroot()
print(f"{float(root.get('line-rate', 0)) * 100:.1f}")
PYEOF
)"

printf 'Zig line coverage: %s%%  (report: %s/index.html)\n' "$pct" "$report"
if [ "$show_open" -eq 1 ]; then
    printf '  open: file://%s/index.html\n' "$report"
fi

if [ -n "$fail_under" ]; then
    if ! "$PY" -c "import sys; sys.exit(0 if float('$pct') >= float('$fail_under') else 1)"; then
        printf 'FAIL: coverage %s%% < threshold %s%%\n' "$pct" "$fail_under" >&2
        exit 1
    fi
fi
