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
56pub fn parse_tarpaulin_json(output: &str) -> Result<TarpaulinOutput> {
57    serde_json::from_str(output)
58        .map_err(|e| Error::process(format!("Failed to parse tarpaulin output: {}", e)))
59}
60
61/// Process file coverage data
62pub fn process_file_coverage(
63    files: &HashMap<String, TarpaulinFile>,
64) -> (HashMap<String, FileCoverage>, FunctionStats) {
65    let mut file_coverage = HashMap::new();
66    let mut total_functions_tested = 0;
67    let mut total_functions = 0;
68
69    for (file_path, file_data) in files {
70        let (estimated_functions, estimated_functions_tested) =
71            estimate_function_coverage(file_data);
72
73        total_functions += estimated_functions;
74        total_functions_tested += estimated_functions_tested;
75
76        let coverage = create_file_coverage(
77            file_path,
78            file_data,
79            estimated_functions,
80            estimated_functions_tested,
81        );
82        file_coverage.insert(file_path.clone(), coverage);
83    }
84
85    let function_coverage =
86        calculate_function_coverage_percentage(total_functions_tested, total_functions);
87
88    (
89        file_coverage,
90        FunctionStats {
91            coverage: function_coverage,
92            tested: total_functions_tested,
93            total: total_functions,
94        },
95    )
96}
97
98/// Estimate function coverage from line coverage data
99fn estimate_function_coverage(file_data: &TarpaulinFile) -> (u32, u32) {
100    let estimated_functions = (file_data.lines_total / 10).max(1);
101    let estimated_functions_tested =
102        ((file_data.line_coverage / 100.0) * estimated_functions as f64) as u32;
103    (estimated_functions, estimated_functions_tested)
104}
105
106/// Create file coverage object
107fn create_file_coverage(
108    file_path: &str,
109    file_data: &TarpaulinFile,
110    estimated_functions: u32,
111    estimated_functions_tested: u32,
112) -> FileCoverage {
113    FileCoverage {
114        file_path: file_path.to_string(),
115        line_coverage: file_data.line_coverage,
116        function_coverage: calculate_function_coverage_percentage(
117            estimated_functions_tested,
118            estimated_functions,
119        ),
120        lines_tested: file_data.lines_covered,
121        total_lines: file_data.lines_total,
122        functions_tested: estimated_functions_tested,
123        total_functions: estimated_functions,
124    }
125}
126
127/// Calculate function coverage percentage
128pub fn calculate_function_coverage_percentage(tested: u32, total: u32) -> f64 {
129    if total == 0 {
130        0.0
131    } else {
132        (tested as f64 / total as f64) * 100.0
133    }
134}
135
136/// Calculate branch coverage
137pub fn calculate_branch_coverage(data: &TarpaulinOutput) -> f64 {
138    match (data.branches_covered, data.branches_total) {
139        (Some(covered), Some(total)) if total > 0 => (covered as f64 / total as f64) * 100.0,
140        _ => 0.0,
141    }
142}