From 6961bed99debd5a3fe69c4f9f3c05dd555c4bd5f Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 16 Oct 2026 19:51:46 -0700 Subject: [PATCH] Tests: CLI tests do not fail on folders an earlier run left behind The tmp() helpers in apps/vectorcraft-cli/tests/cli.rs and fileio.rs name their folder after the process id (vectorcraft-cli-test-, vectorcraft-cli-fileio- in the temp folder) and never empty or remove it. When a later run gets the same process id, it finds the earlier run's files there. mcp_automation_roots then fails on Unix with EEXIST (Os { code: 17, kind: AlreadyExists, message: "File exists" }): its first symlink() call finds the link in/escape.svg that the earlier run made. A stale output file can also let a fileio test read an earlier run's export. The first tmp() call in each test process now removes the folder and creates it again empty. The roots test's body moves into automation_roots(), and mcp_automation_roots_in_a_folder_an_earlier_run_left runs it twice in one folder, emptying the folder in between as tmp() does. Without the removal the second run fails with the same error. --- apps/vectorcraft-cli/tests/cli.rs | 27 ++++++++++++++++++++++++--- apps/vectorcraft-cli/tests/fileio.rs | 11 +++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/apps/vectorcraft-cli/tests/cli.rs b/apps/vectorcraft-cli/tests/cli.rs index 37bb28c22..133c51df1 100644 --- a/apps/vectorcraft-cli/tests/cli.rs +++ b/apps/vectorcraft-cli/tests/cli.rs @@ -7,10 +7,18 @@ use serde_json::{Value, json}; const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft-cli"); +/// Returns the path of `name` in `vectorcraft-cli-test-` in the temp folder. The first call removes +/// that folder and creates it again empty. fn tmp(name: &str) -> std::path::PathBuf { - let dir = std::env::temp_dir().join(format!("vectorcraft-cli-test-{}", std::process::id())); + static DIR: std::sync::OnceLock = std::sync::OnceLock::new(); + DIR.get_or_init(|| empty_dir(std::env::temp_dir().join(format!("vectorcraft-cli-test-{}", std::process::id())))).join(name) +} + +/// Removes `dir` and everything in it, creates it again empty and returns it. +fn empty_dir(dir: std::path::PathBuf) -> std::path::PathBuf { + let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); - dir.join(name) + dir } #[test] @@ -87,7 +95,20 @@ fn mcp_headless_over_stdio() { /// server starts, and the process then reads and writes only inside its roots. #[test] fn mcp_automation_roots() { - let base = tmp("roots"); + automation_roots(&tmp("roots")); +} + +/// A run that has the process id of an earlier run finds that run's files and links in its folder. +/// `empty_dir`, which `tmp` uses, removes them, so the run can make them again instead of failing +/// with "File exists". +#[test] +fn mcp_automation_roots_in_a_folder_an_earlier_run_left() { + let dir = tmp("earlier-run"); + automation_roots(&dir.join("roots")); + automation_roots(&empty_dir(dir).join("roots")); +} + +fn automation_roots(base: &std::path::Path) { for d in ["in", "out", "outside"] { std::fs::create_dir_all(base.join(d)).unwrap(); } diff --git a/apps/vectorcraft-cli/tests/fileio.rs b/apps/vectorcraft-cli/tests/fileio.rs index db1174188..19e2b7b72 100644 --- a/apps/vectorcraft-cli/tests/fileio.rs +++ b/apps/vectorcraft-cli/tests/fileio.rs @@ -8,9 +8,16 @@ use serde_json::Value; const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft-cli"); +/// Returns the path of `name` in `vectorcraft-cli-fileio-` in the temp folder. The first call +/// removes that folder and creates it again empty. fn tmp(name: &str) -> String { - let dir = std::env::temp_dir().join(format!("vectorcraft-cli-fileio-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); + static DIR: std::sync::OnceLock = std::sync::OnceLock::new(); + let dir = DIR.get_or_init(|| { + let dir = std::env::temp_dir().join(format!("vectorcraft-cli-fileio-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + dir + }); dir.join(name).to_string_lossy().to_string() }