import io import json import tempfile import unittest from pathlib import Path from h8536.eeprom_layout import ( analyze_eeprom_layout, format_text_report, main, write_eeprom_layout, ) def reference(address: int) -> dict: return {"address": address} def instruction(address: int, mnemonic: str, operands: str = "", refs: list[int] | None = None) -> dict: return { "address": address, "mnemonic": mnemonic, "operands": operands, "text": f"{mnemonic} {operands}".strip(), "references": [reference(item) for item in (refs or [])], "targets": [], } def payload() -> dict: return { "instructions": [ instruction(0x410C, "MOV:G.W", "R5, @(-H'0C00,R0)"), instruction(0x41E8, "MOV:G.W", "R5, @(-H'0850,R1)"), instruction(0x40FA, "CMP:G.W", "#H'6B6F, @H'F402", [0xF402]), instruction(0xBD3D, "MOV:G.B", "@(-H'3A9B,R4), R1"), instruction(0xBD45, "MOV:G.W", "R0, @(-H'0C00,R1)"), instruction(0xBD49, "BTST.B", "#7, @H'F76E", [0xF76E]), ], "call_graph": { "nodes": [ {"start": 0x4000, "end": 0x4216, "label": "loc_4000"}, {"start": 0xBD0E, "end": 0xBD67, "label": "loc_BD0E"}, ], }, } def rom_bytes() -> bytes: rom = bytearray([0xFF] * 0xCB00) rom[0xC564 : 0xC564 + 0x400] = b"\x00" * 0x400 rom[0xC564 + 0x015 * 2 : 0xC564 + 0x015 * 2 + 2] = b"\x40\xAA" rom[0xC966:0xC968] = b"\x6B\x6F" rom[0xC968:0xC96A] = b"\xFE\x00" rom[0xCA0E:0xCA10] = b"\x80\x00" return bytes(rom) class EepromLayoutTest(unittest.TestCase): def test_analysis_extracts_factory_defaults_records_and_selector_mapping(self): with tempfile.TemporaryDirectory() as tmp: rom_path = Path(tmp) / "rom.bin" rom_path.write_bytes(rom_bytes()) analysis = analyze_eeprom_layout(payload(), rom_path=rom_path) self.assertEqual(analysis["kind"], "eeprom_layout") self.assertEqual(analysis["factory_defaults"]["entries"][1]["factory_word_hex"], "0x6B6F") self.assertEqual(analysis["persistent_records"][3]["ram_base_hex"], "H'F7C8") self.assertEqual(analysis["persistent_records"][3]["eeprom_base_hex"], "0x300") mapped = analysis["serial_persistence_mapping"]["entries"][0] self.assertEqual(mapped["selector_hex"], "0x015") self.assertEqual(mapped["shadow_address_hex"], "H'F4AA") self.assertEqual(mapped["eeprom_word_offset_hex"], "0xAA") self.assertEqual(mapped["factory_word_hex"], "0x8000") def test_text_report_mentions_core_layout(self): with tempfile.TemporaryDirectory() as tmp: rom_path = Path(tmp) / "rom.bin" rom_path.write_bytes(rom_bytes()) analysis = analyze_eeprom_layout(payload(), rom_path=rom_path) text = format_text_report(analysis) self.assertIn("Persistent 8-Byte Records", text) self.assertIn("selector 0x015", text) self.assertIn("F76E", text) def test_write_json_output(self): with tempfile.TemporaryDirectory() as tmp: input_path = Path(tmp) / "rom.json" output_path = Path(tmp) / "eeprom.json" rom_path = Path(tmp) / "rom.bin" input_path.write_text(json.dumps(payload()), encoding="utf-8") rom_path.write_bytes(rom_bytes()) write_eeprom_layout(input_path, output_path, rom_path=rom_path, as_json=True) written = json.loads(output_path.read_text(encoding="utf-8")) self.assertEqual(written["kind"], "eeprom_layout") self.assertEqual(written["summary"]["persistent_record_count"], 16) def test_cli_writes_text_report(self): with tempfile.TemporaryDirectory() as tmp: input_path = Path(tmp) / "rom.json" output_path = Path(tmp) / "eeprom.txt" rom_path = Path(tmp) / "rom.bin" input_path.write_text(json.dumps(payload()), encoding="utf-8") rom_path.write_bytes(rom_bytes()) stdout = io.StringIO() rc = main([str(input_path), "--rom", str(rom_path), "--out", str(output_path)], stdout=stdout) self.assertEqual(rc, 0) self.assertIn("wrote", stdout.getvalue()) self.assertIn("EEPROM Layout Report", output_path.read_text(encoding="utf-8")) if __name__ == "__main__": unittest.main()