diff options
| author | blasty <blasty@local> | 2026-07-10 12:38:51 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-10 12:38:51 +0200 |
| commit | 50cbb7e982e03be863ad7f1979638520ffc95f55 (patch) | |
| tree | 4fb97251f1715e7a34dfa14535448bf01875e09f /tests | |
| parent | clipboard: keyboard copy (OSC 52 + tmux) since mouse-select is captured (diff) | |
| download | ida-tui-50cbb7e982e03be863ad7f1979638520ffc95f55.tar.gz ida-tui-50cbb7e982e03be863ad7f1979638520ffc95f55.tar.xz ida-tui-50cbb7e982e03be863ad7f1979638520ffc95f55.zip | |
struct editor: loud save errors + guard unsaved edits
A struct with a field IDA's C parser rejects (notably '__unused', a predefined
macro) failed to re-declare, but the failure was silent: the edit looked applied,
then reloading showed the old definition (lost work). Now:
- a failed declare shows a loud, styled 'save failed' status and, when it can,
names the offending reserved field ('__unused') instead of IDA's empty
'Failed to parse'; the type is left unchanged and the editor keeps your text.
- switching structs / starting new / closing with unsaved edits prompts to
discard first (ConfirmScreen), so edits aren't silently reloaded away.
Pilot: rejected save is loud/named/non-destructive; switching structs guards
unsaved edits. full suite 91/91.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_tui.py | 22 |
1 files changed, 22 insertions, 0 deletions
diff --git a/tests/test_tui.py b/tests/test_tui.py index 6384ec6..e990bef 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -193,6 +193,28 @@ async def run(db): check("Ctrl+S updates an existing struct in place", next((s.members for s in se._structs if s.name == sname), 0) == 3, "member count not 3") + # A save IDA's C parser rejects (the reserved field name '__unused') + # fails loudly and non-destructively, naming the offending field. + ta.text = f"struct {sname} {{ int a; char b[8]; long c; int __unused; }};" + await pilot.press("ctrl+s") + await wait_until( + pilot, lambda: "save failed" in str(se.query_one("#se-status").render()), 15) + st = str(se.query_one("#se-status").render()) + check("a rejected save fails loudly, naming the reserved field", + "save failed" in st and "__unused" in st, f"status={st!r}") + check("a rejected save leaves the struct unchanged", + next((s.members for s in se._structs if s.name == sname), 0) == 3, + "members changed") + check("a rejected save keeps your edited text", "__unused" in ta.text) + # Unsaved edits are guarded when switching structs. + other = next(i for i, s in enumerate(se._structs) if s.name != sname) + se.on_option_list_option_selected(type("E", (), {"option_index": other})()) + guard = await wait_until(pilot, lambda: isinstance(app.screen, ConfirmScreen), 10) + check("unsaved edits prompt before switching structs", guard, + f"screen={type(app.screen).__name__}") + await pilot.press("enter") # confirm discard -> loads the other (clean) + await wait_until( + pilot, lambda: isinstance(app.screen, StructEditor) and not se._is_dirty(), 15) # delete: 'd' asks to confirm, Enter deletes (server del_type). If the # server lacks del_type, the status reports it instead. idx = next(i for i, s in enumerate(se._structs) if s.name == sname) |
