"""Unit tests for the diff engine + an integration test over the committed golden pair.""" from __future__ import annotations from pathlib import Path import pytest from ams.diff import compute_diff, filter_by_owner from ams.render import render_text from ams.snapshot import Snapshot SNAP_DIR = Path(__file__).resolve().parents[1] / "snapshots" def _snap(**axes) -> Snapshot: base = {"binary": {"name": "x", "engine": "e", "compiler": "c"}, "types": [], "methods": [], "events": [], "fields": [], "struct_layout": [], "method_inheritance": [], "field_inheritance": []} base.update(axes) return Snapshot(base) # --- unit -------------------------------------------------------------------------------------- def test_types_added_removed_changed(): old = _snap(types=[ {"script_name": "ANIMO", "cpp_class": "CMC_Animo", "object_size": 108, "via_module_iface": False}, {"script_name": "OLD", "cpp_class": "CMC_Old", "object_size": 10, "via_module_iface": False}, ]) new = _snap(types=[ {"script_name": "ANIMO", "cpp_class": "CMC_Animo", "object_size": 128, "via_module_iface": False}, {"script_name": "NEW", "cpp_class": "CMC_New", "object_size": 20, "via_module_iface": False}, ]) d = compute_diff(old, new)["types"] assert [t["script_name"] for t in d["added"]] == ["NEW"] assert [t["script_name"] for t in d["removed"]] == ["OLD"] assert len(d["changed"]) == 1 assert d["changed"][0]["changes"]["object_size"] == [108, 128] def test_dual_multiarray_kept_distinct(): # same script_name, different via_module_iface -> two distinct, stable keys items = [ {"script_name": "MULTIARRAY", "cpp_class": "CMC_MultiArray", "object_size": 88, "via_module_iface": False}, {"script_name": "MULTIARRAY", "cpp_class": "CMC_MultiArray", "object_size": 88, "via_module_iface": True}, ] d = compute_diff(_snap(types=items), _snap(types=items))["types"] assert d == {"added": [], "removed": [], "changed": []} def test_method_id_change_and_move(): old = _snap(methods=[ {"owner": "CMC_Animo", "name": "SHOW", "id": 1}, {"owner": "CMC_Animo", "name": "PING", "id": 9}, ]) new = _snap(methods=[ {"owner": "CMC_Animo", "name": "SHOW", "id": 2}, # id changed {"owner": "CMC", "name": "PING", "id": 9}, # moved Animo -> base CMC ]) d = compute_diff(old, new) assert d["methods"]["changed"][0]["changes"]["id"] == [1, 2] assert d["moved_methods"] == [{"name": "PING", "from_owners": ["CMC_Animo"], "to_owners": ["CMC"]}] def test_field_type_change_and_owner_filter(): old = _snap(fields=[{"owner": "CMC_Animo", "name": "FPS", "type": "int", "order": 0}, {"owner": "CMC_Sound", "name": "VOLUME", "type": "int", "order": 0}]) new = _snap(fields=[{"owner": "CMC_Animo", "name": "FPS", "type": "double", "order": 0}, {"owner": "CMC_Sound", "name": "VOLUME", "type": "int", "order": 0}]) d = filter_by_owner(compute_diff(old, new), "CMC_Animo") assert d["fields"]["changed"][0]["changes"]["type"] == ["int", "double"] assert d["fields"]["added"] == [] and d["fields"]["removed"] == [] def test_render_no_diff(): out = render_text(compute_diff(_snap(), _snap())) assert "(no differences)" in out # --- integration over the committed golden pair ------------------------------------------------ @pytest.mark.skipif(not (SNAP_DIR / "PIKLIB8.dll.snapshot.json").exists(), reason="golden snapshots not present") def test_golden_pair_piklib_to_bloomoo(): old = Snapshot.load(str(SNAP_DIR / "PIKLIB8.dll.snapshot.json")) # VS6 new = Snapshot.load(str(SNAP_DIR / "bloomoodll.dll.snapshot.json")) # VS8 d = compute_diff(old, new) added_types = {t["script_name"] for t in d["types"]["added"]} assert {"GRBUFFER", "INTERNET"} <= added_types assert d["types"]["removed"] == [] size_changes = {c["item"]["script_name"]: c["changes"].get("object_size") for c in d["types"]["changed"]} assert size_changes.get("MOUSE") == [104, 128] # BlooMoo added Animo methods; none removed for Animo animo = filter_by_owner(d, "CMC_Animo") assert "GETFPS" in {m["name"] for m in animo["methods"]["added"]} assert animo["fields"]["added"] == [] # Animo's script fields are identical across the pair # rendering must not raise and must mention the new types text = render_text(d) assert "GRBUFFER" in text and "MOUSE" in text