atlas-iac/testing/quality_coverage.py
jenkins e3ecc18d0e hermes: refresh capabilities and readiness per loop
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:03:50 -03:00

124 lines
4.2 KiB
Python

"""Per-file coverage threshold validation for quality-managed modules."""
from __future__ import annotations
import xml.etree.ElementTree as ET
import math
from dataclasses import dataclass
from pathlib import Path
from typing import Any
@dataclass(frozen=True)
class CoverageRates:
"""Validated line and branch percentages for one source file."""
line: float
branch: float
def _percentage(class_node: ET.Element, attribute: str) -> float:
"""Parse one finite Cobertura rate and fail closed outside its domain."""
raw = class_node.attrib.get(attribute)
if raw is None:
raise ValueError(f"coverage class missing {attribute}")
value = float(raw)
if not math.isfinite(value) or value < 0.0 or value > 1.0:
raise ValueError(f"coverage class has invalid {attribute}: {raw}")
return value * 100.0
def _load_rates(xml_path: Path, root: Path) -> dict[str, CoverageRates]:
"""Load validated per-file line and branch rates from Cobertura XML."""
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, CoverageRates] = {}
for class_node in xml_root.findall(".//class"):
filename = class_node.attrib.get("filename")
if not filename:
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
rates[key] = CoverageRates(
line=_percentage(class_node, "line-rate"),
branch=_percentage(class_node, "branch-rate"),
)
return rates
def _load_percentages(xml_path: Path, root: Path) -> dict[str, float]:
"""Load per-file line percentages for backward-compatible callers."""
return {path: rates.line for path, rates in _load_rates(xml_path, root).items()}
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.
The report is intentionally per-file so a single weak module cannot hide
behind aggregate suite coverage.
"""
if not xml_path.exists():
return [f"coverage xml missing: {xml_path.relative_to(root)}"]
try:
rates_by_path = _load_rates(xml_path, root)
except (ET.ParseError, OSError, UnicodeError, ValueError) as error:
return [f"coverage xml invalid: {error}"]
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("\\", "/")
rates = rates_by_path.get(normalized)
if rates is None:
issues.append(f"coverage missing for tracked file: {relative_path}")
continue
if rates.line + 1e-9 < minimum:
issues.append(
f"line coverage below {minimum:.1f}%: {relative_path} "
f"({rates.line:.1f}%)"
)
if rates.branch + 1e-9 < minimum:
issues.append(
f"branch coverage below {minimum:.1f}%: {relative_path} "
f"({rates.branch:.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
try:
percentages = _load_percentages(xml_path, root)
except (ET.ParseError, OSError, UnicodeError, ValueError):
return 0.0
samples: list[float] = []
for relative_path in contract.get("coverage", {}).get("tracked_files", []):
normalized = relative_path.replace("\\", "/")
percent = percentages.get(normalized)
if percent is not None:
samples.append(percent)
if not samples:
return 0.0
return round(sum(samples) / len(samples), 3)