workspacer_cleanup/
cleanup.rs

1// ---------------- [ File: workspacer-cleanup/src/cleanup.rs ]
2crate::ix!();
3
4#[async_trait]
5pub trait CleanupWorkspace {
6
7    async fn cleanup_workspace(&self) -> Result<(), WorkspaceError>;
8}
9
10#[async_trait]
11impl<P,H:CrateHandleInterface<P>> CleanupWorkspace for Workspace<P,H> 
12where for<'async_trait> P: From<PathBuf> + AsRef<Path> + Send + Sync + 'async_trait
13{
14    /// Asynchronously cleans up unnecessary files and directories in the workspace.
15    async fn cleanup_workspace(&self) -> Result<(), WorkspaceError> {
16
17        // Directories and files to clean up
18        let dirs_to_clean  = vec![self.as_ref().join("target")];
19        let files_to_clean = vec![self.as_ref().join("Cargo.lock")];
20
21        // Remove directories
22        for dir in dirs_to_clean {
23            if fs::metadata(&dir).await.is_ok() {
24                fs::remove_dir_all(&dir).await.map_err(|_| WorkspaceError::DirectoryRemovalError)?;
25            }
26        }
27
28        // Remove files
29        for file in files_to_clean {
30            if fs::metadata(&file).await.is_ok() {
31                fs::remove_file(&file).await.map_err(|_| WorkspaceError::FileRemovalError)?;
32            }
33        }
34
35        Ok(())
36    }
37}
38
39#[cfg(test)]
40mod test_cleanup_workspace {
41    use super::*;
42    use std::path::{Path, PathBuf};
43    use tempfile::tempdir;
44    use tokio::fs;
45    use workspacer_3p::async_trait; 
46    // or however you're importing async_trait, etc.
47
48    // -----------------------------------------------------------------------
49    // 1) A minimal MockWorkspace that implements AsRef<Path> and references a
50    //    temp directory. We'll also implement the CleanupWorkspace trait if needed.
51    // -----------------------------------------------------------------------
52
53    #[derive(Debug)]
54    struct MockWorkspace {
55        root_dir: PathBuf,
56    }
57
58    impl MockWorkspace {
59        fn new(path: PathBuf) -> Self {
60            Self { root_dir: path }
61        }
62    }
63
64    impl AsRef<Path> for MockWorkspace {
65        fn as_ref(&self) -> &Path {
66            &self.root_dir
67        }
68    }
69
70    // We'll also implement the CleanupWorkspace trait if your code
71    // requires the workspace itself to implement it:
72    #[async_trait]
73    impl CleanupWorkspace for MockWorkspace {
74        async fn cleanup_workspace(&self) -> Result<(), WorkspaceError> {
75            // We'll copy the logic from your snippet or call the same function:
76            // In real usage, if you have a workspace type that references the same code,
77            // you can simply delegate or replicate the snippet:
78
79            let dirs_to_clean = vec![self.as_ref().join("target")];
80            let files_to_clean = vec![self.as_ref().join("Cargo.lock")];
81
82            // Remove directories
83            for dir in dirs_to_clean {
84                if fs::metadata(&dir).await.is_ok() {
85                    fs::remove_dir_all(&dir)
86                        .await
87                        .map_err(|_| WorkspaceError::DirectoryRemovalError)?;
88                }
89            }
90
91            // Remove files
92            for file in files_to_clean {
93                if fs::metadata(&file).await.is_ok() {
94                    fs::remove_file(&file)
95                        .await
96                        .map_err(|_| WorkspaceError::FileRemovalError)?;
97                }
98            }
99
100            Ok(())
101        }
102    }
103
104    // -----------------------------------------------------------------------
105    // 2) Test Cases
106    // -----------------------------------------------------------------------
107
108    /// 1) If neither `target/` nor `Cargo.lock` exist, cleanup_workspace should succeed with no errors.
109    #[tokio::test]
110    async fn test_cleanup_with_no_target_or_cargo_lock() {
111        let tmp_dir = tempdir().expect("Failed to create temp dir");
112        let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
113
114        // The directory is empty => no target/, no Cargo.lock
115        // Call cleanup
116        let result = workspace.cleanup_workspace().await;
117        assert!(result.is_ok(), "Cleanup should succeed if nothing to remove");
118    }
119
120    /// 2) If `target/` exists, it should be removed by cleanup.
121    #[tokio::test]
122    async fn test_cleanup_removes_target_directory() {
123        let tmp_dir = tempdir().expect("Failed to create temp dir");
124        let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
125
126        // Create `target` dir
127        let target_dir = workspace.as_ref().join("target");
128        fs::create_dir_all(&target_dir).await.expect("Failed to create target dir");
129        // Optionally put some sub-files in there
130        let sub_file = target_dir.join("some_file.txt");
131        fs::write(&sub_file, b"dummy").await.expect("Failed to write sub file");
132
133        // Confirm it exists
134        let meta = fs::metadata(&target_dir).await;
135        assert!(meta.is_ok(), "target/ directory should exist before cleanup");
136
137        // Now run cleanup
138        workspace.cleanup_workspace().await.expect("Cleanup should succeed");
139
140        // Confirm it's removed
141        let meta_after = fs::metadata(&target_dir).await;
142        assert!(meta_after.is_err(), "target/ should be removed by cleanup");
143    }
144
145    /// 3) If `Cargo.lock` exists, it should be removed.
146    #[tokio::test]
147    async fn test_cleanup_removes_cargo_lock_file() {
148        let tmp_dir = tempdir().expect("Failed to create temp dir");
149        let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
150
151        // Create Cargo.lock
152        let lock_path = workspace.as_ref().join("Cargo.lock");
153        fs::write(&lock_path, b"dummy lock content").await.expect("Failed to write Cargo.lock");
154
155        // Confirm it exists
156        let meta = fs::metadata(&lock_path).await;
157        assert!(meta.is_ok(), "Cargo.lock should exist");
158
159        // cleanup
160        workspace.cleanup_workspace().await.expect("Cleanup should succeed");
161
162        // confirm removed
163        let meta_after = fs::metadata(&lock_path).await;
164        assert!(meta_after.is_err(), "Cargo.lock should be removed");
165    }
166
167    /// 4) If both `target/` and `Cargo.lock` exist, we remove both.
168    #[tokio::test]
169    async fn test_cleanup_removes_both_target_and_cargo_lock() {
170        let tmp_dir = tempdir().expect("Failed to create temp dir");
171        let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
172
173        // create target
174        let target_dir = workspace.as_ref().join("target");
175        fs::create_dir_all(&target_dir).await.expect("create target dir");
176        // create Cargo.lock
177        let lock_path = workspace.as_ref().join("Cargo.lock");
178        fs::write(&lock_path, b"dummy").await.expect("write cargo.lock");
179
180        // call cleanup
181        workspace.cleanup_workspace().await.expect("cleanup ok");
182
183        // confirm both removed
184        let target_meta = fs::metadata(&target_dir).await;
185        let lock_meta = fs::metadata(&lock_path).await;
186        assert!(target_meta.is_err(), "target/ removed");
187        assert!(lock_meta.is_err(), "Cargo.lock removed");
188    }
189
190    #[cfg(unix)]
191    #[tokio::test]
192    async fn test_cleanup_failure_on_unix() {
193        use std::os::unix::fs::PermissionsExt;
194
195        let tmp_dir = tempdir().expect("create tempdir");
196        let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
197
198        // 1) Create Cargo.lock
199        let lock_path = workspace.as_ref().join("Cargo.lock");
200        fs::write(&lock_path, b"some content").await.unwrap();
201
202        // 2) Remove *directory* write permission so we can't remove files inside it
203        let mut perms = std::fs::metadata(tmp_dir.path()).unwrap().permissions();
204        // Typically, `0o500` => user read & execute, no write
205        // (or you can do 0o555 for read+execute for user/group/other)
206        perms.set_mode(0o500);
207        std::fs::set_permissions(tmp_dir.path(), perms).expect("set perms on directory");
208
209        // 3) Now attempt cleanup; removing Cargo.lock should fail due to no write permission
210        let result = workspace.cleanup_workspace().await;
211        assert!(result.is_err(), "Should fail if we can't remove Cargo.lock");
212        match result {
213            Err(WorkspaceError::FileRemovalError) => {
214                // expected
215            }
216            other => panic!("Expected FileRemovalError, got {:?}", other),
217        }
218    }
219
220    /// 6) If we call cleanup multiple times, subsequent calls should succeed even if there’s nothing to remove.
221    #[tokio::test]
222    async fn test_cleanup_idempotent() {
223        let tmp_dir = tempdir().expect("Failed to create temp dir");
224        let workspace = MockWorkspace::new(tmp_dir.path().to_path_buf());
225
226        // Create target + cargo.lock
227        fs::create_dir_all(workspace.as_ref().join("target")).await.unwrap();
228        fs::write(workspace.as_ref().join("Cargo.lock"), b"xyz").await.unwrap();
229
230        // First cleanup
231        workspace.cleanup_workspace().await.unwrap();
232        // Everything removed
233
234        // Second cleanup => no target/ or Cargo.lock => should not fail
235        workspace.cleanup_workspace().await.unwrap();
236    }
237}