huskies: merge 474_story_per_file_test_coverage_report_with_improvement_targets

This commit is contained in:
dave
2026-04-04 15:07:37 +00:00
parent c4e2f600de
commit df135e9373
4 changed files with 338 additions and 38 deletions
+103 -17
View File
@@ -4,6 +4,15 @@
# Runs Rust tests with llvm-cov and frontend tests with vitest --coverage.
# Reports line coverage percentages for each.
#
# After collecting coverage, writes a language-agnostic .coverage_report.json
# at the project root in the standard format:
#
# {
# "overall": <float>,
# "threshold": <float>,
# "files": [{ "path": <string>, "coverage": <float> }]
# }
#
# Threshold: reads from COVERAGE_THRESHOLD env var, or .coverage_baseline file.
# Default: 0% (any coverage passes; baseline is written on first run).
#
@@ -19,6 +28,7 @@ set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BASELINE_FILE="$PROJECT_ROOT/.coverage_baseline"
RUST_COV_JSON="$PROJECT_ROOT/.coverage_rust_raw.json"
# ── Load threshold ────────────────────────────────────────────────────────────
if [ -n "${COVERAGE_THRESHOLD:-}" ]; then
@@ -38,20 +48,29 @@ 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 \
# Run tests and generate both a text summary and a JSON report for per-file data.
cargo llvm-cov \
--manifest-path "$PROJECT_ROOT/Cargo.toml" \
--summary-only \
2>&1) || true
echo "$RUST_REPORT"
2>&1
# 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"
# Generate JSON report from the already-collected profdata (no re-run).
cargo llvm-cov report \
--manifest-path "$PROJECT_ROOT/Cargo.toml" \
--json \
--output-path "$RUST_COV_JSON" \
>/dev/null 2>&1 || true
# Parse overall Rust line coverage from the JSON (more reliable than awk on text).
if [ -f "$RUST_COV_JSON" ]; then
RUST_LINE_COV=$(python3 -c "
import json, sys
with open('$RUST_COV_JSON') as f:
data = json.load(f)
pct = data['data'][0]['totals']['lines']['percent']
print(f'{pct:.1f}')
" 2>/dev/null) || RUST_LINE_COV=0
fi
else
echo "cargo-llvm-cov not available; skipping Rust coverage"
@@ -64,14 +83,18 @@ 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" && npm run test:coverage 2>&1) || true
echo "$FRONTEND_REPORT"
(cd "$FRONTEND_DIR" && npm run test:coverage 2>&1) || true
# 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"
# Parse overall from vitest's json-summary report (more reliable than text table).
FRONTEND_SUMMARY="$FRONTEND_DIR/coverage/coverage-summary.json"
if [ -f "$FRONTEND_SUMMARY" ]; then
FRONTEND_LINE_COV=$(python3 -c "
import json
with open('$FRONTEND_SUMMARY') as f:
data = json.load(f)
pct = data['total']['lines']['pct']
print(f'{pct:.1f}')
" 2>/dev/null) || FRONTEND_LINE_COV=0
fi
else
echo "No frontend/ directory found; skipping frontend coverage"
@@ -115,6 +138,69 @@ if [ "$PASS" = "true" ]; then
fi
fi
# ── Write .coverage_report.json ───────────────────────────────────────────────
# This language-agnostic JSON file is consumed by the huskies `coverage` bot
# command to show per-file improvement targets without any language-specific
# logic in the server.
FRONTEND_SUMMARY="${FRONTEND_DIR}/coverage/coverage-summary.json"
OVERALL_VAL="$OVERALL" \
THRESHOLD_VAL="$THRESHOLD" \
RUST_COV_JSON="$RUST_COV_JSON" \
FRONTEND_SUMMARY="$FRONTEND_SUMMARY" \
PROJECT_ROOT="$PROJECT_ROOT" \
python3 - << 'PYEOF'
import json, os, sys
overall = float(os.environ["OVERALL_VAL"])
threshold = float(os.environ["THRESHOLD_VAL"])
rust_json = os.environ["RUST_COV_JSON"]
frontend_summary = os.environ["FRONTEND_SUMMARY"]
project_root = os.environ["PROJECT_ROOT"] + "/"
files = []
# Collect per-file Rust coverage from cargo llvm-cov JSON output.
if os.path.exists(rust_json):
with open(rust_json) as f:
data = json.load(f)
for file_entry in data["data"][0]["files"]:
path = file_entry["filename"]
if path.startswith(project_root):
path = path[len(project_root):]
pct = file_entry["summary"]["lines"]["percent"]
files.append({"path": path, "coverage": round(pct, 2)})
# Collect per-file frontend coverage from vitest json-summary output.
if os.path.exists(frontend_summary):
with open(frontend_summary) as f:
data = json.load(f)
for key, value in data.items():
if key == "total":
continue
path = key
if path.startswith(project_root):
path = path[len(project_root):]
pct = value["lines"]["pct"]
files.append({"path": path, "coverage": round(pct, 2)})
report = {
"overall": overall,
"threshold": threshold,
"files": sorted(files, key=lambda x: x["coverage"]),
}
output_path = os.path.join(project_root, ".coverage_report.json")
with open(output_path, "w") as f:
json.dump(report, f, indent=2)
f.write("\n")
print(f"Coverage report written: .coverage_report.json ({len(files)} files)")
PYEOF
# ── Cleanup temp files ────────────────────────────────────────────────────────
rm -f "$RUST_COV_JSON"
if [ "$PASS" = "false" ]; then
exit 1
fi