1use indexmap::IndexMap;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5 coverage::Coverage,
6 percent,
7 types::{Branch, BranchCoverageMap, BranchHitMap, BranchMap, Function, FunctionMap},
8 CoveragePercentage, CoverageSummary, LineHitMap, Range, SourceMap, StatementMap, Totals,
9};
10use std::fmt::Debug;
11
12fn key_from_loc(range: &Range) -> String {
13 format!(
14 "{}|{}|{}|{}",
15 range.start.line, range.start.column, range.end.line, range.end.column
16 )
17}
18
19fn merge_properties_hits_vec(
20 first_hits: &BranchHitMap,
21 first_map: &BranchMap,
22 second_hits: &BranchHitMap,
23 second_map: &BranchMap,
24 get_item_key_fn: for<'r> fn(&'r Branch) -> String,
25) -> (BranchHitMap, IndexMap<u32, Branch>) {
26 let mut items: IndexMap<String, (Vec<u32>, Branch)> = Default::default();
27
28 for (key, item_hits) in first_hits {
29 let item = first_map
30 .get(key)
31 .expect("Corresponding map value should exist");
32 let item_key = get_item_key_fn(item);
33
34 items.insert(item_key, (item_hits.clone(), item.clone()));
35 }
36
37 for (key, item_hits) in second_hits {
38 let item = second_map
39 .get(key)
40 .expect("Corresponding map value should exist");
41 let item_key = get_item_key_fn(item);
42
43 items
44 .entry(item_key)
45 .and_modify(|pair| {
46 if pair.0.len() < item_hits.len() {
47 pair.0.resize(item_hits.len(), 0);
48 }
49
50 for (h, hits) in item_hits.iter().enumerate() {
51 pair.0[h] += hits;
52 }
53 })
54 .or_insert((item_hits.clone(), item.clone()));
55 }
56
57 let mut hits: BranchHitMap = Default::default();
58 let mut map: BranchMap = Default::default();
59
60 for (idx, (hit, item)) in items.values().enumerate() {
61 hits.insert(idx as u32, hit.clone());
62 map.insert(idx as u32, item.clone());
63 }
64
65 (hits, map)
66}
67
68fn merge_properties<T>(
69 first_hits: &LineHitMap,
70 first_map: &IndexMap<u32, T>,
71 second_hits: &LineHitMap,
72 second_map: &IndexMap<u32, T>,
73 get_item_key_fn: for<'r> fn(&'r T) -> String,
74) -> (LineHitMap, IndexMap<u32, T>)
75where
76 T: Clone + Debug,
77{
78 let mut items: IndexMap<String, (u32, T)> = Default::default();
79
80 for (key, item_hits) in first_hits {
81 let item = first_map
82 .get(key)
83 .expect("Corresponding map value should exist");
84 let item_key = get_item_key_fn(item);
85
86 items.insert(item_key, (*item_hits, item.clone()));
87 }
88
89 for (key, item_hits) in second_hits {
90 let item = second_map
91 .get(key)
92 .expect("Corresponding map value should exist");
93 let item_key = get_item_key_fn(item);
94
95 items
96 .entry(item_key)
97 .and_modify(|pair| {
98 pair.0 += *item_hits;
99 })
100 .or_insert((*item_hits, item.clone()));
101 }
102
103 let mut hits: LineHitMap = Default::default();
104 let mut map: IndexMap<u32, T> = Default::default();
105
106 for (idx, (hit, item)) in items.values().enumerate() {
107 hits.insert(idx as u32, *hit);
108 map.insert(idx as u32, item.clone());
109 }
110
111 (hits, map)
112}
113
114#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct FileCoverage {
130 #[serde(default)]
131 pub all: bool,
132 pub path: String,
133 pub statement_map: StatementMap,
134 pub fn_map: FunctionMap,
135 pub branch_map: BranchMap,
136 pub s: LineHitMap,
137 pub f: LineHitMap,
138 pub b: BranchHitMap,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub b_t: Option<BranchHitMap>,
141 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub input_source_map: Option<SourceMap>,
143}
144
145impl FileCoverage {
146 pub fn empty(file_path: String, report_logic: bool) -> FileCoverage {
147 FileCoverage {
148 all: false,
149 path: file_path,
150 statement_map: Default::default(),
151 fn_map: Default::default(),
152 branch_map: Default::default(),
153 s: Default::default(),
154 b: Default::default(),
155 f: Default::default(),
156 b_t: if report_logic {
157 Some(Default::default())
158 } else {
159 None
160 },
161 input_source_map: Default::default(),
162 }
163 }
164
165 pub fn from_file_path(file_path: String, report_logic: bool) -> FileCoverage {
166 FileCoverage::empty(file_path, report_logic)
167 }
168
169 pub fn from_file_coverage(coverage: &FileCoverage) -> FileCoverage {
170 coverage.clone()
171 }
172
173 pub fn get_line_coverage(&self) -> LineHitMap {
176 let statements_map = &self.statement_map;
177 let statements = &self.s;
178
179 let mut line_map: LineHitMap = Default::default();
180
181 for (st, count) in statements {
182 let line = statements_map
183 .get(st)
184 .expect("statement not found")
185 .start
186 .line;
187 let pre_val = line_map.get(&line);
188
189 match pre_val {
190 Some(pre_val) if pre_val < count => {
191 line_map.insert(line, *count);
192 }
193 None => {
194 line_map.insert(line, *count);
195 }
196 _ => {
197 }
199 }
200 }
201
202 line_map
203 }
204
205 pub fn get_uncovered_lines(&self) -> Vec<u32> {
207 let lc = self.get_line_coverage();
208 let mut ret: Vec<u32> = Default::default();
209
210 for (l, hits) in lc {
211 if hits == 0 {
212 ret.push(l);
213 }
214 }
215
216 ret
217 }
218
219 pub fn get_branch_coverage_by_line(&self) -> BranchCoverageMap {
220 let branch_map = &self.branch_map;
221 let branches = &self.b;
222
223 let mut prefilter_data: BranchHitMap = Default::default();
224 let mut ret: BranchCoverageMap = Default::default();
225
226 for (k, map) in branch_map {
227 let line = if let Some(line) = map.line {
228 line
229 } else {
230 map.loc.expect("Either line or loc should exist").start.line
231 };
232
233 let branch_data = branches.get(k).expect("branch data not found");
234
235 if let Some(line_data) = prefilter_data.get_mut(&line) {
236 line_data.append(&mut branch_data.clone());
237 } else {
238 prefilter_data.insert(line, branch_data.clone());
239 }
240 }
241
242 for (k, data_array) in prefilter_data {
243 let covered: Vec<&u32> = data_array.iter().filter(|&x| *x > 0).collect();
244 let coverage = covered.len() as f32 / data_array.len() as f32 * 100 as f32;
245
246 ret.insert(
247 k,
248 Coverage::new(covered.len() as u32, data_array.len() as u32, coverage),
249 );
250 }
251
252 ret
253 }
254
255 pub fn to_json() {
256 unimplemented!()
257 }
258 pub fn merge(&mut self, coverage: &FileCoverage) {
260 if coverage.all {
261 return;
262 }
263
264 if self.all {
265 *self = coverage.clone();
266 return;
267 }
268
269 let (statement_hits_merged, statement_map_merged) = merge_properties(
270 &self.s,
271 &self.statement_map,
272 &coverage.s,
273 &coverage.statement_map,
274 |range: &Range| key_from_loc(range),
275 );
276
277 self.s = statement_hits_merged;
278 self.statement_map = statement_map_merged;
279
280 let (fn_hits_merged, fn_map_merged) = merge_properties(
281 &self.f,
282 &self.fn_map,
283 &coverage.f,
284 &coverage.fn_map,
285 |map: &Function| key_from_loc(&map.loc),
286 );
287
288 self.f = fn_hits_merged;
289 self.fn_map = fn_map_merged;
290
291 let (branches_hits_merged, branches_map_merged) = merge_properties_hits_vec(
292 &self.b,
293 &self.branch_map,
294 &coverage.b,
295 &coverage.branch_map,
296 |branch: &Branch| key_from_loc(&branch.locations[0]),
297 );
298 self.b = branches_hits_merged;
299 self.branch_map = branches_map_merged;
300
301 if let Some(branches_true) = &self.b_t {
304 if let Some(coverage_branches_true) = &coverage.b_t {
305 let (branches_true_hits_merged, _) = merge_properties_hits_vec(
306 branches_true,
307 &self.branch_map,
308 coverage_branches_true,
309 &coverage.branch_map,
310 |branch: &Branch| key_from_loc(&branch.locations[0]),
311 );
312
313 self.b_t = Some(branches_true_hits_merged);
314 }
315 }
316 }
317
318 pub fn compute_simple_totals<T>(line_map: &IndexMap<T, u32>) -> Totals {
319 let total = line_map.len() as u32;
320 let covered = line_map.values().filter(|&x| *x > 0).count() as u32;
321 Totals {
322 total,
323 covered,
324 skipped: 0,
325 pct: CoveragePercentage::Value(percent(covered, total)),
326 }
327 }
328
329 fn compute_branch_totals(branch_map: &BranchHitMap) -> Totals {
330 let mut ret: Totals = Default::default();
331
332 branch_map.values().for_each(|branches| {
333 ret.covered += branches.iter().filter(|hits| **hits > 0).count() as u32;
334 ret.total += branches.len() as u32;
335 });
336
337 ret.pct = CoveragePercentage::Value(percent(ret.covered, ret.total));
338 ret
339 }
340
341 pub fn reset_hits(&mut self) {
342 for val in self.s.values_mut() {
343 *val = 0;
344 }
345
346 for val in self.f.values_mut() {
347 *val = 0;
348 }
349
350 for val in self.b.values_mut() {
351 val.iter_mut().for_each(|x| *x = 0);
352 }
353
354 if let Some(branches_true) = &mut self.b_t {
355 for val in branches_true.values_mut() {
356 val.iter_mut().for_each(|x| *x = 0);
357 }
358 }
359 }
360
361 pub fn to_summary(&self) -> CoverageSummary {
362 let line_coverage = self.get_line_coverage();
363
364 let line = FileCoverage::compute_simple_totals(&line_coverage);
365 let function = FileCoverage::compute_simple_totals(&self.f);
366 let statement = FileCoverage::compute_simple_totals(&self.s);
367 let branches = FileCoverage::compute_branch_totals(&self.b);
368
369 let branches_true = if let Some(branches_true) = &self.b_t {
370 Some(FileCoverage::compute_branch_totals(&branches_true))
371 } else {
372 None
373 };
374
375 CoverageSummary::new(line, statement, function, branches, branches_true)
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use indexmap::IndexMap;
382
383 use crate::{
384 coverage::Coverage,
385 coverage_summary::{CoveragePercentage, Totals},
386 types::{Branch, Function},
387 BranchType, FileCoverage, Range,
388 };
389
390 #[test]
391 fn should_able_to_merge_another_file() {
392 let base = FileCoverage {
393 all: false,
394 path: "/path/to/file".to_string(),
395 statement_map: IndexMap::from([
396 (0, Range::new(1, 1, 1, 100)),
397 (1, Range::new(2, 1, 2, 50)),
398 (2, Range::new(2, 51, 2, 100)),
399 (3, Range::new(2, 101, 3, 100)),
400 ]),
401 fn_map: IndexMap::from([(
402 0,
403 Function {
404 name: "foobar".to_string(),
405 line: 1,
406 loc: Range::new(1, 1, 1, 50),
407 decl: Default::default(),
408 },
409 )]),
410 branch_map: IndexMap::from([(
411 0,
412 Branch::from_line(
413 BranchType::If,
414 2,
415 vec![Range::new(2, 1, 2, 20), Range::new(2, 50, 2, 100)],
416 ),
417 )]),
418 s: IndexMap::from([(0, 0), (1, 0), (2, 0), (3, 0)]),
419 f: IndexMap::from([(0, 0)]),
420 b: IndexMap::from([(0, vec![0, 0])]),
421 b_t: None,
422 input_source_map: None,
423 };
424
425 let mut first = base.clone();
426 let mut second = base.clone();
427
428 first.s.insert(0, 1);
429 first.f.insert(0, 1);
430 first.b.entry(0).and_modify(|v| v[0] = 1);
431
432 second.s.insert(1, 1);
433 second.f.insert(0, 1);
434 second.b.entry(0).and_modify(|v| v[1] = 2);
435
436 let summary = first.to_summary();
437 assert_eq!(
438 summary.statements,
439 Totals::new(4, 1, 0, CoveragePercentage::Value(25.0))
440 );
441 assert_eq!(
442 summary.lines,
443 Totals::new(2, 1, 0, CoveragePercentage::Value(50.0))
444 );
445 assert_eq!(
446 summary.functions,
447 Totals::new(1, 1, 0, CoveragePercentage::Value(100.0))
448 );
449 assert_eq!(
450 summary.branches,
451 Totals::new(2, 1, 0, CoveragePercentage::Value(50.0))
452 );
453
454 first.merge(&second);
455 let summary = first.to_summary();
456
457 assert_eq!(
458 summary.statements,
459 Totals::new(4, 2, 0, CoveragePercentage::Value(50.0))
460 );
461 assert_eq!(
462 summary.lines,
463 Totals::new(2, 2, 0, CoveragePercentage::Value(100.0))
464 );
465 assert_eq!(
466 summary.functions,
467 Totals::new(1, 1, 0, CoveragePercentage::Value(100.0))
468 );
469
470 assert_eq!(
471 summary.branches,
472 Totals::new(2, 2, 0, CoveragePercentage::Value(100.0))
473 );
474
475 assert_eq!(first.s.get(&0), Some(&1));
476 assert_eq!(first.s.get(&1), Some(&1));
477 assert_eq!(first.f.get(&0), Some(&2));
478 assert_eq!(first.b.get(&0).unwrap()[0], 1);
479 assert_eq!(first.b.get(&0).unwrap()[1], 2);
480 }
481
482 #[test]
483 fn should_able_to_merge_another_file_with_different_starting_indices() {
484 let base = FileCoverage {
485 all: false,
486 path: "/path/to/file".to_string(),
487 statement_map: IndexMap::from([
488 (0, Range::new(1, 1, 1, 100)),
489 (1, Range::new(2, 1, 2, 50)),
490 (2, Range::new(2, 51, 2, 100)),
491 (3, Range::new(2, 101, 3, 100)),
492 ]),
493 fn_map: IndexMap::from([(
494 0,
495 Function {
496 name: "foobar".to_string(),
497 line: 1,
498 loc: Range::new(1, 1, 1, 50),
499 decl: Default::default(),
500 },
501 )]),
502 branch_map: IndexMap::from([(
503 0,
504 Branch::from_line(
505 BranchType::If,
506 2,
507 vec![Range::new(2, 1, 2, 20), Range::new(2, 50, 2, 100)],
508 ),
509 )]),
510 s: IndexMap::from([(0, 0), (1, 0), (2, 0), (3, 0)]),
511 f: IndexMap::from([(0, 0)]),
512 b: IndexMap::from([(0, vec![0, 0])]),
513 b_t: None,
514 input_source_map: None,
515 };
516
517 let base_other = FileCoverage {
518 all: false,
519 path: "/path/to/file".to_string(),
520 statement_map: IndexMap::from([
521 (1, Range::new(1, 1, 1, 100)),
522 (2, Range::new(2, 1, 2, 50)),
523 (3, Range::new(2, 51, 2, 100)),
524 (4, Range::new(2, 101, 3, 100)),
525 ]),
526 fn_map: IndexMap::from([(
527 1,
528 Function {
529 name: "foobar".to_string(),
530 line: 1,
531 loc: Range::new(1, 1, 1, 50),
532 decl: Default::default(),
533 },
534 )]),
535 branch_map: IndexMap::from([(
536 1,
537 Branch::from_line(
538 BranchType::If,
539 2,
540 vec![Range::new(2, 1, 2, 20), Range::new(2, 50, 2, 100)],
541 ),
542 )]),
543 s: IndexMap::from([(1, 0), (2, 0), (3, 0), (4, 0)]),
544 f: IndexMap::from([(1, 0)]),
545 b: IndexMap::from([(1, vec![0, 0])]),
546 b_t: None,
547 input_source_map: None,
548 };
549
550 let mut first = base.clone();
551 let mut second = base_other.clone();
552
553 first.s.insert(0, 1);
554 first.f.insert(0, 1);
555 first.b.entry(0).and_modify(|v| v[0] = 1);
556
557 second.s.insert(2, 1);
558 second.f.insert(1, 1);
559 second.b.entry(1).and_modify(|v| v[1] = 2);
560
561 let summary = first.to_summary();
562 assert_eq!(
563 summary.statements,
564 Totals::new(4, 1, 0, CoveragePercentage::Value(25.0))
565 );
566 assert_eq!(
567 summary.lines,
568 Totals::new(2, 1, 0, CoveragePercentage::Value(50.0))
569 );
570 assert_eq!(
571 summary.functions,
572 Totals::new(1, 1, 0, CoveragePercentage::Value(100.0))
573 );
574 assert_eq!(
575 summary.branches,
576 Totals::new(2, 1, 0, CoveragePercentage::Value(50.0))
577 );
578
579 first.merge(&second);
580 let summary = first.to_summary();
581
582 assert_eq!(
583 summary.statements,
584 Totals::new(4, 2, 0, CoveragePercentage::Value(50.0))
585 );
586 assert_eq!(
587 summary.lines,
588 Totals::new(2, 2, 0, CoveragePercentage::Value(100.0))
589 );
590 assert_eq!(
591 summary.functions,
592 Totals::new(1, 1, 0, CoveragePercentage::Value(100.0))
593 );
594
595 assert_eq!(
596 summary.branches,
597 Totals::new(2, 2, 0, CoveragePercentage::Value(100.0))
598 );
599
600 assert_eq!(first.s.get(&0), Some(&1));
601 assert_eq!(first.s.get(&1), Some(&1));
602 assert_eq!(first.f.get(&0), Some(&2));
603 assert_eq!(first.b.get(&0).unwrap()[0], 1);
604 assert_eq!(first.b.get(&0).unwrap()[1], 2);
605 }
606
607 #[test]
608 fn should_drop_data_while_merge() {
609 let base = FileCoverage {
610 all: false,
611 path: "/path/to/file".to_string(),
612 statement_map: IndexMap::from([
613 (1, Range::new(1, 1, 1, 100)),
614 (2, Range::new(2, 1, 2, 50)),
615 (3, Range::new(2, 51, 2, 100)),
616 (4, Range::new(2, 101, 3, 100)),
617 ]),
618 fn_map: IndexMap::from([(
619 1,
620 Function {
621 name: "foobar".to_string(),
622 line: 1,
623 loc: Range::new(1, 1, 1, 50),
624 decl: Default::default(),
625 },
626 )]),
627 branch_map: IndexMap::from([(
628 1,
629 Branch::from_line(
630 BranchType::If,
631 2,
632 vec![Range::new(2, 1, 2, 20), Range::new(2, 50, 2, 100)],
633 ),
634 )]),
635 s: IndexMap::from([(1, 0), (2, 0), (3, 0), (4, 0)]),
636 f: IndexMap::from([(1, 0)]),
637 b: IndexMap::from([(1, vec![0, 0])]),
638 b_t: None,
639 input_source_map: None,
640 };
641
642 let create_coverage = |all: bool| {
643 let mut ret = base.clone();
644 if all {
645 ret.all = true;
646 } else {
647 ret.s.insert(1, 1);
648 ret.f.insert(1, 1);
649 ret.b.entry(1).and_modify(|v| v[0] = 1);
650 }
651
652 ret
653 };
654
655 let expected = create_coverage(false);
656
657 let mut cov = create_coverage(true);
658 cov.merge(&create_coverage(false));
659 assert_eq!(cov, expected);
660
661 let mut cov = create_coverage(false);
662 cov.merge(&create_coverage(true));
663 assert_eq!(cov, expected);
664 }
665
666 #[test]
667 fn merges_another_file_coverage_tracks_logical_truthiness() {
668 let base = FileCoverage {
669 all: false,
670 path: "/path/to/file".to_string(),
671 statement_map: IndexMap::from([
672 (0, Range::new(1, 1, 1, 100)),
673 (1, Range::new(2, 1, 2, 50)),
674 (2, Range::new(2, 51, 2, 100)),
675 (3, Range::new(2, 101, 3, 100)),
676 ]),
677 fn_map: IndexMap::from([(
678 0,
679 Function {
680 name: "foobar".to_string(),
681 line: 1,
682 loc: Range::new(1, 1, 1, 50),
683 decl: Default::default(),
684 },
685 )]),
686 branch_map: IndexMap::from([(
687 0,
688 Branch::from_line(
689 BranchType::If,
690 2,
691 vec![Range::new(2, 1, 2, 20), Range::new(2, 50, 2, 100)],
692 ),
693 )]),
694 s: IndexMap::from([(0, 0), (1, 0), (2, 0), (3, 0)]),
695 f: IndexMap::from([(0, 0)]),
696 b: IndexMap::from([(0, vec![0, 0])]),
697 b_t: None,
698 input_source_map: None,
699 };
700
701 let mut first = base.clone();
702 let mut second = base.clone();
703
704 first.s.insert(0, 1);
705 first.f.insert(0, 1);
706 first.b.entry(0).and_modify(|v| v[0] = 1);
707 first.b_t = Some(IndexMap::from([(0, vec![1])]));
708
709 second.s.insert(1, 1);
710 second.f.insert(0, 1);
711 second.b.entry(0).and_modify(|v| v[1] = 2);
712 second.b_t = Some(IndexMap::from([(0, vec![0, 2])]));
713
714 let summary = first.to_summary();
715
716 assert_eq!(
717 summary.statements,
718 Totals::new(4, 1, 0, CoveragePercentage::Value(25.0))
719 );
720 assert_eq!(
721 summary.lines,
722 Totals::new(2, 1, 0, CoveragePercentage::Value(50.0))
723 );
724 assert_eq!(
725 summary.functions,
726 Totals::new(1, 1, 0, CoveragePercentage::Value(100.0))
727 );
728
729 assert_eq!(
730 summary.branches,
731 Totals::new(2, 1, 0, CoveragePercentage::Value(50.0))
732 );
733
734 first.merge(&second);
735 let summary = first.to_summary();
736
737 assert_eq!(
738 summary.statements,
739 Totals::new(4, 2, 0, CoveragePercentage::Value(50.0))
740 );
741 assert_eq!(
742 summary.lines,
743 Totals::new(2, 2, 0, CoveragePercentage::Value(100.0))
744 );
745 assert_eq!(
746 summary.functions,
747 Totals::new(1, 1, 0, CoveragePercentage::Value(100.0))
748 );
749
750 assert_eq!(
751 summary.branches,
752 Totals::new(2, 2, 0, CoveragePercentage::Value(100.0))
753 );
754
755 assert_eq!(first.s.get(&0), Some(&1));
756 assert_eq!(first.s.get(&1), Some(&1));
757 assert_eq!(first.f.get(&0), Some(&2));
758 assert_eq!(first.b.get(&0).unwrap()[0], 1);
759 assert_eq!(first.b.get(&0).unwrap()[1], 2);
760 let b_t = first.b_t.unwrap();
761 assert_eq!(b_t.get(&0).unwrap()[0], 1);
762 assert_eq!(b_t.get(&0).unwrap()[1], 2);
763 }
764
765 #[test]
766 fn should_reset_hits() {
767 let base = FileCoverage {
768 all: false,
769 path: "/path/to/file".to_string(),
770 statement_map: IndexMap::from([
771 (1, Range::new(1, 1, 1, 100)),
772 (2, Range::new(2, 1, 2, 50)),
773 (3, Range::new(2, 51, 2, 100)),
774 (4, Range::new(2, 101, 3, 100)),
775 ]),
776 fn_map: IndexMap::from([(
777 1,
778 Function {
779 name: "foobar".to_string(),
780 line: 1,
781 loc: Range::new(1, 1, 1, 50),
782 decl: Default::default(),
783 },
784 )]),
785 branch_map: IndexMap::from([(
786 1,
787 Branch::from_line(
788 BranchType::If,
789 2,
790 vec![Range::new(2, 1, 2, 20), Range::new(2, 50, 2, 100)],
791 ),
792 )]),
793 s: IndexMap::from([(1, 2), (2, 3), (3, 1), (4, 0)]),
794 f: IndexMap::from([(1, 54)]),
795 b: IndexMap::from([(1, vec![1, 50])]),
796 b_t: Some(IndexMap::from([(1, vec![1, 50])])),
797 input_source_map: None,
798 };
799
800 let mut value = base.clone();
801 value.reset_hits();
802
803 assert_eq!(IndexMap::from([(1, 0), (2, 0), (3, 0), (4, 0)]), value.s);
804 assert_eq!(IndexMap::from([(1, 0)]), value.f);
805 assert_eq!(IndexMap::from([(1, vec![0, 0])]), value.b);
806 assert_eq!(Some(IndexMap::from([(1, vec![0, 0])])), value.b_t);
807 }
808
809 #[test]
810 fn should_return_uncovered_lines() {
811 let base = FileCoverage {
812 all: false,
813 path: "/path/to/file".to_string(),
814 statement_map: IndexMap::from([
815 (1, Range::new(1, 1, 1, 100)),
816 (2, Range::new(1, 101, 1, 200)),
817 (3, Range::new(2, 1, 2, 100)),
818 ]),
819 fn_map: Default::default(),
820 branch_map: Default::default(),
821 s: IndexMap::from([(1, 0), (2, 1), (3, 0)]),
822 f: Default::default(),
823 b: Default::default(),
824 b_t: None,
825 input_source_map: None,
826 };
827
828 assert_eq!(base.get_uncovered_lines(), vec![2]);
829 }
830
831 #[test]
832 fn should_return_branch_coverage_by_line() {
833 let base = FileCoverage {
834 all: false,
835 path: "/path/to/file".to_string(),
836 statement_map: Default::default(),
837 fn_map: Default::default(),
838 branch_map: IndexMap::from([
839 (1, Branch::from_line(BranchType::If, 1, Default::default())),
840 (2, Branch::from_line(BranchType::If, 2, Default::default())),
841 ]),
842 s: Default::default(),
843 f: Default::default(),
844 b: IndexMap::from([(1, vec![1, 0]), (2, vec![0, 0, 0, 1])]),
845 b_t: None,
846 input_source_map: None,
847 };
848
849 let coverage = base.get_branch_coverage_by_line();
850 assert_eq!(
851 coverage,
852 IndexMap::from([
853 (1, Coverage::new(1, 2, 50.0)),
854 (2, Coverage::new(1, 4, 25.0)),
855 ])
856 );
857 }
858
859 #[test]
860 fn should_return_branch_coverage_by_line_with_cobertura_branchmap_structure() {
861 let base = FileCoverage {
862 all: false,
863 path: "/path/to/file".to_string(),
864 statement_map: Default::default(),
865 fn_map: Default::default(),
866 branch_map: IndexMap::from([
867 (
868 1,
869 Branch::from_loc(BranchType::If, Range::new(1, 1, 1, 100), Default::default()),
870 ),
871 (
872 2,
873 Branch::from_loc(
874 BranchType::If,
875 Range::new(2, 50, 2, 100),
876 Default::default(),
877 ),
878 ),
879 ]),
880 s: Default::default(),
881 f: Default::default(),
882 b: IndexMap::from([(1, vec![1, 0]), (2, vec![0, 0, 0, 1])]),
883 b_t: None,
884 input_source_map: None,
885 };
886
887 let coverage = base.get_branch_coverage_by_line();
888 assert_eq!(
889 coverage,
890 IndexMap::from([
891 (1, Coverage::new(1, 2, 50.0)),
892 (2, Coverage::new(1, 4, 25.0)),
893 ])
894 )
895 }
896
897 #[test]
898 fn should_allow_file_coverage_to_be_init_with_logical_truthiness() {
899 assert_eq!(
900 FileCoverage::from_file_path("".to_string(), false).b_t,
901 None
902 );
903 assert_eq!(
904 FileCoverage::from_file_path("".to_string(), true).b_t,
905 Some(Default::default())
906 );
907 }
908}