jugar_probar/coverage/
collector.rs1use super::{BlockId, CoverageReport, CoverageViolation, JidokaAction, ThreadLocalCounters};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum Granularity {
12 #[default]
14 Function,
15 BasicBlock,
17 Edge,
19 Path,
21}
22
23#[derive(Debug, Clone)]
25pub struct CoverageConfig {
26 pub granularity: Granularity,
28 pub parallel: bool,
30 pub jidoka_enabled: bool,
32 pub checkpoint_interval: Option<u64>,
34 pub max_blocks: usize,
36}
37
38impl CoverageConfig {
39 #[must_use]
41 pub fn builder() -> CoverageConfigBuilder {
42 CoverageConfigBuilder::default()
43 }
44}
45
46impl Default for CoverageConfig {
47 fn default() -> Self {
48 Self {
49 granularity: Granularity::Function,
50 parallel: false,
51 jidoka_enabled: true,
52 checkpoint_interval: None,
53 max_blocks: 100_000,
54 }
55 }
56}
57
58#[derive(Debug, Default)]
60pub struct CoverageConfigBuilder {
61 granularity: Granularity,
62 parallel: bool,
63 jidoka_enabled: bool,
64 checkpoint_interval: Option<u64>,
65 max_blocks: usize,
66}
67
68impl CoverageConfigBuilder {
69 #[must_use]
71 pub fn granularity(mut self, granularity: Granularity) -> Self {
72 self.granularity = granularity;
73 self
74 }
75
76 #[must_use]
78 pub fn parallel(mut self, enabled: bool) -> Self {
79 self.parallel = enabled;
80 self
81 }
82
83 #[must_use]
85 pub fn jidoka_enabled(mut self, enabled: bool) -> Self {
86 self.jidoka_enabled = enabled;
87 self
88 }
89
90 #[must_use]
92 pub fn checkpoint_interval(mut self, seconds: u64) -> Self {
93 self.checkpoint_interval = Some(seconds);
94 self
95 }
96
97 #[must_use]
99 pub fn max_blocks(mut self, max: usize) -> Self {
100 self.max_blocks = max;
101 self
102 }
103
104 #[must_use]
106 pub fn build(self) -> CoverageConfig {
107 CoverageConfig {
108 granularity: self.granularity,
109 parallel: self.parallel,
110 jidoka_enabled: self.jidoka_enabled,
111 checkpoint_interval: self.checkpoint_interval,
112 max_blocks: if self.max_blocks == 0 {
113 100_000
114 } else {
115 self.max_blocks
116 },
117 }
118 }
119}
120
121#[derive(Debug)]
123pub struct CoverageCollector {
124 config: CoverageConfig,
126 report: Option<CoverageReport>,
128 current_test: Option<String>,
130 counters: ThreadLocalCounters,
132 session_active: bool,
134 test_active: bool,
136}
137
138impl CoverageCollector {
139 #[must_use]
141 pub fn new(config: CoverageConfig) -> Self {
142 let max_blocks = config.max_blocks;
143 Self {
144 config,
145 report: None,
146 current_test: None,
147 counters: ThreadLocalCounters::new(max_blocks),
148 session_active: false,
149 test_active: false,
150 }
151 }
152
153 pub fn begin_session(&mut self, name: &str) {
155 let mut report = CoverageReport::new(self.config.max_blocks);
156 report.set_session_name(name);
157 self.report = Some(report);
158 self.session_active = true;
159 }
160
161 #[must_use]
163 pub fn end_session(&mut self) -> CoverageReport {
164 self.flush_counters();
165 self.session_active = false;
166 self.report.take().unwrap_or_default()
167 }
168
169 pub fn begin_test(&mut self, name: &str) {
171 self.current_test = Some(name.to_string());
172 self.test_active = true;
173 if let Some(report) = &mut self.report {
174 report.add_test(name);
175 }
176 }
177
178 pub fn end_test(&mut self) {
180 self.flush_counters();
181 self.current_test = None;
182 self.test_active = false;
183 }
184
185 pub fn record_hit(&mut self, block: BlockId) {
187 self.counters.increment(block);
188 }
189
190 pub fn record_violation(&mut self, violation: CoverageViolation) {
192 if self.config.jidoka_enabled {
193 let action = violation.action();
194 if let Some(report) = &mut self.report {
195 report.record_violation(violation);
196 }
197
198 if action == JidokaAction::Stop {
200 }
203 }
204 }
205
206 fn flush_counters(&mut self) {
208 let counts = self.counters.flush();
209 if let Some(report) = &mut self.report {
210 for (idx, count) in counts.iter().enumerate() {
211 if *count > 0 {
212 report.record_hits(BlockId::new(idx as u32), *count);
213 }
214 }
215 }
216 }
217
218 #[must_use]
220 pub fn is_session_active(&self) -> bool {
221 self.session_active
222 }
223
224 #[must_use]
226 pub fn is_test_active(&self) -> bool {
227 self.test_active
228 }
229
230 #[must_use]
232 pub fn current_test(&self) -> Option<&str> {
233 self.current_test.as_deref()
234 }
235
236 #[must_use]
238 pub fn config(&self) -> &CoverageConfig {
239 &self.config
240 }
241}