use super::{BlockId, CoverageReport, CoverageViolation, JidokaAction, ThreadLocalCounters};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Granularity {
#[default]
Function,
BasicBlock,
Edge,
Path,
}
#[derive(Debug, Clone)]
pub struct CoverageConfig {
pub granularity: Granularity,
pub parallel: bool,
pub jidoka_enabled: bool,
pub checkpoint_interval: Option<u64>,
pub max_blocks: usize,
}
impl CoverageConfig {
#[must_use]
pub fn builder() -> CoverageConfigBuilder {
CoverageConfigBuilder::default()
}
}
impl Default for CoverageConfig {
fn default() -> Self {
Self {
granularity: Granularity::Function,
parallel: false,
jidoka_enabled: true,
checkpoint_interval: None,
max_blocks: 100_000,
}
}
}
#[derive(Debug, Default)]
pub struct CoverageConfigBuilder {
granularity: Granularity,
parallel: bool,
jidoka_enabled: bool,
checkpoint_interval: Option<u64>,
max_blocks: usize,
}
impl CoverageConfigBuilder {
#[must_use]
pub fn granularity(mut self, granularity: Granularity) -> Self {
self.granularity = granularity;
self
}
#[must_use]
pub fn parallel(mut self, enabled: bool) -> Self {
self.parallel = enabled;
self
}
#[must_use]
pub fn jidoka_enabled(mut self, enabled: bool) -> Self {
self.jidoka_enabled = enabled;
self
}
#[must_use]
pub fn checkpoint_interval(mut self, seconds: u64) -> Self {
self.checkpoint_interval = Some(seconds);
self
}
#[must_use]
pub fn max_blocks(mut self, max: usize) -> Self {
self.max_blocks = max;
self
}
#[must_use]
pub fn build(self) -> CoverageConfig {
CoverageConfig {
granularity: self.granularity,
parallel: self.parallel,
jidoka_enabled: self.jidoka_enabled,
checkpoint_interval: self.checkpoint_interval,
max_blocks: if self.max_blocks == 0 {
100_000
} else {
self.max_blocks
},
}
}
}
#[derive(Debug)]
pub struct CoverageCollector {
config: CoverageConfig,
report: Option<CoverageReport>,
current_test: Option<String>,
counters: ThreadLocalCounters,
session_active: bool,
test_active: bool,
}
impl CoverageCollector {
#[must_use]
pub fn new(config: CoverageConfig) -> Self {
let max_blocks = config.max_blocks;
Self {
config,
report: None,
current_test: None,
counters: ThreadLocalCounters::new(max_blocks),
session_active: false,
test_active: false,
}
}
pub fn begin_session(&mut self, name: &str) {
let mut report = CoverageReport::new(self.config.max_blocks);
report.set_session_name(name);
self.report = Some(report);
self.session_active = true;
}
#[must_use]
pub fn end_session(&mut self) -> CoverageReport {
self.flush_counters();
self.session_active = false;
self.report.take().unwrap_or_default()
}
pub fn begin_test(&mut self, name: &str) {
self.current_test = Some(name.to_string());
self.test_active = true;
if let Some(report) = &mut self.report {
report.add_test(name);
}
}
pub fn end_test(&mut self) {
self.flush_counters();
self.current_test = None;
self.test_active = false;
}
pub fn record_hit(&mut self, block: BlockId) {
self.counters.increment(block);
}
pub fn record_violation(&mut self, violation: CoverageViolation) {
if self.config.jidoka_enabled {
let action = violation.action();
if let Some(report) = &mut self.report {
report.record_violation(violation);
}
if action == JidokaAction::Stop {
}
}
}
fn flush_counters(&mut self) {
let counts = self.counters.flush();
if let Some(report) = &mut self.report {
for (idx, count) in counts.iter().enumerate() {
if *count > 0 {
report.record_hits(BlockId::new(idx as u32), *count);
}
}
}
}
#[must_use]
pub fn is_session_active(&self) -> bool {
self.session_active
}
#[must_use]
pub fn is_test_active(&self) -> bool {
self.test_active
}
#[must_use]
pub fn current_test(&self) -> Option<&str> {
self.current_test.as_deref()
}
#[must_use]
pub fn config(&self) -> &CoverageConfig {
&self.config
}
}