workspacer_docs/
generate_docs.rs

1// ---------------- [ File: workspacer-docs/src/generate_docs.rs ]
2crate::ix!();
3
4#[async_trait]
5pub trait GenerateDocs {
6    type Error;
7    async fn generate_docs(&self) -> Result<(), Self::Error>;
8}
9
10#[async_trait]
11impl<P,H:CrateHandleInterface<P>> GenerateDocs for Workspace<P,H> 
12where for<'async_trait> P: From<PathBuf> + AsRef<Path> + Send + Sync + 'async_trait
13{
14    type Error = WorkspaceError;
15
16    /// Generates the documentation for the entire workspace by running `cargo doc`.
17    async fn generate_docs(&self) -> Result<(), WorkspaceError> {
18        let workspace_path = self.as_ref();  // Assuming `self.path()` returns the workspace root path.
19        
20        // Execute `cargo doc` in the workspace directory.
21        let output = Command::new("cargo")
22            .arg("doc")
23            .current_dir(workspace_path)
24            .output()
25            .await
26            .map_err(|e| CargoDocError::CommandError { io: e.into() })?;  // Handle any I/O error from the process execution.
27
28        if !output.status.success() {
29            // If the command failed, return an error with the captured output.
30            return Err(WorkspaceError::from(CargoDocError::UnknownError {
31                stderr: Some(String::from_utf8_lossy(&output.stderr).to_string()),
32                stdout: Some(String::from_utf8_lossy(&output.stdout).to_string()),
33            }));
34        }
35
36        Ok(())  // If the command was successful, return Ok.
37    }
38}
39
40#[cfg(test)]
41mod test_generate_docs_real {
42    use super::*;
43    use std::path::PathBuf;
44    use tempfile::tempdir;
45    use workspacer_3p::tokio::process::Command;
46    use workspacer_3p::tokio;
47    // Adjust imports to match your code. For instance:
48    // use crate::{ GenerateDocs, WorkspaceInterface, ... };
49
50    // We define a minimal "MockWorkspace" or use your real `Workspace<P,H>` if possible.
51    #[derive(Debug)]
52    struct MockWorkspace {
53        root: PathBuf,
54    }
55    impl AsRef<std::path::Path> for MockWorkspace {
56        fn as_ref(&self) -> &std::path::Path {
57            &self.root
58        }
59    }
60
61    // We also implement `GenerateDocs` or rely on your real implementation:
62    // but let's say we replicate your snippet in a trait impl for this mock:
63    #[async_trait]
64    impl GenerateDocs for MockWorkspace {
65        type Error = WorkspaceError; // or your real error type
66
67        async fn generate_docs(&self) -> Result<(), Self::Error> {
68            let workspace_path = self.as_ref();
69            let output = Command::new("cargo")
70                .arg("doc")
71                .current_dir(workspace_path)
72                .output()
73                .await
74                .map_err(|e| CargoDocError::CommandError { io: e.into() })?;
75
76            if !output.status.success() {
77                return Err(WorkspaceError::from(CargoDocError::UnknownError {
78                    stderr: Some(String::from_utf8_lossy(&output.stderr).to_string()),
79                    stdout: Some(String::from_utf8_lossy(&output.stdout).to_string()),
80                }));
81            }
82
83            Ok(())
84        }
85    }
86
87    // We'll define test scenarios:
88
89    /// 1) Succeeds if we have a valid cargo project
90    #[tokio::test]
91    async fn test_generate_docs_success() {
92        let tmp_dir = tempdir().expect("Failed to create temp dir");
93        let path = tmp_dir.path();
94
95        // 1) Initialize a cargo project in this directory. We'll do a minimal approach:
96        // cargo init .
97        let init_status = Command::new("cargo")
98            .arg("init")
99            .arg("--name")
100            .arg("test_docs_proj")
101            .arg("--vcs")
102            .arg("none")
103            .current_dir(path)
104            .output()
105            .await
106            .expect("Failed to run cargo init");
107        assert!(init_status.status.success(), "cargo init must succeed for test");
108
109        // 2) Create the mock or real workspace referencing that directory
110        let ws = MockWorkspace { root: path.to_path_buf() };
111
112        // 3) Run generate_docs
113        let result = ws.generate_docs().await;
114        // We expect Ok(()) if everything is installed correctly.
115        // On a real system with cargo doc, this should pass.
116        // If docs fail for some reason, you might get an error. 
117        // Possibly your environment might not have cargo doc or correct toolchain.
118        assert!(result.is_ok(), "cargo doc should succeed on a minimal project");
119    }
120
121    /// 2) If the doc build fails for some reason (like a broken code), 
122    ///    we expect an error with the captured stdout/stderr in CargoDocError::UnknownError.
123    #[tokio::test]
124    async fn test_generate_docs_failure() {
125        let tmp_dir = tempdir().expect("tempdir failed");
126        let path = tmp_dir.path();
127
128        // Initialize a cargo project
129        let init_status = Command::new("cargo")
130            .arg("init")
131            .arg("--name")
132            .arg("broken_docs")
133            .arg("--vcs")
134            .arg("none")
135            .current_dir(path)
136            .output()
137            .await
138            .expect("cargo init");
139        assert!(init_status.status.success());
140
141        // Insert invalid rust code in src/lib.rs so docs fail
142        let src_lib = path.join("src").join("lib.rs");
143        tokio::fs::write(&src_lib, b"broken code ???").await.expect("write broken code");
144
145        let ws = MockWorkspace { root: path.to_path_buf() };
146        let result = ws.generate_docs().await;
147        match result {
148            Err(WorkspaceError::CargoDocError(CargoDocError::UnknownError { stderr, stdout })) => {
149                // We'll see some compiler error messages in stderr
150                assert!(stderr.as_ref().unwrap_or(&String::new()).contains("error"), "stderr should mention an error");
151            }
152            Ok(_) => {
153                panic!("Expected doc build to fail, but got Ok(())");
154            }
155            other => panic!("Expected UnknownError, got {:?}", other),
156        }
157    }
158
159    /// 3) If cargo doc can't be spawned or cargo isn't installed, we get `CargoDocError::CommandError`.
160    #[tokio::test]
161    async fn test_generate_docs_cannot_spawn_command() {
162        // We'll skip creating a real project. We'll do a scenario that might not have cargo installed
163        // or you can forcibly rename cargo. The simplest is we rename cargo in PATH or remove it, 
164        // but let's just see what happens:
165        let ws = MockWorkspace { root: PathBuf::from("/non/existent/path") };
166
167        // If cargo doesn't exist or something, we get CommandError. 
168        // But if cargo is installed, we might get a different error about not a cargo project. 
169        // We'll just do partial matching:
170        let result = ws.generate_docs().await;
171        match result {
172            Err(WorkspaceError::CargoDocError(CargoDocError::CommandError{..})) => {
173                // Good
174            }
175            other => {
176                println!("Got something else: {:?}", other);
177            }
178        }
179    }
180}