#!/usr/bin/env bash # Test coverage collection and threshold enforcement. # # Runs Rust tests with llvm-cov and frontend tests with vitest --coverage. # Reports line coverage percentages for each. # # Threshold: reads from COVERAGE_THRESHOLD env var, or .coverage_baseline file. # Default: 0% (any coverage passes; baseline is written on first run). # # Coverage can only go up: if current coverage is above the stored baseline, # the baseline is updated automatically. # # Exit codes: # 0 — all coverage at or above threshold # 1 — coverage below threshold set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" BASELINE_FILE="$PROJECT_ROOT/.coverage_baseline" # ── Load threshold ──────────────────────────────────────────────────────────── if [ -n "${COVERAGE_THRESHOLD:-}" ]; then THRESHOLD="$COVERAGE_THRESHOLD" elif [ -f "$BASELINE_FILE" ]; then THRESHOLD=$(cat "$BASELINE_FILE") else THRESHOLD=0 fi echo "=== Coverage threshold: ${THRESHOLD}% ===" echo "" PASS=true RUST_LINE_COV=0 FRONTEND_LINE_COV=0 # ── Rust coverage ───────────────────────────────────────────────────────────── echo "=== Running Rust tests with coverage ===" RUST_REPORT="" if cargo llvm-cov --version >/dev/null 2>&1; then RUST_REPORT=$(cargo llvm-cov \ --manifest-path "$PROJECT_ROOT/Cargo.toml" \ --summary-only \ 2>&1) || true echo "$RUST_REPORT" # Parse the TOTAL line: columns are space-separated with % on coverage cols. # Format: TOTAL ... # We want field 10 (lines cover %). RUST_RAW=$(echo "$RUST_REPORT" | awk '/^TOTAL/ { print $10 }' | tr -d '%') if [ -n "$RUST_RAW" ]; then RUST_LINE_COV="$RUST_RAW" fi else echo "cargo-llvm-cov not available; skipping Rust coverage" fi echo "Rust line coverage: ${RUST_LINE_COV}%" echo "" # ── Frontend coverage ───────────────────────────────────────────────────────── echo "=== Running frontend tests with coverage ===" FRONTEND_DIR="$PROJECT_ROOT/frontend" FRONTEND_LINE_COV=0 if [ -d "$FRONTEND_DIR" ]; then FRONTEND_REPORT=$(cd "$FRONTEND_DIR" && pnpm run test:coverage 2>&1) || true echo "$FRONTEND_REPORT" # Parse "All files" line from vitest coverage text table. # Format: All files | % Stmts | % Branch | % Funcs | % Lines | ... FRONTEND_RAW=$(echo "$FRONTEND_REPORT" | awk -F'|' '/All files/ { gsub(/ /, "", $5); print $5 }' | head -1) if [ -n "$FRONTEND_RAW" ]; then FRONTEND_LINE_COV="$FRONTEND_RAW" fi else echo "No frontend/ directory found; skipping frontend coverage" fi echo "Frontend line coverage: ${FRONTEND_LINE_COV}%" echo "" # ── Overall (average of available measurements) ─────────────────────────────── if [ "$RUST_LINE_COV" != "0" ] && [ "$FRONTEND_LINE_COV" != "0" ]; then OVERALL=$(awk "BEGIN { printf \"%.1f\", ($RUST_LINE_COV + $FRONTEND_LINE_COV) / 2 }") elif [ "$RUST_LINE_COV" != "0" ]; then OVERALL="$RUST_LINE_COV" elif [ "$FRONTEND_LINE_COV" != "0" ]; then OVERALL="$FRONTEND_LINE_COV" else OVERALL=0 fi # ── Summary ─────────────────────────────────────────────────────────────────── echo "=== Coverage Summary ===" echo " Rust: ${RUST_LINE_COV}%" echo " Frontend: ${FRONTEND_LINE_COV}%" echo " Overall: ${OVERALL}%" echo " Threshold: ${THRESHOLD}%" echo "" # ── Threshold check ─────────────────────────────────────────────────────────── if awk "BEGIN { exit (($OVERALL + 0) < ($THRESHOLD + 0)) ? 0 : 1 }"; then echo "FAIL: Coverage ${OVERALL}% is below threshold ${THRESHOLD}%" PASS=false else echo "PASS: Coverage ${OVERALL}% meets threshold ${THRESHOLD}%" fi # ── Update baseline when coverage improves ──────────────────────────────────── if [ "$PASS" = "true" ]; then STORED_BASELINE="${THRESHOLD}" if awk "BEGIN { exit (($OVERALL + 0) > ($STORED_BASELINE + 0)) ? 0 : 1 }"; then echo "${OVERALL}" > "$BASELINE_FILE" echo "Baseline updated: ${STORED_BASELINE}% → ${OVERALL}%" fi fi if [ "$PASS" = "false" ]; then exit 1 fi