ci(ariadne): enforce per-file coverage contract
This commit is contained in:
parent
bdb9b47291
commit
e36fc5229d
6
Jenkinsfile
vendored
6
Jenkinsfile
vendored
@ -196,11 +196,11 @@ if [ "${install_rc}" -eq 0 ]; then
|
||||
-m pytest -ra -vv --durations=20 --junitxml "${JUNIT_XML}"
|
||||
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
|
||||
if [ -f "${COVERAGE_JSON}" ] && [ -f scripts/check_coverage_contract.py ] && [ -f ci/coverage_contract.json ]; then
|
||||
python scripts/check_coverage_contract.py "${COVERAGE_JSON}" ci/coverage_contract.json
|
||||
if [ -f "${COVERAGE_JSON}" ] && [ -f scripts/check_coverage_contract.py ]; then
|
||||
python scripts/check_coverage_contract.py "${COVERAGE_JSON}" --source-root ariadne --threshold "${COVERAGE_MIN}"
|
||||
coverage_contract_rc=$?
|
||||
else
|
||||
echo "coverage contract check skipped: checker, contract, or coverage report missing"
|
||||
echo "coverage contract check skipped: checker or coverage report missing"
|
||||
fi
|
||||
fi
|
||||
printf '%s\n' "${docs_rc}" > build/docs-naming.rc
|
||||
|
||||
66
scripts/check_coverage_contract.py
Normal file
66
scripts/check_coverage_contract.py
Normal 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())
|
||||
Loading…
x
Reference in New Issue
Block a user