1use crate::source_analysis::Function;
2use serde::{Deserialize, Serialize};
3use std::cmp::{Ord, Ordering};
4use std::collections::btree_map::Iter;
5use std::collections::{BTreeMap, HashMap, HashSet};
6use std::ops::Add;
7use std::path::{Path, PathBuf};
8use tracing::trace;
9
10#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
12pub struct LogicState {
13 pub been_true: bool,
15 pub been_false: bool,
17}
18
19impl<'a> Add for &'a LogicState {
20 type Output = LogicState;
21
22 fn add(self, other: &'a LogicState) -> LogicState {
23 LogicState {
24 been_true: self.been_true || other.been_true,
25 been_false: self.been_false || other.been_false,
26 }
27 }
28}
29
30#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
32pub enum CoverageStat {
33 Line(u64),
35 Branch(LogicState),
37 Condition(Vec<LogicState>),
39}
40
41impl Add for CoverageStat {
42 type Output = CoverageStat;
43
44 fn add(self, other: CoverageStat) -> CoverageStat {
45 match (self, other) {
46 (CoverageStat::Line(ref l), CoverageStat::Line(ref r)) => CoverageStat::Line(l + r),
47 (CoverageStat::Branch(ref l), CoverageStat::Branch(ref r)) => {
48 CoverageStat::Branch(l + r)
49 }
50 t => t.0,
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
56pub struct Trace {
57 pub line: u64,
59 pub address: HashSet<u64>,
61 pub length: usize,
63 pub stats: CoverageStat,
65}
66
67#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
68pub struct Location {
69 pub file: PathBuf,
71 pub line: u64,
73}
74
75impl Trace {
76 pub fn new(line: u64, address: HashSet<u64>, length: usize) -> Self {
77 Self {
78 line,
79 address,
80 length,
81 stats: CoverageStat::Line(0),
82 }
83 }
84
85 pub fn new_stub(line: u64) -> Self {
86 Self {
87 line,
88 address: HashSet::new(),
89 length: 0,
90 stats: CoverageStat::Line(0),
91 }
92 }
93}
94
95impl PartialOrd for Trace {
96 fn partial_cmp(&self, other: &Trace) -> Option<Ordering> {
97 self.line.partial_cmp(&other.line)
99 }
100}
101
102impl Ord for Trace {
104 fn cmp(&self, other: &Trace) -> Ordering {
105 self.line.cmp(&other.line)
106 }
107 fn max(self, other: Trace) -> Trace {
108 if self.line > other.line {
109 self
110 } else {
111 other
112 }
113 }
114 fn min(self, other: Trace) -> Trace {
115 if self.line < other.line {
116 self
117 } else {
118 other
119 }
120 }
121}
122
123pub fn amount_coverable<'a>(traces: impl Iterator<Item = &'a Trace>) -> usize {
125 let mut result = 0usize;
126 for t in traces {
127 result += match t.stats {
128 CoverageStat::Branch(_) => 2usize,
129 CoverageStat::Condition(ref x) => x.len() * 2usize,
130 _ => 1usize,
131 };
132 }
133 result
134}
135
136pub fn amount_covered<'a>(traces: impl Iterator<Item = &'a Trace>) -> usize {
138 let mut result = 0usize;
139 for t in traces {
140 result += match t.stats {
141 CoverageStat::Branch(ref x) => usize::from(x.been_true) + usize::from(x.been_false),
142 CoverageStat::Condition(ref x) => x.iter().fold(0, |acc, x| {
143 acc + usize::from(x.been_true) + usize::from(x.been_false)
144 }),
145 CoverageStat::Line(ref x) => (*x > 0).into(),
146 };
147 }
148 result
149}
150
151pub fn coverage_percentage<'a>(traces: impl Iterator<Item = &'a Trace>) -> f64 {
152 let t: Vec<_> = traces.collect();
153 (amount_covered(t.iter().copied()) as f64) / (amount_coverable(t.iter().copied()) as f64)
154}
155
156#[derive(Debug, Default, Deserialize, Serialize)]
159pub struct TraceMap {
160 traces: BTreeMap<PathBuf, Vec<Trace>>,
162 functions: HashMap<PathBuf, Vec<Function>>,
163}
164
165impl TraceMap {
166 pub fn new() -> TraceMap {
168 Self::default()
169 }
170
171 pub fn set_functions(&mut self, functions: HashMap<PathBuf, Vec<Function>>) {
172 self.functions = functions;
173 }
174
175 pub fn is_empty(&self) -> bool {
177 self.traces.is_empty()
178 }
179
180 pub fn iter(&self) -> Iter<PathBuf, Vec<Trace>> {
182 self.traces.iter()
183 }
184
185 pub fn merge(&mut self, other: &TraceMap) {
189 self.functions
190 .extend(other.functions.iter().map(|(k, v)| (k.clone(), v.clone())));
191 for (k, values) in other.iter() {
192 if !self.traces.contains_key(k) {
193 self.traces.insert(k.clone(), values.clone());
194 } else {
195 let existing = self.traces.get_mut(k).unwrap();
196 for v in values.iter() {
197 let mut added = false;
198 if let Some(ref mut t) = existing
199 .iter_mut()
200 .find(|x| x.line == v.line && x.address == v.address)
201 {
202 t.stats = t.stats.clone() + v.stats.clone();
203 added = true;
204 }
205 if !added {
206 existing.push((*v).clone());
207 existing.sort_unstable();
208 }
209 }
210 }
211 }
212 }
213
214 pub fn dedup(&mut self) {
219 for values in self.traces.values_mut() {
220 let mut lines: HashMap<u64, CoverageStat> = HashMap::new();
222 let mut dirty: Vec<u64> = Vec::new();
224 for v in values.iter() {
225 lines
226 .entry(v.line)
227 .and_modify(|e| {
228 dirty.push(v.line);
229 *e = e.clone() + v.stats.clone();
230 })
231 .or_insert_with(|| v.stats.clone());
232 }
233 for d in &dirty {
234 let mut first = true;
235 values.retain(|x| {
236 let res = x.line != *d;
237 if !res {
238 if first {
239 first = false;
240 true
241 } else {
242 false
243 }
244 } else {
245 res
246 }
247 });
248 if let Some(new_stat) = lines.remove(d) {
249 if let Some(ref mut t) = values.iter_mut().find(|x| x.line == *d) {
250 t.stats = new_stat;
251 }
252 }
253 }
254 }
255 }
256
257 pub fn add_trace(&mut self, file: &Path, trace: Trace) {
259 if self.traces.contains_key(file) {
260 if let Some(trace_vec) = self.traces.get_mut(file) {
261 trace_vec.push(trace);
262 trace_vec.sort_unstable();
263 }
264 } else {
265 self.traces.insert(file.to_path_buf(), vec![trace]);
266 }
267 }
268
269 pub fn add_file(&mut self, file: &Path) {
270 if !self.traces.contains_key(file) {
271 self.traces.insert(file.to_path_buf(), vec![]);
272 }
273 }
274
275 pub fn get_trace(&self, address: u64) -> Option<&Trace> {
278 self.all_traces().find(|x| x.address.contains(&address))
279 }
280
281 pub fn increment_hit(&mut self, address: u64) {
282 for trace in self
283 .all_traces_mut()
284 .filter(|x| x.address.contains(&address))
285 {
286 if let CoverageStat::Line(ref mut x) = trace.stats {
287 trace!("Incrementing hit count for trace");
288 *x += 1;
289 }
290 }
291 }
292
293 pub fn get_location(&self, address: u64) -> Option<Location> {
294 for (k, v) in &self.traces {
295 if let Some(s) = v
296 .iter()
297 .find(|x| x.address.iter().any(|x| (*x & !0x7u64) == address))
298 {
299 return Some(Location {
300 file: k.clone(),
301 line: s.line,
302 });
303 }
304 }
305 None
306 }
307
308 pub fn contains_location(&self, file: &Path, line: u64) -> bool {
311 match self.traces.get(file) {
312 Some(traces) => traces.iter().any(|x| x.line == line),
313 None => false,
314 }
315 }
316
317 pub fn contains_file(&self, file: &Path) -> bool {
319 self.traces.contains_key(file)
320 }
321
322 pub fn get_child_traces<'a>(&'a self, root: &'a Path) -> impl Iterator<Item = &'a Trace> + 'a {
324 self.traces
325 .iter()
326 .filter(move |&(k, _)| k.starts_with(root))
327 .flat_map(|(_, v)| v.iter())
328 }
329
330 pub fn get_functions(&self, file: &Path) -> impl Iterator<Item = &Function> {
331 let i: Box<dyn Iterator<Item = &Function>> = match self.functions.get(file) {
332 Some(f) => Box::new(f.iter()),
333 None => Box::new(std::iter::empty()),
334 };
335 i
336 }
337
338 pub fn file_traces_mut(&mut self, file: &Path) -> Option<&mut Vec<Trace>> {
339 self.traces.get_mut(file)
340 }
341
342 pub fn all_traces(&self) -> impl Iterator<Item = &Trace> {
344 self.traces.values().flat_map(|x| x.iter())
345 }
346
347 fn all_traces_mut(&mut self) -> impl Iterator<Item = &mut Trace> {
349 self.traces.values_mut().flat_map(|x| x.iter_mut())
350 }
351
352 pub fn files(&self) -> Vec<&PathBuf> {
353 self.traces.keys().collect()
354 }
355
356 pub fn coverable_in_path(&self, path: &Path) -> usize {
357 amount_coverable(self.get_child_traces(path))
358 }
359
360 pub fn covered_in_path(&self, path: &Path) -> usize {
361 amount_covered(self.get_child_traces(path))
362 }
363
364 pub fn total_coverable(&self) -> usize {
369 amount_coverable(self.all_traces())
370 }
371
372 pub fn total_covered(&self) -> usize {
374 amount_covered(self.all_traces())
375 }
376
377 pub fn coverage_percentage(&self) -> f64 {
379 coverage_percentage(self.all_traces())
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386 use std::path::Path;
387
388 #[test]
389 #[allow(clippy::many_single_char_names)]
390 fn stat_addition() {
391 let x = CoverageStat::Line(0);
392 let y = CoverageStat::Line(5);
393 let z = CoverageStat::Line(7);
394 let xy = x.clone() + y.clone();
395 let yx = y.clone() + x;
396 let yy = y.clone() + y.clone();
397 let zy = z + y;
398 assert_eq!(&xy, &CoverageStat::Line(5));
399 assert_eq!(&yx, &xy);
400 assert_eq!(&yy, &CoverageStat::Line(10));
401 assert_eq!(&zy, &CoverageStat::Line(12));
402
403 let tf = LogicState {
404 been_true: true,
405 been_false: true,
406 };
407 let t = LogicState {
408 been_true: true,
409 been_false: false,
410 };
411 let f = LogicState {
412 been_true: false,
413 been_false: true,
414 };
415 let n = LogicState {
416 been_true: false,
417 been_false: false,
418 };
419
420 assert_eq!(&t + &f, tf);
421 assert_eq!(&t + &t, t);
422 assert_eq!(&tf + &f, tf);
423 assert_eq!(&tf + &t, tf);
424 assert_eq!(&t + &n, t);
425 assert_eq!(&n + &f, f);
426 assert_eq!(&n + &n, n);
427 }
428
429 #[test]
430 fn multiple_traces_per_line() {
431 let mut t1 = TraceMap::new();
432 let mut address = HashSet::new();
433 address.insert(0);
434 address.insert(128);
435 let trace_1 = Trace {
436 line: 1,
437 address,
438 length: 0,
439 stats: CoverageStat::Line(1),
440 };
441 t1.add_trace(Path::new("file.rs"), trace_1);
442
443 let coverable = t1.total_coverable();
444 assert_eq!(coverable, 1);
445 let total_covered = t1.total_covered();
446 assert_eq!(total_covered, 1);
447 }
448
449 #[test]
450 fn merge_address_mismatch_and_dedup() {
451 let mut t1 = TraceMap::new();
452 let mut t2 = TraceMap::new();
453
454 let mut address = HashSet::new();
455 address.insert(5);
456 let a_trace = Trace {
457 line: 1,
458 address,
459 length: 0,
460 stats: CoverageStat::Line(1),
461 };
462 t1.add_trace(Path::new("file.rs"), a_trace.clone());
463 t2.add_trace(
464 Path::new("file.rs"),
465 Trace {
466 line: 1,
467 address: HashSet::new(),
468 length: 0,
469 stats: CoverageStat::Line(2),
470 },
471 );
472
473 t1.merge(&t2);
474 assert_eq!(t1.all_traces().count(), 2);
475 assert_eq!(t1.get_trace(5), Some(&a_trace));
476 t1.dedup();
477 let all = t1.all_traces().collect::<Vec<_>>();
478 assert_eq!(all.len(), 1);
479 assert_eq!(all[0].stats, CoverageStat::Line(3));
480 }
481
482 #[test]
483 fn no_merge_dedup_needed() {
484 let mut t1 = TraceMap::new();
485 let mut t2 = TraceMap::new();
486
487 let mut address = HashSet::new();
488 address.insert(5);
489 let a_trace = Trace {
490 line: 1,
491 address,
492 length: 0,
493 stats: CoverageStat::Line(1),
494 };
495 t1.add_trace(Path::new("file.rs"), a_trace.clone());
496 t2.add_trace(
497 Path::new("file.rs"),
498 Trace {
499 line: 2,
500 address: HashSet::new(),
501 length: 0,
502 stats: CoverageStat::Line(2),
503 },
504 );
505
506 t1.merge(&t2);
507 assert_eq!(t1.all_traces().count(), 2);
508 assert_eq!(t1.get_trace(5), Some(&a_trace));
509 t1.dedup();
510 let all = t1.all_traces();
511 assert_eq!(all.count(), 2);
512 }
513
514 #[test]
515 fn merge_needed() {
516 let mut t1 = TraceMap::new();
517 let mut t2 = TraceMap::new();
518
519 let mut address = HashSet::new();
520 address.insert(1);
521 t1.add_trace(
522 Path::new("file.rs"),
523 Trace {
524 line: 2,
525 address: address.clone(),
526 length: 0,
527 stats: CoverageStat::Line(5),
528 },
529 );
530 t2.add_trace(
531 Path::new("file.rs"),
532 Trace {
533 line: 2,
534 address: address.clone(),
535 length: 0,
536 stats: CoverageStat::Line(2),
537 },
538 );
539 t1.merge(&t2);
540 assert_eq!(t1.all_traces().count(), 1);
541 assert_eq!(
542 t1.get_trace(1),
543 Some(&Trace {
544 line: 2,
545 address: address.clone(),
546 length: 0,
547 stats: CoverageStat::Line(7),
548 })
549 );
550 t1.dedup();
552 assert_eq!(t1.all_traces().count(), 1);
553 assert_eq!(
554 t1.get_trace(1),
555 Some(&Trace {
556 line: 2,
557 address,
558 length: 0,
559 stats: CoverageStat::Line(7),
560 })
561 );
562 }
563}