import io import json import sys import tempfile import time import unittest from pathlib import Path from cross_client_sync_gate import SCENARIOS, run_gate class CrossClientSyncGateTest(unittest.TestCase): def test_records_start_finish_and_summary_for_every_scenario(self) -> None: lines = [f" ✓ {scenario.id} {scenario.name} (0.0s)" for scenario in SCENARIOS] result, summary, events = self.run_fake(lines) self.assertEqual(0, result) self.assertEqual("pass", summary["overallStatus"]) self.assertEqual(["pass"] * len(SCENARIOS), [row["status"] for row in summary["scenarios"]]) self.assertEqual(len(SCENARIOS), sum(event["event"] == "scenario_start" for event in events)) self.assertEqual(len(SCENARIOS), sum(event["event"] == "scenario_finish" for event in events)) self.assertEqual("summary", events[-1]["event"]) def test_timeout_terminates_runner_and_records_every_scenario(self) -> None: command = [sys.executable, "-u", "-c", "import time; time.sleep(5)"] started = time.monotonic() result, summary, events = self.run_command(command, scenario_timeout=0.05) self.assertLess(time.monotonic() - started, 1.0) self.assertEqual(1, result) self.assertEqual("fail", summary["overallStatus"]) self.assertEqual("timeout", summary["scenarios"][0]["status"]) self.assertEqual( ["fail"] * (len(SCENARIOS) - 1), [row["status"] for row in summary["scenarios"][1:]], ) self.assertEqual("scenario_timeout", summary["scenarios"][0]["reason"]) self.assertEqual("summary", events[-1]["event"]) def test_nonzero_exit_marks_unreported_rows_failed(self) -> None: first = SCENARIOS[0] code = f"print({f' ✓ {first.id} {first.name} (0.0s)'!r}, flush=True); raise SystemExit(9)" result, summary, _ = self.run_command([sys.executable, "-u", "-c", code]) self.assertEqual(1, result) self.assertEqual("pass", summary["scenarios"][0]["status"]) self.assertTrue(all(row["status"] == "fail" for row in summary["scenarios"][1:])) self.assertEqual("runner_exit_9", summary["scenarios"][1]["reason"]) def run_fake(self, lines: list[str]) -> tuple[int, dict, list[dict]]: code = "import json, sys; [print(line, flush=True) for line in json.loads(sys.argv[1])]" return self.run_command([sys.executable, "-u", "-c", code, json.dumps(lines)]) def run_command( self, command: list[str], scenario_timeout: float = 1.0, ) -> tuple[int, dict, list[dict]]: events: list[dict] = [] with tempfile.TemporaryDirectory() as directory: summary_path = Path(directory) / "summary.json" result = run_gate( command, summary_path, scenario_timeout_seconds=scenario_timeout, total_timeout_seconds=2.0, emit=events.append, raw_output=io.StringIO(), ) summary = json.loads(summary_path.read_text(encoding="utf-8")) return result, summary, events if __name__ == "__main__": unittest.main()