import io import json import tempfile import unittest from pathlib import Path from h8536.serial_scenario import DEFAULT_ACK_FRAME, DEFAULT_ACK_TARGET, main class SerialScenarioTest(unittest.TestCase): def test_dry_run_summarizes_ack_aware_table_sweep(self): scenario = { "name": "unit-sweep", "steps": [ { "action": "table_sweep", "start": "0x000", "count": "0x002", "gap": 0.75, "ack_on": { "frames": ["07 80 40 20 90 2D"], "ack_frame": "05 00 40 00 00 1F", "max_acks": 8, "max_target_hits": 32, }, } ], } with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) / "scenario.json" path.write_text(json.dumps(scenario), encoding="utf-8") stdout = io.StringIO() exit_code = main([str(path), "--dry-run"], stdout=stdout) output = stdout.getvalue() self.assertEqual(exit_code, 0) self.assertIn("scenario=unit-sweep", output) self.assertIn("selectors=2 first=0x000 last=0x001", output) self.assertIn("ack_target=07 80 40 20 90 2D", output) self.assertIn("ack_frame=05 00 40 00 00 1F", output) self.assertIn("max_acks=8 max_target_hits=32", output) def test_default_ack_frames_match_current_rom_probe_candidate(self): self.assertEqual(DEFAULT_ACK_TARGET, bytes.fromhex("07804020902D")) self.assertEqual(DEFAULT_ACK_FRAME, bytes.fromhex("05004000001F")) def test_dry_run_summarizes_webcam_snapshot_options(self): scenario = { "name": "unit-camera", "steps": [ { "action": "send", "label": "camera_test", "frame": "00 00 00 80 80 5A", } ], } with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) / "scenario.json" snapshot_dir = Path(tmpdir) / "shots" path.write_text(json.dumps(scenario), encoding="utf-8") stdout = io.StringIO() exit_code = main( [ str(path), "--dry-run", "--snapshot-dir", str(snapshot_dir), "--camera-index", "1", "--snapshot-before-send", "--snapshot-delays", "0,0.25,1", ], stdout=stdout, ) output = stdout.getvalue() self.assertEqual(exit_code, 0) self.assertIn(f"snapshots={snapshot_dir}", output) self.assertIn("camera_index=1", output) self.assertIn("before_send=1", output) self.assertIn("delays=0,0.25,1", output) def test_dry_run_summarizes_listen_ack_until_quiet(self): scenario = { "name": "unit-quiet-ack", "steps": [ { "action": "listen_ack_until_quiet", "seconds": 12.0, "quiet_seconds": 0.9, "target_mode": "queued_reports", "ack_mode": "cmd5_selector", "max_acks": 16, } ], } with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) / "scenario.json" path.write_text(json.dumps(scenario), encoding="utf-8") stdout = io.StringIO() exit_code = main([str(path), "--dry-run"], stdout=stdout) output = stdout.getvalue() self.assertEqual(exit_code, 0) self.assertIn("step[1]=listen_ack_until_quiet", output) self.assertIn("seconds=12.000", output) self.assertIn("quiet=0.900s", output) self.assertIn("target_mode=queued_reports", output) self.assertIn("ack_mode=cmd5_selector", output) def test_dry_run_summarizes_respond_on_rules(self): scenario = { "name": "unit-respond-on", "steps": [ { "action": "listen_ack", "seconds": 2.0, "target_mode": "queued_reports", "ack_mode": "cmd5_selector", "respond_on": [ { "frames": ["00 00 13 40 00 09"], "send": "00 00 13 40 00 09", "label": "mirror_selector_0013_active_from_button", } ], } ], } with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) / "scenario.json" path.write_text(json.dumps(scenario), encoding="utf-8") stdout = io.StringIO() exit_code = main([str(path), "--dry-run"], stdout=stdout) output = stdout.getvalue() self.assertEqual(exit_code, 0) self.assertIn("respond_on=1", output) self.assertIn("send=00 00 13 40 00 09", output) self.assertIn("label=mirror_selector_0013_active_from_button", output) if __name__ == "__main__": unittest.main()