import json import tempfile import unittest from pathlib import Path from h8536.table_xrefs import analyze_table_xrefs, generate_table_xref_report, write_table_xrefs def reference(address: int) -> dict: return {"address": address} def instruction( address: int, mnemonic: str, operands: str = "", references: list[int] | None = None, text: str | None = None, ) -> dict: return { "address": address, "mnemonic": mnemonic, "operands": operands, "text": text or f"{mnemonic} {operands}".strip(), "references": [reference(item) for item in (references or [])], "targets": [], } def payload() -> dict: return { "call_graph": { "nodes": [ {"start": 0xC000, "end": 0xC0FF, "label": "loc_C000"}, {"start": 0xD000, "end": 0xD0FF, "label": "loc_D000"}, ], }, "serial_semantics": { "table_map_candidates": [ { "kind": "logical_table_map_candidate", "name_candidate": "primary_value_table_candidate", "confidence": "candidate-medium", "accesses": [{"instruction_address": 0xC004}], } ], }, "lcd_text": { "strings": [ { "address": 0x77F4, "text": "COMM LINK ITEM-1", "trimmed": "COMM LINK ITEM-1", "confidence": "high", "xrefs": [ { "address": 0x7804, "following_bsr": {"address": 0x7807, "target": 0x5A91}, } ], "xref_count": 1, } ], }, "lcd_driver": { "routines": [ { "start": 0x3F40, "end": 0x3F74, "role_hint": "lcd_wait_and_transfer", "roles": ["lcd_data_write"], } ], }, "instructions": [ instruction(0xC000, "MOV:G.W", "#H'0006, R3"), instruction(0xC004, "MOV:G.W", "@(-H'2000,R3), R0"), instruction(0xC008, "MOV:G.W", "R1, @(-H'1800,R4)"), instruction(0xD000, "MOV:G.W", "@H'F900, R2", [0xF900]), instruction(0xD004, "BSET.B", "#7, @(-H'1400,R5)"), instruction(0xD008, "CMP:G.W", "@H'E124, R0", [0xE124]), instruction(0xD00C, "MOV:G.W", "R3, @H'F922", [0xF922]), ], } class TableXrefsTest(unittest.TestCase): def test_reports_logical_direct_static_and_dynamic_accesses(self): analysis = analyze_table_xrefs(payload()) tables = {table["name"]: table for table in analysis["tables"]} primary = tables["primary_value_table_candidate"] self.assertEqual(primary["access_count"], 3) self.assertEqual(primary["read_count"], 3) self.assertEqual(primary["static_offsets"], [0, 6, 0x124]) static_access = primary["accesses"][0] self.assertEqual(static_access["index"], 6) self.assertEqual(static_access["logical_address"], 0xE006) self.assertEqual(static_access["function_label"], "loc_C000") self.assertEqual(static_access["semantic_candidates"][0]["confidence"], "candidate-medium") self.assertEqual(primary["accesses"][2]["kind"], "direct_logical_address_access") self.assertEqual(primary["accesses"][2]["logical_address"], 0xE124) current = tables["current_value_table_candidate"] self.assertEqual(current["access_count"], 2) self.assertEqual(current["dynamic_index_count"], 1) self.assertEqual(current["accesses"][0]["index"], "dynamic") self.assertEqual(current["accesses"][0]["index_register"], "R4") self.assertEqual(current["accesses"][1]["direct_address"], 0xF922) self.assertEqual(current["accesses"][1]["offset"], 2) self.assertEqual(current["write_count"], 2) flags = tables["flag_table_candidate"] self.assertEqual(flags["write_count"], 1) self.assertEqual(flags["dynamic_index_count"], 1) lcd_terms = { item["term"]: item for item in analysis["lcd_correlation"]["term_hits"] } self.assertEqual(lcd_terms["CONNECT"]["hit_count"], 0) self.assertEqual(lcd_terms["COMM LINK"]["hit_count"], 1) self.assertEqual( analysis["lcd_correlation"]["display_builder_targets"][0]["target"], 0x5A91, ) def test_text_report_names_dynamic_registers_and_functions(self): text = generate_table_xref_report(payload(), source_name="sample.json") self.assertIn("Table/Index Cross-Reference Report for sample.json", text) self.assertIn("primary_value_table_candidate H'E000", text) self.assertIn("offset H'0006 selector 0x003 -> H'E006", text) self.assertIn("offset H'0124 selector 0x092 -> H'E124", text) self.assertIn("offset H'0002 selector 0x001 at H'F922", text) self.assertIn("index dynamic via R4", text) self.assertIn("term 'CONNECT': no LCD/text candidate hits", text) self.assertIn("term 'COMM LINK': 1 candidate", text) self.assertIn("display builder xrefs: H'5A91:1", text) self.assertIn("loc_C000", text) def test_write_json_output(self): with tempfile.TemporaryDirectory() as tmp: input_path = Path(tmp) / "rom.json" output_path = Path(tmp) / "xrefs.json" input_path.write_text(json.dumps(payload()), encoding="utf-8") write_table_xrefs(input_path, output_path, as_json=True) written = json.loads(output_path.read_text(encoding="utf-8")) self.assertEqual(written["kind"], "table_xrefs") self.assertEqual(written["summary"]["access_count"], 6) self.assertEqual(written["source"], str(input_path)) if __name__ == "__main__": unittest.main()