"""Enforce separate line and branch floors from a coverage.py JSON report.""" from __future__ import annotations import argparse import json import math from pathlib import Path from typing import Any def validate_coverage( report: dict[str, Any], *, min_line: float, min_branch: float, ) -> list[str]: """Return human-readable failures for coverage totals below their floors.""" totals = report.get("totals") if not isinstance(totals, dict): return ["coverage report is missing the totals object"] failures: list[str] = [] metrics = ( ("line", "percent_statements_covered", min_line), ("branch", "percent_branches_covered", min_branch), ) for label, key, minimum in metrics: value = totals.get(key) if ( isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) ): failures.append(f"coverage report is missing numeric {key}") elif value < minimum: failures.append( f"{label} coverage {value:.2f}% is below the {minimum:.2f}% floor", ) return failures def _parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("report", type=Path, help="coverage.py JSON report") parser.add_argument("--min-line", type=float, required=True) parser.add_argument("--min-branch", type=float, required=True) return parser def main() -> int: args = _parser().parse_args() report = json.loads(args.report.read_text(encoding="utf-8")) failures = validate_coverage( report, min_line=args.min_line, min_branch=args.min_branch, ) if failures: for failure in failures: print(f"coverage gate failed: {failure}") return 1 totals = report["totals"] print( "coverage gate passed: " f"line={totals['percent_statements_covered']:.2f}% " f"branch={totals['percent_branches_covered']:.2f}%", ) return 0 if __name__ == "__main__": raise SystemExit(main())