use crate::lcov_cov::FileReport;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct LineTotals {
pub(crate) count: u32,
pub(crate) covered: u32,
}
impl LineTotals {
pub(crate) fn percent(self) -> Option<f64> {
if self.count == 0 {
None
} else {
let pct = 100.0 * f64::from(self.covered) / f64::from(self.count);
Some(pct)
}
}
}
pub(crate) fn aggregate(files: &[&FileReport]) -> LineTotals {
let mut totals = LineTotals::default();
for f in files {
totals.count = totals.count.saturating_add(f.lines_total);
totals.covered = totals.covered.saturating_add(f.lines_covered);
}
totals
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::path::PathBuf;
use super::*;
fn entry(path: &str, count: u32, covered: u32) -> FileReport {
FileReport {
filename: PathBuf::from(path),
lines_total: count,
lines_covered: covered,
coverable_lines: (1..=count).collect(),
uncovered_lines: ((covered + 1)..=count).collect(),
}
}
#[test]
fn sums_counters() {
let a = entry("/repo/a.rs", 10, 5);
let b = entry("/repo/b.rs", 20, 18);
let files = [&a, &b];
let totals = aggregate(&files);
assert_eq!(totals.count, 30);
assert_eq!(totals.covered, 23);
}
#[test]
fn percent_handles_zero_lines() {
let totals = LineTotals { count: 0, covered: 0 };
assert!(totals.percent().is_none());
}
#[test]
fn percent_computes_correctly() {
let totals = LineTotals { count: 100, covered: 82 };
let pct = totals.percent().expect("non-empty totals");
assert!((pct - 82.0).abs() < f64::EPSILON);
}
#[test]
fn deterministic_across_input_order() {
let a = entry("/repo/aaa.rs", 7, 3);
let b = entry("/repo/bbb.rs", 11, 9);
let c = entry("/repo/ccc.rs", 13, 12);
let abc = aggregate(&[&a, &b, &c]);
let cba = aggregate(&[&c, &b, &a]);
assert_eq!(abc, cba);
}
#[test]
fn empty_input_is_zero_totals() {
let totals = aggregate(&[]);
assert_eq!(totals, LineTotals::default());
}
}