58 lines
2.3 KiB
Python
58 lines
2.3 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Enforce per-file coverage thresholds from SlipCover JSON output."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import json
|
||
|
|
from datetime import date
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int: # noqa: C901
|
||
|
|
"""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)
|
||
|
|
parser.add_argument("--exceptions-file", default="testing/coverage_exceptions.json")
|
||
|
|
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 {}
|
||
|
|
exceptions_path = Path(args.exceptions_file)
|
||
|
|
per_file_thresholds: dict[str, float] = {}
|
||
|
|
if exceptions_path.exists():
|
||
|
|
payload = json.loads(exceptions_path.read_text(encoding="utf-8"))
|
||
|
|
expires_on = str(payload.get("expires_on") or "").strip()
|
||
|
|
if expires_on and date.today() > date.fromisoformat(expires_on):
|
||
|
|
print(f"coverage exceptions expired on {expires_on}: {exceptions_path}")
|
||
|
|
return 1
|
||
|
|
overrides = payload.get("per_file_thresholds")
|
||
|
|
if isinstance(overrides, dict):
|
||
|
|
for path, threshold in overrides.items():
|
||
|
|
if isinstance(path, str) and isinstance(threshold, (int, float)):
|
||
|
|
per_file_thresholds[path] = float(threshold)
|
||
|
|
violations: list[tuple[float, str]] = []
|
||
|
|
for path, payload in sorted(files.items()):
|
||
|
|
if not path.startswith(f"{args.root}/"):
|
||
|
|
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)):
|
||
|
|
continue
|
||
|
|
threshold = per_file_thresholds.get(path, float(args.threshold))
|
||
|
|
if float(percent) < threshold:
|
||
|
|
violations.append((float(percent), f"{path} (min {threshold:.2f}%)"))
|
||
|
|
|
||
|
|
if violations:
|
||
|
|
for percent, path in sorted(violations):
|
||
|
|
print(f"{path}: {percent:.2f}% < {args.threshold:.2f}%")
|
||
|
|
return 1
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|