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