workspacer_test_coverage/
test_coverage.rs

1// ---------------- [ File: workspacer-test-coverage/src/test_coverage.rs ]
2crate::ix!();
3
4#[async_trait]
5pub trait RunTestsWithCoverage {
6
7    type Report;
8    type Error;
9
10    async fn run_tests_with_coverage(&self) 
11        -> Result<Self::Report, Self::Error>;
12}
13
14// We'll show a naive approach: run "cargo tarpaulin --package crate_name" from the workspace root.
15// Then parse stdout => produce a TestCoverageReport
16//
17#[async_trait]
18impl RunTestsWithCoverage for CrateHandle {
19    type Report = TestCoverageReport;
20    type Error  = WorkspaceError;
21
22    async fn run_tests_with_coverage(&self) -> Result<Self::Report, Self::Error> {
23        let workspace_root = self
24            .root_dir_path_buf()
25            .parent()
26            .ok_or_else(|| {
27                // If the crate root is the workspace, you might do something else. 
28                // We'll do a naive approach for demonstration.
29                error!("Cannot get parent directory for crate_path={:?}", self.root_dir_path_buf());
30                WorkspaceError::IoError {
31                    io_error: std::sync::Arc::new(std::io::Error::new(
32                        std::io::ErrorKind::NotFound, 
33                        "No parent directory"
34                    )),
35                    context: "finding workspace root from crate path".to_string(),
36                }
37            })?
38            .to_path_buf();
39
40        // We'll run cargo tarpaulin with `--package crate_name`
41        let crate_name = self.name(); 
42        let coverage_cmd = TestCoverageCommand::run_with_package(&workspace_root, &crate_name).await?;
43        let report = coverage_cmd.generate_report()?;
44        Ok(report)
45    }
46}
47
48#[async_trait]
49impl<P,H:CrateHandleInterface<P>> RunTestsWithCoverage for Workspace<P,H> 
50where for<'async_trait> P: From<PathBuf> + AsRef<Path> + Send + Sync + 'async_trait
51{
52    type Report = TestCoverageReport;
53    type Error  = WorkspaceError;
54
55    /// Runs tests and gathers code coverage.
56    async fn run_tests_with_coverage(&self) 
57        -> Result<Self::Report, Self::Error> 
58    {
59        let workspace_path = self.as_ref();  // Assuming `self.path()` returns the workspace root path.
60
61        let test_coverage = TestCoverageCommand::run_in(workspace_path).await?;
62
63        let report = test_coverage.generate_report()?;
64
65        Ok(report)
66    }
67}
68
69#[cfg(test)]
70mod test_run_tests_with_coverage_real {
71    use super::*;
72    use std::path::PathBuf;
73    use tempfile::tempdir;
74    use workspacer_3p::tokio::process::Command;
75    use workspacer_3p::tokio;
76
77    // We'll define or import your real `Workspace<P,H>` and `RunTestsWithCoverage` trait:
78    // use crate::{Workspace, RunTestsWithCoverage, TestCoverageReport, TestCoverageCommand, ...};
79
80    /// A minimal mock or real workspace object that implements your coverage method
81    #[derive(Debug)]
82    struct MockWorkspace {
83        path: PathBuf,
84    }
85
86    impl AsRef<std::path::Path> for MockWorkspace {
87        fn as_ref(&self) -> &std::path::Path {
88            &self.path
89        }
90    }
91
92    // If you have a real `Workspace<P,H>::new(...)`, you can skip the mock struct.
93
94    // Suppose we replicate or rely on your posted code:
95    #[async_trait]
96    impl RunTestsWithCoverage for MockWorkspace {
97        type Report = TestCoverageReport;
98        type Error = WorkspaceError;
99
100        async fn run_tests_with_coverage(&self) -> Result<Self::Report, Self::Error> {
101            let workspace_path = self.as_ref();
102            let test_coverage = TestCoverageCommand::run_in(workspace_path).await?;
103            let report = test_coverage.generate_report()?;
104            Ok(report)
105        }
106    }
107
108    #[traced_test]
109    async fn test_run_tests_with_coverage_succeeds() {
110        trace!("Beginning test_run_tests_with_coverage_succeeds...");
111
112        let tmp_dir = tempdir().expect("Failed to create temp directory");
113        let path = tmp_dir.path();
114
115        let init_status = Command::new("cargo")
116            .arg("init")
117            .arg("--bin")
118            .arg("--vcs")
119            .arg("none")
120            .current_dir(path)
121            .output()
122            .await
123            .expect("Failed to spawn `cargo init`");
124
125        if !init_status.status.success() {
126            warn!("Skipping test because `cargo init` failed with status: {:?}", init_status.status);
127            return;
128        }
129
130        // Insert a small passing test
131        let main_rs = path.join("src").join("main.rs");
132        let code = r#"
133            fn main() {}
134        #[test]
135        fn test_ok() { assert_eq!(2+2,4); }
136        "#;
137        tokio::fs::write(&main_rs, code)
138            .await
139            .expect("Failed to write test code to main.rs");
140
141        let ws = MockWorkspace { path: path.to_path_buf() };
142
143        // Running tests with coverage should succeed
144        match ws.run_tests_with_coverage().await {
145            Ok(report) => {
146                info!("Coverage succeeded: {:?}", report);
147                assert!(report.covered_lines() > 0, "Expected at least one covered line");
148                assert!(report.total_coverage() > 0.0, "Expected non-zero coverage");
149            }
150            Err(e) => panic!("Expected coverage to succeed, but got error: {:?}", e),
151        }
152    }
153
154    #[traced_test]
155    async fn test_run_tests_with_coverage_fails() {
156        trace!("Beginning test_run_tests_with_coverage_fails...");
157
158        let tmp_dir = tempdir().expect("Failed to create temp directory");
159        let path = tmp_dir.path();
160
161        let init_status = Command::new("cargo")
162            .arg("init")
163            .arg("--bin")
164            .arg("--vcs")
165            .arg("none")
166            .current_dir(path)
167            .output()
168            .await
169            .expect("Failed to spawn `cargo init`");
170
171        if !init_status.status.success() {
172            warn!("Skipping test because `cargo init` failed with status: {:?}", init_status.status);
173            return;
174        }
175
176        // Insert a failing test
177        let main_rs = path.join("src").join("main.rs");
178        let code = r#"
179            fn main(){}
180        #[test]
181        fn test_fail(){ assert_eq!(1+1,3); }
182        "#;
183        tokio::fs::write(&main_rs, code)
184            .await
185            .expect("Failed to write test code to main.rs");
186
187        let ws = MockWorkspace { path: path.to_path_buf() };
188
189        // We expect coverage or test to fail
190        match ws.run_tests_with_coverage().await {
191            Ok(report) => panic!("Expected coverage to fail, got success: {:?}", report),
192            Err(e) => {
193                info!("Coverage or test failure as expected: {:?}", e);
194            }
195        }
196    }
197}