import io import json import tempfile import unittest from pathlib import Path from unittest.mock import patch from h8536.emulator.state_search import ( SearchCase, SearchResult, StatePatch, build_cases, classify_display, main, parse_address, parse_matrix_patch, parse_single_patch, target_matches, ) class EmulatorStateSearchTest(unittest.TestCase): def test_parse_patch_accepts_hex_address_and_value(self): patch = parse_single_patch("F730=0x41", size=1) self.assertEqual(patch, StatePatch(1, 0xF730, 0x41, "user")) self.assertEqual(patch.label(), "byte:H'F730=0x41") def test_parse_matrix_patch_expands_values(self): patches = parse_matrix_patch("E000=0x4080,0x8080", size=2) self.assertEqual(patches, [ StatePatch(2, 0xE000, 0x4080, "user"), StatePatch(2, 0xE000, 0x8080, "user"), ]) def test_parse_address_accepts_h_quote(self): self.assertEqual(parse_address("H'F970"), 0xF970) def test_connect_queue_preset_builds_small_rom_driven_matrix(self): parser = __import__("h8536.emulator.state_search", fromlist=["build_arg_parser"]).build_arg_parser() args = parser.parse_args(["--preset", "connect-queue"]) cases = build_cases(args) self.assertEqual(len(cases), 25) first = cases[0] self.assertEqual(first.pc, 0x2806) self.assertIn(StatePatch(2, 0xF970, 0x0000, "preset"), first.patches) self.assertIn(StatePatch(2, 0xE000, 0x0000, "preset"), first.patches) def test_custom_matrix_combines_fixed_and_matrix_patches(self): parser = __import__("h8536.emulator.state_search", fromlist=["build_arg_parser"]).build_arg_parser() args = parser.parse_args([ "--preset", "custom", "--pc", "0x2CB9", "--byte", "F730=0", "--matrix-word", "E000=0x4080,0x8080", ]) cases = build_cases(args) self.assertEqual(len(cases), 2) self.assertEqual(cases[0], SearchCase((StatePatch(1, 0xF730, 0, "user"), StatePatch(2, 0xE000, 0x4080, "user")), 0x2CB9)) def test_classify_display(self): self.assertEqual(classify_display(" CONNECT: OK | "), "ok") self.assertEqual(classify_display(" CONNECT:DXC-637 | "), "dxc") self.assertEqual(classify_display(" CONNECT:NOT ACT | "), "not-act") def test_target_matching(self): self.assertTrue(target_matches("ok", "ok")) self.assertTrue(target_matches("dxc", "any-connect")) self.assertTrue(target_matches("ok", "changed")) self.assertFalse(target_matches("not-act", "changed")) def test_cli_dry_run_lists_cases(self): stdout = io.StringIO() rc = main(["--dry-run", "--preset", "connect-branch", "--limit", "2"], stdout=stdout) self.assertEqual(rc, 0) output = stdout.getvalue() self.assertIn("preset=connect-branch cases=2", output) self.assertIn("case[0] pc=H'2CB9", output) def test_cli_json_output_uses_results_from_run_search(self): fake_result = SearchResult( case_index=0, patches=(StatePatch(2, 0xE000, 0x8080, "preset"),), pc=0x2CB9, steps=10, stopped_reason="stop_pc", final_pc=0xFFFF, display=" CONNECT: OK | ", line0=" CONNECT: OK ", outcome="ok", f730=0x81, e000=0x8080, f9b4=0, f9b9=0, ) with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) / "out.json" stdout = io.StringIO() with patch("h8536.emulator.state_search.run_search", return_value=[fake_result]): rc = main(["--preset", "custom", "--word", "E000=0x8080", "--json-out", str(path)], stdout=stdout) payload = json.loads(path.read_text(encoding="utf-8")) self.assertEqual(rc, 0) self.assertIn("hits=1", stdout.getvalue()) self.assertEqual(payload["hits"][0]["outcome"], "ok") if __name__ == "__main__": unittest.main()