1use super::{BlockId, CoverageViolation, TaintedBlocks};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone)]
16pub struct CoverageSummary {
17 pub total_blocks: usize,
19 pub covered_blocks: usize,
21 pub coverage_percent: f64,
23 pub confidence_interval: Option<(f64, f64)>,
25 pub effect_size: Option<f64>,
27}
28
29#[derive(Debug, Clone)]
31pub struct BlockCoverage {
32 pub block_id: BlockId,
34 pub hit_count: u64,
36 pub source_location: Option<String>,
38 pub function_name: Option<String>,
40}
41
42#[derive(Debug)]
44pub struct CoverageReport {
45 total_blocks: usize,
47 hit_counts: HashMap<BlockId, u64>,
49 source_locations: HashMap<BlockId, String>,
51 function_names: HashMap<BlockId, String>,
53 tainted: TaintedBlocks,
55 session_name: Option<String>,
57 tests: Vec<String>,
59}
60
61impl CoverageReport {
62 #[must_use]
64 pub fn new(total_blocks: usize) -> Self {
65 Self {
66 total_blocks,
67 hit_counts: HashMap::new(),
68 source_locations: HashMap::new(),
69 function_names: HashMap::new(),
70 tainted: TaintedBlocks::new(),
71 session_name: None,
72 tests: Vec::new(),
73 }
74 }
75
76 pub fn set_session_name(&mut self, name: &str) {
78 self.session_name = Some(name.to_string());
79 }
80
81 pub fn add_test(&mut self, name: &str) {
83 self.tests.push(name.to_string());
84 }
85
86 pub fn record_hit(&mut self, block: BlockId) {
88 *self.hit_counts.entry(block).or_insert(0) += 1;
89 }
90
91 pub fn record_hits(&mut self, block: BlockId, count: u64) {
93 *self.hit_counts.entry(block).or_insert(0) += count;
94 }
95
96 pub fn record_violation(&mut self, violation: CoverageViolation) {
98 self.tainted.record_violation(violation);
99 }
100
101 pub fn set_source_location(&mut self, block: BlockId, location: &str) {
103 let _ = self.source_locations.insert(block, location.to_string());
104 }
105
106 pub fn set_function_name(&mut self, block: BlockId, name: &str) {
108 let _ = self.function_names.insert(block, name.to_string());
109 }
110
111 #[must_use]
113 pub fn get_hit_count(&self, block: BlockId) -> u64 {
114 self.hit_counts.get(&block).copied().unwrap_or(0)
115 }
116
117 #[must_use]
119 pub fn is_covered(&self, block: BlockId) -> bool {
120 self.get_hit_count(block) > 0
121 }
122
123 #[must_use]
125 pub fn covered_count(&self) -> usize {
126 (0..self.total_blocks as u32)
127 .map(BlockId::new)
128 .filter(|b| self.is_covered(*b))
129 .count()
130 }
131
132 #[must_use]
134 pub fn coverage_percent(&self) -> f64 {
135 if self.total_blocks == 0 {
136 return 100.0; }
138 (self.covered_count() as f64 / self.total_blocks as f64) * 100.0
139 }
140
141 #[must_use]
143 pub fn uncovered_blocks(&self) -> Vec<BlockId> {
144 (0..self.total_blocks as u32)
145 .map(BlockId::new)
146 .filter(|b| !self.is_covered(*b))
147 .collect()
148 }
149
150 #[must_use]
152 pub fn covered_blocks(&self) -> Vec<BlockId> {
153 (0..self.total_blocks as u32)
154 .map(BlockId::new)
155 .filter(|b| self.is_covered(*b))
156 .collect()
157 }
158
159 #[must_use]
161 pub fn summary(&self) -> CoverageSummary {
162 CoverageSummary {
163 total_blocks: self.total_blocks,
164 covered_blocks: self.covered_count(),
165 coverage_percent: self.coverage_percent(),
166 confidence_interval: None,
167 effect_size: None,
168 }
169 }
170
171 #[must_use]
173 pub fn block_coverages(&self) -> Vec<BlockCoverage> {
174 (0..self.total_blocks as u32)
175 .map(|i| {
176 let block_id = BlockId::new(i);
177 BlockCoverage {
178 block_id,
179 hit_count: self.get_hit_count(block_id),
180 source_location: self.source_locations.get(&block_id).cloned(),
181 function_name: self.function_names.get(&block_id).cloned(),
182 }
183 })
184 .collect()
185 }
186
187 #[must_use]
189 pub fn violation_count(&self) -> usize {
190 self.tainted.violation_count()
191 }
192
193 #[must_use]
195 pub fn violations(&self) -> &[CoverageViolation] {
196 self.tainted.all_violations()
197 }
198
199 #[must_use]
201 pub fn is_tainted(&self, block: BlockId) -> bool {
202 self.tainted.is_tainted(block)
203 }
204
205 #[must_use]
207 pub fn total_blocks(&self) -> usize {
208 self.total_blocks
209 }
210
211 #[must_use]
213 pub fn session_name(&self) -> Option<&str> {
214 self.session_name.as_deref()
215 }
216
217 #[must_use]
219 pub fn tests(&self) -> &[String] {
220 &self.tests
221 }
222
223 pub fn merge(&mut self, other: &CoverageReport) {
225 for (block, count) in &other.hit_counts {
226 self.record_hits(*block, *count);
227 }
228 for (block, location) in &other.source_locations {
229 if !self.source_locations.contains_key(block) {
230 let _ = self.source_locations.insert(*block, location.clone());
231 }
232 }
233 for (block, name) in &other.function_names {
234 if !self.function_names.contains_key(block) {
235 let _ = self.function_names.insert(*block, name.clone());
236 }
237 }
238 for test in &other.tests {
239 if !self.tests.contains(test) {
240 self.tests.push(test.clone());
241 }
242 }
243 }
244}
245
246impl Default for CoverageReport {
247 fn default() -> Self {
248 Self::new(0)
249 }
250}
251
252#[cfg(test)]
253#[allow(clippy::unwrap_used, clippy::expect_used)]
254mod tests {
255 use super::*;
256
257 #[test]
263 fn test_report_new_various_sizes() {
264 let report_zero = CoverageReport::new(0);
265 assert_eq!(report_zero.total_blocks(), 0);
266
267 let report_one = CoverageReport::new(1);
268 assert_eq!(report_one.total_blocks(), 1);
269
270 let report_large = CoverageReport::new(10000);
271 assert_eq!(report_large.total_blocks(), 10000);
272 }
273
274 #[test]
276 fn test_report_session_name_none() {
277 let report = CoverageReport::new(5);
278 assert!(report.session_name().is_none());
279 }
280
281 #[test]
283 fn test_report_session_name_set() {
284 let mut report = CoverageReport::new(5);
285 report.set_session_name("test_session");
286 assert_eq!(report.session_name(), Some("test_session"));
287 }
288
289 #[test]
291 fn test_report_tests_empty() {
292 let report = CoverageReport::new(5);
293 assert!(report.tests().is_empty());
294 }
295
296 #[test]
298 fn test_report_add_multiple_tests() {
299 let mut report = CoverageReport::new(5);
300 report.add_test("test_1");
301 report.add_test("test_2");
302 report.add_test("test_3");
303
304 let tests = report.tests();
305 assert_eq!(tests.len(), 3);
306 assert_eq!(tests[0], "test_1");
307 assert_eq!(tests[1], "test_2");
308 assert_eq!(tests[2], "test_3");
309 }
310
311 #[test]
313 fn test_report_record_hit_increments() {
314 let mut report = CoverageReport::new(5);
315 report.record_hit(BlockId::new(0));
316 assert_eq!(report.get_hit_count(BlockId::new(0)), 1);
317
318 report.record_hit(BlockId::new(0));
319 assert_eq!(report.get_hit_count(BlockId::new(0)), 2);
320
321 report.record_hit(BlockId::new(0));
322 assert_eq!(report.get_hit_count(BlockId::new(0)), 3);
323 }
324
325 #[test]
327 fn test_report_record_hits_bulk() {
328 let mut report = CoverageReport::new(5);
329 report.record_hits(BlockId::new(0), 100);
330 assert_eq!(report.get_hit_count(BlockId::new(0)), 100);
331
332 report.record_hits(BlockId::new(0), 50);
333 assert_eq!(report.get_hit_count(BlockId::new(0)), 150);
334 }
335
336 #[test]
338 fn test_report_get_hit_count_unhit() {
339 let report = CoverageReport::new(10);
340 assert_eq!(report.get_hit_count(BlockId::new(5)), 0);
341 assert_eq!(report.get_hit_count(BlockId::new(9)), 0);
342 }
343
344 #[test]
346 fn test_report_is_covered() {
347 let mut report = CoverageReport::new(5);
348 assert!(!report.is_covered(BlockId::new(0)));
349
350 report.record_hit(BlockId::new(0));
351 assert!(report.is_covered(BlockId::new(0)));
352 assert!(!report.is_covered(BlockId::new(1)));
353 }
354
355 #[test]
357 fn test_report_covered_count() {
358 let mut report = CoverageReport::new(10);
359 assert_eq!(report.covered_count(), 0);
360
361 report.record_hit(BlockId::new(0));
362 assert_eq!(report.covered_count(), 1);
363
364 report.record_hit(BlockId::new(0)); assert_eq!(report.covered_count(), 1);
366
367 report.record_hit(BlockId::new(5));
368 assert_eq!(report.covered_count(), 2);
369 }
370
371 #[test]
373 fn test_report_coverage_percent_zero_blocks() {
374 let report = CoverageReport::new(0);
375 assert!((report.coverage_percent() - 100.0).abs() < 0.001);
376 }
377
378 #[test]
380 fn test_report_coverage_percent_no_hits() {
381 let report = CoverageReport::new(10);
382 assert!((report.coverage_percent() - 0.0).abs() < 0.001);
383 }
384
385 #[test]
387 fn test_report_coverage_percent_full() {
388 let mut report = CoverageReport::new(5);
389 for i in 0..5 {
390 report.record_hit(BlockId::new(i));
391 }
392 assert!((report.coverage_percent() - 100.0).abs() < 0.001);
393 }
394
395 #[test]
397 fn test_report_coverage_percent_partial() {
398 let mut report = CoverageReport::new(4);
399 report.record_hit(BlockId::new(0));
400 report.record_hit(BlockId::new(1));
401 assert!((report.coverage_percent() - 50.0).abs() < 0.001);
402 }
403
404 #[test]
406 fn test_report_uncovered_blocks_all() {
407 let report = CoverageReport::new(3);
408 let uncovered = report.uncovered_blocks();
409 assert_eq!(uncovered.len(), 3);
410 assert!(uncovered.contains(&BlockId::new(0)));
411 assert!(uncovered.contains(&BlockId::new(1)));
412 assert!(uncovered.contains(&BlockId::new(2)));
413 }
414
415 #[test]
417 fn test_report_uncovered_blocks_none() {
418 let mut report = CoverageReport::new(3);
419 for i in 0..3 {
420 report.record_hit(BlockId::new(i));
421 }
422 let uncovered = report.uncovered_blocks();
423 assert!(uncovered.is_empty());
424 }
425
426 #[test]
428 fn test_report_covered_blocks_none() {
429 let report = CoverageReport::new(5);
430 let covered = report.covered_blocks();
431 assert!(covered.is_empty());
432 }
433
434 #[test]
436 fn test_report_covered_blocks_all() {
437 let mut report = CoverageReport::new(3);
438 for i in 0..3 {
439 report.record_hit(BlockId::new(i));
440 }
441 let covered = report.covered_blocks();
442 assert_eq!(covered.len(), 3);
443 }
444
445 #[test]
447 fn test_report_summary() {
448 let mut report = CoverageReport::new(10);
449 report.record_hit(BlockId::new(0));
450 report.record_hit(BlockId::new(1));
451
452 let summary = report.summary();
453 assert_eq!(summary.total_blocks, 10);
454 assert_eq!(summary.covered_blocks, 2);
455 assert!((summary.coverage_percent - 20.0).abs() < 0.001);
456 assert!(summary.confidence_interval.is_none());
457 assert!(summary.effect_size.is_none());
458 }
459
460 #[test]
462 fn test_report_block_coverages_all() {
463 let mut report = CoverageReport::new(3);
464 report.record_hit(BlockId::new(0));
465 report.record_hits(BlockId::new(1), 5);
466 report.set_source_location(BlockId::new(0), "test.rs:1");
467 report.set_function_name(BlockId::new(0), "test_fn");
468
469 let coverages = report.block_coverages();
470 assert_eq!(coverages.len(), 3);
471
472 assert_eq!(coverages[0].block_id, BlockId::new(0));
473 assert_eq!(coverages[0].hit_count, 1);
474 assert_eq!(coverages[0].source_location, Some("test.rs:1".to_string()));
475 assert_eq!(coverages[0].function_name, Some("test_fn".to_string()));
476
477 assert_eq!(coverages[1].hit_count, 5);
478 assert!(coverages[1].source_location.is_none());
479 assert!(coverages[1].function_name.is_none());
480
481 assert_eq!(coverages[2].hit_count, 0);
482 }
483
484 #[test]
486 fn test_report_violation_count() {
487 let mut report = CoverageReport::new(5);
488 assert_eq!(report.violation_count(), 0);
489
490 report.record_violation(CoverageViolation::CounterOverflow {
491 block_id: BlockId::new(0),
492 });
493 assert_eq!(report.violation_count(), 1);
494 }
495
496 #[test]
498 fn test_report_violations() {
499 let mut report = CoverageReport::new(5);
500 report.record_violation(CoverageViolation::CounterOverflow {
501 block_id: BlockId::new(0),
502 });
503
504 let violations = report.violations();
505 assert_eq!(violations.len(), 1);
506 }
507
508 #[test]
510 fn test_report_is_tainted() {
511 let mut report = CoverageReport::new(5);
512 assert!(!report.is_tainted(BlockId::new(0)));
513
514 report.record_violation(CoverageViolation::CounterOverflow {
515 block_id: BlockId::new(0),
516 });
517 assert!(report.is_tainted(BlockId::new(0)));
518 assert!(!report.is_tainted(BlockId::new(1)));
519 }
520
521 #[test]
527 fn test_merge_hit_counts() {
528 let mut report1 = CoverageReport::new(5);
529 report1.record_hit(BlockId::new(0));
530 report1.record_hit(BlockId::new(1));
531
532 let mut report2 = CoverageReport::new(5);
533 report2.record_hit(BlockId::new(1));
534 report2.record_hit(BlockId::new(2));
535 report2.record_hits(BlockId::new(0), 10);
536
537 report1.merge(&report2);
538
539 assert_eq!(report1.get_hit_count(BlockId::new(0)), 11); assert_eq!(report1.get_hit_count(BlockId::new(1)), 2); assert_eq!(report1.get_hit_count(BlockId::new(2)), 1);
542 }
543
544 #[test]
546 fn test_merge_source_locations_no_overwrite() {
547 let mut report1 = CoverageReport::new(5);
548 report1.set_source_location(BlockId::new(0), "original.rs:1");
549
550 let mut report2 = CoverageReport::new(5);
551 report2.set_source_location(BlockId::new(0), "overwrite.rs:1"); report2.set_source_location(BlockId::new(1), "new.rs:1"); report1.merge(&report2);
555
556 let coverages = report1.block_coverages();
557 assert_eq!(
559 coverages[0].source_location,
560 Some("original.rs:1".to_string())
561 );
562 assert_eq!(coverages[1].source_location, Some("new.rs:1".to_string()));
564 }
565
566 #[test]
568 fn test_merge_function_names_no_overwrite() {
569 let mut report1 = CoverageReport::new(5);
570 report1.set_function_name(BlockId::new(0), "original_fn");
571
572 let mut report2 = CoverageReport::new(5);
573 report2.set_function_name(BlockId::new(0), "overwrite_fn"); report2.set_function_name(BlockId::new(1), "new_fn"); report1.merge(&report2);
577
578 let coverages = report1.block_coverages();
579 assert_eq!(coverages[0].function_name, Some("original_fn".to_string()));
581 assert_eq!(coverages[1].function_name, Some("new_fn".to_string()));
583 }
584
585 #[test]
587 fn test_merge_tests_no_duplicates() {
588 let mut report1 = CoverageReport::new(5);
589 report1.add_test("test_1");
590 report1.add_test("test_2");
591
592 let mut report2 = CoverageReport::new(5);
593 report2.add_test("test_2"); report2.add_test("test_3"); report1.merge(&report2);
597
598 let tests = report1.tests();
599 assert_eq!(tests.len(), 3);
600 assert!(tests.contains(&"test_1".to_string()));
601 assert!(tests.contains(&"test_2".to_string()));
602 assert!(tests.contains(&"test_3".to_string()));
603 }
604
605 #[test]
607 fn test_merge_tests_from_empty() {
608 let mut report1 = CoverageReport::new(5);
609
610 let mut report2 = CoverageReport::new(5);
611 report2.add_test("test_a");
612 report2.add_test("test_b");
613
614 report1.merge(&report2);
615
616 assert_eq!(report1.tests().len(), 2);
617 }
618
619 #[test]
621 fn test_merge_empty_other() {
622 let mut report1 = CoverageReport::new(5);
623 report1.record_hit(BlockId::new(0));
624 report1.add_test("test_1");
625
626 let report2 = CoverageReport::new(5);
627
628 report1.merge(&report2);
629
630 assert_eq!(report1.get_hit_count(BlockId::new(0)), 1);
631 assert_eq!(report1.tests().len(), 1);
632 }
633
634 #[test]
636 fn test_merge_into_empty() {
637 let mut report1 = CoverageReport::new(5);
638
639 let mut report2 = CoverageReport::new(5);
640 report2.record_hit(BlockId::new(0));
641 report2.set_source_location(BlockId::new(0), "test.rs:1");
642 report2.set_function_name(BlockId::new(0), "test_fn");
643 report2.add_test("test_1");
644
645 report1.merge(&report2);
646
647 assert_eq!(report1.get_hit_count(BlockId::new(0)), 1);
648 let coverages = report1.block_coverages();
649 assert_eq!(coverages[0].source_location, Some("test.rs:1".to_string()));
650 assert_eq!(coverages[0].function_name, Some("test_fn".to_string()));
651 assert_eq!(report1.tests().len(), 1);
652 }
653
654 #[test]
660 fn test_coverage_summary_clone() {
661 let summary1 = CoverageSummary {
662 total_blocks: 100,
663 covered_blocks: 80,
664 coverage_percent: 80.0,
665 confidence_interval: Some((78.0, 82.0)),
666 effect_size: Some(0.5),
667 };
668
669 let summary2 = summary1;
670
671 assert_eq!(summary2.total_blocks, 100);
672 assert_eq!(summary2.covered_blocks, 80);
673 assert!((summary2.coverage_percent - 80.0).abs() < 0.001);
674 assert_eq!(summary2.confidence_interval, Some((78.0, 82.0)));
675 assert_eq!(summary2.effect_size, Some(0.5));
676 }
677
678 #[test]
680 fn test_coverage_summary_debug() {
681 let summary = CoverageSummary {
682 total_blocks: 10,
683 covered_blocks: 5,
684 coverage_percent: 50.0,
685 confidence_interval: None,
686 effect_size: None,
687 };
688
689 let debug = format!("{:?}", summary);
690 assert!(debug.contains("CoverageSummary"));
691 assert!(debug.contains("10"));
692 assert!(debug.contains("50"));
693 }
694
695 #[test]
701 fn test_block_coverage_clone() {
702 let bc1 = BlockCoverage {
703 block_id: BlockId::new(42),
704 hit_count: 100,
705 source_location: Some("test.rs:42".to_string()),
706 function_name: Some("my_function".to_string()),
707 };
708
709 let bc2 = bc1;
710
711 assert_eq!(bc2.block_id, BlockId::new(42));
712 assert_eq!(bc2.hit_count, 100);
713 assert_eq!(bc2.source_location, Some("test.rs:42".to_string()));
714 assert_eq!(bc2.function_name, Some("my_function".to_string()));
715 }
716
717 #[test]
719 fn test_block_coverage_debug() {
720 let bc = BlockCoverage {
721 block_id: BlockId::new(1),
722 hit_count: 5,
723 source_location: None,
724 function_name: None,
725 };
726
727 let debug = format!("{:?}", bc);
728 assert!(debug.contains("BlockCoverage"));
729 }
730
731 #[test]
733 fn test_block_coverage_all_none() {
734 let bc = BlockCoverage {
735 block_id: BlockId::new(0),
736 hit_count: 0,
737 source_location: None,
738 function_name: None,
739 };
740
741 assert!(bc.source_location.is_none());
742 assert!(bc.function_name.is_none());
743 }
744
745 #[test]
751 fn test_report_default() {
752 let report = CoverageReport::default();
753 assert_eq!(report.total_blocks(), 0);
754 assert_eq!(report.covered_count(), 0);
755 assert!((report.coverage_percent() - 100.0).abs() < 0.001);
756 }
757
758 #[test]
764 fn test_report_record_violation_no_block() {
765 let mut report = CoverageReport::new(5);
766 report.record_violation(CoverageViolation::CoverageRegression {
767 expected: 95.0,
768 actual: 90.0,
769 });
770
771 assert_eq!(report.violation_count(), 1);
772 for i in 0..5 {
774 assert!(!report.is_tainted(BlockId::new(i)));
775 }
776 }
777
778 #[test]
780 fn test_set_source_location_overwrite() {
781 let mut report = CoverageReport::new(5);
782 report.set_source_location(BlockId::new(0), "first.rs:1");
783 report.set_source_location(BlockId::new(0), "second.rs:2");
784
785 let coverages = report.block_coverages();
786 assert_eq!(
787 coverages[0].source_location,
788 Some("second.rs:2".to_string())
789 );
790 }
791
792 #[test]
794 fn test_set_function_name_overwrite() {
795 let mut report = CoverageReport::new(5);
796 report.set_function_name(BlockId::new(0), "first_fn");
797 report.set_function_name(BlockId::new(0), "second_fn");
798
799 let coverages = report.block_coverages();
800 assert_eq!(coverages[0].function_name, Some("second_fn".to_string()));
801 }
802
803 #[test]
805 fn test_block_coverages_partial_metadata() {
806 let mut report = CoverageReport::new(5);
807 report.set_source_location(BlockId::new(0), "a.rs:1");
808 report.set_function_name(BlockId::new(1), "fn_b");
809 report.record_hit(BlockId::new(2));
810 let coverages = report.block_coverages();
813
814 assert_eq!(coverages[0].source_location, Some("a.rs:1".to_string()));
815 assert!(coverages[0].function_name.is_none());
816
817 assert!(coverages[1].source_location.is_none());
818 assert_eq!(coverages[1].function_name, Some("fn_b".to_string()));
819
820 assert!(coverages[2].source_location.is_none());
821 assert!(coverages[2].function_name.is_none());
822 assert_eq!(coverages[2].hit_count, 1);
823
824 assert!(coverages[3].source_location.is_none());
825 assert!(coverages[3].function_name.is_none());
826 assert_eq!(coverages[3].hit_count, 0);
827 }
828
829 #[test]
831 fn test_large_block_count() {
832 let mut report = CoverageReport::new(10000);
833
834 for i in (0..10000).step_by(10) {
836 report.record_hit(BlockId::new(i));
837 }
838
839 assert_eq!(report.covered_count(), 1000);
840 assert!((report.coverage_percent() - 10.0).abs() < 0.001);
841 }
842
843 #[test]
845 fn test_multiple_merges() {
846 let mut report1 = CoverageReport::new(5);
847 report1.record_hit(BlockId::new(0));
848
849 let mut report2 = CoverageReport::new(5);
850 report2.record_hit(BlockId::new(1));
851
852 let mut report3 = CoverageReport::new(5);
853 report3.record_hit(BlockId::new(2));
854
855 report1.merge(&report2);
856 report1.merge(&report3);
857
858 assert_eq!(report1.covered_count(), 3);
859 }
860
861 #[test]
863 fn test_hit_count_out_of_range() {
864 let mut report = CoverageReport::new(5);
865 report.record_hit(BlockId::new(100));
867
868 assert_eq!(report.get_hit_count(BlockId::new(100)), 1);
870 assert_eq!(report.covered_count(), 0);
872 }
873
874 #[test]
876 fn test_blocks_list_range() {
877 let mut report = CoverageReport::new(3);
878 report.record_hit(BlockId::new(0));
879 report.record_hit(BlockId::new(100)); let covered = report.covered_blocks();
882 let uncovered = report.uncovered_blocks();
883
884 assert_eq!(covered.len(), 1);
886 assert!(covered.contains(&BlockId::new(0)));
887
888 assert_eq!(uncovered.len(), 2);
889 assert!(uncovered.contains(&BlockId::new(1)));
890 assert!(uncovered.contains(&BlockId::new(2)));
891 }
892}