Skip to main content

ferrous_forge/test_coverage/
utils.rs

1//! Test coverage utility functions
2
3use super::types::FileCoverage;
4use crate::{Error, Result};
5use serde::Deserialize;
6use std::collections::HashMap;
7
8/// Tarpaulin JSON output structure
9#[derive(Deserialize)]
10pub struct TarpaulinOutput {
11    /// Line coverage percentage
12    #[serde(rename = "coverage")]
13    pub line_coverage: f64,
14    /// Number of lines covered
15    #[serde(rename = "linesCovered")]
16    pub lines_covered: u32,
17    /// Total number of lines
18    #[serde(rename = "linesTotal")]
19    pub lines_total: u32,
20    /// Number of branches covered
21    #[serde(rename = "branchesCovered")]
22    pub branches_covered: Option<u32>,
23    /// Total number of branches
24    #[serde(rename = "branchesTotal")]
25    pub branches_total: Option<u32>,
26    /// Per-file coverage data
27    #[serde(rename = "files")]
28    pub files: HashMap<String, TarpaulinFile>,
29}
30
31/// Tarpaulin file coverage data
32#[derive(Deserialize)]
33pub struct TarpaulinFile {
34    /// Line coverage percentage
35    #[serde(rename = "coverage")]
36    pub line_coverage: f64,
37    /// Number of lines covered
38    #[serde(rename = "linesCovered")]
39    pub lines_covered: u32,
40    /// Total number of lines
41    #[serde(rename = "linesTotal")]
42    pub lines_total: u32,
43}
44
45/// Function coverage statistics
46pub struct FunctionStats {
47    /// Function coverage percentage
48    pub coverage: f64,
49    /// Number of functions tested
50    pub tested: u32,
51    /// Total number of functions
52    pub total: u32,
53}
54
55/// Parse tarpaulin JSON output
56///
57/// # Errors
58///
59/// Returns an error if the output string is not valid JSON or does not
60/// match the expected tarpaulin format.
61pub fn parse_tarpaulin_json(output: &str) -> Result<TarpaulinOutput> {
62    serde_json::from_str(output)
63        .map_err(|e| Error::process(format!("Failed to parse tarpaulin output: {}", e)))
64}
65
66/// Process file coverage data
67pub fn process_file_coverage(
68    files: &HashMap<String, TarpaulinFile>,
69) -> (HashMap<String, FileCoverage>, FunctionStats) {
70    let mut file_coverage = HashMap::new();
71    let mut total_functions_tested = 0;
72    let mut total_functions = 0;
73
74    for (file_path, file_data) in files {
75        let (estimated_functions, estimated_functions_tested) =
76            estimate_function_coverage(file_data);
77
78        total_functions += estimated_functions;
79        total_functions_tested += estimated_functions_tested;
80
81        let coverage = create_file_coverage(
82            file_path,
83            file_data,
84            estimated_functions,
85            estimated_functions_tested,
86        );
87        file_coverage.insert(file_path.clone(), coverage);
88    }
89
90    let function_coverage =
91        calculate_function_coverage_percentage(total_functions_tested, total_functions);
92
93    (
94        file_coverage,
95        FunctionStats {
96            coverage: function_coverage,
97            tested: total_functions_tested,
98            total: total_functions,
99        },
100    )
101}
102
103/// Estimate function coverage from line coverage data
104fn estimate_function_coverage(file_data: &TarpaulinFile) -> (u32, u32) {
105    let estimated_functions = (file_data.lines_total / 10).max(1);
106    let estimated_functions_tested =
107        ((file_data.line_coverage / 100.0) * estimated_functions as f64) as u32;
108    (estimated_functions, estimated_functions_tested)
109}
110
111/// Create file coverage object
112fn create_file_coverage(
113    file_path: &str,
114    file_data: &TarpaulinFile,
115    estimated_functions: u32,
116    estimated_functions_tested: u32,
117) -> FileCoverage {
118    FileCoverage {
119        file_path: file_path.to_string(),
120        line_coverage: file_data.line_coverage,
121        function_coverage: calculate_function_coverage_percentage(
122            estimated_functions_tested,
123            estimated_functions,
124        ),
125        lines_tested: file_data.lines_covered,
126        total_lines: file_data.lines_total,
127        functions_tested: estimated_functions_tested,
128        total_functions: estimated_functions,
129    }
130}
131
132/// Calculate function coverage percentage
133pub fn calculate_function_coverage_percentage(tested: u32, total: u32) -> f64 {
134    if total == 0 {
135        0.0
136    } else {
137        (tested as f64 / total as f64) * 100.0
138    }
139}
140
141/// Calculate branch coverage
142pub fn calculate_branch_coverage(data: &TarpaulinOutput) -> f64 {
143    match (data.branches_covered, data.branches_total) {
144        (Some(covered), Some(total)) if total > 0 => (covered as f64 / total as f64) * 100.0,
145        _ => 0.0,
146    }
147}