Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 31s
- u2~u5: tests/integration/test_multi_mdx_regression.py — MDX_SET=(01..05) cached integration runs + status/structural/visual snapshots + full_mdx_coverage assertion (9 snapshots populated for 01-05). - u6~u11: F0 normalize / F1 V4 ranking / F2 slot_payload / F3 classifier-only AI / F4 layout / F5 final.html axis per MDX_SET. - u12: pyproject.toml — pytest-json-report>=1.5 in dev extras. - u13: .github/workflows/multi-mdx-regression.yml — pytest+artifact CI. - u14: scripts/update_status_board.py + tests/scripts/test_update_status_board.py — idempotent JSON marker updater (3 unit tests pass). - u15: PHASE-Z-PIPELINE-STATUS-BOARD.md — 30 F0-F5 × mdx01-05 markers initialized `?` + workflow wiring. Stage 4 verify: 59/59 PASS targeted (smoke 6 + updater 3 + integration 50), 386/386 PASS regression umbrella, 0 failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
2.7 KiB
Python
76 lines
2.7 KiB
Python
"""IMP-#91 u14 — idempotent status-board marker updater.
|
|
|
|
Reads a pytest-json-report artifact emitted by the IMP-91 CI workflow and
|
|
rewrites paired ``<!-- IMP-91:<axis>:<mdx> -->...<!-- /IMP-91 -->`` markers
|
|
inside the Phase Z status board with a single-character outcome symbol.
|
|
|
|
Pure functions (``parse_outcomes`` / ``update_board_text``) are exposed so
|
|
``tests/scripts/test_update_status_board.py`` can exercise the contract
|
|
without invoking pytest. The CLI just wires file IO around them so the
|
|
GitHub Actions step in u15 can call it deterministically. The updater is
|
|
additive: untouched markers stay; missing outcomes render ``?`` so a
|
|
collection failure is loud, not silent. [[feedback_auto_pipeline_first]]
|
|
[[feedback_artifact_status_naming]]
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Dict, Mapping, Tuple
|
|
|
|
AXIS_FROM_TEST = {
|
|
"test_normalize_snapshot_matches": "F0",
|
|
"test_v4_ranking_snapshot_matches": "F1",
|
|
"test_slot_payload_snapshot_matches": "F2",
|
|
"test_ai_classifier_snapshot_matches": "F3",
|
|
"test_layout_snapshot_matches": "F4",
|
|
"test_final_html_snapshot_matches": "F5",
|
|
}
|
|
SYMBOL = {"passed": "PASS", "failed": "FAIL", "error": "ERR", "skipped": "SKIP"}
|
|
NODEID_RE = re.compile(r"::(test_[a-z0-9_]+)\[(\d{2})\]$")
|
|
MARKER_RE = re.compile(
|
|
r"(<!-- IMP-91:(F[0-5]):(\d{2}) -->)(.*?)(<!-- /IMP-91 -->)", re.DOTALL
|
|
)
|
|
|
|
|
|
def parse_outcomes(report: Mapping[str, object]) -> Dict[Tuple[str, str], str]:
|
|
out: Dict[Tuple[str, str], str] = {}
|
|
for test in report.get("tests", []) or []:
|
|
m = NODEID_RE.search(str(test.get("nodeid", "")))
|
|
if not m:
|
|
continue
|
|
axis = AXIS_FROM_TEST.get(m.group(1))
|
|
if not axis:
|
|
continue
|
|
out[(axis, m.group(2))] = SYMBOL.get(str(test.get("outcome")), "?")
|
|
return out
|
|
|
|
|
|
def update_board_text(board: str, outcomes: Mapping[Tuple[str, str], str]) -> str:
|
|
def repl(match: "re.Match[str]") -> str:
|
|
key = (match.group(2), match.group(3))
|
|
symbol = outcomes.get(key, "?")
|
|
return f"{match.group(1)}{symbol}{match.group(5)}"
|
|
|
|
return MARKER_RE.sub(repl, board)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="IMP-91 status-board updater")
|
|
parser.add_argument("--report", required=True, type=Path)
|
|
parser.add_argument("--board", required=True, type=Path)
|
|
args = parser.parse_args()
|
|
report = json.loads(args.report.read_text(encoding="utf-8"))
|
|
outcomes = parse_outcomes(report)
|
|
args.board.write_text(
|
|
update_board_text(args.board.read_text(encoding="utf-8"), outcomes),
|
|
encoding="utf-8",
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|