aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_blob_ui.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-08-07 21:23:44 +0200
committerblasty <blasty@local>2026-08-07 21:23:44 +0200
commit32ab74b03e9a50274396b4a32aae8baece7628a1 (patch)
tree547d9d1efe77411faca8e3df3e2fdad147fa8c5d /tests/test_blob_ui.py
parentStruct editor: '/' fuzzy-filters the struct list (diff)
downloadida-tui-32ab74b03e9a50274396b4a32aae8baece7628a1.tar.gz
ida-tui-32ab74b03e9a50274396b4a32aae8baece7628a1.tar.xz
ida-tui-32ab74b03e9a50274396b4a32aae8baece7628a1.zip
tests: gate on real signals, not sleeps (117s -> 49s)
The suite spent its time in two kinds of guess. **Flat pauses.** ~140 `pause(d)` calls were 20.4s of the pilot's 62s, and `test_trace_ui` was 13.5s of `pilot.pause(1.0)` out of 19.6s. `Ctx.pause` is now `settle` (`d` is the upper bound, not the cost) and the other suites' sleeps became gates on the thing the check is about. `Ctx.sleep` stays for what a timer really drives. **Textual's keypress path.** `Pilot.press` calls `wait_for_idle` twice per key, which sleeps in 20ms granules until process time stops advancing -- 84ms per keypress here, 23s of the pilot's 43s. `_fixtures.fast_keys()` replaces it with the gate the suites already use: send the keys, then settle. Deleting the heuristic *without* that broke nine checks, so it was doing a job, badly. Four checks turned out to be riding on those sleeps: they read geometry or a repaint (`si.region`, `gv._minimap_rect()`, glyphs off `gv.render_line`, a repaint trace), and a settled app has not necessarily been laid out or painted. They now wait for the frame. The debounced function filter (`set_timer(0.08)`) likewise waits for its effect. Also fixed two waits on signals that never arrive: the comment wait in `rename` carried a `dec.loaded_ea == app._cur.ea` conjunct that cost 9s of timeout and then let the check pass vacuously, and `listing_view` -- the one entry under "Known-flaky" -- waited on `lst.total`, which is true before a single row exists. `--profile` reports, per scenario, seconds settling / waiting / pressing, and names any wait that expired with its line number. It is how the above was found and how the next 20s should be. Verified: 4 full `tests/run.py` runs, 800 passed each, 49.0-49.2s (was 117.4s); 4 consecutive pilot runs, 313 passed each, 21.2s (was 63.7s).
Diffstat (limited to 'tests/test_blob_ui.py')
-rw-r--r--tests/test_blob_ui.py16
1 files changed, 9 insertions, 7 deletions
diff --git a/tests/test_blob_ui.py b/tests/test_blob_ui.py
index 1055e5a..d4740d0 100644
--- a/tests/test_blob_ui.py
+++ b/tests/test_blob_ui.py
@@ -22,7 +22,9 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from textual.widgets import Input, Static # noqa: E402
from idatui.app import ConfirmScreen, IdaTui, ListingView # noqa: E402
-from _fixtures import staged, synthetic # noqa: E402
+from _fixtures import fast_keys, staged, synthetic # noqa: E402
+
+fast_keys() # ~85ms -> ~2ms per keypress; see _fixtures.fast_keys
from idatui._sync import settle # noqa: E402
PASS = FAIL = 0
@@ -135,7 +137,7 @@ async def run() -> int:
lst.focus()
await pilot.press("down")
await pilot.press("down")
- await pilot.pause(0.3)
+ await settle(app)
status2 = str(app.query_one("#status", Static).render())
check("the hint survives navigating", "no functions" in status2,
status2[:90])
@@ -167,7 +169,7 @@ async def run() -> int:
target = 0x4000 + PLANTED # a NOP we put there ourselves
lst.cursor = m.index_of_ea(target)
lst._scroll_cursor_into_view()
- await pilot.pause(0.1)
+ await settle(app, lambda: lst._cursor_ea() == target)
check("the cursor sits on the byte we aimed at",
lst._cursor_ea() == target,
f"{lst._cursor_ea():#x} want {target:#x}")
@@ -219,7 +221,7 @@ async def run() -> int:
# suite it can mutate the database freely.
lst.cursor = m.index_of_ea(0x4200)
lst._scroll_cursor_into_view()
- await pilot.pause(0.4)
+ await settle(app, lambda: lst._cursor_ea() == 0x4200)
ctop = lst.model.get(round(lst.scroll_offset.y)).ea
ccur = lst._cursor_ea()
old = lst.model
@@ -254,7 +256,7 @@ async def run() -> int:
far = 0x4000 + 0x600
lst.cursor = lst.model.index_of_ea(far)
lst._scroll_cursor_into_view()
- await pilot.pause(0.4)
+ await settle(app, lambda: lst._cursor_ea() == far)
top_before = lst.model.get(round(lst.scroll_offset.y)).ea
cur_before = lst._cursor_ea()
check("scrolled somewhere with rows above us",
@@ -286,7 +288,7 @@ async def run() -> int:
f"n={len(app._func_index)}")
lst.cursor = lst.model.index_of_ea(target)
lst._scroll_cursor_into_view()
- await pilot.pause(0.3)
+ await settle(app, lambda: lst._cursor_ea() == target)
mp = lst.model
await pilot.press("p")
await wait(lambda: lst.model is not mp and lst.model is not None,
@@ -313,7 +315,7 @@ async def run() -> int:
"1 function," in note and "nothing is lost" not in note,
note[:80])
await pilot.press("escape")
- await pilot.pause(0.3)
+ await settle(app, lambda: not isinstance(app.screen, ConfirmScreen))
check("declining leaves the binary open",
not isinstance(app.screen, ConfirmScreen) and app._cur is not None)