fix(test): anchor all test sandboxes on reclaimable pid-free names

Every hand-rolled test-sandbox helper keyed its directory on
std::process::id() under env::temp_dir(), so the pre-create wipe never
matched a prior run's path: one directory stranded per helper site per
test-binary invocation, >50k dirs / 15.5 GiB on the 16 GiB tmpfs by
2026-07-13.

The remedy extends commit 84e1075's knob-lab pattern to every site:
fixed, tag-keyed, PID-FREE names under the build-tree tmp anchor, each
site keeping its pre-create remove_dir_all — which now genuinely
reclaims the previous run. Integration/bench targets use
env!("CARGO_TARGET_TMPDIR"); src-internal unit-test helpers (cargo sets
that var only for integration/bench targets) derive the same anchor
from env!("CARGO_MANIFEST_DIR") + ../../target/tmp. Residue is bounded
to one directory per site, on disk instead of the tmpfs, per checkout.

One deliberate exception: project_new.rs's tmp() stays under
env::temp_dir() because two of its tests (new_outside_a_work_tree_*)
need a base genuinely detached from any git work tree, and target/
lives inside the checkout's work tree. Its name carries a per-checkout
hash discriminator instead, so concurrently running checkouts (which
share /tmp) cannot race on the fixed name while each run still
reclaims its predecessor.

