aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_project.py (follow)
Commit message (Collapse)AuthorAgeFilesLines
* Rebase MISTER EXO's ida-codemode port onto the current treeblasty29 hours1-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* tests: one front doorblasty44 hours1-0/+4
| | | | | | | | | | | | | | | | Fourteen test files, each its own __main__, and no way to run them but from memory -- so in practice you ran the one you were working on and hoped. Worse, nothing said which files need a licensed IDA and a real worker (minutes) and which are pure stdlib (milliseconds), so the cheap ones nobody ran either. tests/run.py runs the lot and prints one table. --fast selects only the suites that need nothing, which is 257 checks in half a second under any python3 -- that's the one you run between edits. The classification lives in the test files, not in a table here that would rot the first time someone adds a test: each declares NEEDS_IDA at module scope and run.py reads it with ast (it can't import them -- they run their suite at import). A file without the marker is a hard error rather than a silent guess.
* loading: say how to read a headerless blob (processor, base)blasty14 days1-0/+40
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A raw firmware dump has no format to detect, so IDA fell back to x86 at address 0. It doesn't fail — it opens, analyses, and finds nothing. An AArch64 image loaded this way gave 0 functions; told the truth it gives 35. ida-tui fw.bin --processor arm --base 0x8000000 and per binary in a project, which is what a multi-image firmware actually needs: {"path": "app.bin", "processor": "arm", "base": "0x8000000"} idapro.open_database() already accepted IDA command-line switches; nothing was passing any. Plumbed BinaryRef -> WorkerPool -> WorkerClient -> worker argv, plus a load_args for the single-binary path that has no project ref. base is written the way people say it (0x8000000, int or string, any base). IDA's -b is in PARAGRAPHS — -b1000 loads at 0x10000 — so BinaryRef.load_args converts, and a base that isn't 16-byte aligned is refused rather than silently landing 16x off. ida_args passes anything else through. Two bugs found by testing the whole path rather than the happy one: * Project.load() whitelisted path/label when normalising entries, so the load options were dropped the first time a project was reopened — set a processor, come back tomorrow, it's gone. * Re-passing the switches to an EXISTING database makes IDA refuse the open (rc != 0, no functions). The .i64 already records how the image was loaded, so the worker skips them once a database exists. My first guard checked splitext(path) + ".i64" and never fired, because IDA names it "<file>.i64" — keeping the extension. It checks both spellings now. Verified on a real AArch64 blob: fresh load 35 functions based at 0x8002440, reopen 35 again, and the CLI rejects an unaligned or non-numeric --base. tests: +6 project (options recorded, paragraph conversion, file round-trip, add() takes them, an ELF passes nothing, hex-string base). 39/0 project, 195/0 scenarios, 30/0 project UI, 36/0 index, 27/0 pool.
* projects: don't duplicate a binary that's already in the projectblasty2026-07-251-0/+35
| | | | | | | | | | | | | | | | | | | | | | | | | | | | `--project fw.json a.elf b.elf` where those are already listed appended them again, so the project grew a second copy on every launch — each duplicate then staged its own file, opened its own worker and got its own index entries. Dedupe on the RESOLVED SOURCE PATH, which is the only identity that's actually correct here: two different foo.elf from different directories are different binaries and must both be accepted (the label disambiguator already gives them foo.elf and foo.elf_2), while ./a.elf, /abs/a.elf and a symlink to it are all the same file and must collapse to one entry. * Project.by_source(path) — lookup by realpath. * Project.add() returns the existing entry instead of appending a duplicate. * Project.create() drops repeats on one command line too. * launch.py reports what it did: "added N binary(ies)" / "N already in the project (matched by path) — left alone", and only rewrites the file when something actually changed. Not deduped in _build_refs on load: remove() maps refs to entries by index, so collapsing there would desync them, and a hand-edited duplicate still works (labels disambiguate). tests/test_project.py +8 checks: re-add is a no-op, so are a relative spelling, a messy ../ path and a symlink; a same-named file from another directory IS added and gets a distinct label; create() drops repeats. 33/33. Verified on the real CLI: re-running the reported command leaves the project at 3 entries.
* projects: project model + binary staging (phase 1a)blasty2026-07-251-0/+158
First slice of multi-binary projects (docs/PROJECTS.md): the on-disk model, with no runtime wiring yet. idatui/project.py (stdlib-only, like domain/worker): * Project.load/create/save — an explicit JSON project file listing binaries; paths resolve relative to it, labels default to the basename and are disambiguated on collision (they name files). * A sidecar dir beside the project file (<stem>.idatui.d/) holds bin/ (staged binaries), their .i64 + scratch, and idx/ for phase 2. IDA opens the STAGED file, so nothing lands in the source tree — today targets/ carries ~244MB of IDA litter around ~13MB of binaries, much of it stale wedge files. * stage() copies rather than hardlinks. A hardlink is free but makes source and staged one inode, so an in-place rebuild (cp over the path truncates instead of replacing) would silently swap the bytes under an analysed DB with nothing to detect it. The unit test caught exactly that. A copy also leaves the sidecar self-contained once the sources are gone. * Re-staging a changed source drops its now-stale DB; sweep_scratch() clears the unpacked working files a hard-killed worker leaves behind (never the .i64). tests/test_project.py: 27 checks, pure stdlib (no IDA/textual/worker), <1s. docs/PROJECTS.md: the full design — the one-worker-per-DB constraint with measured costs (bash worker = 126MB RSS/117MB PSS; libcrypto's DB is 72MB, so residency is budgeted by MEMORY, not a worker count), the switch between "switching needs a live worker" and "searching doesn't (cached index)", and phases 1-4.