lcov_diff/
lib.rs

1use std::collections::btree_map::Entry;
2use std::collections::BTreeMap;
3
4use lcov::Report;
5
6use lcov::report::MergeError;
7
8use lcov::report::section::branch::Value as BranchValue;
9use lcov::report::section::function::Value as FunctionValue;
10use lcov::report::section::line::Value as LineValue;
11use lcov::report::section::Value as SectionValue;
12
13pub fn diff_reports(first: &Report, second: &Report) -> Result<Report, MergeError> {
14    let mut rep = Report::new();
15    rep.merge(first.to_owned())?;
16    rep.diff(second)?;
17    Ok(rep)
18}
19
20pub trait Diff {
21    fn diff(&mut self, other: &Self) -> Result<(), MergeError>;
22}
23
24impl Diff for Report {
25    fn diff(&mut self, other: &Self) -> Result<(), MergeError> {
26        self.sections.diff(&other.sections)
27    }
28}
29
30impl Diff for BranchValue {
31    fn diff(&mut self, other: &Self) -> Result<(), MergeError> {
32        if let BranchValue { taken: Some(taken) } = *other {
33            // We don't care about exact count. It's only important is the branch covered or not
34            if taken > 0 {
35                self.taken = None;
36            }
37        };
38        Ok(())
39    }
40}
41
42impl Diff for SectionValue {
43    fn diff(&mut self, other: &Self) -> Result<(), MergeError> {
44        self.functions.diff(&other.functions)?;
45        self.branches.diff(&other.branches)?;
46        self.lines.diff(&other.lines)?;
47        Ok(())
48    }
49}
50
51impl Diff for FunctionValue {
52    fn diff(&mut self, other: &Self) -> Result<(), MergeError> {
53        if let Some(start_line) = other.start_line.as_ref() {
54            if let Some(my_start_line) = self.start_line.as_ref() {
55                if start_line != my_start_line {
56                    return Err(MergeError::UnmatchedFunctionLine);
57                }
58            }
59        }
60        // As for branch it's only important if it covered or not
61        if other.count > 0 {
62            self.count = 0;
63        }
64        Ok(())
65    }
66}
67
68impl Diff for LineValue {
69    fn diff(&mut self, other: &Self) -> Result<(), MergeError> {
70        if let Some(checksum) = other.checksum.as_ref() {
71            if let Some(my_checksum) = self.checksum.as_ref() {
72                if checksum != my_checksum {
73                    return Err(MergeError::UnmatchedChecksum);
74                }
75            }
76        }
77        // As for branch it's only important if it covered or not
78        if other.count > 0 {
79            self.count = 0;
80        }
81        Ok(())
82    }
83}
84
85impl<K, V> Diff for BTreeMap<K, V>
86where
87    K: Ord + Clone,
88    V: Diff,
89{
90    fn diff(&mut self, other: &Self) -> Result<(), MergeError> {
91        for (key, value) in other {
92            match self.entry(key.clone()) {
93                Entry::Vacant(_) => {}
94                Entry::Occupied(mut e) => e.get_mut().diff(value)?,
95            }
96        }
97        Ok(())
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use crate::diff_reports;
104    use lcov::report::MergeError;
105    use lcov::{Reader, Report};
106
107    #[test]
108    fn diff_report() -> Result<(), MergeError> {
109        let input = "\
110TN:
111SF:target.c
112FN:1,main
113FNDA:1,main
114DA:1,1
115DA:3,1
116DA:4,1
117DA:5,1
118DA:6,1
119DA:7,1
120DA:8,0
121DA:11,1
122DA:12,0
123DA:14,1
124DA:15,1
125DA:17,1
126end_of_record
127";
128        let reader1 = Reader::new(input.as_bytes());
129        let report1 = Report::from_reader(reader1).unwrap();
130
131        let input2 = "\
132TN:
133SF:target.c
134FN:1,main
135FNDA:1,main
136DA:1,1
137DA:3,1
138DA:4,1
139DA:5,1
140DA:6,1
141DA:7,1
142DA:8,1
143DA:11,1
144DA:12,0
145DA:14,1
146DA:15,1
147DA:17,1
148end_of_record
149";
150
151        let expected_lcov = "\
152TN:
153SF:target.c
154FN:1,main
155FNDA:0,main
156FNF:1
157FNH:0
158DA:1,0
159DA:3,0
160DA:4,0
161DA:5,0
162DA:6,0
163DA:7,0
164DA:8,1
165DA:11,0
166DA:12,0
167DA:14,0
168DA:15,0
169DA:17,0
170LF:12
171LH:1
172end_of_record
173";
174        let reader2 = Reader::new(input2.as_bytes());
175        let report2 = Report::from_reader(reader2).unwrap();
176
177        let expected_report = Report::from_reader(Reader::new(expected_lcov.as_bytes())).unwrap();
178
179        let diff_rep = diff_reports(&report2, &report1).unwrap();
180
181        for pair in diff_rep.into_records().zip(expected_report.into_records()) {
182            assert_eq!(pair.0, pair.1)
183        }
184        Ok(())
185    }
186}