tempfile-RAII was considered and rejected (issue #258 decision log):
it cannot cover the process-lifetime OnceLock scaffolds (statics never
drop), adds a dependency, and still strands kill-leftovers on the
tmpfs.

Verified: full workspace suite green (incl. the new RED regression
test temp_cwd_sandbox_does_not_leak_under_env_temp_dir), clippy clean,
sandboxes observed under target/tmp with stable names across runs.
Stale pre-fix PID-keyed dirs in /tmp are not swept by this change and
age out only by manual cleanup.

closes #258
This commit is contained in:
2026-07-13 20:15:31 +02:00
parent 05565ee8a2
commit 1ccce9ad32
18 changed files with 100 additions and 45 deletions
+4 -2
View File
@@ -885,8 +885,10 @@ mod wf_tests {
/// A per-test registry in a fresh temp dir (a Registry's family store
/// isolates per DIRECTORY, not per filename — see Registry::open).
fn wf_registry(name: &str) -> Registry {
let dir = std::env::temp_dir()
.join(format!("aura-campaign-wf-{}-{}", std::process::id(), name));
// fixed, tag-keyed, pid-free name under the build-tree tmp anchor (#258): the
// pre-create wipe below then genuinely reclaims the previous run.
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join(format!("aura-campaign-wf-{name}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("temp dir");
Registry::open(dir.join("runs.jsonl"))
+2 -2
View File
@@ -219,8 +219,8 @@ fn strategies() -> Vec<(String, String)> {
/// Per-test registry DIRECTORY (family/campaign stores are per-directory
/// siblings of the runs path — the aura-registry temp-dir idiom).
fn temp_registry(name: &str) -> Registry {
let dir = std::env::temp_dir()
.join(format!("aura-campaign-exec-{}-{}", std::process::id(), name));
let dir = std::path::Path::new(env!("CARGO_TARGET_TMPDIR"))
.join(format!("aura-campaign-exec-{name}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create temp registry dir");
Registry::open(dir.join("runs.jsonl"))
+10 -5
View File
@@ -5321,7 +5321,8 @@ mod tests {
#[test]
fn reproduce_family_re_derives_every_member_bit_identically() {
// a unique temp runs store so the on-disk family + blueprint store do not collide.
let dir = std::env::temp_dir().join(format!("aura-repro-{}", std::process::id()));
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-repro");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("temp dir");
let reg = Registry::open(dir.join("runs.jsonl"));
@@ -5366,7 +5367,8 @@ mod tests {
// under a NON-default vol-stop regime (a campaign risk-regime cell, or wf/mc/
// generalize with --stop-length/--stop-k) must still reproduce bit-identically;
// silently re-running it under the default Vol{3,2.0} reports a spurious DIVERGED.
let dir = std::env::temp_dir().join(format!("aura-repro-stop-{}", std::process::id()));
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-repro-stop");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("temp dir");
let reg = Registry::open(dir.join("runs.jsonl"));
@@ -5434,7 +5436,8 @@ mod tests {
/// vol proxy).
#[test]
fn reproduce_family_re_derives_a_costed_member_bit_identically() {
let dir = std::env::temp_dir().join(format!("aura-repro-cost-{}", std::process::id()));
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-repro-cost");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("temp dir");
let reg = Registry::open(dir.join("runs.jsonl"));
@@ -5489,7 +5492,8 @@ mod tests {
#[test]
fn reproduce_family_re_derives_every_mc_member_bit_identically() {
let dir = std::env::temp_dir().join(format!("aura-repro-mc-{}", std::process::id()));
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-repro-mc");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("temp dir");
let reg = Registry::open(dir.join("runs.jsonl"));
@@ -5590,7 +5594,8 @@ mod tests {
/// must show. Sweep / walk-forward labels still echo their params (covered elsewhere).
#[test]
fn reproduce_mc_member_labels_carry_the_seed() {
let dir = std::env::temp_dir().join(format!("aura-repro-mc-seed-{}", std::process::id()));
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-repro-mc-seed");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("temp dir");
let reg = Registry::open(dir.join("runs.jsonl"));
+18 -6
View File
@@ -588,7 +588,9 @@ mod tests {
#[test]
fn discover_walks_up_to_aura_toml() {
let tmp = std::env::temp_dir().join(format!("aura-disc-{}", std::process::id()));
let tmp = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-disc");
let _ = std::fs::remove_dir_all(&tmp);
let nested = tmp.join("a/b/c");
std::fs::create_dir_all(&nested).unwrap();
assert_eq!(discover_from(&nested), None);
@@ -611,7 +613,9 @@ mod tests {
/// and stamps commit-only provenance.
#[test]
fn data_only_project_loads_without_a_crate() {
let tmp = std::env::temp_dir().join(format!("aura-dataonly-{}", std::process::id()));
let tmp = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-dataonly");
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).unwrap();
std::fs::write(tmp.join("Aura.toml"), "[paths]\nruns = \"runs\"\n").unwrap();
let p = load(&tmp, false).expect("data-only load");
@@ -632,7 +636,9 @@ mod tests {
/// changes the data location depending on the caller's working directory.
#[test]
fn data_path_resolves_a_relative_paths_data_against_the_project_root() {
let tmp = std::env::temp_dir().join(format!("aura-datarel-{}", std::process::id()));
let tmp = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-datarel");
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).unwrap();
std::fs::write(tmp.join("Aura.toml"), "[paths]\ndata = \"data\"\n").unwrap();
let p = load(&tmp, false).expect("data-only load");
@@ -643,7 +649,9 @@ mod tests {
#[test]
fn two_nodes_pointers_refuse_with_multi_crate() {
let tmp = std::env::temp_dir().join(format!("aura-multi-{}", std::process::id()));
let tmp = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-multi");
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).unwrap();
std::fs::write(tmp.join("Aura.toml"), "[nodes]\ncrates = [\"a\", \"b\"]\n").unwrap();
let e = load(&tmp, false).unwrap_err();
@@ -653,7 +661,9 @@ mod tests {
#[test]
fn a_broken_nodes_pointer_names_the_pointer() {
let tmp = std::env::temp_dir().join(format!("aura-badptr-{}", std::process::id()));
let tmp = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-badptr");
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).unwrap();
std::fs::write(tmp.join("Aura.toml"), "[nodes]\ncrates = [\"../nope\"]\n").unwrap();
let e = load(&tmp, false).unwrap_err();
@@ -671,7 +681,9 @@ mod tests {
/// that (`load` still refuses → CLI exit 1).
#[test]
fn a_missing_nodes_pointer_dir_refuses_without_the_cargo_metadata_leak() {
let tmp = std::env::temp_dir().join(format!("aura-missingptr-{}", std::process::id()));
let tmp = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-missingptr");
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).unwrap();
// `missing-node-crate` is never created — the pointer target is absent.
std::fs::write(
+6 -2
View File
@@ -488,7 +488,9 @@ mod tests {
#[test]
fn scaffold_project_refuses_existing_destination() {
let tmp = std::env::temp_dir().join(format!("aura-pscaf-{}", std::process::id()));
let tmp = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-pscaf");
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(tmp.join("x")).unwrap();
let spec = project_scaffold_spec("x", &tmp).unwrap();
let e = scaffold_project(&spec).unwrap_err();
@@ -498,7 +500,9 @@ mod tests {
#[test]
fn scaffold_node_crate_refuses_existing_dir_and_missing_engine() {
let tmp = std::env::temp_dir().join(format!("aura-scaf-{}", std::process::id()));
let tmp = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-scaf");
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).unwrap();
// existing destination
let mut spec = scaffold_spec("x", None, None, &tmp).unwrap();
+9 -12
View File
@@ -895,10 +895,9 @@ mod tests {
/// that silently, before the dispatch rewire ever exercises the path live.
#[test]
fn register_generated_g_stores_documents_the_campaign_can_actually_resolve() {
let dir = std::env::temp_dir().join(format!(
"aura-verb-sugar-generalize-registry-test-{}",
std::process::id()
));
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-verb-sugar-generalize-registry-test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let reg = aura_registry::Registry::open(dir.join("runs.jsonl"));
@@ -1047,10 +1046,9 @@ mod tests {
#[test]
fn register_generated_wf_stores_documents_the_campaign_can_actually_resolve() {
let dir = std::env::temp_dir().join(format!(
"aura-verb-sugar-walkforward-registry-test-{}",
std::process::id()
));
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-verb-sugar-walkforward-registry-test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let reg = aura_registry::Registry::open(dir.join("runs.jsonl"));
let ax = wf_axes();
@@ -1140,10 +1138,9 @@ mod tests {
#[test]
fn register_generated_mc_stores_documents_the_campaign_can_actually_resolve() {
let dir = std::env::temp_dir().join(format!(
"aura-verb-sugar-mc-registry-test-{}",
std::process::id()
));
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join("aura-verb-sugar-mc-registry-test");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let reg = aura_registry::Registry::open(dir.join("runs.jsonl"));
let ax = wf_axes();
+1 -1
View File
@@ -15,7 +15,7 @@ const BIN: &str = env!("CARGO_BIN_EXE_aura");
/// `./runs/runs.jsonl` into a throwaway dir, never dirtying the repo. Unique
/// per test + per process; no external tempfile dependency (mirrors `cli_run.rs`).
fn temp_cwd(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("aura-cli-{}-{}", std::process::id(), name));
let dir = std::path::Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("aura-cli-{name}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create temp cwd");
dir
+1 -1
View File
@@ -15,7 +15,7 @@ const BIN: &str = env!("CARGO_BIN_EXE_aura");
/// records (so `aura sweep`'s `./runs/runs.jsonl` never dirties the repo).
/// Unique per test + per process; no external tempfile dependency.
fn temp_cwd(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("aura-cli-{}-{}", std::process::id(), name));
let dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("aura-cli-{name}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create temp cwd");
dir
+9
View File
@@ -55,6 +55,15 @@ impl Drop for FreshProject {
/// Mints a unique, empty tempdir under `std::env::temp_dir()`, named
/// `aura-e2e-<pid>-<counter>` so concurrent test threads (same process) and
/// concurrent `cargo test` processes never collide.
///
/// #258 sweep note: this site keeps `std::process::id()` deliberately, unlike
/// the fixed, pid-free sandbox names the rest of #258's fix uses — it has no
/// pre-create wipe to defeat in the first place. `FreshProject::drop` (above)
/// unconditionally removes the returned root when the owning test ends (pass,
/// fail, or panic-unwind), so nothing strands across runs; the pid instead
/// disambiguates concurrent `cargo test` *processes* racing this one function
/// from dozens of call sites, which a fixed tag-keyed name cannot do without
/// plumbing a unique tag through every one of them.
fn mint_tempdir() -> PathBuf {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let root = std::env::temp_dir().join(format!(
+1 -1
View File
@@ -382,7 +382,7 @@ fn graph_introspect_no_flag_is_usage_exit_2() {
/// A fresh, unique working directory for a test that persists content-addressed
/// blueprints under `./runs/` (mirrors `research_docs.rs`'s `temp_cwd`).
fn temp_cwd(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("aura-graph-{}-{}", std::process::id(), name));
let dir = std::path::Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("aura-graph-{name}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create temp cwd");
dir
+4 -2
View File
@@ -368,7 +368,8 @@ fn nodes_pointer_crate_resolves_and_stamps_its_namespace() {
/// "binds no node crate" hint (not the outside-project "no Aura.toml" text).
#[test]
fn data_only_project_hints_attach_for_namespaced_ids() {
let tmp = std::env::temp_dir().join(format!("aura-dataonly-hint-{}", std::process::id()));
let tmp = Path::new(env!("CARGO_TARGET_TMPDIR")).join("aura-dataonly-hint");
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(&tmp).unwrap();
std::fs::write(tmp.join("Aura.toml"), "").unwrap();
let bp = tmp.join("bp.json");
@@ -388,7 +389,8 @@ fn data_only_project_hints_attach_for_namespaced_ids() {
#[test]
fn missing_artifact_refuses_with_build_hint() {
// A minimal never-built project: valid cargo metadata, no dylib on disk.
let tmp = std::env::temp_dir().join(format!("aura-nobuild-{}", std::process::id()));
let tmp = Path::new(env!("CARGO_TARGET_TMPDIR")).join("aura-nobuild");
let _ = std::fs::remove_dir_all(&tmp);
std::fs::create_dir_all(tmp.join("src")).unwrap();
std::fs::write(tmp.join("Aura.toml"), "").unwrap();
std::fs::write(tmp.join("src/lib.rs"), "").unwrap();
+12 -1
View File
@@ -14,8 +14,19 @@ fn aura(args: &[&str], cwd: &Path) -> Output {
.expect("run aura")
}
/// Fixed, tag-keyed, pid-free name (#258) — kept under `env::temp_dir()`
/// rather than the `CARGO_TARGET_TMPDIR` build-tree anchor used by the other
/// sandbox helpers: two of this file's tests (`new_outside_a_work_tree_*`)
/// require a base genuinely detached from any git work tree, and
/// `CARGO_TARGET_TMPDIR` resolves inside this very checkout's `target/`.
/// The checkout discriminator keeps concurrently running checkouts/worktrees
/// (which share `env::temp_dir()`) off each other's fixed names while the
/// name stays deterministic per checkout, so each run still reclaims the last.
fn tmp(tag: &str) -> PathBuf {
let d = std::env::temp_dir().join(format!("aura-new-{tag}-{}", std::process::id()));
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
env!("CARGO_MANIFEST_DIR").hash(&mut h);
let d = std::env::temp_dir().join(format!("aura-new-{tag}-{:08x}", h.finish() as u32));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
d
+1 -1
View File
@@ -4,7 +4,7 @@ use std::path::Path;
use std::process::Command;
fn temp_cwd(tag: &str) -> std::path::PathBuf {
let d = std::env::temp_dir().join(format!("aura-nodes-{tag}-{}", std::process::id()));
let d = Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("aura-nodes-{tag}"));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
d
+1 -1
View File
@@ -12,7 +12,7 @@ use common::{ScratchGuard, ScratchPath, fresh_project};
/// never dirties the repo). Unique per test + per process; no external
/// tempfile dependency (mirrors `cli_run.rs` / `cli_broken_pipe.rs`).
fn temp_cwd(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("aura-cli-{}-{}", std::process::id(), name));
let dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("aura-cli-{name}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("create temp cwd");
dir
+6
View File
@@ -551,6 +551,12 @@ mod tests {
/// mirrors `aura-cli`'s own `tests/common::mint_tempdir` pattern (unique
/// name via pid + an atomic counter) so this crate's tests need no
/// `tempfile` dev-dependency for a handful of throwaway fixture dirs.
///
/// #258 sweep note: deliberately kept pid-suffixed like the sibling it
/// mirrors — there is no pre-create wipe here for a stale pid to defeat;
/// `Drop` (below) unconditionally reclaims the directory when the owning
/// test ends, so nothing strands across runs. The pid instead
/// disambiguates concurrent `cargo test` processes.
struct ScratchDir(std::path::PathBuf);
impl ScratchDir {
+9 -4
View File
@@ -909,8 +909,11 @@ mod tests {
}
fn temp_path(name: &str) -> PathBuf {
// unique per test + per process; no external tempfile dependency
std::env::temp_dir().join(format!("aura-registry-{}-{}.jsonl", std::process::id(), name))
// fixed, tag-keyed, pid-free name under the build-tree tmp anchor (#258):
// no external tempfile dependency, and every call site already wipes
// before use, so the fixed name genuinely reclaims the previous run.
std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join(format!("aura-registry-{name}.jsonl"))
}
#[test]
@@ -1081,8 +1084,10 @@ mod tests {
fn temp_family_dir(name: &str) -> PathBuf {
// a unique per-test directory so the families.jsonl sibling never collides
// across tests sharing the temp dir; the registry binds to runs.jsonl inside.
let dir =
std::env::temp_dir().join(format!("aura-registry-fam-{}-{}", std::process::id(), name));
// Fixed, tag-keyed, pid-free name under the build-tree tmp anchor (#258): the
// pre-create wipe below then genuinely reclaims the previous run.
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join(format!("aura-registry-fam-{name}"));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).expect("create temp family dir");
dir.join("runs.jsonl")
+4 -2
View File
@@ -288,8 +288,10 @@ mod tests {
use aura_core::{Scalar, ScalarKind, Timestamp};
fn temp_traces_root(name: &str) -> PathBuf {
let dir =
std::env::temp_dir().join(format!("aura-tracestore-{}-{}", std::process::id(), name));
// fixed, tag-keyed, pid-free name under the build-tree tmp anchor (#258): the
// pre-create wipe below then genuinely reclaims the previous run.
let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp"))
.join(format!("aura-tracestore-{name}"));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).expect("create temp traces root");
dir
@@ -18,8 +18,8 @@ use aura_registry::{
};
fn temp_dir(name: &str) -> PathBuf {
let dir = std::env::temp_dir()
.join(format!("aura-registry-campaign-e2e-{}-{}", std::process::id(), name));
let dir = std::path::Path::new(env!("CARGO_TARGET_TMPDIR"))
.join(format!("aura-registry-campaign-e2e-{name}"));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).expect("create temp dir");
dir.join("runs.jsonl")