tarp/
traces.rs

1use serde::{Deserialize, Serialize};
2use std::cmp::{Ord, Ordering};
3use std::collections::btree_map::Iter;
4use std::collections::{BTreeMap, HashMap};
5use std::fmt::{Display, Formatter, Result};
6use std::ops::Add;
7use std::path::{Path, PathBuf};
8
9/// Used to track the state of logical conditions
10#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
11pub struct LogicState {
12    /// Whether the condition has been observed as true
13    pub been_true: bool,
14    /// Whether the condition has been observed as false
15    pub been_false: bool,
16}
17
18impl<'a> Add for &'a LogicState {
19    type Output = LogicState;
20
21    fn add(self, other: &'a LogicState) -> LogicState {
22        LogicState {
23            been_true: self.been_true || other.been_true,
24            been_false: self.been_false || other.been_false,
25        }
26    }
27}
28
29/// Shows what type of coverage data is being collected by a given trace
30#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
31pub enum CoverageStat {
32    /// Line coverage data (whether line has been hit)
33    Line(u64),
34    /// Branch coverage data (whether branch has been true and false
35    Branch(LogicState),
36    /// Condition coverage data (each boolean subcondition true and false)
37    Condition(Vec<LogicState>),
38}
39
40impl Add for CoverageStat {
41    type Output = CoverageStat;
42
43    fn add(self, other: CoverageStat) -> CoverageStat {
44        match (self, other) {
45            (CoverageStat::Line(ref l), CoverageStat::Line(ref r)) => CoverageStat::Line(l + r),
46            (CoverageStat::Branch(ref l), CoverageStat::Branch(ref r)) => {
47                CoverageStat::Branch(l + r)
48            }
49            t => t.0,
50        }
51    }
52}
53
54impl Display for CoverageStat {
55    fn fmt(&self, f: &mut Formatter) -> Result {
56        match *self {
57            CoverageStat::Line(x) => write!(f, "hits: {}", x),
58            _ => write!(f, ""),
59        }
60    }
61}
62
63#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
64pub struct Trace {
65    /// Line the trace is on in the file
66    pub line: u64,
67    /// Optional address showing location in the test artefact
68    pub address: Option<u64>,
69    /// Length of the instruction (useful to get entire condition/branch)
70    pub length: usize,
71    /// Coverage stats
72    pub stats: CoverageStat,
73}
74
75/// Implemented to allow Traces to be sorted by line number
76impl Ord for Trace {
77    fn cmp(&self, other: &Trace) -> Ordering {
78        self.line.cmp(&other.line)
79    }
80    fn max(self, other: Trace) -> Trace {
81        if self.line > other.line {
82            self
83        } else {
84            other
85        }
86    }
87    fn min(self, other: Trace) -> Trace {
88        if self.line < other.line {
89            self
90        } else {
91            other
92        }
93    }
94}
95
96/// Amount of data coverable in the provided slice traces
97pub fn amount_coverable(traces: &[&Trace]) -> usize {
98    let mut result = 0usize;
99    for t in traces {
100        result += match t.stats {
101            CoverageStat::Branch(_) => 2usize,
102            CoverageStat::Condition(ref x) => x.len() * 2usize,
103            _ => 1usize,
104        };
105    }
106    result
107}
108
109/// Amount of data covered in the provided trace slice
110pub fn amount_covered(traces: &[&Trace]) -> usize {
111    let mut result = 0usize;
112    for t in traces {
113        result += match t.stats {
114            CoverageStat::Branch(ref x) => (x.been_true as usize) + (x.been_false as usize),
115            CoverageStat::Condition(ref x) => x.iter().fold(0, |acc, ref x| {
116                acc + (x.been_true as usize) + (x.been_false as usize)
117            }),
118            CoverageStat::Line(ref x) => (*x > 0) as usize,
119        };
120    }
121    result
122}
123
124pub fn coverage_percentage(traces: &[&Trace]) -> f64 {
125    (amount_covered(traces) as f64) / (amount_coverable(traces) as f64)
126}
127
128/// Stores all the program traces mapped to files and provides an interface to
129/// add, query and change traces.
130#[derive(Debug, Default, Deserialize, Serialize)]
131pub struct TraceMap {
132    /// Traces in the program mapped to the given file
133    traces: BTreeMap<PathBuf, Vec<Trace>>,
134}
135
136impl TraceMap {
137    /// Create a new TraceMap
138    pub fn new() -> TraceMap {
139        TraceMap {
140            traces: BTreeMap::new(),
141        }
142    }
143
144    /// Returns true if there are no traces
145    pub fn is_empty(&self) -> bool {
146        self.traces.is_empty()
147    }
148
149    /// Provides an interator to the underlying map of PathBufs to Vec<Trace>
150    pub fn iter(&self) -> Iter<PathBuf, Vec<Trace>> {
151        self.traces.iter()
152    }
153
154    /// Merges the results of one tracemap into the current one.
155    /// This adds records which are missing and adds the statistics gathered to
156    /// existing records
157    pub fn merge(&mut self, other: &TraceMap) {
158        for (k, values) in other.iter() {
159            if !self.traces.contains_key(k) {
160                self.traces.insert(k.to_path_buf(), values.to_vec());
161            } else {
162                let existing = self.traces.get_mut(k).unwrap();
163                for ref v in values.iter() {
164                    let mut added = false;
165                    if let Some(ref mut t) = existing
166                        .iter_mut()
167                        .find(|ref x| x.line == v.line && x.address == v.address)
168                    {
169                        t.stats = t.stats.clone() + v.stats.clone();
170                        added = true;
171                    }
172                    if !added {
173                        existing.push((*v).clone());
174                        existing.sort_unstable();
175                    }
176                }
177            }
178        }
179    }
180
181    /// This will collapse duplicate Traces into a single trace. Warning this
182    /// will lose the addresses of the duplicate traces but increment the results
183    /// should be called only if you don't need those addresses from then on
184    /// TODO possibly not the cleanest solution
185    pub fn dedup(&mut self) {
186        for values in self.traces.values_mut() {
187            // Map of lines and stats, merge duplicated stats here
188            let mut lines: HashMap<u64, CoverageStat> = HashMap::new();
189            // Duplicated traces need cleaning up. Maintain a list of them!
190            let mut dirty: Vec<u64> = Vec::new();
191            for v in values.iter() {
192                lines
193                    .entry(v.line)
194                    .and_modify(|e| {
195                        dirty.push(v.line);
196                        *e = e.clone() + v.stats.clone();
197                    })
198                    .or_insert_with(|| v.stats.clone());
199            }
200            for d in &dirty {
201                let mut first = true;
202                values.retain(|x| {
203                    let res = x.line != *d;
204                    if !res {
205                        if first {
206                            first = false;
207                            true
208                        } else {
209                            false
210                        }
211                    } else {
212                        res
213                    }
214                });
215                if let Some(new_stat) = lines.remove(&d) {
216                    if let Some(ref mut t) = values.iter_mut().find(|x| x.line == *d) {
217                        t.stats = new_stat;
218                    }
219                }
220            }
221        }
222    }
223
224    /// Add a trace to the tracemap for the given file
225    pub fn add_trace(&mut self, file: &Path, trace: Trace) {
226        if self.traces.contains_key(file) {
227            if let Some(trace_vec) = self.traces.get_mut(file) {
228                trace_vec.push(trace);
229                trace_vec.sort_unstable();
230            }
231        } else {
232            self.traces.insert(file.to_path_buf(), vec![trace]);
233        }
234    }
235
236    /// Gets an immutable reference to a trace from an address. Returns None if
237    /// there is no trace at that address
238    pub fn get_trace(&self, address: u64) -> Option<&Trace> {
239        self.all_traces()
240            .iter()
241            .find(|x| x.address == Some(address))
242            .map(|x| *x)
243    }
244
245    /// Gets a mutable reference to a trace at a given address
246    /// Returns None if there is no trace at that address
247    pub fn get_trace_mut(&mut self, address: u64) -> Option<&mut Trace> {
248        for val in self.all_traces_mut() {
249            if val.address == Some(address) {
250                return Some(val);
251            }
252        }
253        None
254    }
255
256    /// Returns true if the location described by file and line number is present
257    /// in the tracemap
258    pub fn contains_location(&self, file: &Path, line: u64) -> bool {
259        match self.traces.get(file) {
260            Some(traces) => traces.iter().any(|x| x.line == line),
261            None => false,
262        }
263    }
264
265    /// Returns true if the file is among the traces
266    pub fn contains_file(&self, file: &Path) -> bool {
267        self.traces.contains_key(file)
268    }
269
270    /// Gets all traces below a certain path
271    pub fn get_child_traces(&self, root: &Path) -> Vec<&Trace> {
272        self.traces
273            .iter()
274            .filter(|&(ref k, _)| k.starts_with(root))
275            .flat_map(|(_, ref v)| v.iter())
276            .collect()
277    }
278
279    /// Gets all traces in folder, doesn't go into other folders for that you
280    /// want get_child_traces
281    pub fn get_traces(&self, root: &Path) -> Vec<&Trace> {
282        if root.is_file() {
283            self.get_child_traces(root)
284        } else {
285            self.traces
286                .iter()
287                .filter(|&(ref k, _)| k.parent() == Some(root))
288                .flat_map(|(_, ref v)| v.iter())
289                .collect()
290        }
291    }
292
293    /// Gets all traces
294    pub fn all_traces(&self) -> Vec<&Trace> {
295        self.traces.values().flat_map(|ref x| x.iter()).collect()
296    }
297
298    /// Gets a vector of all the traces to mutate
299    fn all_traces_mut(&mut self) -> Vec<&mut Trace> {
300        self.traces
301            .values_mut()
302            .flat_map(|x| x.iter_mut())
303            .collect()
304    }
305
306    pub fn files(&self) -> Vec<&PathBuf> {
307        self.traces.keys().collect()
308    }
309
310    pub fn coverable_in_path(&self, path: &Path) -> usize {
311        amount_coverable(self.get_child_traces(path).as_slice())
312    }
313
314    pub fn covered_in_path(&self, path: &Path) -> usize {
315        amount_covered(self.get_child_traces(path).as_slice())
316    }
317
318    /// Give the total amount of coverable points in the code. This will vary
319    /// based on the statistics available for line coverage it will be total
320    /// lines whereas for condition or decision it will count the number of
321    /// conditions available
322    pub fn total_coverable(&self) -> usize {
323        amount_coverable(self.all_traces().as_slice())
324    }
325
326    /// From all the coverable data return the amount covered
327    pub fn total_covered(&self) -> usize {
328        amount_covered(self.all_traces().as_slice())
329    }
330
331    /// Returns coverage percentage ranging from 0.0-1.0
332    pub fn coverage_percentage(&self) -> f64 {
333        coverage_percentage(self.all_traces().as_slice())
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340    use std::path::Path;
341
342    #[test]
343    fn stat_addition() {
344        let x = CoverageStat::Line(0);
345        let y = CoverageStat::Line(5);
346        let z = CoverageStat::Line(7);
347        let xy = x.clone() + y.clone();
348        let yx = y.clone() + x.clone();
349        let yy = y.clone() + y.clone();
350        let zy = z.clone() + y.clone();
351        assert_eq!(&xy, &CoverageStat::Line(5));
352        assert_eq!(&yx, &xy);
353        assert_eq!(&yy, &CoverageStat::Line(10));
354        assert_eq!(&zy, &CoverageStat::Line(12));
355
356        let tf = LogicState {
357            been_true: true,
358            been_false: true,
359        };
360        let t = LogicState {
361            been_true: true,
362            been_false: false,
363        };
364        let f = LogicState {
365            been_true: false,
366            been_false: true,
367        };
368        let n = LogicState {
369            been_true: false,
370            been_false: false,
371        };
372
373        assert_eq!(&t + &f, tf);
374        assert_eq!(&t + &t, t);
375        assert_eq!(&tf + &f, tf);
376        assert_eq!(&tf + &t, tf);
377        assert_eq!(&t + &n, t);
378        assert_eq!(&n + &f, f);
379        assert_eq!(&n + &n, n);
380    }
381
382    #[test]
383    fn merge_address_mismatch_and_dedup() {
384        let mut t1 = TraceMap::new();
385        let mut t2 = TraceMap::new();
386
387        let a_trace = Trace {
388            line: 1,
389            address: Some(5),
390            length: 0,
391            stats: CoverageStat::Line(1),
392        };
393        t1.add_trace(Path::new("file.rs"), a_trace.clone());
394        t2.add_trace(
395            Path::new("file.rs"),
396            Trace {
397                line: 1,
398                address: None,
399                length: 0,
400                stats: CoverageStat::Line(2),
401            },
402        );
403
404        t1.merge(&t2);
405        assert_eq!(t1.all_traces().len(), 2);
406        assert_eq!(t1.get_trace(5), Some(&a_trace));
407        t1.dedup();
408        let all = t1.all_traces();
409        assert_eq!(all.len(), 1);
410        assert_eq!(all[0].stats, CoverageStat::Line(3));
411    }
412
413    #[test]
414    fn no_merge_dedup_needed() {
415        let mut t1 = TraceMap::new();
416        let mut t2 = TraceMap::new();
417
418        let a_trace = Trace {
419            line: 1,
420            address: Some(5),
421            length: 0,
422            stats: CoverageStat::Line(1),
423        };
424        t1.add_trace(Path::new("file.rs"), a_trace.clone());
425        t2.add_trace(
426            Path::new("file.rs"),
427            Trace {
428                line: 2,
429                address: None,
430                length: 0,
431                stats: CoverageStat::Line(2),
432            },
433        );
434
435        t1.merge(&t2);
436        assert_eq!(t1.all_traces().len(), 2);
437        assert_eq!(t1.get_trace(5), Some(&a_trace));
438        t1.dedup();
439        let all = t1.all_traces();
440        assert_eq!(all.len(), 2);
441    }
442
443    #[test]
444    fn merge_needed() {
445        let mut t1 = TraceMap::new();
446        let mut t2 = TraceMap::new();
447
448        t1.add_trace(
449            Path::new("file.rs"),
450            Trace {
451                line: 2,
452                address: Some(1),
453                length: 0,
454                stats: CoverageStat::Line(5),
455            },
456        );
457        t2.add_trace(
458            Path::new("file.rs"),
459            Trace {
460                line: 2,
461                address: Some(1),
462                length: 0,
463                stats: CoverageStat::Line(2),
464            },
465        );
466        t1.merge(&t2);
467        assert_eq!(t1.all_traces().len(), 1);
468        assert_eq!(
469            t1.get_trace(1),
470            Some(&Trace {
471                line: 2,
472                address: Some(1),
473                length: 0,
474                stats: CoverageStat::Line(7)
475            })
476        );
477        // Deduplicating should have no effect.
478        t1.dedup();
479        assert_eq!(t1.all_traces().len(), 1);
480        assert_eq!(
481            t1.get_trace(1),
482            Some(&Trace {
483                line: 2,
484                address: Some(1),
485                length: 0,
486                stats: CoverageStat::Line(7)
487            })
488        );
489    }
490}