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