ferrous_forge/test_coverage/
utils.rs1use super::types::FileCoverage;
4use crate::{Error, Result};
5use serde::Deserialize;
6use std::collections::HashMap;
7
8#[derive(Deserialize)]
16pub struct TarpaulinOutput {
17 pub coverage: f64,
19 pub covered: u32,
21 pub coverable: u32,
23 pub files: Vec<TarpaulinFile>,
25 #[serde(skip)]
27 pub branches_covered: Option<u32>,
28 #[serde(skip)]
30 pub branches_total: Option<u32>,
31 #[serde(skip)]
33 pub lines_covered: u32,
34 #[serde(skip)]
36 pub lines_total: u32,
37 #[serde(skip)]
39 pub line_coverage: f64,
40}
41
42#[derive(Deserialize)]
44pub struct TarpaulinFile {
45 pub path: Vec<String>,
47 pub covered: u32,
49 pub coverable: u32,
51}
52
53pub struct FunctionStats {
55 pub coverage: f64,
57 pub tested: u32,
59 pub total: u32,
61}
62
63pub fn parse_tarpaulin_json(output: &str) -> Result<TarpaulinOutput> {
73 let mut data: TarpaulinOutput = serde_json::from_str(output)
74 .map_err(|e| Error::process(format!("Failed to parse tarpaulin output: {}", e)))?;
75
76 data.line_coverage = data.coverage;
78 data.lines_covered = data.covered;
79 data.lines_total = data.coverable;
80
81 Ok(data)
82}
83
84pub fn process_file_coverage(
86 files: &[TarpaulinFile],
87) -> (HashMap<String, FileCoverage>, FunctionStats) {
88 let mut file_coverage = HashMap::new();
89 let mut total_functions_tested = 0;
90 let mut total_functions = 0;
91
92 for file_data in files {
93 let file_path = file_data
95 .path
96 .join("/")
97 .trim_start_matches("//")
98 .to_string();
99 let (estimated_functions, estimated_functions_tested) =
100 estimate_function_coverage(file_data);
101
102 total_functions += estimated_functions;
103 total_functions_tested += estimated_functions_tested;
104
105 let coverage = create_file_coverage(
106 &file_path,
107 file_data,
108 estimated_functions,
109 estimated_functions_tested,
110 );
111 file_coverage.insert(file_path, coverage);
112 }
113
114 let function_coverage =
115 calculate_function_coverage_percentage(total_functions_tested, total_functions);
116
117 (
118 file_coverage,
119 FunctionStats {
120 coverage: function_coverage,
121 tested: total_functions_tested,
122 total: total_functions,
123 },
124 )
125}
126
127fn estimate_function_coverage(file_data: &TarpaulinFile) -> (u32, u32) {
129 let estimated_functions = (file_data.coverable / 10).max(1);
130 let line_coverage_pct = if file_data.coverable == 0 {
131 0.0
132 } else {
133 file_data.covered as f64 / file_data.coverable as f64
134 };
135 let estimated_functions_tested = (line_coverage_pct * estimated_functions as f64) as u32;
136 (estimated_functions, estimated_functions_tested)
137}
138
139fn create_file_coverage(
141 file_path: &str,
142 file_data: &TarpaulinFile,
143 estimated_functions: u32,
144 estimated_functions_tested: u32,
145) -> FileCoverage {
146 let line_coverage = if file_data.coverable == 0 {
147 0.0
148 } else {
149 (file_data.covered as f64 / file_data.coverable as f64) * 100.0
150 };
151 FileCoverage {
152 file_path: file_path.to_string(),
153 line_coverage,
154 function_coverage: calculate_function_coverage_percentage(
155 estimated_functions_tested,
156 estimated_functions,
157 ),
158 lines_tested: file_data.covered,
159 total_lines: file_data.coverable,
160 functions_tested: estimated_functions_tested,
161 total_functions: estimated_functions,
162 }
163}
164
165pub fn calculate_function_coverage_percentage(tested: u32, total: u32) -> f64 {
167 if total == 0 {
168 0.0
169 } else {
170 (tested as f64 / total as f64) * 100.0
171 }
172}
173
174pub fn calculate_branch_coverage(data: &TarpaulinOutput) -> f64 {
176 match (data.branches_covered, data.branches_total) {
177 (Some(covered), Some(total)) if total > 0 => (covered as f64 / total as f64) * 100.0,
178 _ => 0.0,
179 }
180}