crate::ix!();
pub struct TestCoverageCommand {
stdout: String,
stderr: String,
}
impl GenerateReport for TestCoverageCommand {
type Report = TestCoverageReport;
type Error = TestCoverageError;
fn generate_report(&self) -> Result<Self::Report, Self::Error> {
if let Ok(report) = TestCoverageReport::from_maybe_plaintext_coverage_summary(self.stdout()) {
return Ok(report);
}
if self.is_stdout_empty() || self.has_stderr_errors() {
return Err(TestCoverageError::CoverageParseError);
}
let coverage_data = self.parse_json_output()?;
TestCoverageReport::try_from(&coverage_data)
}
}
impl TestCoverageCommand {
pub fn stdout(&self) -> &str {
&self.stdout
}
pub fn stderr(&self) -> &str {
&self.stderr
}
pub fn is_stdout_empty(&self) -> bool {
self.stdout.trim().is_empty()
}
pub fn has_stderr_errors(&self) -> bool {
self.stderr.contains("error")
}
pub fn parse_json_output(&self) -> Result<serde_json::Value, TestCoverageError> {
serde_json::from_str(&self.stdout).map_err(|e| {
error!("Failed to parse coverage report: {}", e);
TestCoverageError::CoverageParseError
})
}
pub async fn run_in(workspace_path: impl AsRef<Path>)
-> Result<Self,TestCoverageError>
{
let output = tokio::process::Command::new("cargo")
.arg("tarpaulin")
.arg("--out")
.arg("Json")
.arg("--")
.arg("--quiet")
.current_dir(workspace_path)
.output()
.await
.map_err(|e| TestCoverageError::CommandError { io: e.into() })?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
info!("stdout: {}", stdout);
info!("stderr: {}", stderr);
match output.status.success() {
true => Ok(Self { stdout, stderr }),
false => {
if stderr.contains("Test failed during run") {
error!("Test coverage run failed due to test failure.");
Err(TestCoverageError::TestFailure {
stderr: Some(stderr),
stdout: Some(stdout),
})
} else {
error!("Test coverage run failed for unknown reasons.");
Err(TestCoverageError::UnknownError {
stdout: Some(stdout),
stderr: Some(stderr),
})
}
}
}
}
}