ci(metis): dedupe test case metrics before publish

This commit is contained in:
codex 2026-06-04 20:49:41 -03:00
parent 3f6be39da0
commit 5d1b28a3b0

View File

@ -6,6 +6,7 @@ from __future__ import annotations
import json import json
import os import os
from pathlib import Path from pathlib import Path
import urllib.error
import urllib.request import urllib.request
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@ -88,6 +89,32 @@ def _load_junit_cases(path: str) -> list[tuple[str, str]]:
return cases 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: def _load_exit_code(path: str) -> int | None:
if not path or not os.path.exists(path): if not path or not os.path.exists(path):
return None return None
@ -108,9 +135,14 @@ def _post_text(url: str, payload: str) -> None:
method="PUT", method="PUT",
headers={"Content-Type": "text/plain"}, headers={"Content-Type": "text/plain"},
) )
with urllib.request.urlopen(req, timeout=10) as resp: try:
if resp.status >= 400: with urllib.request.urlopen(req, timeout=10) as resp:
raise RuntimeError(f"metrics push failed status={resp.status}") 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: def _read_http(url: str) -> str:
@ -247,8 +279,9 @@ def main() -> int:
raise RuntimeError(f"missing junit file {junit_path}") raise RuntimeError(f"missing junit file {junit_path}")
coverage = _load_coverage(coverage_path) coverage = _load_coverage(coverage_path)
totals = _load_junit(junit_path) raw_test_cases = _load_junit_cases(junit_path)
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) test_exit_code = _load_exit_code(test_exit_code_path)
docs_exit_code = _load_exit_code(docs_exit_code_path) docs_exit_code = _load_exit_code(docs_exit_code_path)
source_files_total = _count_source_files(repo_root) source_files_total = _count_source_files(repo_root)