crate::ix!();
#[async_trait]
pub trait CleanupWorkspace {
async fn cleanup_workspace(&self) -> Result<(), WorkspaceError>;
}
#[async_trait]
impl<P,H:CrateHandleInterface<P>> CleanupWorkspace for Workspace<P,H>
where for<'async_trait> P: From<PathBuf> + AsRef<Path> + Send + Sync + 'async_trait
{
async fn cleanup_workspace(&self) -> Result<(), WorkspaceError> {
let dirs_to_clean = vec![self.as_ref().join("target")];
let files_to_clean = vec![self.as_ref().join("Cargo.lock")];
for dir in dirs_to_clean {
if fs::metadata(&dir).await.is_ok() {
fs::remove_dir_all(&dir).await.map_err(|_| WorkspaceError::DirectoryRemovalError)?;
}
}
for file in files_to_clean {
if fs::metadata(&file).await.is_ok() {
fs::remove_file(&file).await.map_err(|_| WorkspaceError::FileRemovalError)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod test_cleanup_workspace {
use super::*;
use std::path::{Path, PathBuf};
use tempfile::tempdir;
use tokio::fs;
use workspacer_3p::async_trait;
#[derive(Debug)]
struct MockWorkspace {
root_dir: PathBuf,
}
impl MockWorkspace {
fn new(path: PathBuf) -> Self {
Self { root_dir: path }
}
}
impl AsRef<Path> for MockWorkspace {
fn as_ref(&self) -> &Path {
&self.root_dir
}
}
#[async_trait]
impl CleanupWorkspace for MockWorkspace {
async fn cleanup_workspace(&self) -> Result<(), WorkspaceError> {
let dirs_to_clean = vec![self.as_ref().join("target")];
let files_to_clean = vec![self.as_ref().join("Cargo.lock")];
for dir in dirs_to_clean {
if fs::metadata(&dir).await.is_ok() {
fs::remove_dir_all(&dir)
.await
.map_err(|_| WorkspaceError::DirectoryRemovalError)?;
}
}
for file in files_to_clean {
if fs::metadata(&file).await.is_ok() {
fs::remove_file(&file)
.await
.map_err(|_| WorkspaceError::FileRemovalError)?;
}
}
Ok(())
}
}
#[tokio::test]
async fn test_cleanup_with_no_target_or_cargo_lock() {
let tmp_dir = tempdir().expect("Failed to create temp dir");
let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
let result = workspace.cleanup_workspace().await;
assert!(result.is_ok(), "Cleanup should succeed if nothing to remove");
}
#[tokio::test]
async fn test_cleanup_removes_target_directory() {
let tmp_dir = tempdir().expect("Failed to create temp dir");
let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
let target_dir = workspace.as_ref().join("target");
fs::create_dir_all(&target_dir).await.expect("Failed to create target dir");
let sub_file = target_dir.join("some_file.txt");
fs::write(&sub_file, b"dummy").await.expect("Failed to write sub file");
let meta = fs::metadata(&target_dir).await;
assert!(meta.is_ok(), "target/ directory should exist before cleanup");
workspace.cleanup_workspace().await.expect("Cleanup should succeed");
let meta_after = fs::metadata(&target_dir).await;
assert!(meta_after.is_err(), "target/ should be removed by cleanup");
}
#[tokio::test]
async fn test_cleanup_removes_cargo_lock_file() {
let tmp_dir = tempdir().expect("Failed to create temp dir");
let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
let lock_path = workspace.as_ref().join("Cargo.lock");
fs::write(&lock_path, b"dummy lock content").await.expect("Failed to write Cargo.lock");
let meta = fs::metadata(&lock_path).await;
assert!(meta.is_ok(), "Cargo.lock should exist");
workspace.cleanup_workspace().await.expect("Cleanup should succeed");
let meta_after = fs::metadata(&lock_path).await;
assert!(meta_after.is_err(), "Cargo.lock should be removed");
}
#[tokio::test]
async fn test_cleanup_removes_both_target_and_cargo_lock() {
let tmp_dir = tempdir().expect("Failed to create temp dir");
let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
let target_dir = workspace.as_ref().join("target");
fs::create_dir_all(&target_dir).await.expect("create target dir");
let lock_path = workspace.as_ref().join("Cargo.lock");
fs::write(&lock_path, b"dummy").await.expect("write cargo.lock");
workspace.cleanup_workspace().await.expect("cleanup ok");
let target_meta = fs::metadata(&target_dir).await;
let lock_meta = fs::metadata(&lock_path).await;
assert!(target_meta.is_err(), "target/ removed");
assert!(lock_meta.is_err(), "Cargo.lock removed");
}
#[cfg(unix)]
#[tokio::test]
async fn test_cleanup_failure_on_unix() {
use std::os::unix::fs::PermissionsExt;
let tmp_dir = tempdir().expect("create tempdir");
let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
let lock_path = workspace.as_ref().join("Cargo.lock");
fs::write(&lock_path, b"some content").await.unwrap();
let mut perms = std::fs::metadata(tmp_dir.path()).unwrap().permissions();
perms.set_mode(0o500);
std::fs::set_permissions(tmp_dir.path(), perms).expect("set perms on directory");
let result = workspace.cleanup_workspace().await;
assert!(result.is_err(), "Should fail if we can't remove Cargo.lock");
match result {
Err(WorkspaceError::FileRemovalError) => {
}
other => panic!("Expected FileRemovalError, got {:?}", other),
}
}
#[tokio::test]
async fn test_cleanup_idempotent() {
let tmp_dir = tempdir().expect("Failed to create temp dir");
let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
fs::create_dir_all(workspace.as_ref().join("target")).await.unwrap();
fs::write(workspace.as_ref().join("Cargo.lock"), b"xyz").await.unwrap();
workspace.cleanup_workspace().await.unwrap();
workspace.cleanup_workspace().await.unwrap();
}
}