aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_launch.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-08-07 12:39:54 +0200
committerblasty <blasty@local>2026-08-07 12:40:14 +0200
commitc9208de05d8583b677117fe43c9d3567e89eb2ce (patch)
tree8f7b7487d9939be31b7c2b1a7932ee3a16c7403d /tests/test_launch.py
parentStop tracking 157MB of core dumps, and ignore them (diff)
downloadida-tui-c9208de05d8583b677117fe43c9d3567e89eb2ce.tar.gz
ida-tui-c9208de05d8583b677117fe43c9d3567e89eb2ce.tar.xz
ida-tui-c9208de05d8583b677117fe43c9d3567e89eb2ce.zip
Rebase MISTER EXO's ida-codemode port onto the current tree
Mechanical part of the port: the 27-file patch was cut against a base ~148 commits behind us, so it did not apply. Resolved 11 conflicts (all of them diff drift, not semantic clashes) and the three file deletions: - app.py: the patch re-inserted _do_rename/_do_name_addr/_seek_split etc. as "theirs" because our tree moved them to edit_ctl.py/trace_ctl.py. Kept ours and applied the real intent (WorkerClient->CodeModeClient, .call->.invoke, _open_worker_client->_open_database_client) at their current homes. - domain.py: kept Head as a NamedTuple -- the patch reverted it to a frozen dataclass, which the perf work measured at 2.9us vs 1.9us per row on a quarter-million-row walk. Dropped _fetch_output (no download_url under Code Mode) and its now-dead urllib/json imports. - pane.py: the patch's deletion swallowed our zellij support along with the worker-reaping block it meant to remove. Kept zellij, removed the reaping. - test_scenarios.py: the idb_save->save_database teardown hunk belongs to tests/_fixtures.py in our tree; applied it there and kept our pc_num_format scenario that the drift landed on. Three defects in the patch itself, fixed here: - It made "import idatui" hard-require ida_codemode, so every offline suite died at import -- including the pure ones (graph/index/trace) that are the house rule for "tests/run.py --fast". The import is now deferred and gated on the binding, which is also what lets the port's own contract tests inject a fake DatabaseHandle. - project.stage() inlined an ida_codemode.registry import and treated "library not installed" as "someone owns this database", which broke IDA-free project staging. Ownership lookup moved to codemode_client.database_owner(). - tests/test_codemode_client.py had no NEEDS_IDA marker, which tests/run.py rejects outright. Offline suite: 301 passed, 0 failed. Against master's 344 the whole delta is accounted for: -40 worker_client (module deleted), -18 launch sweep checks (behaviour deliberately removed) +3 guarding that it stays removed, +2 pool (GUI-save semantics), +13 new codemode_client contract tests. NOT yet done, and the port is not functional without it: the adapter is missing five operations our tree grew since the patch's base (flowchart, op_format, pc_nums, pc_num_format, survey_binary) and its "heads" predates back-walking and digest/expect.
Diffstat (limited to '')
-rw-r--r--tests/test_launch.py124
1 files changed, 21 insertions, 103 deletions
diff --git a/tests/test_launch.py b/tests/test_launch.py
index c6ed8f1..d57ee5f 100644
--- a/tests/test_launch.py
+++ b/tests/test_launch.py
@@ -1,12 +1,14 @@
#!/usr/bin/env python3
-"""The launcher's file handling -- the part that deletes things.
+"""The launcher's option handling, and the file handling it must NOT do.
-`_sweep_locks` runs automatically when a database fails to open, and it removes
-files next to the user's binary. That is exactly the kind of code that must not
-be tested by trying it, so it is tested here: which files it takes, which it
-must never take, and what it reports.
+The old `_sweep_locks` deleted `.id0/.id1/.id2/.nam/.til` next to the user's
+binary when a database failed to open. That was only defensible while the TUI
+exclusively owned a private worker; under Code Mode a GUI or another client may
+own the database, so the sweep is gone. Its tests are replaced by one that keeps
+it gone -- deleting a shared database's working files is unrecoverable, and this
+is the cheapest guard against someone reintroducing the "helpful" cleanup.
-Pure: no IDA, no worker, no Textual.
+Pure: no IDA, no Code Mode library, no Textual.
"""
from __future__ import annotations
@@ -20,7 +22,8 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
#: Read by tests/run.py (--fast skips every NEEDS_IDA file).
NEEDS_IDA = False
-from idatui.launch import _LOCK_SUFFIXES, _load_args, _sweep_locks # noqa: E402
+import idatui.launch as launch # noqa: E402
+from idatui.launch import _load_args # noqa: E402
PASS = FAIL = 0
@@ -41,100 +44,18 @@ def touch(*paths):
fh.write(b"x")
-def t_sweeps_the_scratch_files():
- """IDA unpacks a .i64 into .id0/.id1/.id2/.nam/.til while it is open; a
- hard-killed worker leaves them and the .i64 then refuses to reopen."""
- with tempfile.TemporaryDirectory() as d:
- binary = os.path.join(d, "echo")
- touch(binary, *[binary + s for s in _LOCK_SUFFIXES])
- n = _sweep_locks(binary)
- check("every unpacked scratch file is swept", n == len(_LOCK_SUFFIXES),
- f"swept {n} of {len(_LOCK_SUFFIXES)}")
- check("none of them survive",
- not any(os.path.exists(binary + s) for s in _LOCK_SUFFIXES))
- check("the binary itself is untouched", os.path.exists(binary))
+def t_no_lock_sweeping():
+ """The launcher must not delete database working files any more.
-
-def t_sweeps_by_stem_too():
- """IDA keys the scratch on the full name or the stem depending on how the
- database was created, so both are swept."""
- with tempfile.TemporaryDirectory() as d:
- binary = os.path.join(d, "prog.elf")
- stem = os.path.join(d, "prog")
- touch(binary, stem + ".id0", stem + ".nam", binary + ".id1")
- n = _sweep_locks(binary)
- check("scratch named after the stem is swept too", n == 3, f"n={n}")
- check("stem-keyed files are gone",
- not os.path.exists(stem + ".id0")
- and not os.path.exists(stem + ".nam"))
- check("full-name-keyed files are gone", not os.path.exists(binary + ".id1"))
- check("the binary itself is untouched", os.path.exists(binary))
-
-
-def t_never_the_database():
- """The .i64 IS the database. Nothing is saved unless idb_save was called, so
- deleting it throws away every rename and comment in the session."""
- with tempfile.TemporaryDirectory() as d:
- binary = os.path.join(d, "echo")
- db = binary + ".i64"
- stem_db = os.path.join(d, "echo.i64")
- touch(binary, db, binary + ".id0")
- _sweep_locks(binary)
- check("the .i64 is never swept", os.path.exists(db))
- check("nor the stem-keyed .i64", os.path.exists(stem_db))
- check(".i64 is not in the suffix list", ".i64" not in _LOCK_SUFFIXES,
- str(_LOCK_SUFFIXES))
-
-
-def t_never_the_input_itself():
- """`.til` is both an unpacked-DB suffix and the extension of an IDA type
- library, so `ida-tui mylib.til` used to sweep its own argument out of
- existence -- irreversibly, on a path that runs automatically when an open
- fails. Same for anything named *.id0/*.id1/*.id2/*.nam.
+ Code Mode's registry locks, health probes and IDA itself arbitrate database
+ ownership now. A sweep here would delete files out from under a live GUI.
"""
- for suf in _LOCK_SUFFIXES:
- with tempfile.TemporaryDirectory() as d:
- binary = os.path.join(d, "mylib" + suf)
- touch(binary)
- _sweep_locks(binary)
- check(f"a binary named *{suf} is not deleted by its own sweep",
- os.path.exists(binary), f"{binary} was removed")
-
-
-def t_relative_path_is_still_the_input():
- """The guard compares absolute paths -- a relative argument names the same
- file and must be protected the same way."""
- with tempfile.TemporaryDirectory() as d:
- cwd = os.getcwd()
- try:
- os.chdir(d)
- touch("mylib.til")
- _sweep_locks("mylib.til")
- check("a relative path to the input is protected too",
- os.path.exists("mylib.til"))
- finally:
- os.chdir(cwd)
-
-
-def t_missing_files_are_fine():
- with tempfile.TemporaryDirectory() as d:
- binary = os.path.join(d, "nothing-here")
- touch(binary)
- n = _sweep_locks(binary)
- check("sweeping with nothing to sweep reports 0", n == 0, f"n={n}")
- check("and does not raise", True)
-
-
-def t_leaves_the_neighbours_alone():
- with tempfile.TemporaryDirectory() as d:
- binary = os.path.join(d, "echo")
- other = os.path.join(d, "other.id0") # another binary's scratch
- src = os.path.join(d, "echo.c")
- touch(binary, other, src, binary + ".id0")
- _sweep_locks(binary)
- check("another binary's scratch is left alone", os.path.exists(other))
- check("unrelated neighbours are left alone", os.path.exists(src))
- check("our own scratch is still swept", not os.path.exists(binary + ".id0"))
+ check("_sweep_locks is gone", not hasattr(launch, "_sweep_locks"))
+ check("the scratch-suffix list is gone", not hasattr(launch, "_LOCK_SUFFIXES"))
+ src = open(launch.__file__, encoding="utf-8").read()
+ check("the launcher does not remove files at all",
+ "os.remove" not in src and "shutil.rmtree" not in src,
+ "launch.py deletes something again")
def t_load_args():
@@ -154,10 +75,7 @@ def t_load_args():
def main() -> int:
- for fn in (t_sweeps_the_scratch_files, t_sweeps_by_stem_too,
- t_never_the_database, t_never_the_input_itself,
- t_relative_path_is_still_the_input, t_missing_files_are_fine,
- t_leaves_the_neighbours_alone, t_load_args):
+ for fn in (t_no_lock_sweeping, t_load_args):
print(f"\n{fn.__name__}")
try:
fn()