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#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
11pub struct LogicState {
12 pub been_true: bool,
14 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#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
31pub enum CoverageStat {
32 Line(u64),
34 Branch(LogicState),
36 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 pub line: u64,
67 pub address: Option<u64>,
69 pub length: usize,
71 pub stats: CoverageStat,
73}
74
75impl 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
96pub 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
109pub 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#[derive(Debug, Default, Deserialize, Serialize)]
131pub struct TraceMap {
132 traces: BTreeMap<PathBuf, Vec<Trace>>,
134}
135
136impl TraceMap {
137 pub fn new() -> TraceMap {
139 TraceMap {
140 traces: BTreeMap::new(),
141 }
142 }
143
144 pub fn is_empty(&self) -> bool {
146 self.traces.is_empty()
147 }
148
149 pub fn iter(&self) -> Iter<PathBuf, Vec<Trace>> {
151 self.traces.iter()
152 }
153
154 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 pub fn dedup(&mut self) {
186 for values in self.traces.values_mut() {
187 let mut lines: HashMap<u64, CoverageStat> = HashMap::new();
189 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 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 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 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 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 pub fn contains_file(&self, file: &Path) -> bool {
267 self.traces.contains_key(file)
268 }
269
270 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 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 pub fn all_traces(&self) -> Vec<&Trace> {
295 self.traces.values().flat_map(|ref x| x.iter()).collect()
296 }
297
298 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 pub fn total_coverable(&self) -> usize {
323 amount_coverable(self.all_traces().as_slice())
324 }
325
326 pub fn total_covered(&self) -> usize {
328 amount_covered(self.all_traces().as_slice())
329 }
330
331 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 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}