121 lines
4.8 KiB
Plaintext
121 lines
4.8 KiB
Plaintext
|
|
#!/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 <regions> <missed> <cover%> <funcs> <missed> <exec%> <lines> <missed> <cover%> ...
|
||
|
|
# 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
|