Skip to content

fix(controller): stop leaking test tempdirs (closes #273) - #274

Merged
WaylandYang merged 2 commits into
mainfrom
fix/test-tempdir-leak-273
Jul 31, 2026
Merged

fix(controller): stop leaking test tempdirs (closes #273)#274
WaylandYang merged 2 commits into
mainfrom
fix/test-tempdir-leak-273

Conversation

@WaylandYang

Copy link
Copy Markdown
Contributor

Closes #273.

The leak

test_state and the two branch_slot_* tests in crates/forkd-controller/src/http.rs each built their scratch directory like this:

let td = tempfile::TempDir::new().unwrap();
// Leak the TempDir so it survives the test (Drop deletes the dir).
std::mem::forget(td);

It keeps the directory alive, but Drop never runs — so every cargo test -p forkd-controller orphans one directory per construction in $TMPDIR. The issue reported ~10 per run; on today's test count it is 56.

The fix

Bind the TempDir to the state rather than leaking it — the "cheapest" option from the issue. AppState gains a #[cfg(test)] pub _tempdir: Option<tempfile::TempDir> field, declared last so it drops after registry. Dropping the final Arc now reaps the directory.

This mirrors the _td: TempDir ownership pattern that tests/http_integration.rs already uses, so the unit tests and the integration tests handle scratch dirs the same way.

Two cleanups carried along:

  • The duplicated inline AppState literals in branch_slot_global_cap_blocks and branch_slot_capacity_recovers_on_drop collapse into a shared test_state_with_cap(cap); test_state() delegates to it. Net −24 lines, and there is now one place to update when AppState grows a field.
  • prewarm_scratch_dir moves off the shared $TMPDIR/forkd-test-prewarm into the per-test tempdir, so nothing the daemon writes escapes it.

Verification

Run on Linux, using the reporter's own measurement (ls -d /tmp/.tmp* | wc -l after a cargo test -p forkd-controller in a fresh container):

tests leftover /tmp/.tmp*
before 71 + 7 passed 56
after 71 + 7 passed 0

Full CI matrix green locally: cargo fmt --all -- --check, cargo clippy --all-targets --all-features -- -D warnings (no warnings), cargo build --all, cargo test --all.

Notes

  • No CHANGELOG entry — this is test-only hygiene with no user-facing effect.
  • #[cfg(test)] only applies to the lib crate's own unit-test build, so the field does not exist for integration tests or downstream consumers; the single production construction site in lib.rs gets a matching #[cfg(test)] _tempdir: None.
  • Swept the rest of the tree for the same class of leak: no remaining mem::forget, ManuallyDrop, TempDir::into_path(), or .keep() anywhere in crates/.

🤖 Generated with Claude Code

The three `test_state`-style constructors in `http.rs` called
`std::mem::forget` on their `TempDir` to keep the scratch directory
alive for the duration of the test. That works, but Drop never runs,
so every `cargo test -p forkd-controller` orphaned one directory per
construction in $TMPDIR -- 56 of them per run on today's test count.

Bind the `TempDir` to the state instead: a `#[cfg(test)] _tempdir`
field on `AppState`, declared last so it drops after `registry`.
Dropping the last `Arc` now reaps the directory. This mirrors the
`_td: TempDir` ownership pattern the integration tests in
`tests/http_integration.rs` already use.

While here, collapse the two duplicated inline `AppState` literals in
`branch_slot_global_cap_blocks` and `branch_slot_capacity_recovers_on_drop`
into a shared `test_state_with_cap(cap)`, and move `prewarm_scratch_dir`
off the shared `$TMPDIR/forkd-test-prewarm` into the per-test tempdir so
nothing the daemon writes escapes it.

Verified on Linux: leftover `/tmp/.tmp*` after `cargo test -p
forkd-controller` goes 56 -> 0, with the same 71 + 7 tests passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@WaylandYang
WaylandYang merged commit 40fb744 into main Jul 31, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tests std::mem::forget tempdirs — leaks per test run

1 participant