istanbul_oxide/
coverage_map.rs

1use indexmap::IndexMap;
2
3use crate::{CoverageSummary, FileCoverage};
4
5/// a map of `FileCoverage` objects keyed by file paths
6#[derive(Clone, PartialEq, Default)]
7pub struct CoverageMap {
8    inner: IndexMap<String, FileCoverage>,
9}
10
11impl CoverageMap {
12    pub fn new() -> CoverageMap {
13        CoverageMap {
14            inner: Default::default(),
15        }
16    }
17
18    pub fn default() -> CoverageMap {
19        CoverageMap {
20            inner: Default::default(),
21        }
22    }
23
24    pub fn from_iter<'a>(value: impl IntoIterator<Item = &'a FileCoverage>) -> CoverageMap {
25        let mut ret = CoverageMap {
26            inner: Default::default(),
27        };
28
29        for coverage in value.into_iter() {
30            ret.add_coverage_for_file(coverage)
31        }
32
33        ret
34    }
35
36    /// Merges a second coverage map into this one
37    pub fn merge(&mut self, map: &CoverageMap) {
38        for (_, coverage) in map.inner.iter() {
39            self.add_coverage_for_file(coverage);
40        }
41    }
42
43    /// Filter the coverage map with a predicate. If the predicate returns false,
44    /// the coverage is removed from the map.
45    pub fn filter(&mut self, predicate: impl Fn(&FileCoverage) -> bool) {
46        let mut filtered: IndexMap<String, FileCoverage> = Default::default();
47
48        for (_, coverage) in self.inner.drain(..) {
49            if predicate(&coverage) {
50                filtered.insert(coverage.path.clone(), coverage);
51            }
52        }
53
54        self.inner = filtered;
55    }
56
57    pub fn to_json() {
58        unimplemented!()
59    }
60
61    pub fn get_files(&self) -> Vec<&String> {
62        self.inner.keys().collect()
63    }
64
65    pub fn get_coverage_for_file(&self, file_path: &str) -> Option<&FileCoverage> {
66        self.inner.get(file_path)
67    }
68
69    pub fn add_coverage_for_file(&mut self, coverage: &FileCoverage) {
70        if let Some(value) = self.inner.get_mut(coverage.path.as_str()) {
71            value.merge(coverage);
72        } else {
73            self.inner.insert(coverage.path.clone(), coverage.clone());
74        }
75    }
76
77    pub fn get_coverage_summary(&self) -> CoverageSummary {
78        let mut ret: CoverageSummary = Default::default();
79
80        for coverage in self.inner.values() {
81            ret.merge(&coverage.to_summary());
82        }
83
84        ret
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use crate::{CoverageMap, FileCoverage};
91
92    #[test]
93    fn should_able_to_merge_another_coverage_map() {
94        let mut base = CoverageMap::from_iter(vec![
95            &FileCoverage::from_file_path("foo.js".to_string(), false),
96            &FileCoverage::from_file_path("bar.js".to_string(), false),
97        ]);
98
99        let second = CoverageMap::from_iter(vec![
100            &FileCoverage::from_file_path("foo.js".to_string(), false),
101            &FileCoverage::from_file_path("baz.js".to_string(), false),
102        ]);
103        base.merge(&second);
104        assert_eq!(
105            base.get_files(),
106            vec![
107                &"foo.js".to_string(),
108                &"bar.js".to_string(),
109                &"baz.js".to_string()
110            ]
111        );
112    }
113
114    #[test]
115    fn should_able_to_return_file_coverage() {
116        let base = CoverageMap::from_iter(vec![
117            &FileCoverage::from_file_path("foo.js".to_string(), false),
118            &FileCoverage::from_file_path("bar.js".to_string(), false),
119        ]);
120
121        assert!(base.get_coverage_for_file("foo.js").is_some());
122        assert!(base.get_coverage_for_file("bar.js").is_some());
123
124        assert!(base.get_coverage_for_file("baz.js").is_none());
125    }
126
127    #[test]
128    fn should_able_to_filter_coverage() {
129        let mut base = CoverageMap::from_iter(vec![
130            &FileCoverage::from_file_path("foo.js".to_string(), false),
131            &FileCoverage::from_file_path("bar.js".to_string(), false),
132        ]);
133
134        assert_eq!(
135            base.get_files(),
136            vec![&"foo.js".to_string(), &"bar.js".to_string()]
137        );
138
139        base.filter(|x| x.path == "foo.js");
140        assert_eq!(base.get_files(), vec![&"foo.js".to_string()]);
141    }
142
143    #[test]
144    fn should_return_coverage_summary_for_all_files() {
145        let mut base = CoverageMap::from_iter(vec![
146            &FileCoverage::from_file_path("foo.js".to_string(), false),
147            &FileCoverage::from_file_path("bar.js".to_string(), false),
148        ]);
149
150        base.add_coverage_for_file(&FileCoverage::from_file_path("foo.js".to_string(), false));
151        base.add_coverage_for_file(&FileCoverage::from_file_path("baz.js".to_string(), false));
152
153        let summary = base.get_coverage_summary();
154        assert_eq!(summary.statements.total, 0);
155    }
156}