ci(ariadne): enforce per-file coverage contract

This commit is contained in:
codex 2026-04-21 18:40:35 -03:00
parent bdb9b47291
commit e36fc5229d
2 changed files with 69 additions and 3 deletions

6
Jenkinsfile vendored
View File

@ -196,11 +196,11 @@ if [ "${install_rc}" -eq 0 ]; then
-m pytest -ra -vv --durations=20 --junitxml "${JUNIT_XML}" -m pytest -ra -vv --durations=20 --junitxml "${JUNIT_XML}"
tests_rc=$? tests_rc=$?
python -c "import json; payload=json.load(open('build/coverage.json', encoding='utf-8')); percent=(payload.get('summary') or {}).get('percent_covered'); print(f'Coverage summary: {percent:.2f}%' if percent is not None else 'Coverage summary unavailable')" || true python -c "import json; payload=json.load(open('build/coverage.json', encoding='utf-8')); percent=(payload.get('summary') or {}).get('percent_covered'); print(f'Coverage summary: {percent:.2f}%' if percent is not None else 'Coverage summary unavailable')" || true
if [ -f "${COVERAGE_JSON}" ] && [ -f scripts/check_coverage_contract.py ] && [ -f ci/coverage_contract.json ]; then if [ -f "${COVERAGE_JSON}" ] && [ -f scripts/check_coverage_contract.py ]; then
python scripts/check_coverage_contract.py "${COVERAGE_JSON}" ci/coverage_contract.json python scripts/check_coverage_contract.py "${COVERAGE_JSON}" --source-root ariadne --threshold "${COVERAGE_MIN}"
coverage_contract_rc=$? coverage_contract_rc=$?
else else
echo "coverage contract check skipped: checker, contract, or coverage report missing" echo "coverage contract check skipped: checker or coverage report missing"
fi fi
fi fi
printf '%s\n' "${docs_rc}" > build/docs-naming.rc printf '%s\n' "${docs_rc}" > build/docs-naming.rc

View File

@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Enforce Ariadne's per-file source coverage contract."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
def _source_files(root: Path) -> list[str]:
files: list[str] = []
for path in sorted(root.rglob("*.py")):
if "__pycache__" in path.parts:
continue
files.append(path.as_posix())
return files
def _coverage_percent(file_payload: object) -> float | None:
if not isinstance(file_payload, dict):
return None
summary = file_payload.get("summary")
if not isinstance(summary, dict):
return None
value = summary.get("percent_covered")
if isinstance(value, (int, float)):
return float(value)
return None
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("coverage_json")
parser.add_argument("--source-root", default="ariadne")
parser.add_argument("--threshold", type=float, default=95.0)
args = parser.parse_args()
coverage_path = Path(args.coverage_json)
source_root = Path(args.source_root)
payload = json.loads(coverage_path.read_text(encoding="utf-8"))
files = payload.get("files") if isinstance(payload, dict) else None
if not isinstance(files, dict):
print(f"{coverage_path}: missing files coverage map")
return 1
failures: list[str] = []
for source_file in _source_files(source_root):
percent = _coverage_percent(files.get(source_file))
if percent is None:
failures.append(f"{source_file}: missing from coverage report")
elif percent < args.threshold:
failures.append(f"{source_file}: {percent:.2f}% below {args.threshold:.2f}%")
if failures:
print("coverage contract failed:")
for failure in failures:
print(f" - {failure}")
return 1
print(f"coverage contract passed: {len(_source_files(source_root))} files >= {args.threshold:.2f}%")
return 0
if __name__ == "__main__":
raise SystemExit(main())