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 } }