use super::types::FileCoverage;
use crate::{Error, Result};
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Deserialize)]
pub struct TarpaulinOutput {
pub coverage: f64,
pub covered: u32,
pub coverable: u32,
pub files: Vec<TarpaulinFile>,
#[serde(skip)]
pub branches_covered: Option<u32>,
#[serde(skip)]
pub branches_total: Option<u32>,
#[serde(skip)]
pub lines_covered: u32,
#[serde(skip)]
pub lines_total: u32,
#[serde(skip)]
pub line_coverage: f64,
}
#[derive(Deserialize)]
pub struct TarpaulinFile {
pub path: Vec<String>,
pub covered: u32,
pub coverable: u32,
}
pub struct FunctionStats {
pub coverage: f64,
pub tested: u32,
pub total: u32,
}
pub fn parse_tarpaulin_json(output: &str) -> Result<TarpaulinOutput> {
let mut data: TarpaulinOutput = serde_json::from_str(output)
.map_err(|e| Error::process(format!("Failed to parse tarpaulin output: {}", e)))?;
data.line_coverage = data.coverage;
data.lines_covered = data.covered;
data.lines_total = data.coverable;
Ok(data)
}
pub fn process_file_coverage(
files: &[TarpaulinFile],
) -> (HashMap<String, FileCoverage>, FunctionStats) {
let mut file_coverage = HashMap::new();
let mut total_functions_tested = 0;
let mut total_functions = 0;
for file_data in files {
let file_path = file_data
.path
.join("/")
.trim_start_matches("//")
.to_string();
let (estimated_functions, estimated_functions_tested) =
estimate_function_coverage(file_data);
total_functions += estimated_functions;
total_functions_tested += estimated_functions_tested;
let coverage = create_file_coverage(
&file_path,
file_data,
estimated_functions,
estimated_functions_tested,
);
file_coverage.insert(file_path, coverage);
}
let function_coverage =
calculate_function_coverage_percentage(total_functions_tested, total_functions);
(
file_coverage,
FunctionStats {
coverage: function_coverage,
tested: total_functions_tested,
total: total_functions,
},
)
}
fn estimate_function_coverage(file_data: &TarpaulinFile) -> (u32, u32) {
let estimated_functions = (file_data.coverable / 10).max(1);
let line_coverage_pct = if file_data.coverable == 0 {
0.0
} else {
file_data.covered as f64 / file_data.coverable as f64
};
let estimated_functions_tested = (line_coverage_pct * estimated_functions as f64) as u32;
(estimated_functions, estimated_functions_tested)
}
fn create_file_coverage(
file_path: &str,
file_data: &TarpaulinFile,
estimated_functions: u32,
estimated_functions_tested: u32,
) -> FileCoverage {
let line_coverage = if file_data.coverable == 0 {
0.0
} else {
(file_data.covered as f64 / file_data.coverable as f64) * 100.0
};
FileCoverage {
file_path: file_path.to_string(),
line_coverage,
function_coverage: calculate_function_coverage_percentage(
estimated_functions_tested,
estimated_functions,
),
lines_tested: file_data.covered,
total_lines: file_data.coverable,
functions_tested: estimated_functions_tested,
total_functions: estimated_functions,
}
}
pub fn calculate_function_coverage_percentage(tested: u32, total: u32) -> f64 {
if total == 0 {
0.0
} else {
(tested as f64 / total as f64) * 100.0
}
}
pub fn calculate_branch_coverage(data: &TarpaulinOutput) -> f64 {
match (data.branches_covered, data.branches_total) {
(Some(covered), Some(total)) if total > 0 => (covered as f64 / total as f64) * 100.0,
_ => 0.0,
}
}