diff --git a/scripts/publish_test_metrics.py b/scripts/publish_test_metrics.py index 6b1c52b..972df6b 100644 --- a/scripts/publish_test_metrics.py +++ b/scripts/publish_test_metrics.py @@ -6,6 +6,7 @@ from __future__ import annotations import json import os from pathlib import Path +import urllib.error import urllib.request import xml.etree.ElementTree as ET @@ -88,6 +89,32 @@ def _load_junit_cases(path: str) -> list[tuple[str, str]]: return cases +def _collapse_test_cases(test_cases: list[tuple[str, str]]) -> list[tuple[str, str]]: + """Return one final sample per test, keeping the last retry result.""" + + order: list[str] = [] + results: dict[str, str] = {} + for test_id, status in test_cases: + if test_id not in results: + order.append(test_id) + results[test_id] = status + return [(test_id, results[test_id]) for test_id in order] + + +def _totals_from_cases(test_cases: list[tuple[str, str]]) -> dict[str, int]: + """Build aggregate JUnit-style totals from final per-test statuses.""" + + totals = {"tests": len(test_cases), "failures": 0, "errors": 0, "skipped": 0} + for _, status in test_cases: + if status == "failed": + totals["failures"] += 1 + elif status == "error": + totals["errors"] += 1 + elif status == "skipped": + totals["skipped"] += 1 + return totals + + def _load_exit_code(path: str) -> int | None: if not path or not os.path.exists(path): return None @@ -108,9 +135,14 @@ def _post_text(url: str, payload: str) -> None: method="PUT", headers={"Content-Type": "text/plain"}, ) - with urllib.request.urlopen(req, timeout=10) as resp: - if resp.status >= 400: - raise RuntimeError(f"metrics push failed status={resp.status}") + try: + with urllib.request.urlopen(req, timeout=10) as resp: + if resp.status >= 400: + raise RuntimeError(f"metrics push failed status={resp.status}") + except urllib.error.HTTPError as exc: + body = exc.read().decode("utf-8", errors="replace").strip() + detail = f": {body}" if body else "" + raise RuntimeError(f"metrics push failed status={exc.code}{detail}") from exc def _read_http(url: str) -> str: @@ -247,8 +279,9 @@ def main() -> int: raise RuntimeError(f"missing junit file {junit_path}") coverage = _load_coverage(coverage_path) - totals = _load_junit(junit_path) - test_cases = _load_junit_cases(junit_path) + raw_test_cases = _load_junit_cases(junit_path) + test_cases = _collapse_test_cases(raw_test_cases) + totals = _totals_from_cases(test_cases) if test_cases else _load_junit(junit_path) test_exit_code = _load_exit_code(test_exit_code_path) docs_exit_code = _load_exit_code(docs_exit_code_path) source_files_total = _count_source_files(repo_root)