aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_project_ui.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_project_ui.py')
-rw-r--r--tests/test_project_ui.py72
1 files changed, 72 insertions, 0 deletions
diff --git a/tests/test_project_ui.py b/tests/test_project_ui.py
index 731811d..54c06bd 100644
--- a/tests/test_project_ui.py
+++ b/tests/test_project_ui.py
@@ -168,6 +168,78 @@ async def run(bins):
await pilot.press("escape")
await pilot.pause(0.2)
+ # -- a cross-binary jump is not a one-way door ----------------- #
+ # Nav history is per-binary, so arriving in another binary lands you
+ # in an empty history. Esc must fall through to the binary you came
+ # from, or a project search hit (and, since phase 3, following an
+ # import) strands you.
+ here, there = app._binary, (first if app._binary == second else second)
+ hops0 = len(app._hops)
+ target = app._index.search("main", limit=200)
+ tgt = next((h for h in target if h.binary == there), None)
+ if tgt is None:
+ check("cross-binary jump records a hop", False, "no hit in the other binary")
+ else:
+ app._switch_then_goto(tgt.binary, tgt.addr)
+ jumped = await settle(lambda: app._binary == there
+ and app._func_index is not None
+ and app._func_index.complete, 180)
+ check("a project hit switches to the other binary", jumped,
+ f"binary={app._binary} want={there}")
+ check("the jump records where it came from",
+ len(app._hops) == hops0 + 1 and app._hops[-1] == here,
+ f"hops={app._hops}")
+ # spend the local history first, then Esc must cross back
+ for _ in range(6):
+ if not app._hops or app._binary != there:
+ break
+ await pilot.press("escape")
+ await pilot.pause(0.6)
+ returned = await settle(lambda: app._binary == here, 180)
+ check("Esc crosses back to the binary the jump came from",
+ returned, f"binary={app._binary} want={here} hops={app._hops}")
+ check("the hop is consumed, not repeated",
+ not app._hops, f"hops={app._hops}")
+
+ # -- xrefs: callers in OTHER project binaries ------------------ #
+ # xrefs_to only sees this database, so an exported function looks
+ # unused from the inside even when the rest of the project calls it.
+ # The selection rule is "only for a symbol we actually export"; two
+ # executables share no linkage, so here it must stay quiet.
+ from idatui.app import XrefsScreen
+ fake = app._foreign_importers(app._cur.ea, "strrchr", None)
+ check("no cross-binary callers for a symbol this binary doesn't export",
+ fake == [], f"{fake}")
+
+ # The routing a real cross-binary caller takes: the dialog carries a
+ # (binary, addr) payload instead of a bare address, and choosing it
+ # goes through the same switch path as a search hit — hop included,
+ # so Esc comes back.
+ where_from = app._binary
+ other = first if where_from == second else second
+ hit = next((h for h in app._index.search("main", limit=200)
+ if h.binary == other), None)
+ if hit is None:
+ check("a cross-binary xref jumps to the other binary", False,
+ "no symbol found in the other binary")
+ else:
+ hops0 = len(app._hops)
+ app.push_screen(
+ XrefsScreen("xrefs to fake", [((hit.binary, hit.addr),
+ f"{hit.addr:08X} import [{hit.binary}]")]),
+ app._on_xref_chosen)
+ await settle(lambda: isinstance(app.screen, XrefsScreen), 20)
+ await pilot.press("enter")
+ jumped = await settle(lambda: app._binary == other
+ and app._func_index is not None
+ and app._func_index.complete, 180)
+ check("a cross-binary xref jumps to the other binary", jumped,
+ f"binary={app._binary} want={other}")
+ check("and records a hop so Esc returns",
+ len(app._hops) == hops0 + 1 and app._hops[-1] == where_from,
+ f"hops={app._hops}")
+ app._hops.clear()
+
# -- and the same toggle for strings --------------------------- #
from idatui.app import StringsPalette
await pilot.press("quotation_mark")