mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-10-31 18:21:58 +08:00
103 lines
2.8 KiB
Python
103 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate the aggregate JaCoCo XML report against repository thresholds."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Counter:
|
|
name: str
|
|
missed: int
|
|
covered: int
|
|
|
|
@property
|
|
def total(self) -> int:
|
|
return self.missed + self.covered
|
|
|
|
@property
|
|
def ratio(self) -> float:
|
|
if self.total == 0:
|
|
return 1.0
|
|
return self.covered / self.total
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Fail the build if aggregate JaCoCo coverage is below threshold.",
|
|
)
|
|
parser.add_argument("xml_report", type=Path, help="Path to jacoco.xml")
|
|
parser.add_argument("--minimum-line", type=float, required=True)
|
|
parser.add_argument("--minimum-branch", type=float, required=True)
|
|
return parser.parse_args()
|
|
|
|
|
|
def read_counters(xml_report: Path) -> dict[str, Counter]:
|
|
if not xml_report.is_file():
|
|
raise FileNotFoundError(f"JaCoCo XML report does not exist: {xml_report}")
|
|
|
|
root = ET.parse(xml_report).getroot()
|
|
counters: dict[str, Counter] = {}
|
|
for element in root.findall("counter"):
|
|
name = element.attrib["type"]
|
|
counters[name] = Counter(
|
|
name=name,
|
|
missed=int(element.attrib["missed"]),
|
|
covered=int(element.attrib["covered"]),
|
|
)
|
|
return counters
|
|
|
|
|
|
def format_percent(value: float) -> str:
|
|
return f"{value * 100:.2f}%"
|
|
|
|
|
|
def check(counter: Counter, minimum: float) -> bool:
|
|
print(
|
|
f"Aggregate {counter.name.lower()} coverage: "
|
|
f"{format_percent(counter.ratio)} "
|
|
f"({counter.covered}/{counter.total}), "
|
|
f"minimum {format_percent(minimum)}"
|
|
)
|
|
return counter.ratio >= minimum
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
try:
|
|
counters = read_counters(args.xml_report)
|
|
except Exception as exc: # noqa: BLE001 - build tool should show the exact cause.
|
|
print(f"Coverage check failed: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
required = {
|
|
"LINE": args.minimum_line,
|
|
"BRANCH": args.minimum_branch,
|
|
}
|
|
missing = [name for name in required if name not in counters]
|
|
if missing:
|
|
print(
|
|
f"Coverage check failed: missing counters {', '.join(missing)}",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
passed = True
|
|
for name, minimum in required.items():
|
|
passed = check(counters[name], minimum) and passed
|
|
|
|
if not passed:
|
|
print("Coverage check failed: aggregate coverage is below threshold.", file=sys.stderr)
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|