use super::analyzer::CoverageAnalyzer;
use super::types::CoverageReport;
use crate::{Error, Result};
use std::path::Path;
impl CoverageAnalyzer {
pub fn validate_coverage(&self, report: &CoverageReport) -> Result<()> {
let mut violations = Vec::new();
if report.line_coverage < self.config().min_line_coverage {
violations.push(format!(
"Line coverage {:.1}% is below minimum {:.1}%",
report.line_coverage,
self.config().min_line_coverage
));
}
if report.function_coverage < self.config().min_function_coverage {
violations.push(format!(
"Function coverage {:.1}% is below minimum {:.1}%",
report.function_coverage,
self.config().min_function_coverage
));
}
if report.branch_coverage < self.config().min_branch_coverage {
violations.push(format!(
"Branch coverage {:.1}% is below minimum {:.1}%",
report.branch_coverage,
self.config().min_branch_coverage
));
}
if !violations.is_empty() {
let message = format!("Coverage violations:\n โข {}", violations.join("\n โข "));
if self.config().fail_on_low_coverage {
return Err(Error::validation(message));
}
tracing::warn!("{}", message);
}
Ok(())
}
pub fn format_coverage_report(&self, report: &CoverageReport) -> String {
let mut output = String::new();
self.add_report_header(&mut output);
self.add_overall_coverage(&mut output, report);
self.add_threshold_status(&mut output, report);
self.add_low_coverage_files(&mut output, report);
self.add_improvement_suggestions(&mut output);
output
}
fn add_report_header(&self, output: &mut String) {
output.push_str("๐ Test Coverage Report\n");
output.push_str("=".repeat(39).as_str());
output.push_str("\n\n");
}
fn add_overall_coverage(&self, output: &mut String, report: &CoverageReport) {
output.push_str(&format!("๐ Overall Coverage:\n"));
output.push_str(&format!(
" โข Lines: {:.1}% ({}/{})\n",
report.line_coverage, report.lines_tested, report.total_lines
));
output.push_str(&format!(
" โข Functions: {:.1}% ({}/{})\n",
report.function_coverage, report.functions_tested, report.total_functions
));
output.push_str(&format!(
" โข Branches: {:.1}% ({}/{})\n\n",
report.branch_coverage, report.branches_tested, report.total_branches
));
}
fn add_threshold_status(&self, output: &mut String, report: &CoverageReport) {
let line_status = if report.line_coverage >= self.config().min_line_coverage {
"โ
"
} else {
"โ"
};
let func_status = if report.function_coverage >= self.config().min_function_coverage {
"โ
"
} else {
"โ"
};
let branch_status = if report.branch_coverage >= self.config().min_branch_coverage {
"โ
"
} else {
"โ"
};
output.push_str("๐ฏ Threshold Status:\n");
output.push_str(&format!(
" {} Lines: {:.1}% (min: {:.1}%)\n",
line_status,
report.line_coverage,
self.config().min_line_coverage
));
output.push_str(&format!(
" {} Functions: {:.1}% (min: {:.1}%)\n",
func_status,
report.function_coverage,
self.config().min_function_coverage
));
output.push_str(&format!(
" {} Branches: {:.1}% (min: {:.1}%)\n\n",
branch_status,
report.branch_coverage,
self.config().min_branch_coverage
));
}
fn add_low_coverage_files(&self, output: &mut String, report: &CoverageReport) {
let mut low_coverage_files: Vec<_> = report
.file_coverage
.values()
.filter(|file| file.line_coverage < self.config().min_line_coverage)
.collect();
low_coverage_files.sort_by(|a, b| {
a.line_coverage
.partial_cmp(&b.line_coverage)
.unwrap_or(std::cmp::Ordering::Equal)
});
if !low_coverage_files.is_empty() {
output.push_str("โ ๏ธ Files Below Threshold:\n");
for file in low_coverage_files.iter().take(5) {
output.push_str(&format!(
" โข {}: {:.1}%\n",
file.file_path, file.line_coverage
));
}
if low_coverage_files.len() > 5 {
output.push_str(&format!(
" ... and {} more files\n",
low_coverage_files.len() - 5
));
}
output.push('\n');
}
}
fn add_improvement_suggestions(&self, output: &mut String) {
output.push_str("๐ก To improve coverage:\n");
output.push_str(" โข Add tests for uncovered code paths\n");
output.push_str(" โข Remove dead code\n");
output.push_str(" โข Test error conditions and edge cases\n");
output.push_str(" โข Use property-based testing\n");
}
pub async fn check_project_coverage(&self, project_path: &Path) -> Result<()> {
println!("๐งช Checking test coverage...");
let report = self.run_coverage(project_path).await?;
println!("{}", self.format_coverage_report(&report));
self.validate_coverage(&report)?;
println!("โ
Coverage check completed successfully");
Ok(())
}
}