#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct CoverageReport {
pub covered_offsets: u32,
pub bitmap_size: u32,
pub bitmap: Vec<u8>,
}
impl CoverageReport {
#[must_use]
pub fn coverage_ratio(&self) -> f64 {
if self.bitmap_size == 0 {
return 0.0;
}
f64::from(self.covered_offsets) / (f64::from(self.bitmap_size) * 8.0)
}
#[must_use]
pub fn new_coverage_since(&self, previous: &CoverageReport) -> u32 {
if self.bitmap.len() != previous.bitmap.len() {
return self.covered_offsets;
}
let mut new_bits = 0u32;
for (current, prev) in self.bitmap.iter().zip(previous.bitmap.iter()) {
let new = current & !prev;
new_bits += new.count_ones();
}
new_bits
}
pub fn merge(&mut self, other: &CoverageReport) {
if self.bitmap.len() != other.bitmap.len() {
return;
}
for (a, b) in self.bitmap.iter_mut().zip(other.bitmap.iter()) {
*a |= b;
}
self.covered_offsets = self.bitmap.iter().map(|b| b.count_ones()).sum();
}
}
#[derive(Debug, Clone, Default)]
pub struct CoverageAccumulator {
merged: Vec<u8>,
total_runs: u64,
total_covered: u32,
}
impl CoverageAccumulator {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_run(&mut self, report: &CoverageReport) -> u32 {
if self.merged.is_empty() {
self.merged.clone_from(&report.bitmap);
self.total_runs = 1;
self.total_covered = report.covered_offsets;
return report.covered_offsets;
}
if self.merged.len() != report.bitmap.len() {
return 0;
}
let mut new_bits = 0u32;
for (acc, run) in self.merged.iter_mut().zip(report.bitmap.iter()) {
let new = run & !*acc;
new_bits += new.count_ones();
*acc |= run;
}
self.total_runs += 1;
self.total_covered = self.merged.iter().map(|b| b.count_ones()).sum();
new_bits
}
#[must_use]
pub fn total_covered(&self) -> u32 {
self.total_covered
}
#[must_use]
pub fn total_runs(&self) -> u64 {
self.total_runs
}
#[must_use]
#[expect(clippy::cast_precision_loss)]
pub fn coverage_ratio(&self) -> f64 {
if self.merged.is_empty() {
return 0.0;
}
f64::from(self.total_covered) / (self.merged.len() as f64 * 8.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_coverage_detected() {
let prev = CoverageReport {
covered_offsets: 2,
bitmap_size: 1,
bitmap: vec![0b00000011],
};
let curr = CoverageReport {
covered_offsets: 3,
bitmap_size: 1,
bitmap: vec![0b00000111],
};
assert_eq!(curr.new_coverage_since(&prev), 1);
}
#[test]
fn accumulator_tracks_total() {
let mut acc = CoverageAccumulator::new();
let r1 = CoverageReport {
covered_offsets: 2,
bitmap_size: 1,
bitmap: vec![0b00000011],
};
let new1 = acc.add_run(&r1);
assert_eq!(new1, 2);
let r2 = CoverageReport {
covered_offsets: 2,
bitmap_size: 1,
bitmap: vec![0b00001100],
};
let new2 = acc.add_run(&r2);
assert_eq!(new2, 2);
assert_eq!(acc.total_covered(), 4);
}
#[test]
fn merge_unions_bitmaps() {
let mut a = CoverageReport {
covered_offsets: 2,
bitmap_size: 1,
bitmap: vec![0b00000011],
};
let b = CoverageReport {
covered_offsets: 2,
bitmap_size: 1,
bitmap: vec![0b00001100],
};
a.merge(&b);
assert_eq!(a.covered_offsets, 4);
assert_eq!(a.bitmap, vec![0b00001111]);
}
}