ferrous_forge/test_coverage/
reporting.rs1use super::analyzer::CoverageAnalyzer;
4use super::types::CoverageReport;
5use crate::{Error, Result};
6use std::path::Path;
7
8impl CoverageAnalyzer {
9 pub fn validate_coverage(&self, report: &CoverageReport) -> Result<()> {
16 let mut violations = Vec::new();
17
18 if report.line_coverage < self.config().min_line_coverage {
19 violations.push(format!(
20 "Line coverage {:.1}% is below minimum {:.1}%",
21 report.line_coverage,
22 self.config().min_line_coverage
23 ));
24 }
25
26 if report.function_coverage < self.config().min_function_coverage {
27 violations.push(format!(
28 "Function coverage {:.1}% is below minimum {:.1}%",
29 report.function_coverage,
30 self.config().min_function_coverage
31 ));
32 }
33
34 if report.branch_coverage < self.config().min_branch_coverage {
35 violations.push(format!(
36 "Branch coverage {:.1}% is below minimum {:.1}%",
37 report.branch_coverage,
38 self.config().min_branch_coverage
39 ));
40 }
41
42 if !violations.is_empty() {
43 let message = format!("Coverage violations:\n โข {}", violations.join("\n โข "));
44
45 if self.config().fail_on_low_coverage {
46 return Err(Error::validation(message));
47 }
48 tracing::warn!("{}", message);
49 }
50
51 Ok(())
52 }
53
54 pub fn format_coverage_report(&self, report: &CoverageReport) -> String {
56 let mut output = String::new();
57
58 self.add_report_header(&mut output);
59 self.add_overall_coverage(&mut output, report);
60 self.add_threshold_status(&mut output, report);
61 self.add_low_coverage_files(&mut output, report);
62 self.add_improvement_suggestions(&mut output);
63
64 output
65 }
66
67 fn add_report_header(&self, output: &mut String) {
69 output.push_str("๐ Test Coverage Report\n");
70 output.push_str("=".repeat(39).as_str());
71 output.push_str("\n\n");
72 }
73
74 fn add_overall_coverage(&self, output: &mut String, report: &CoverageReport) {
76 output.push_str(&format!("๐ Overall Coverage:\n"));
77 output.push_str(&format!(
78 " โข Lines: {:.1}% ({}/{})\n",
79 report.line_coverage, report.lines_tested, report.total_lines
80 ));
81 output.push_str(&format!(
82 " โข Functions: {:.1}% ({}/{})\n",
83 report.function_coverage, report.functions_tested, report.total_functions
84 ));
85 output.push_str(&format!(
86 " โข Branches: {:.1}% ({}/{})\n\n",
87 report.branch_coverage, report.branches_tested, report.total_branches
88 ));
89 }
90
91 fn add_threshold_status(&self, output: &mut String, report: &CoverageReport) {
93 let line_status = if report.line_coverage >= self.config().min_line_coverage {
94 "โ
"
95 } else {
96 "โ"
97 };
98 let func_status = if report.function_coverage >= self.config().min_function_coverage {
99 "โ
"
100 } else {
101 "โ"
102 };
103 let branch_status = if report.branch_coverage >= self.config().min_branch_coverage {
104 "โ
"
105 } else {
106 "โ"
107 };
108
109 output.push_str("๐ฏ Threshold Status:\n");
110 output.push_str(&format!(
111 " {} Lines: {:.1}% (min: {:.1}%)\n",
112 line_status,
113 report.line_coverage,
114 self.config().min_line_coverage
115 ));
116 output.push_str(&format!(
117 " {} Functions: {:.1}% (min: {:.1}%)\n",
118 func_status,
119 report.function_coverage,
120 self.config().min_function_coverage
121 ));
122 output.push_str(&format!(
123 " {} Branches: {:.1}% (min: {:.1}%)\n\n",
124 branch_status,
125 report.branch_coverage,
126 self.config().min_branch_coverage
127 ));
128 }
129
130 fn add_low_coverage_files(&self, output: &mut String, report: &CoverageReport) {
132 let mut low_coverage_files: Vec<_> = report
133 .file_coverage
134 .values()
135 .filter(|file| file.line_coverage < self.config().min_line_coverage)
136 .collect();
137 low_coverage_files.sort_by(|a, b| {
138 a.line_coverage
139 .partial_cmp(&b.line_coverage)
140 .unwrap_or(std::cmp::Ordering::Equal)
141 });
142
143 if !low_coverage_files.is_empty() {
144 output.push_str("โ ๏ธ Files Below Threshold:\n");
145 for file in low_coverage_files.iter().take(5) {
146 output.push_str(&format!(
147 " โข {}: {:.1}%\n",
148 file.file_path, file.line_coverage
149 ));
150 }
151 if low_coverage_files.len() > 5 {
152 output.push_str(&format!(
153 " ... and {} more files\n",
154 low_coverage_files.len() - 5
155 ));
156 }
157 output.push('\n');
158 }
159 }
160
161 fn add_improvement_suggestions(&self, output: &mut String) {
163 output.push_str("๐ก To improve coverage:\n");
164 output.push_str(" โข Add tests for uncovered code paths\n");
165 output.push_str(" โข Remove dead code\n");
166 output.push_str(" โข Test error conditions and edge cases\n");
167 output.push_str(" โข Use property-based testing\n");
168 }
169
170 pub async fn check_project_coverage(&self, project_path: &Path) -> Result<()> {
177 println!("๐งช Checking test coverage...");
178
179 let report = self.run_coverage(project_path).await?;
180
181 println!("{}", self.format_coverage_report(&report));
182
183 self.validate_coverage(&report)?;
184
185 println!("โ
Coverage check completed successfully");
186 Ok(())
187 }
188}