From ed73b463e914e8f980134a4b15de50b4c845f866 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Wed, 19 Aug 2026 11:30:37 +0100 Subject: [PATCH] Name every test scratch directory with tempfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twenty-one test scratch roots across the workspace built their name from the process id and a SystemTime nanosecond stamp. That reads as though it could not repeat, and on one thread it does not, because each SystemTime::now() is ordered after the last. Across threads there is no such ordering and the clock does not advance a nanosecond at a time, so two threads reading inside one step read the same number and land on the same directory — the same database, the same git repo. Four of those sites are shared helpers with several callers and no tag to tell one call from another, so they could collide today rather than after some future copy-paste: worktree::repo (eight callers), cli::ctx_with_toml (six), app::app_with_agents (four) and app::pr_ready_app (three). Move them all onto tempfile, which creates the directory exclusively and retries under a different name on a clash, so distinctness is a property of the call rather than an argument about clock granularity. Fold in store::scratch_db too: its own counter kept concurrent callers apart but left the file exposed to reuse by a later run under a recycled pid, and leaving it in place would mean the workspace still had two mechanisms. keep() holds today's behaviour — the directories stay behind, as they always have, so a failing test's scratch state survives for inspection. Cleanup is deliberately not part of this change: TempDir deletes on drop, so the guard would have to outlive each test and every helper returning a PathBuf would have to return it, reshaping call sites the dispatch fixture alone has fifty-eight of. The dispatch fixture's counter and the test that measured its clock collisions go with it. That test named four thousand roots to show a clock-only name repeats; under tempfile every name is created, so re-running it would leave four thousand kept directories behind per run to prove a property that now holds by construction. CLAUDE.md gains the convention under its testing rules, so it sits where a contributor looks rather than inside one test module. The twenty-first site arrived in #178 while this change was being written, which is the copy-paste the convention exists to stop. tempfile is dev-only, so it reaches neither the shipped binary nor a consumer build, and it adds one leaf crate: every other transitive dependency was already in Cargo.lock. Verified: cargo clippy --workspace --all-targets -D warnings clean, and cargo test --workspace (895 tests) green. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 8 ++ Cargo.lock | 21 +++++ Cargo.toml | 1 + crates/voro-core/Cargo.toml | 3 + crates/voro-core/src/config_edit.rs | 13 ++- crates/voro-core/src/store.rs | 24 +++--- crates/voro/Cargo.toml | 3 + crates/voro/src/app.rs | 54 +++++------- crates/voro/src/cli.rs | 80 +++++++----------- crates/voro/src/dispatch.rs | 59 ++----------- crates/voro/src/ui.rs | 84 +++++++------------ crates/voro/src/worktree.rs | 14 ++-- .../tests/propose_ignores_ambient_task_id.rs | 14 ++-- 13 files changed, 149 insertions(+), 229 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2b0155b..f175ddf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,6 +34,14 @@ and say so explicitly. `voro-core` requires tests; state-machine transitions and scheduler ordering are the highest-value targets. TUI code is tested where practical, not dogmatically. +A test that needs a scratch directory names it one way, with `tempfile`: +`tempfile::Builder::new().prefix("voro--").tempdir().unwrap().keep()`. +Never build the name from the process id and a clock stamp — the clock does not +advance a nanosecond at a time, so two threads reading inside one step read the +same number and land on the same directory. `keep` leaves the directory behind +deliberately, so a failing test's scratch state survives for inspection; nothing +cleans the temp dir up yet. + ## Git conventions - Feature branches, squash-merged to `main`. One logical change per PR. diff --git a/Cargo.lock b/Cargo.lock index 5b8f690..919d372 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,6 +477,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + [[package]] name = "filedescriptor" version = "0.8.3" @@ -1461,6 +1467,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "termina" version = "0.3.3" @@ -1716,6 +1735,7 @@ version = "0.1.0" dependencies = [ "clap", "ratatui", + "tempfile", "voro-core", ] @@ -1726,6 +1746,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", + "tempfile", "thiserror 2.0.18", "toml", "toml_edit", diff --git a/Cargo.toml b/Cargo.toml index e0f82f6..eaf9a20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ toml = "0.8" toml_edit = "0.22" ratatui = { version = "0.30", features = ["unstable-rendered-line-info"] } uuid = { version = "1", features = ["v4"] } +tempfile = "3" # The profile that 'dist' will build with [profile.dist] diff --git a/crates/voro-core/Cargo.toml b/crates/voro-core/Cargo.toml index 307ae3d..c9343d8 100644 --- a/crates/voro-core/Cargo.toml +++ b/crates/voro-core/Cargo.toml @@ -18,3 +18,6 @@ thiserror.workspace = true toml.workspace = true toml_edit.workspace = true uuid.workspace = true + +[dev-dependencies] +tempfile.workspace = true diff --git a/crates/voro-core/src/config_edit.rs b/crates/voro-core/src/config_edit.rs index 8e0cdf4..583a4ef 100644 --- a/crates/voro-core/src/config_edit.rs +++ b/crates/voro-core/src/config_edit.rs @@ -253,14 +253,11 @@ mod tests { /// A unique scratch path per test, cleaned up by the caller. fn scratch(tag: &str) -> std::path::PathBuf { - std::env::temp_dir().join(format!( - "voro-config-edit-{tag}-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )) + tempfile::Builder::new() + .prefix(&format!("voro-config-edit-{tag}-")) + .tempdir() + .unwrap() + .keep() } #[test] diff --git a/crates/voro-core/src/store.rs b/crates/voro-core/src/store.rs index 9f795bc..f0108af 100644 --- a/crates/voro-core/src/store.rs +++ b/crates/voro-core/src/store.rs @@ -2049,16 +2049,11 @@ mod schema_guard_tests { /// A unique scratch directory per test, cleaned up by the caller. fn scratch(tag: &str) -> PathBuf { - let dir = std::env::temp_dir().join(format!( - "voro-store-{tag}-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - std::fs::create_dir_all(&dir).unwrap(); - dir + tempfile::Builder::new() + .prefix(&format!("voro-store-{tag}-")) + .tempdir() + .unwrap() + .keep() } #[test] @@ -4869,9 +4864,12 @@ mod tests { /// A unique scratch database path under the OS temp dir. fn scratch_db() -> PathBuf { - static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); - let n = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - std::env::temp_dir().join(format!("voro-dataversion-{}-{n}.db", std::process::id())) + tempfile::Builder::new() + .prefix("voro-dataversion-") + .tempdir() + .unwrap() + .keep() + .join("voro.db") } #[test] diff --git a/crates/voro/Cargo.toml b/crates/voro/Cargo.toml index 1c68c52..8de40b0 100644 --- a/crates/voro/Cargo.toml +++ b/crates/voro/Cargo.toml @@ -14,3 +14,6 @@ categories = ["command-line-utilities"] voro-core = { path = "../voro-core", version = "0.1.0" } ratatui.workspace = true clap = { version = "4.6.1", features = ["derive"] } + +[dev-dependencies] +tempfile.workspace = true diff --git a/crates/voro/src/app.rs b/crates/voro/src/app.rs index 48289df..7d1a0b2 100644 --- a/crates/voro/src/app.rs +++ b/crates/voro/src/app.rs @@ -4145,14 +4145,11 @@ mod tests { ) -> (Store, crate::dispatch::DispatchCtx, std::path::PathBuf) { use std::process::{Command, Stdio}; - let root = std::env::temp_dir().join(format!( - "voro-app-{name}-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let root = tempfile::Builder::new() + .prefix(&format!("voro-app-{name}-")) + .tempdir() + .unwrap() + .keep(); let project_path = root.join("project"); std::fs::create_dir_all(&project_path).unwrap(); let status = Command::new("git") @@ -4189,14 +4186,11 @@ mod tests { fn resuming_a_task_with_a_live_session_spawns_no_continuation() { use std::process::{Command, Stdio}; - let root = std::env::temp_dir().join(format!( - "voro-app-resume-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let root = tempfile::Builder::new() + .prefix("voro-app-resume-") + .tempdir() + .unwrap() + .keep(); let project_path = root.join("project"); std::fs::create_dir_all(&project_path).unwrap(); let status = Command::new("git") @@ -7334,15 +7328,11 @@ mod tests { /// decides whether that directory is a git repository at all, which is the /// whole of what the press-time gate reads (DESIGN.md §8). fn pr_ready_app(with_repo: bool) -> (App, i64, std::path::PathBuf) { - let dir = std::env::temp_dir().join(format!( - "voro-review-key-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - std::fs::create_dir_all(&dir).unwrap(); + let dir = tempfile::Builder::new() + .prefix("voro-review-key-") + .tempdir() + .unwrap() + .keep(); if with_repo { let status = std::process::Command::new("git") .arg("-C") @@ -7661,15 +7651,11 @@ mod tests { /// config and PATH. fn app_with_agents(agents_toml: &str) -> App { let mut app = app_with(&[TaskState::Ready]); - let dir = std::env::temp_dir().join(format!( - "voro-plan-key-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - std::fs::create_dir_all(&dir).unwrap(); + let dir = tempfile::Builder::new() + .prefix("voro-plan-key-") + .tempdir() + .unwrap() + .keep(); let agents_path = dir.join("voro.toml"); std::fs::write(&agents_path, agents_toml).unwrap(); app.dispatch_ctx = crate::dispatch::DispatchCtx { diff --git a/crates/voro/src/cli.rs b/crates/voro/src/cli.rs index 2d6e323..199c2f2 100644 --- a/crates/voro/src/cli.rs +++ b/crates/voro/src/cli.rs @@ -2601,14 +2601,11 @@ mod tests { #[test] fn agent_init_then_list_through_the_cli() { - let dir = std::env::temp_dir().join(format!( - "voro-cli-agents-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let dir = tempfile::Builder::new() + .prefix("voro-cli-agents-") + .tempdir() + .unwrap() + .keep(); let agents_path = dir.join("voro/voro.toml"); let ctx = DispatchCtx { db_path: dir.join("voro.db"), @@ -2653,14 +2650,11 @@ mod tests { #[test] fn viewer_add_remove_round_trip_through_the_cli() { - let dir = std::env::temp_dir().join(format!( - "voro-cli-viewers-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let dir = tempfile::Builder::new() + .prefix("voro-cli-viewers-") + .tempdir() + .unwrap() + .keep(); let agents_path = dir.join("voro/voro.toml"); let ctx = DispatchCtx { db_path: dir.join("voro.db"), @@ -3672,15 +3666,11 @@ mod tests { /// A throwaway checkout with no remotes — the shape of a first project /// (DESIGN.md §8), which advertises `open` rather than `pr`. fn remoteless_checkout(tag: &str) -> std::path::PathBuf { - let path = std::env::temp_dir().join(format!( - "voro-cli-{tag}-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - std::fs::create_dir_all(&path).unwrap(); + let path = tempfile::Builder::new() + .prefix(&format!("voro-cli-{tag}-")) + .tempdir() + .unwrap() + .keep(); git_in(&path, &["init", "-q"]); path } @@ -4622,15 +4612,11 @@ mod tests { /// viewers. The default `ctx()` points at the developer's real config, /// which these tests must not depend on (or launch viewers from). fn ctx_with_toml(toml: &str) -> DispatchCtx { - let root = std::env::temp_dir().join(format!( - "voro-cli-viewer-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - std::fs::create_dir_all(&root).unwrap(); + let root = tempfile::Builder::new() + .prefix("voro-cli-viewer-") + .tempdir() + .unwrap() + .keep(); let agents_path = root.join("voro.toml"); std::fs::write(&agents_path, toml).unwrap(); DispatchCtx { @@ -5104,14 +5090,11 @@ mod tests { fn a_dead_dispatched_session_is_finalised_and_stalled_on_read() { use std::process::{Command, Stdio}; - let root = std::env::temp_dir().join(format!( - "voro-cli-reconcile-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let root = tempfile::Builder::new() + .prefix("voro-cli-reconcile-") + .tempdir() + .unwrap() + .keep(); let project = root.join("project"); std::fs::create_dir_all(&project).unwrap(); let git = |args: &[&str]| { @@ -5188,14 +5171,11 @@ mod tests { fn scratch_env(cmd: &str) -> (Store, DispatchCtx, std::path::PathBuf) { use std::process::{Command, Stdio}; - let root = std::env::temp_dir().join(format!( - "voro-cli-answer-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let root = tempfile::Builder::new() + .prefix("voro-cli-answer-") + .tempdir() + .unwrap() + .keep(); let project = root.join("project"); std::fs::create_dir_all(&project).unwrap(); let status = Command::new("git") diff --git a/crates/voro/src/dispatch.rs b/crates/voro/src/dispatch.rs index 3ab0f95..cda16f5 100644 --- a/crates/voro/src/dispatch.rs +++ b/crates/voro/src/dispatch.rs @@ -1920,20 +1920,8 @@ fn default_base_branch(repo_path: &str) -> String { #[cfg(test)] mod tests { use super::*; - use std::sync::atomic::{AtomicU64, Ordering}; use voro_core::{LivenessSource, NewTask, Priority}; - /// Distinguishes fixtures built within the same clock tick. - /// - /// A nanosecond stamp reads as though it could not repeat, and on one - /// thread it does not: consecutive `SystemTime::now()` calls always differ, - /// because each is ordered after the last. Across threads there is no such - /// ordering, and the clock does not advance a nanosecond at a time — it - /// steps roughly every 20-30ns, so any two threads reading inside one step - /// read the same number. Measured on this workstation: 4000 reads across - /// eight threads yielded 1493 duplicates. - static FIXTURE_SEQ: AtomicU64 = AtomicU64::new(0); - /// A scratch database, a freshly-`git init`ed clean project, and an /// `voro.toml` whose one agent is a stub command that just reads the /// prompt. Returns the store, the dispatch context, and the project path. @@ -1967,18 +1955,14 @@ mod tests { } /// A scratch directory no other fixture can name, however many are built - /// at once: the process id separates test binaries, the counter separates - /// fixtures within one, and the stamp keeps reruns of a recycled pid apart. + /// at once, because `tempfile` creates it exclusively and retries under a + /// different name on a clash. `keep` then leaves it behind for inspection. fn fixture_root() -> PathBuf { - std::env::temp_dir().join(format!( - "voro-dispatch-{}-{}-{}", - std::process::id(), - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_nanos(), - FIXTURE_SEQ.fetch_add(1, Ordering::Relaxed) - )) + tempfile::Builder::new() + .prefix("voro-dispatch-") + .tempdir() + .unwrap() + .keep() } #[test] @@ -2003,35 +1987,6 @@ mod tests { assert_eq!(distinct.len(), THREADS, "roots collided: {roots:?}"); } - /// Eight fixtures spend long enough in `git init` to drift apart on the - /// clock, so the test above can pass even on a name that has no counter in - /// it. This one names four thousand roots with nothing in between, where a - /// clock-only name collides tens of times over. - #[test] - fn roots_named_back_to_back_on_many_threads_are_all_distinct() { - const THREADS: usize = 8; - const EACH: usize = 500; - let gate = std::sync::Barrier::new(THREADS); - - let roots: Vec = std::thread::scope(|scope| { - let handles: Vec<_> = (0..THREADS) - .map(|_| { - scope.spawn(|| { - gate.wait(); - (0..EACH).map(|_| fixture_root()).collect::>() - }) - }) - .collect(); - handles - .into_iter() - .flat_map(|h| h.join().unwrap()) - .collect() - }); - - let distinct: std::collections::HashSet<_> = roots.iter().collect(); - assert_eq!(distinct.len(), THREADS * EACH, "roots collided"); - } - fn git(dir: &Path, args: &[&str]) { let status = Command::new("git") .arg("-C") diff --git a/crates/voro/src/ui.rs b/crates/voro/src/ui.rs index 4bd0c3b..ef2d8e3 100644 --- a/crates/voro/src/ui.rs +++ b/crates/voro/src/ui.rs @@ -2924,16 +2924,12 @@ mod tests { use ratatui::backend::TestBackend; use voro_core::Store; - let dir = std::env::temp_dir().join(format!( - "voro-ui-config-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let dir = tempfile::Builder::new() + .prefix("voro-ui-config-") + .tempdir() + .unwrap() + .keep(); let agents_path = dir.join("voro.toml"); - std::fs::create_dir_all(&dir).unwrap(); std::fs::write(&agents_path, "[viewers.zed]\ncmd = \"zed {path}\"\n").unwrap(); let store = Store::open_in_memory().unwrap(); @@ -3004,16 +3000,12 @@ mod tests { use ratatui::backend::TestBackend; use voro_core::Store; - let dir = std::env::temp_dir().join(format!( - "voro-ui-config-many-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let dir = tempfile::Builder::new() + .prefix("voro-ui-config-many-") + .tempdir() + .unwrap() + .keep(); let agents_path = dir.join("voro.toml"); - std::fs::create_dir_all(&dir).unwrap(); let mut toml = String::from("[viewers.zed]\ncmd = \"zed {path}\"\n"); for n in 1..=4 { toml.push_str(&format!( @@ -3071,16 +3063,12 @@ mod tests { use ratatui::crossterm::event::{KeyCode, KeyEvent}; use voro_core::Store; - let dir = std::env::temp_dir().join(format!( - "voro-ui-config-scroll-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let dir = tempfile::Builder::new() + .prefix("voro-ui-config-scroll-") + .tempdir() + .unwrap() + .keep(); let agents_path = dir.join("voro.toml"); - std::fs::create_dir_all(&dir).unwrap(); let mut toml = String::from("[viewers.zed]\ncmd = \"zed {path}\"\n"); for n in 1..=6 { toml.push_str(&format!( @@ -3333,15 +3321,11 @@ mod tests { use std::process::{Command, Stdio}; use voro_core::{Action, NewTask, Store}; - let project = std::env::temp_dir().join(format!( - "voro-ui-remoteless-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - std::fs::create_dir_all(&project).unwrap(); + let project = tempfile::Builder::new() + .prefix("voro-ui-remoteless-") + .tempdir() + .unwrap() + .keep(); let status = Command::new("git") .arg("-C") .arg(&project) @@ -3415,15 +3399,11 @@ mod tests { use std::process::{Command, Stdio}; use voro_core::{Action, NewTask, Store}; - let project = std::env::temp_dir().join(format!( - "voro-ui-half-report-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - std::fs::create_dir_all(&project).unwrap(); + let project = tempfile::Builder::new() + .prefix("voro-ui-half-report-") + .tempdir() + .unwrap() + .keep(); let status = Command::new("git") .arg("-C") .arg(&project) @@ -6159,15 +6139,11 @@ mod tests { use ratatui::crossterm::event::{KeyCode, KeyEvent}; use voro_core::Store; - let dir = std::env::temp_dir().join(format!( - "voro-ui-mouse-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - std::fs::create_dir_all(&dir).unwrap(); + let dir = tempfile::Builder::new() + .prefix("voro-ui-mouse-") + .tempdir() + .unwrap() + .keep(); let agents_path = dir.join("voro.toml"); std::fs::write( &agents_path, diff --git a/crates/voro/src/worktree.rs b/crates/voro/src/worktree.rs index d974f20..5a75643 100644 --- a/crates/voro/src/worktree.rs +++ b/crates/voro/src/worktree.rs @@ -232,7 +232,6 @@ fn pr_is_merged(url: &str) -> bool { #[cfg(test)] mod tests { use super::*; - use std::time::{SystemTime, UNIX_EPOCH}; use voro_core::{NewTask, Priority, Store, TaskState}; fn git(dir: &Path, args: &[&str]) { @@ -250,14 +249,11 @@ mod tests { /// A git repo at `/project` with one commit on `main` and a /// `git`-configured identity, ready to grow worktrees. fn repo() -> PathBuf { - let root = std::env::temp_dir().join(format!( - "voro-worktree-{}-{}", - std::process::id(), - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_nanos() - )); + let root = tempfile::Builder::new() + .prefix("voro-worktree-") + .tempdir() + .unwrap() + .keep(); let project = root.join("project"); std::fs::create_dir_all(&project).unwrap(); git(&project, &["init", "-q", "-b", "main"]); diff --git a/crates/voro/tests/propose_ignores_ambient_task_id.rs b/crates/voro/tests/propose_ignores_ambient_task_id.rs index 6773a3a..310d626 100644 --- a/crates/voro/tests/propose_ignores_ambient_task_id.rs +++ b/crates/voro/tests/propose_ignores_ambient_task_id.rs @@ -32,15 +32,11 @@ fn voro(db: &Path, args: &[&str]) -> String { #[test] fn propose_ignores_ambient_voro_task_id() { - let root = std::env::temp_dir().join(format!( - "voro-it-{}-{}", - std::process::id(), - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos() - )); - std::fs::create_dir_all(&root).unwrap(); + let root = tempfile::Builder::new() + .prefix("voro-it-") + .tempdir() + .unwrap() + .keep(); let db = root.join("voro.db"); voro(&db, &["project", "add", "demo", root.to_str().unwrap()]);