More decompiling work
This commit is contained in:
157
tests/test_table_xrefs.py
Normal file
157
tests/test_table_xrefs.py
Normal file
@@ -0,0 +1,157 @@
|
||||
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 -> H'E006", text)
|
||||
self.assertIn("offset H'0124 -> H'E124", text)
|
||||
self.assertIn("offset H'0002 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()
|
||||
Reference in New Issue
Block a user