use super::types::FunctionCoverage;
use rayon::prelude::*;
use std::collections::HashMap;
#[derive(Debug)]
pub struct FunctionCoverageData {
pub coverage_percentage: f64,
pub uncovered_lines: Vec<usize>,
}
pub fn calculate_function_coverage_data(
func_start: usize,
func_boundaries: &[usize],
sorted_lines: &[(usize, u64)],
) -> FunctionCoverageData {
let next_func_idx = func_boundaries
.binary_search(&func_start)
.unwrap_or_else(|idx| idx);
let func_end = if next_func_idx + 1 < func_boundaries.len() {
func_boundaries[next_func_idx + 1]
} else {
usize::MAX
};
let start_idx = sorted_lines
.binary_search_by_key(&func_start, |(line, _)| *line)
.unwrap_or_else(|idx| idx);
let end_idx = sorted_lines
.binary_search_by_key(&func_end, |(line, _)| *line)
.unwrap_or_else(|idx| idx);
let func_lines = &sorted_lines[start_idx..end_idx];
if !func_lines.is_empty() {
let covered = func_lines
.par_iter()
.filter(|(_, count)| *count > 0)
.count();
let coverage_percentage = (covered as f64 / func_lines.len() as f64) * 100.0;
let uncovered_lines = func_lines
.par_iter()
.filter(|(_, count)| *count == 0)
.map(|(line, _)| *line)
.collect();
FunctionCoverageData {
coverage_percentage,
uncovered_lines,
}
} else {
FunctionCoverageData {
coverage_percentage: 0.0,
uncovered_lines: Vec::new(),
}
}
}
pub fn process_function_coverage_parallel(
file_functions: &mut HashMap<String, FunctionCoverage>,
file_lines: &HashMap<usize, u64>,
) {
if file_functions.is_empty() || file_lines.is_empty() {
return;
}
let func_boundaries: Vec<usize> = file_functions
.par_iter()
.map(|(_, func)| func.start_line)
.collect::<Vec<_>>()
.into_iter()
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.collect();
let sorted_lines: Vec<(usize, u64)> = file_lines
.par_iter()
.map(|(line, count)| (*line, *count))
.collect::<Vec<_>>()
.into_par_iter()
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.collect();
let coverage_results: HashMap<usize, FunctionCoverageData> = func_boundaries
.par_iter()
.map(|&func_start| {
(
func_start,
calculate_function_coverage_data(func_start, &func_boundaries, &sorted_lines),
)
})
.collect();
let mut sorted_names: Vec<String> = file_functions.keys().cloned().collect();
sorted_names.sort();
for name in sorted_names {
if let Some(func) = file_functions.get_mut(&name) {
if let Some(data) = coverage_results.get(&func.start_line) {
func.coverage_percentage = data.coverage_percentage;
func.uncovered_lines = data.uncovered_lines.clone();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::risk::lcov::types::NormalizedFunctionName;
#[test]
fn test_calculate_function_coverage_full() {
let func_boundaries = vec![10, 20, 30];
let sorted_lines = vec![
(10, 5),
(11, 3),
(12, 7),
(20, 0),
(21, 0),
(30, 1),
(31, 1),
];
let result = calculate_function_coverage_data(10, &func_boundaries, &sorted_lines);
assert_eq!(result.coverage_percentage, 100.0);
assert!(result.uncovered_lines.is_empty());
}
#[test]
fn test_calculate_function_coverage_partial() {
let func_boundaries = vec![10, 20, 30];
let sorted_lines = vec![
(10, 5),
(11, 0),
(12, 7),
(20, 0),
(21, 0),
(30, 1),
(31, 1),
];
let result = calculate_function_coverage_data(10, &func_boundaries, &sorted_lines);
assert!((result.coverage_percentage - 66.67).abs() < 0.1);
assert_eq!(result.uncovered_lines, vec![11]);
}
#[test]
fn test_calculate_function_coverage_none() {
let func_boundaries = vec![10, 20, 30];
let sorted_lines = vec![
(10, 5),
(11, 3),
(12, 7),
(20, 0),
(21, 0),
(30, 1),
(31, 1),
];
let result = calculate_function_coverage_data(20, &func_boundaries, &sorted_lines);
assert_eq!(result.coverage_percentage, 0.0);
assert_eq!(result.uncovered_lines, vec![20, 21]);
}
#[test]
fn test_calculate_function_coverage_empty() {
let func_boundaries = vec![10];
let sorted_lines: Vec<(usize, u64)> = vec![];
let result = calculate_function_coverage_data(10, &func_boundaries, &sorted_lines);
assert_eq!(result.coverage_percentage, 0.0);
assert!(result.uncovered_lines.is_empty());
}
#[test]
fn test_process_function_coverage_parallel() {
let mut functions = HashMap::new();
functions.insert(
"func_a".to_string(),
FunctionCoverage {
name: "func_a".to_string(),
start_line: 10,
execution_count: 1,
coverage_percentage: 0.0,
uncovered_lines: vec![],
normalized: NormalizedFunctionName::simple("func_a"),
},
);
functions.insert(
"func_b".to_string(),
FunctionCoverage {
name: "func_b".to_string(),
start_line: 20,
execution_count: 0,
coverage_percentage: 0.0,
uncovered_lines: vec![],
normalized: NormalizedFunctionName::simple("func_b"),
},
);
let mut lines = HashMap::new();
lines.insert(10, 5);
lines.insert(11, 5);
lines.insert(20, 0);
lines.insert(21, 0);
process_function_coverage_parallel(&mut functions, &lines);
let func_a = functions.get("func_a").unwrap();
assert_eq!(func_a.coverage_percentage, 100.0);
let func_b = functions.get("func_b").unwrap();
assert_eq!(func_b.coverage_percentage, 0.0);
assert_eq!(func_b.uncovered_lines, vec![20, 21]);
}
#[test]
fn test_process_function_coverage_empty_functions() {
let mut functions = HashMap::new();
let lines = HashMap::new();
process_function_coverage_parallel(&mut functions, &lines);
}
#[test]
fn test_process_function_coverage_empty_lines() {
let mut functions = HashMap::new();
functions.insert(
"func".to_string(),
FunctionCoverage {
name: "func".to_string(),
start_line: 10,
execution_count: 1,
coverage_percentage: 50.0, uncovered_lines: vec![],
normalized: NormalizedFunctionName::simple("func"),
},
);
let lines = HashMap::new();
process_function_coverage_parallel(&mut functions, &lines);
let func = functions.get("func").unwrap();
assert_eq!(func.coverage_percentage, 50.0);
}
}