#!/usr/bin/env python3 """Enforce per-file coverage thresholds from SlipCover JSON output.""" from __future__ import annotations import argparse import json from pathlib import Path def _normalize_report_path(path: str, cwd: Path) -> str: """Return a stable repository-relative path for a coverage report entry.""" candidate = Path(path) if candidate.is_absolute(): try: return candidate.relative_to(cwd).as_posix() except ValueError: return candidate.as_posix() return candidate.as_posix() def _production_files(root: Path, cwd: Path) -> set[str]: """List production Python files that must appear in the coverage report.""" required: set[str] = set() for path in root.rglob("*.py"): if path.name == "__init__.py" or "__pycache__" in path.parts: continue try: required.add(path.relative_to(cwd).as_posix()) except ValueError: required.add(path.as_posix()) return required def main() -> int: """Check each production file against a minimum coverage percentage.""" parser = argparse.ArgumentParser() parser.add_argument("coverage_json") parser.add_argument("--root", default="atlasbot") parser.add_argument("--threshold", type=float, default=95.0) args = parser.parse_args() data = json.loads(Path(args.coverage_json).read_text(encoding="utf-8")) files = data.get("files") if isinstance(data, dict) else {} cwd = Path.cwd().resolve() root = Path(args.root) root_path = (root if root.is_absolute() else cwd / root).resolve() root_prefix = root_path.relative_to(cwd).as_posix() if root_path.is_relative_to(cwd) else root_path.as_posix() covered_paths: set[str] = set() violations: list[str] = [] for path, payload in sorted(files.items()): normalized_path = _normalize_report_path(path, cwd) if not normalized_path.startswith(f"{root_prefix}/"): continue summary = payload.get("summary") if isinstance(payload, dict) else {} percent = summary.get("percent_covered") if isinstance(summary, dict) else None if not isinstance(percent, (int, float)): violations.append(f"{normalized_path}: coverage percent missing") continue covered_paths.add(normalized_path) if float(percent) < args.threshold: violations.append(f"{normalized_path}: {float(percent):.2f}% < {args.threshold:.2f}%") for path in sorted(_production_files(root_path, cwd) - covered_paths): violations.append(f"{path}: missing from coverage report") if violations: for violation in sorted(violations): print(violation) return 1 return 0 if __name__ == "__main__": raise SystemExit(main())