code complexity & repetition analysis tool
1use serde::{Deserialize, Serialize};
2
3/// Coverage report for the entire codebase
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct CoverageReport {
6 pub files: Vec<FileCoverage>,
7 pub totals: CoverageSummary,
8}
9
10impl CoverageReport {
11 pub fn new(files: Vec<FileCoverage>) -> Self {
12 let totals = CoverageSummary::from_files(&files);
13 Self { files, totals }
14 }
15}
16
17/// Coverage data for a single file
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct FileCoverage {
20 pub path: String,
21 pub lines: std::collections::BTreeMap<u32, u64>,
22 pub miss_ranges: Vec<(u32, u32)>,
23 pub summary: CoverageSummary,
24}
25
26impl FileCoverage {
27 pub fn new(path: String, lines: std::collections::BTreeMap<u32, u64>) -> Self {
28 let summary = CoverageSummary::from_lines(&lines);
29 let miss_ranges = super::misses::compute_miss_ranges(&lines);
30 Self { path, lines, miss_ranges, summary }
31 }
32}
33
34/// Coverage summary statistics
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct CoverageSummary {
37 pub total: usize,
38 pub hit: usize,
39 pub miss: usize,
40 pub rate: f64,
41}
42
43impl CoverageSummary {
44 pub fn from_lines(lines: &std::collections::BTreeMap<u32, u64>) -> Self {
45 let total = lines.len();
46 let hit = lines.values().filter(|&&c| c > 0).count();
47 let miss = lines.values().filter(|&&c| c == 0).count();
48 let rate = if total > 0 { (hit as f64 / total as f64) * 100.0 } else { 0.0 };
49
50 Self { total, hit, miss, rate }
51 }
52
53 pub fn from_files(files: &[FileCoverage]) -> Self {
54 let total: usize = files.iter().map(|f| f.summary.total).sum();
55 let hit: usize = files.iter().map(|f| f.summary.hit).sum();
56 let miss: usize = files.iter().map(|f| f.summary.miss).sum();
57 let rate = if total > 0 { (hit as f64 / total as f64) * 100.0 } else { 0.0 };
58
59 Self { total, hit, miss, rate }
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn test_coverage_summary_full() {
69 let mut lines = std::collections::BTreeMap::new();
70 lines.insert(1, 10);
71 lines.insert(2, 5);
72 lines.insert(3, 1);
73
74 let summary = CoverageSummary::from_lines(&lines);
75 assert_eq!(summary.total, 3);
76 assert_eq!(summary.hit, 3);
77 assert_eq!(summary.miss, 0);
78 assert_eq!(summary.rate, 100.0);
79 }
80
81 #[test]
82 fn test_coverage_summary_partial() {
83 let mut lines = std::collections::BTreeMap::new();
84 lines.insert(1, 10);
85 lines.insert(2, 0);
86 lines.insert(3, 5);
87
88 let summary = CoverageSummary::from_lines(&lines);
89 assert_eq!(summary.total, 3);
90 assert_eq!(summary.hit, 2);
91 assert_eq!(summary.miss, 1);
92 assert!((summary.rate - 66.66666666666666).abs() < 0.0001);
93 }
94
95 #[test]
96 fn test_coverage_summary_empty() {
97 let lines = std::collections::BTreeMap::new();
98 let summary = CoverageSummary::from_lines(&lines);
99 assert_eq!(summary.total, 0);
100 assert_eq!(summary.hit, 0);
101 assert_eq!(summary.miss, 0);
102 assert_eq!(summary.rate, 0.0);
103 }
104
105 #[test]
106 fn test_file_coverage() {
107 let mut lines = std::collections::BTreeMap::new();
108 lines.insert(1, 10);
109 lines.insert(2, 0);
110 lines.insert(3, 5);
111
112 let file = FileCoverage::new("test.rs".to_string(), lines.clone());
113 assert_eq!(file.path, "test.rs");
114 assert_eq!(file.summary.total, 3);
115 assert_eq!(file.summary.hit, 2);
116 assert_eq!(file.summary.miss, 1);
117 assert_eq!(file.miss_ranges, vec![(2, 2)]);
118 }
119}