import io import json import tempfile import unittest from pathlib import Path from h8536.state_map_runner import ( CONNECT_FORCE_PRESETS, StateMapEvent, analyze_events, format_analysis_report, main, parse_bench_log, ) class StateMapRunnerTest(unittest.TestCase): def test_force_presets_match_expected_selector_zero_words(self): self.assertEqual(CONNECT_FORCE_PRESETS["dxc"][0], bytes.fromhex("04000040001E")) self.assertEqual(CONNECT_FORCE_PRESETS["dxc"][1], 0x4080) self.assertEqual(CONNECT_FORCE_PRESETS["ok"][0], bytes.fromhex("0400008000DE")) self.assertEqual(CONNECT_FORCE_PRESETS["ok"][1], 0x8080) self.assertEqual(CONNECT_FORCE_PRESETS["both"][0], bytes.fromhex("040000C0009E")) self.assertEqual(CONNECT_FORCE_PRESETS["both"][1], 0xC080) def test_analyzes_successful_selector_zero_readback(self): events = [ StateMapEvent("rx", bytes.fromhex("0780C060205D"), timestamp_ms=1000), StateMapEvent("tx", bytes.fromhex("0400008000DE"), timestamp_ms=1006), StateMapEvent("tx", bytes.fromhex("01000000005B"), timestamp_ms=1060), StateMapEvent("rx", bytes.fromhex("04001280804C"), timestamp_ms=1070), ] analysis = analyze_events(events, expected_word=0x8080) self.assertEqual(analysis["outcome"]["name"], "selector_zero_retained") self.assertEqual(analysis["direct_readbacks"][0]["value"], 0x8080) self.assertTrue(analysis["direct_readbacks"][0]["matches_expected"]) def test_warns_when_command1_readback_is_between_trigger_and_force(self): events = [ StateMapEvent("rx", bytes.fromhex("07804040A07D"), timestamp_ms=1000), StateMapEvent("tx", bytes.fromhex("01000000005B"), timestamp_ms=1005), StateMapEvent("tx", bytes.fromhex("04000040001E"), timestamp_ms=1010), ] analysis = analyze_events(events, expected_word=0x4080) self.assertIn("command1_readback_between_trigger_and_force_can_spend_token", analysis["warnings"]) self.assertEqual(analysis["outcome"]["name"], "force_not_proven") def test_parse_bench_log_uses_detect_lines_for_rx_and_tx_chunks_for_host_frames(self): log = ( "00:00:01.000 RX 006 bytes 07 80 C0 60 20 5D\n" "00:00:01.000 DETECT visible_C0_6020_family_candidate 07 80 C0 60 20 5D\n" "00:00:01.006 TX 006 bytes 04 00 00 80 00 DE\n" ) events = parse_bench_log(log) self.assertEqual([(event.direction, event.frame.hex()) for event in events], [ ("rx", "0780c060205d"), ("tx", "0400008000de"), ]) def test_format_report_mentions_outcome_and_readback_value(self): analysis = analyze_events( [ StateMapEvent("rx", bytes.fromhex("0780C060205D"), timestamp_ms=1000), StateMapEvent("tx", bytes.fromhex("0400008000DE"), timestamp_ms=1006), StateMapEvent("rx", bytes.fromhex("04001280804C"), timestamp_ms=1070), ], expected_word=0x8080, ) report = format_analysis_report(analysis) self.assertIn("outcome=selector_zero_retained", report) self.assertIn("value=0x8080 expected", report) def test_cli_dry_run_prints_state_map_sequence(self): stdout = io.StringIO() rc = main(["--dry-run", "--preset", "dxc", "--prime-frame", "01 80 40 40 30 EB"], stdout=stdout) output = stdout.getvalue() self.assertEqual(rc, 0) self.assertIn("PT2 state-map proof runner", output) self.assertIn("force=04 00 00 40 00 1E", output) self.assertIn("expected_e0000=0x4080", output) self.assertIn("prime=01 80 40 40 30 EB", output) def test_cli_analyze_log_writes_json(self): log = ( "00:00:01.000 DETECT visible_C0_6020_family_candidate 07 80 C0 60 20 5D\n" "00:00:01.006 TX 006 bytes 04 00 00 80 00 DE\n" "00:00:01.070 DETECT table_readback_candidate 04 00 12 80 80 4C\n" ) with tempfile.TemporaryDirectory() as tmpdir: log_path = Path(tmpdir) / "capture.txt" json_path = Path(tmpdir) / "analysis.json" log_path.write_text(log, encoding="utf-8") stdout = io.StringIO() rc = main(["--analyze-log", str(log_path), "--preset", "ok", "--json-out", str(json_path)], stdout=stdout) payload = json.loads(json_path.read_text(encoding="utf-8")) self.assertEqual(rc, 0) self.assertIn("outcome=selector_zero_retained", stdout.getvalue()) self.assertEqual(payload["outcome"]["name"], "selector_zero_retained") if __name__ == "__main__": unittest.main()