#!/usr/bin/env python3 """Enforce non-regressing Go coverage against a checked-in baseline.""" from __future__ import annotations import argparse import json import re import sys from collections import defaultdict from pathlib import Path COVER_RE = re.compile( r"^(?P.+?):(?P\d+)\.(?P\d+),(?P\d+)\.(?P\d+)\s+(?P\d+)\s+(?P\d+)$" ) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("--root", default=".") parser.add_argument("--coverprofile", required=True) parser.add_argument("--min-total", type=float, default=0.0) parser.add_argument("--baseline", required=True) parser.add_argument("--summary-json", default="") return parser.parse_args() def normalize_path(raw: str, root_name: str) -> str: marker = f"/{root_name}/" if marker in raw: return raw.split(marker, 1)[1] path = Path(raw) return path.as_posix() def parse_coverprofile(path: Path, root_name: str) -> tuple[dict[str, tuple[int, int]], float]: per_file: dict[str, list[int]] = defaultdict(lambda: [0, 0]) total_statements = 0 total_covered = 0 for line in path.read_text(encoding="utf-8").splitlines(): if not line or line.startswith("mode:"): continue match = COVER_RE.match(line) if not match: continue rel = normalize_path(match.group("path"), root_name) stmts = int(match.group("stmts")) count = int(match.group("count")) per_file[rel][0] += stmts total_statements += stmts if count > 0: per_file[rel][1] += stmts total_covered += stmts normalized = {k: (v[0], v[1]) for k, v in per_file.items()} total_pct = 100.0 if total_statements == 0 else (total_covered * 100.0 / total_statements) return normalized, total_pct def load_baseline(path: Path) -> dict[str, float]: baseline: dict[str, float] = {} for raw in path.read_text(encoding="utf-8").splitlines(): line = raw.strip() if not line or line.startswith("#"): continue parts = line.split("\t") if len(parts) < 2: continue rel = parts[0] try: pct = float(parts[1]) except ValueError: continue baseline[rel] = pct return baseline def main() -> int: args = parse_args() root = Path(args.root).resolve() coverprofile = Path(args.coverprofile).resolve() baseline = load_baseline(Path(args.baseline).resolve()) if not coverprofile.exists(): print(f"Coverage hygiene check failed: missing coverprofile {coverprofile}") return 1 per_file, total_pct = parse_coverprofile(coverprofile, root.name) violations: list[str] = [] for rel, floor in sorted(baseline.items()): stmts, covered = per_file.get(rel, (0, 0)) pct = 100.0 if stmts == 0 else (covered * 100.0 / stmts) if pct + 1e-9 < floor: violations.append(f"{rel}: {pct:.2f}% < baseline {floor:.2f}%") if total_pct + 1e-9 < args.min_total: violations.append(f"total coverage {total_pct:.2f}% < floor {args.min_total:.2f}%") summary = { "total_percent": round(total_pct, 3), "checked_files": len(baseline), "violations": len(violations), } if args.summary_json: Path(args.summary_json).write_text(json.dumps(summary, indent=2), encoding="utf-8") if violations: print("Coverage hygiene check failed:") for item in violations: print(item) return 1 print(f"Coverage hygiene checks: ok (total {total_pct:.2f}%)") return 0 if __name__ == "__main__": sys.exit(main())