diff options
| -rw-r--r-- | idatui/domain.py | 7 | ||||
| -rw-r--r-- | tests/test_scenarios.py | 42 |
2 files changed, 49 insertions, 0 deletions
diff --git a/idatui/domain.py b/idatui/domain.py index ecc6a80..9690a65 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -820,7 +820,14 @@ class ListingModel: Returns False if the backend cannot supply it, in which case the caller should stream as before -- this is an optimisation, not a new contract. + + IDEMPOTENT, and that is load-bearing: the view re-primes on every switch + back to the listing, so rebuilding here unconditionally put a ~900ms + segment_index in front of every Tab out of the decompiler. """ + with self._lock: + if self._done and self._heads: + return True # already indexed; re-priming is a no-op try: idx = self._prog.client.invoke( "segment_index", addr=hex(self.seg_start), end=hex(self.seg_end), diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index 0bbcf17..c08dc96 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -492,6 +492,48 @@ async def s_segment_index(c: Ctx): == [(h.ea, h.kind, h.size) for h in streamed._heads]) +@scenario("reprime_is_free") +async def s_reprime_is_free(c: Ctx): + """Switching back to the listing must not rebuild the row index. + + The listing view re-primes every time it is shown, and priming builds the + whole index. Building it is ~600-900ms, so doing it again on each Tab out of + the decompiler put nearly a second in front of a keystroke -- the listing + was still CORRECT, which is why every other test passed, it was just slow. + + Counting backend calls is the only way to see that, so this counts them. + """ + app = c.app + await c.open_biggest("listing") + lv = app.query_one(ListingView) + if lv.model is None: + c.check("listing model exists", False) + return + await c.wait(lambda: lv.model.complete, 30) + + client = app.program.client + original = type(client).invoke + seen: list[str] = [] + + def counting(self, operation, *a, **kw): + seen.append(operation) + return original(self, operation, *a, **kw) + + type(client).invoke = counting + try: + for _ in range(3): # decomp and back, three times + await c.press("tab") + await c.pause(0.05) + await c.press("tab") + await c.pause(0.05) + finally: + type(client).invoke = original + + rebuilds = seen.count("segment_index") + c.check("switching views never rebuilds the segment index", rebuilds == 0, + f"segment_index called {rebuilds}x during 3 view switches: {seen}") + + @scenario("skeleton_pages") async def s_skeleton_pages(c: Ctx): """The background grower loads text-less pages; reading one must fill it in. |
