crate::ix!();
#[async_trait]
pub trait RunTestsWithCoverage {
type Report;
type Error;
async fn run_tests_with_coverage(&self)
-> Result<Self::Report, Self::Error>;
}
#[async_trait]
impl RunTestsWithCoverage for CrateHandle {
type Report = TestCoverageReport;
type Error = WorkspaceError;
async fn run_tests_with_coverage(&self) -> Result<Self::Report, Self::Error> {
let workspace_root = self
.root_dir_path_buf()
.parent()
.ok_or_else(|| {
error!("Cannot get parent directory for crate_path={:?}", self.root_dir_path_buf());
WorkspaceError::IoError {
io_error: std::sync::Arc::new(std::io::Error::new(
std::io::ErrorKind::NotFound,
"No parent directory"
)),
context: "finding workspace root from crate path".to_string(),
}
})?
.to_path_buf();
let crate_name = self.name();
let coverage_cmd = TestCoverageCommand::run_with_package(&workspace_root, &crate_name).await?;
let report = coverage_cmd.generate_report()?;
Ok(report)
}
}
#[async_trait]
impl<P,H:CrateHandleInterface<P>> RunTestsWithCoverage for Workspace<P,H>
where for<'async_trait> P: From<PathBuf> + AsRef<Path> + Send + Sync + 'async_trait
{
type Report = TestCoverageReport;
type Error = WorkspaceError;
async fn run_tests_with_coverage(&self)
-> Result<Self::Report, Self::Error>
{
let workspace_path = self.as_ref();
let test_coverage = TestCoverageCommand::run_in(workspace_path).await?;
let report = test_coverage.generate_report()?;
Ok(report)
}
}
#[cfg(test)]
mod test_run_tests_with_coverage_real {
use super::*;
use std::path::PathBuf;
use tempfile::tempdir;
use workspacer_3p::tokio::process::Command;
use workspacer_3p::tokio;
#[derive(Debug)]
struct MockWorkspace {
path: PathBuf,
}
impl AsRef<std::path::Path> for MockWorkspace {
fn as_ref(&self) -> &std::path::Path {
&self.path
}
}
#[async_trait]
impl RunTestsWithCoverage for MockWorkspace {
type Report = TestCoverageReport;
type Error = WorkspaceError;
async fn run_tests_with_coverage(&self) -> Result<Self::Report, Self::Error> {
let workspace_path = self.as_ref();
let test_coverage = TestCoverageCommand::run_in(workspace_path).await?;
let report = test_coverage.generate_report()?;
Ok(report)
}
}
#[traced_test]
async fn test_run_tests_with_coverage_succeeds() {
trace!("Beginning test_run_tests_with_coverage_succeeds...");
let tmp_dir = tempdir().expect("Failed to create temp directory");
let path = tmp_dir.path();
let init_status = Command::new("cargo")
.arg("init")
.arg("--bin")
.arg("--vcs")
.arg("none")
.current_dir(path)
.output()
.await
.expect("Failed to spawn `cargo init`");
if !init_status.status.success() {
warn!("Skipping test because `cargo init` failed with status: {:?}", init_status.status);
return;
}
let main_rs = path.join("src").join("main.rs");
let code = r#"
fn main() {}
#[test]
fn test_ok() { assert_eq!(2+2,4); }
"#;
tokio::fs::write(&main_rs, code)
.await
.expect("Failed to write test code to main.rs");
let ws = MockWorkspace { path: path.to_path_buf() };
match ws.run_tests_with_coverage().await {
Ok(report) => {
info!("Coverage succeeded: {:?}", report);
assert!(report.covered_lines() > 0, "Expected at least one covered line");
assert!(report.total_coverage() > 0.0, "Expected non-zero coverage");
}
Err(e) => panic!("Expected coverage to succeed, but got error: {:?}", e),
}
}
#[traced_test]
async fn test_run_tests_with_coverage_fails() {
trace!("Beginning test_run_tests_with_coverage_fails...");
let tmp_dir = tempdir().expect("Failed to create temp directory");
let path = tmp_dir.path();
let init_status = Command::new("cargo")
.arg("init")
.arg("--bin")
.arg("--vcs")
.arg("none")
.current_dir(path)
.output()
.await
.expect("Failed to spawn `cargo init`");
if !init_status.status.success() {
warn!("Skipping test because `cargo init` failed with status: {:?}", init_status.status);
return;
}
let main_rs = path.join("src").join("main.rs");
let code = r#"
fn main(){}
#[test]
fn test_fail(){ assert_eq!(1+1,3); }
"#;
tokio::fs::write(&main_rs, code)
.await
.expect("Failed to write test code to main.rs");
let ws = MockWorkspace { path: path.to_path_buf() };
match ws.run_tests_with_coverage().await {
Ok(report) => panic!("Expected coverage to fail, got success: {:?}", report),
Err(e) => {
info!("Coverage or test failure as expected: {:?}", e);
}
}
}
}