From 7ea51858189338cbda1a21dc5724d1a8ce3aedb9 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Thu, 18 Jun 2026 00:34:10 +0800 Subject: feat: add test sandbox helper and enable tokio multi-thread runtime Replace `copy_with_temp_rename` module with `test_sandbox` providing automatic cleanup of test directories. Add `rt`, `rt-multi-thread`, and `macros` tokio features. --- Cargo.toml | 5 +- rola-utils/functions/src/lib.rs | 4 +- rola-utils/functions/src/test_sandbox.rs | 115 +++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 rola-utils/functions/src/test_sandbox.rs diff --git a/Cargo.toml b/Cargo.toml index 11ce5f3..50d91d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,8 @@ rola-draft = { path = "rola-draft" } [workspace.dependencies.tokio] version = "1.52.3" features = [ - "fs" + "fs", + "rt", + "rt-multi-thread", + "macros" ] diff --git a/rola-utils/functions/src/lib.rs b/rola-utils/functions/src/lib.rs index 40f2ca9..3ba2507 100644 --- a/rola-utils/functions/src/lib.rs +++ b/rola-utils/functions/src/lib.rs @@ -1,5 +1,5 @@ mod levenshtein_distance; pub use levenshtein_distance::*; -mod copy_with_temp_rename; -pub use copy_with_temp_rename::*; +mod test_sandbox; +pub use test_sandbox::*; diff --git a/rola-utils/functions/src/test_sandbox.rs b/rola-utils/functions/src/test_sandbox.rs new file mode 100644 index 0000000..e0065f6 --- /dev/null +++ b/rola-utils/functions/src/test_sandbox.rs @@ -0,0 +1,115 @@ +use std::fs; +use std::ops::Deref; +use std::path::{Path, PathBuf}; + +/// Takes a name, returns a `Sandbox` pointing to a created empty directory +/// +/// # Example +/// +/// ```rust +/// # use shared_functions::rola_test_sandbox; +/// let sandbox = rola_test_sandbox("my_test"); +/// let file_path = sandbox.join("output.txt"); +/// std::fs::write(&file_path, "hello").unwrap(); +/// assert!(file_path.exists()); +/// // The directory is automatically removed when `sandbox` goes out of scope. +/// ``` +#[must_use] +pub fn rola_test_sandbox(name: &str) -> Sandbox { + let root = find_workspace_root(); + let path = find_available_path(&root.join(".temp").join("test_sandbox").join(name)); + ensure_empty(&path); + Sandbox { path } +} + +/// Searches upward from the current directory until a directory with `.cargo` is found +fn find_workspace_root() -> PathBuf { + let mut dir = std::env::current_dir().expect("failed to get current directory"); + loop { + if dir.join(".cargo").is_dir() { + return dir; + } + if !dir.pop() { + panic!( + "could not find workspace root: no `.cargo` directory found in any parent of `{}`", + std::env::current_dir().unwrap().display() + ); + } + } +} + +/// Finds an available path +fn find_available_path(base: &Path) -> PathBuf { + if !base.exists() { + return base.to_path_buf(); + } + + if try_clear_contents(base).is_ok() { + return base.to_path_buf(); + } + + let parent = base.parent().unwrap(); + let stem = base + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("sandbox"); + + for i in 1.. { + let candidate = parent.join(format!("{}_{}", stem, i)); + if !candidate.exists() { + return candidate; + } + if try_clear_contents(&candidate).is_ok() { + return candidate; + } + } + + unreachable!() +} + +/// Clears directory contents while keeping the directory itself; returns an error on failure. +fn try_clear_contents(path: &Path) -> std::io::Result<()> { + if path.is_dir() { + for entry in fs::read_dir(path)? { + let entry = entry?; + if entry.file_type()?.is_dir() { + fs::remove_dir_all(&entry.path())?; + } else { + fs::remove_file(&entry.path())?; + } + } + } + Ok(()) +} + +fn ensure_empty(path: &Path) { + if path.exists() { + let _ = fs::remove_dir_all(path); + } + fs::create_dir_all(path).expect("failed to create sandbox directory"); +} + +/// A sandbox directory that is automatically cleaned up when dropped. +/// +/// The sandbox directory is created at a path under the workspace's `.temp/test_sandbox/` directory. +/// When the `Sandbox` is dropped, the directory and all its contents are removed. +pub struct Sandbox { + /// The path to the sandbox directory. + pub path: PathBuf, +} + +impl Drop for Sandbox { + fn drop(&mut self) { + if self.path.exists() { + let _ = fs::remove_dir_all(&self.path); + } + } +} + +impl Deref for Sandbox { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.path + } +} -- cgit