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 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 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 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}