From cb1c41c6ea7440c82b45384a224d138bc91eb403 Mon Sep 17 00:00:00 2001 From: jenkins Date: Mon, 20 Apr 2026 08:43:21 -0300 Subject: [PATCH] ci(titan-iac): infer coverage/loc metrics from quality summary artifacts --- ci/scripts/publish_test_metrics.py | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/ci/scripts/publish_test_metrics.py b/ci/scripts/publish_test_metrics.py index 83ab707d..a0a7a9cf 100644 --- a/ci/scripts/publish_test_metrics.py +++ b/ci/scripts/publish_test_metrics.py @@ -159,6 +159,47 @@ def _summary_int(summary: dict, key: str) -> int: return 0 +def _infer_workspace_coverage_percent(summary: dict, default_xml: str) -> float: + """Infer workspace line coverage from quality summary coverage XML metadata.""" + results = summary.get("results", []) if isinstance(summary, dict) else [] + coverage_xml = default_xml + for result in results: + if not isinstance(result, dict): + continue + if str(result.get("name") or "").strip().lower() != "coverage": + continue + candidate = str(result.get("coverage_xml") or "").strip() + if candidate: + coverage_xml = candidate + break + xml_path = Path(coverage_xml) + if not xml_path.exists(): + return 0.0 + try: + root = ET.parse(xml_path).getroot() + line_rate = root.attrib.get("line-rate") + if line_rate is None: + return 0.0 + return float(line_rate) * 100.0 + except (ET.ParseError, OSError, ValueError): + return 0.0 + + +def _infer_source_lines_over_500(summary: dict) -> int: + """Infer over-limit source file count from hygiene issue payloads.""" + results = summary.get("results", []) if isinstance(summary, dict) else [] + for result in results: + if not isinstance(result, dict): + continue + if str(result.get("name") or "").strip().lower() not in {"hygiene", "loc", "smell"}: + continue + issues = result.get("issues") + if not isinstance(issues, list): + continue + return sum(1 for item in issues if isinstance(item, str) and item.startswith("file exceeds")) + return 0 + + def _normalize_result_status(value: str | None, default: str = "failed") -> str: """Map arbitrary check status text into canonical check result buckets.""" if not value: @@ -394,7 +435,11 @@ def main() -> int: status = "ok" if exit_code == 0 else "failed" summary = _load_summary(summary_path) workspace_line_coverage_percent = _summary_float(summary, "workspace_line_coverage_percent") + if workspace_line_coverage_percent <= 0: + workspace_line_coverage_percent = _infer_workspace_coverage_percent(summary, "build/coverage-unit.xml") source_lines_over_500 = _summary_int(summary, "source_lines_over_500") + if source_lines_over_500 <= 0: + source_lines_over_500 = _infer_source_lines_over_500(summary) sonarqube_report = _load_optional_json(os.getenv("QUALITY_GATE_SONARQUBE_REPORT", "build/sonarqube-quality-gate.json")) supply_chain_report = _load_optional_json(os.getenv("QUALITY_GATE_IRONBANK_REPORT", "build/ironbank-compliance.json")) supply_chain_required = os.getenv("QUALITY_GATE_IRONBANK_REQUIRED", "0").strip().lower() in {"1", "true", "yes", "on"}