diff --git a/scripts/pipeline.py b/scripts/pipeline.py index 1d7ca3df..7f5a982f 100644 --- a/scripts/pipeline.py +++ b/scripts/pipeline.py @@ -144,6 +144,15 @@ def check_consistency(verify_output: str, pack_output: str) -> bool: print("\n--- 5/8 consistency check ---") all_ok = True + if not v or not p: + # Nothing parsed is nothing compared: a changed output format would + # otherwise pass the check without a single platform in it. + print(f" parsed {len(v)} verify and {len(p)} pack results: nothing to compare") + all_ok = False + for p_label in sorted(p): + if not any(_match_key(p_label) & _match_key(v_label) for v_label in v): + print(f" {p_label}: pack with no verify result") + all_ok = False for v_label, (v_ok, v_total) in sorted(v.items()): # Match by normalized name overlap. Platform display labels and @@ -444,10 +453,14 @@ def main(): # Step 3b: Destinations both layers claim, and how each was settled. The # ones the pack settles by itself must stay at zero cost; the rest name an # upstream declaration no build can repair, so this reports and never gates. - run( + # A crash is not a report: without --strict slots.py exits 0 on every + # conflict, so a non-zero code means it never finished reading. + ok, _ = run( [sys.executable, "scripts/slots.py"], "3b/8 slot arbitration", ) + results["slots"] = ok + all_ok = all_ok and ok # Step 4: Generate packs pack_output = "" diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index b448945b..8c24bc3c 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -473,6 +473,29 @@ class PipelineRegressions(unittest.TestCase): with contextlib.redirect_stdout(io.StringIO()): self.assertFalse(pipeline.check_consistency(verify, pack)) + def test_nothing_parsed_is_not_consistent(self): + """A changed output format must not pass with no platform compared.""" + with contextlib.redirect_stdout(io.StringIO()): + self.assertFalse(pipeline.check_consistency("RetroArch — 2/2 OK", "")) + self.assertFalse(pipeline.check_consistency("", "")) + + def test_pack_without_verify_result_is_a_failure(self): + verify = "RetroArch: 2/2 OK" + pack = "\n".join( + [ + "Generating pack for RetroArch [source=full]...", + " a.zip: 2 files packed (2 baseline + 0 from cores), 2/2 files OK", + "Generating pack for Batocera [source=full]...", + " b.zip: 2 files packed (2 baseline + 0 from cores), 2/2 files OK", + ] + ) + with contextlib.redirect_stdout(io.StringIO()): + self.assertFalse(pipeline.check_consistency(verify, pack)) + + def test_slot_step_reaches_the_summary(self): + source = Path(pipeline.__file__).read_text(encoding="utf-8") + self.assertIn('results["slots"] = ok', source) + def test_every_refresh_failure_reaches_pipeline_exit_status(self): for failed_label in ( "2/8 refresh data directories",