diff options
| author | blasty <blasty@local> | 2026-07-09 20:39:12 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-09 20:39:12 +0200 |
| commit | 42dd62998989bc80ddc133cbc7c3ad6d809247db (patch) | |
| tree | b8d60c978ba2a4c2c48eb29b43c2c69caea7dc53 | |
| parent | decompiler: xref-select jumps to the reference line in pseudocode (diff) | |
| download | ida-tui-42dd62998989bc80ddc133cbc7c3ad6d809247db.tar.gz ida-tui-42dd62998989bc80ddc133cbc7c3ad6d809247db.tar.xz ida-tui-42dd62998989bc80ddc133cbc7c3ad6d809247db.zip | |
test: --stop-after for fast single-feature iteration; skip needless decompile
- test_tui.py: add --stop-after <substr>, ending the run once a matching check
has been evaluated so you can iterate on one feature without paying for the
expensive tail (e.g. 'search finds' -> 5.6s vs 34.6s full). Default unchanged.
- app: skip _decomp_line_for on a plain function-entry jump (line 0 in both
views), avoiding a needless decompile on every by-name navigation in the
decompiler pane.
full suite 60/60.
| -rw-r--r-- | idatui/app.py | 13 | ||||
| -rw-r--r-- | tests/test_tui.py | 23 |
2 files changed, 30 insertions, 6 deletions
diff --git a/idatui/app.py b/idatui/app.py index ba463e2..d1f3e01 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -1566,11 +1566,14 @@ class IdaTui(App): return model = self.program.disasm(fn.addr, fn.name) idx = 0 if ea == fn.addr else model.index_of_ea(ea) - # The disasm cursor alone doesn't position the pseudocode pane. When it's - # the active view, also resolve the target address to its pseudocode line - # (via the per-line /*0xEA*/ markers) so the jump lands on the reference - # there too (e.g. selecting an xref while in the decompiler view). - dec_idx = self._decomp_line_for(fn.addr, ea) if self._active == "decomp" else -1 + # The disasm cursor alone doesn't position the pseudocode pane. For a + # mid-function target with the decompiler active, resolve the address to + # its pseudocode line (via the per-line /*0xEA*/ markers) so the jump + # lands on the reference there too (e.g. selecting an xref). A plain + # function-entry jump is line 0 in both views -> skip the decompile. + dec_idx = -1 + if self._active == "decomp" and ea != fn.addr: + dec_idx = self._decomp_line_for(fn.addr, ea) self.app.call_from_thread(self._open_at, fn.addr, fn.name, idx, push, dec_idx) def _decomp_line_for(self, fn_addr: int, ea: int) -> int: diff --git a/tests/test_tui.py b/tests/test_tui.py index 4723a34..f74863a 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -2,10 +2,15 @@ """Headless pilot test for the Phase-1 TUI (no real terminal needed). python3 tests/test_tui.py --db <session_id> + python3 tests/test_tui.py --db <session_id> --stop-after "search finds" Drives the app via Textual's Pilot: boots, loads the function list, opens a function into the virtualized disasm view, scrolls it, and checks the cursor / status update. Uses ~/ida-venv python (has textual). + +--stop-after <substr> ends the run once a check whose name contains <substr> has +been evaluated, skipping the (often expensive) tail -- handy for fast iteration +on a single feature. The default (no flag) runs the whole suite. """ import asyncio import os @@ -20,6 +25,11 @@ from textual.widgets import DataTable, Input, OptionList, Static # noqa: E402 from rich.text import Text # noqa: E402 PASS = FAIL = 0 +STOP_AFTER = None # --stop-after <substr>: end the run once a matching check ran + + +class _StopSuite(Exception): + """Unwind cleanly once the requested --stop-after check has been evaluated.""" def check(name, cond, detail=""): @@ -30,6 +40,8 @@ def check(name, cond, detail=""): else: FAIL += 1 print(f" FAIL {name} {detail}") + if STOP_AFTER and STOP_AFTER in name: + raise _StopSuite async def wait_until(pilot, pred, timeout=30.0, step=0.02): @@ -757,12 +769,21 @@ async def run(db): def main(argv): + global STOP_AFTER db = None it = iter(argv) for a in it: if a == "--db": db = next(it) - asyncio.run(run(db)) + elif a == "--stop-after": + # Iterate fast on one feature: run up to (and including) the first + # check whose name contains this substring, then stop (skips the + # expensive tail). Full run is the default (no flag). + STOP_AFTER = next(it) + try: + asyncio.run(run(db)) + except _StopSuite: + print(f" … stopped after '{STOP_AFTER}'") print(f"\n{PASS} passed, {FAIL} failed") return 1 if FAIL else 0 |
