atlas-iac/testing/quality_coverage.py
jenkins f77119b238 hermes: enforce per-file branch coverage in the quality gate
The coverage check read only line-rate, so a file could pass with weak
branch coverage. Load both Cobertura rates and fail any tracked file
below the 95% floor on either metric, failing closed when branch
evidence is absent. Prove enforcement end to end with a synthetic
fully-lined but branch-weak file failing run_profile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:10:17 -03:00

97 lines
3.4 KiB
Python

"""Per-file coverage threshold validation for quality-managed modules."""
from __future__ import annotations
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Any
def _load_rates(xml_path: Path, root: Path) -> dict[str, dict[str, float | None]]:
"""Load per-file line and branch percentages from a Cobertura XML report."""
tree = ET.parse(xml_path)
xml_root = tree.getroot()
source_roots = [
Path(node.text)
for node in xml_root.findall("./sources/source")
if node.text
]
rates: dict[str, dict[str, float | None]] = {}
for class_node in xml_root.findall(".//class"):
filename = class_node.attrib.get("filename")
line_rate = class_node.attrib.get("line-rate")
if not filename or line_rate is None:
continue
normalized = filename.replace("\\", "/")
if normalized.startswith("/"):
key = Path(normalized).relative_to(root).as_posix()
else:
key = normalized
for source_root in source_roots:
candidate = source_root / filename
if candidate.exists():
key = candidate.relative_to(root).as_posix()
break
branch_rate = class_node.attrib.get("branch-rate")
rates[key] = {
"line": float(line_rate) * 100.0,
"branch": None if branch_rate is None else float(branch_rate) * 100.0,
}
return rates
def run_check(contract: dict[str, Any], root: Path, xml_path: Path) -> list[str]:
"""Return human-readable issues for tracked files below the coverage floor.
Line and branch rates are both enforced per file so a single weak module
cannot hide behind aggregate suite coverage or line-only reporting.
"""
if not xml_path.exists():
return [f"coverage xml missing: {xml_path.relative_to(root)}"]
rates = _load_rates(xml_path, root)
minimum = float(contract.get("coverage", {}).get("minimum_percent", 95.0))
issues: list[str] = []
for relative_path in contract.get("coverage", {}).get("tracked_files", []):
normalized = relative_path.replace("\\", "/")
file_rates = rates.get(normalized)
if file_rates is None:
issues.append(f"coverage missing for tracked file: {relative_path}")
continue
for metric in ("line", "branch"):
percent = file_rates[metric]
if percent is None:
issues.append(
f"{metric} coverage missing for tracked file: {relative_path}"
)
elif percent + 1e-9 < minimum:
issues.append(
f"{metric} coverage below {minimum:.1f}%: "
f"{relative_path} ({percent:.1f}%)"
)
return issues
def compute_workspace_line_coverage(
contract: dict[str, Any],
root: Path,
xml_path: Path,
) -> float:
"""Compute mean line coverage across tracked files present in the XML."""
if not xml_path.exists():
return 0.0
rates = _load_rates(xml_path, root)
samples: list[float] = []
for relative_path in contract.get("coverage", {}).get("tracked_files", []):
normalized = relative_path.replace("\\", "/")
file_rates = rates.get(normalized)
if file_rates is not None:
samples.append(file_rates["line"])
if not samples:
return 0.0
return round(sum(samples) / len(samples), 3)