mirror of
https://github.com/elder-plinius/OBLITERATUS.git
synced 2026-08-30 06:30:37 +02:00
ci: adopt tiered contributor validation
This commit is contained in:
@@ -15,7 +15,7 @@ from typing import Any
|
||||
BASELINE_FLOORS = {
|
||||
"repository_statement": 75.0,
|
||||
"repository_branch": 60.0,
|
||||
"changed_line": 95.0,
|
||||
"changed_line": 50.0,
|
||||
"mature_cpu_statement": 94.0,
|
||||
"mature_cpu_branch": 84.0,
|
||||
"mutation_score": 85.0,
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Select deterministic PR tests from the exact diff and source risk map."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import fnmatch
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_POLICY = Path("ci/pr-test-policy.json")
|
||||
DEFAULT_RISK_MAP = Path("ci/test-risk-map.json")
|
||||
|
||||
|
||||
def _load_object(path: Path, label: str) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise ValueError(f"cannot read {label}: {exc}") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"{label} root must be an object")
|
||||
return value
|
||||
|
||||
|
||||
def _risk_map_at_ref(base_ref: str, risk_map: Path) -> dict[str, Any] | None:
|
||||
"""Read the base risk map when it exists so deleted sources remain owned."""
|
||||
|
||||
try:
|
||||
content = subprocess.run(
|
||||
["git", "show", f"{base_ref}:{risk_map.as_posix()}"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
try:
|
||||
value = json.loads(content)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(f"base test risk map is invalid JSON: {exc}") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("base test risk map root must be an object")
|
||||
return value
|
||||
|
||||
|
||||
def changed_paths(base_ref: str) -> list[str]:
|
||||
"""Return exact paths changed from base_ref to the checked-out head."""
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
["git", "rev-parse", "--verify", f"{base_ref}^{{commit}}"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
raw = subprocess.run(
|
||||
["git", "diff", "--name-only", "-z", base_ref, "HEAD"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
).stdout
|
||||
except subprocess.CalledProcessError as exc:
|
||||
raise ValueError(f"cannot calculate changed paths from {base_ref!r}") from exc
|
||||
return sorted(path.decode("utf-8") for path in raw.split(b"\0") if path)
|
||||
|
||||
|
||||
def _surface_tests(risk_map: dict[str, Any]) -> dict[str, set[str]]:
|
||||
result: dict[str, set[str]] = {}
|
||||
surfaces = risk_map.get("contract_surfaces")
|
||||
if not isinstance(surfaces, list):
|
||||
raise ValueError("test risk map requires contract_surfaces")
|
||||
for surface in surfaces:
|
||||
if not isinstance(surface, dict):
|
||||
raise ValueError("test risk map contract surface must be an object")
|
||||
paths = surface.get("paths")
|
||||
tests = surface.get("required_tests")
|
||||
if not isinstance(paths, list) or not isinstance(tests, list):
|
||||
raise ValueError("test risk map contract surface requires paths and required_tests")
|
||||
selected = {test for test in tests if isinstance(test, str)}
|
||||
for path in paths:
|
||||
if not isinstance(path, str):
|
||||
raise ValueError("test risk map source paths must be strings")
|
||||
result.setdefault(path, set()).update(selected)
|
||||
return result
|
||||
|
||||
|
||||
def _production_source(path: str) -> bool:
|
||||
return path == "app.py" or (path.startswith("obliteratus/") and path.endswith(".py"))
|
||||
|
||||
|
||||
def select_tests(
|
||||
paths: list[str],
|
||||
*,
|
||||
policy: dict[str, Any],
|
||||
risk_maps: list[dict[str, Any]],
|
||||
project_root: Path,
|
||||
) -> list[str]:
|
||||
"""Return stable existing CPU tests required by the changed paths."""
|
||||
|
||||
selected = set(policy.get("always_tests", []))
|
||||
infrastructure_tests = set(policy.get("infrastructure_tests", []))
|
||||
infrastructure_patterns = policy.get("infrastructure_paths", [])
|
||||
excluded_prefixes = tuple(policy.get("excluded_test_prefixes", []))
|
||||
mappings = [_surface_tests(risk_map) for risk_map in risk_maps]
|
||||
|
||||
for path in paths:
|
||||
if path.startswith("tests/") and path.endswith(".py"):
|
||||
selected.add(path)
|
||||
if any(fnmatch.fnmatchcase(path, pattern) for pattern in infrastructure_patterns):
|
||||
selected.update(infrastructure_tests)
|
||||
if _production_source(path):
|
||||
mapped = set().union(*(mapping.get(path, set()) for mapping in mappings))
|
||||
if not mapped:
|
||||
raise ValueError(
|
||||
f"changed production source is unmapped in head and base risk maps: {path}",
|
||||
)
|
||||
selected.update(mapped)
|
||||
|
||||
usable = [
|
||||
test
|
||||
for test in selected
|
||||
if isinstance(test, str)
|
||||
and test.startswith("tests/")
|
||||
and not test.startswith(excluded_prefixes)
|
||||
and (project_root / test).is_file()
|
||||
]
|
||||
if not usable:
|
||||
raise ValueError("PR test policy selected no runnable CPU tests")
|
||||
return sorted(usable)
|
||||
|
||||
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--base-ref", required=True, help="exact base commit or ref")
|
||||
parser.add_argument("--policy", type=Path, default=DEFAULT_POLICY)
|
||||
parser.add_argument("--risk-map", type=Path, default=DEFAULT_RISK_MAP)
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = _parser().parse_args()
|
||||
try:
|
||||
policy = _load_object(args.policy, "PR test policy")
|
||||
head_risk_map = _load_object(args.risk_map, "test risk map")
|
||||
base_risk_map = _risk_map_at_ref(args.base_ref, args.risk_map)
|
||||
tests = select_tests(
|
||||
changed_paths(args.base_ref),
|
||||
policy=policy,
|
||||
risk_maps=[head_risk_map, *([base_risk_map] if base_risk_map else [])],
|
||||
project_root=Path.cwd(),
|
||||
)
|
||||
except ValueError as exc:
|
||||
print(f"PR test selection failed: {exc}")
|
||||
return 1
|
||||
for test in tests:
|
||||
print(test)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user