summaryrefslogtreecommitdiff
path: root/rola-utils/functions/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-06-18 00:34:10 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-18 00:34:10 +0800
commit7ea51858189338cbda1a21dc5724d1a8ce3aedb9 (patch)
tree9af8c19b6fa93446a8f95aebebf21690cb6c114c /rola-utils/functions/src
parente342a7f522a236991ba9fa6d8a1daa22465ec217 (diff)
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.
Diffstat (limited to 'rola-utils/functions/src')
-rw-r--r--rola-utils/functions/src/lib.rs4
-rw-r--r--rola-utils/functions/src/test_sandbox.rs115
2 files changed, 117 insertions, 2 deletions
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
+ }
+}