1 //! This crate hosts a selection of "unit tests" for components of the `InstrumentCoverage` MIR
5 //! ./x.py test --keep-stage 1 compiler/rustc_mir --test-args '--show-output coverage'
8 //! The tests construct a few "mock" objects, as needed, to support the `InstrumentCoverage`
9 //! functions and algorithms. Mocked objects include instances of `mir::Body`; including
10 //! `Terminator`s of various `kind`s, and `Span` objects. Some functions used by or used on
11 //! real, runtime versions of these mocked-up objects have constraints (such as cross-thread
12 //! limitations) and deep dependencies on other elements of the full Rust compiler (which is
13 //! *not* constructed or mocked for these tests).
15 //! Of particular note, attempting to simply print elements of the `mir::Body` with default
16 //! `Debug` formatting can fail because some `Debug` format implementations require the
17 //! `TyCtxt`, obtained via a static global variable that is *not* set for these tests.
18 //! Initializing the global type context is prohibitively complex for the scope and scale of these
19 //! tests (essentially requiring initializing the entire compiler).
21 //! Also note, some basic features of `Span` also rely on the `Span`s own "session globals", which
22 //! are unrelated to the `TyCtxt` global. Without initializing the `Span` session globals, some
23 //! basic, coverage-specific features would be impossible to test, but thankfully initializing these
24 //! globals is comparatively simpler. The easiest way is to wrap the test in a closure argument
25 //! to: `rustc_span::create_default_session_globals_then(|| { test_here(); })`.
32 use coverage_test_macros::let_bcb;
34 use itertools::Itertools;
35 use rustc_data_structures::graph::WithNumNodes;
36 use rustc_data_structures::graph::WithSuccessors;
37 use rustc_index::vec::{Idx, IndexVec};
38 use rustc_middle::mir::coverage::CoverageKind;
39 use rustc_middle::mir::*;
40 use rustc_middle::ty::{self, DebruijnIndex, TyS, TypeFlags};
41 use rustc_span::{self, BytePos, Pos, Span, DUMMY_SP};
43 // All `TEMP_BLOCK` targets should be replaced before calling `to_body() -> mir::Body`.
44 const TEMP_BLOCK: BasicBlock = BasicBlock::MAX;
46 fn dummy_ty() -> &'static TyS<'static> {
48 static DUMMY_TYS: &'static TyS<'static> = Box::leak(Box::new(TyS::make_for_test(
51 DebruijnIndex::from_usize(0),
55 &DUMMY_TYS.with(|tys| *tys)
58 struct MockBlocks<'tcx> {
59 blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
60 dummy_place: Place<'tcx>,
64 impl<'tcx> MockBlocks<'tcx> {
67 blocks: IndexVec::new(),
68 dummy_place: Place { local: RETURN_PLACE, projection: ty::List::empty() },
73 fn new_temp(&mut self) -> Local {
74 let index = self.next_local;
79 fn push(&mut self, kind: TerminatorKind<'tcx>) -> BasicBlock {
80 let next_lo = if let Some(last) = self.blocks.last() {
81 self.blocks[last].terminator().source_info.span.hi()
85 let next_hi = next_lo + BytePos(1);
86 self.blocks.push(BasicBlockData {
88 terminator: Some(Terminator {
89 source_info: SourceInfo::outermost(Span::with_root_ctxt(next_lo, next_hi)),
96 fn link(&mut self, from_block: BasicBlock, to_block: BasicBlock) {
97 match self.blocks[from_block].terminator_mut().kind {
98 TerminatorKind::Assert { ref mut target, .. }
99 | TerminatorKind::Call { destination: Some((_, ref mut target)), .. }
100 | TerminatorKind::Drop { ref mut target, .. }
101 | TerminatorKind::DropAndReplace { ref mut target, .. }
102 | TerminatorKind::FalseEdge { real_target: ref mut target, .. }
103 | TerminatorKind::FalseUnwind { real_target: ref mut target, .. }
104 | TerminatorKind::Goto { ref mut target }
105 | TerminatorKind::InlineAsm { destination: Some(ref mut target), .. }
106 | TerminatorKind::Yield { resume: ref mut target, .. } => *target = to_block,
107 ref invalid => bug!("Invalid from_block: {:?}", invalid),
113 some_from_block: Option<BasicBlock>,
114 to_kind: TerminatorKind<'tcx>,
116 let new_block = self.push(to_kind);
117 if let Some(from_block) = some_from_block {
118 self.link(from_block, new_block);
123 fn set_branch(&mut self, switchint: BasicBlock, branch_index: usize, to_block: BasicBlock) {
124 match self.blocks[switchint].terminator_mut().kind {
125 TerminatorKind::SwitchInt { ref mut targets, .. } => {
126 let mut branches = targets.iter().collect::<Vec<_>>();
127 let otherwise = if branch_index == branches.len() {
130 let old_otherwise = targets.otherwise();
131 if branch_index > branches.len() {
132 branches.push((branches.len() as u128, old_otherwise));
133 while branches.len() < branch_index {
134 branches.push((branches.len() as u128, TEMP_BLOCK));
138 branches[branch_index] = (branch_index as u128, to_block);
142 *targets = SwitchTargets::new(branches.into_iter(), otherwise);
144 ref invalid => bug!("Invalid BasicBlock kind or no to_block: {:?}", invalid),
148 fn call(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
151 TerminatorKind::Call {
152 func: Operand::Copy(self.dummy_place.clone()),
154 destination: Some((self.dummy_place.clone(), TEMP_BLOCK)),
156 from_hir_call: false,
162 fn goto(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
163 self.add_block_from(some_from_block, TerminatorKind::Goto { target: TEMP_BLOCK })
166 fn switchint(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
167 let switchint_kind = TerminatorKind::SwitchInt {
168 discr: Operand::Move(Place::from(self.new_temp())),
169 switch_ty: dummy_ty(),
170 targets: SwitchTargets::static_if(0, TEMP_BLOCK, TEMP_BLOCK),
172 self.add_block_from(some_from_block, switchint_kind)
175 fn return_(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
176 self.add_block_from(some_from_block, TerminatorKind::Return)
179 fn to_body(self) -> Body<'tcx> {
180 Body::new_cfg_only(self.blocks)
184 fn debug_basic_blocks<'tcx>(mir_body: &Body<'tcx>) -> String {
191 let term = &data.terminator();
192 let kind = &term.kind;
193 let span = term.source_info.span;
194 let sp = format!("(span:{},{})", span.lo().to_u32(), span.hi().to_u32());
196 TerminatorKind::Assert { target, .. }
197 | TerminatorKind::Call { destination: Some((_, target)), .. }
198 | TerminatorKind::Drop { target, .. }
199 | TerminatorKind::DropAndReplace { target, .. }
200 | TerminatorKind::FalseEdge { real_target: target, .. }
201 | TerminatorKind::FalseUnwind { real_target: target, .. }
202 | TerminatorKind::Goto { target }
203 | TerminatorKind::InlineAsm { destination: Some(target), .. }
204 | TerminatorKind::Yield { resume: target, .. } => {
205 format!("{}{:?}:{} -> {:?}", sp, bb, debug::term_type(kind), target)
207 TerminatorKind::SwitchInt { targets, .. } => {
208 format!("{}{:?}:{} -> {:?}", sp, bb, debug::term_type(kind), targets)
210 _ => format!("{}{:?}:{}", sp, bb, debug::term_type(kind)),
217 static PRINT_GRAPHS: bool = false;
219 fn print_mir_graphviz(name: &str, mir_body: &Body<'_>) {
222 "digraph {} {{\n{}\n}}",
229 " {:?} [label=\"{:?}: {}\"];\n{}",
232 debug::term_type(&data.terminator().kind),
235 .map(|successor| { format!(" {:?} -> {:?};", bb, successor) })
244 fn print_coverage_graphviz(
247 basic_coverage_blocks: &graph::CoverageGraph,
251 "digraph {} {{\n{}\n}}",
253 basic_coverage_blocks
255 .map(|(bcb, bcb_data)| {
257 " {:?} [label=\"{:?}: {}\"];\n{}",
260 debug::term_type(&bcb_data.terminator(mir_body).kind),
261 basic_coverage_blocks
263 .map(|successor| { format!(" {:?} -> {:?};", bcb, successor) })
272 /// Create a mock `Body` with a simple flow.
273 fn goto_switchint<'a>() -> Body<'a> {
274 let mut blocks = MockBlocks::new();
275 let start = blocks.call(None);
276 let goto = blocks.goto(Some(start));
277 let switchint = blocks.switchint(Some(goto));
278 let then_call = blocks.call(None);
279 let else_call = blocks.call(None);
280 blocks.set_branch(switchint, 0, then_call);
281 blocks.set_branch(switchint, 1, else_call);
282 blocks.return_(Some(then_call));
283 blocks.return_(Some(else_call));
285 let mir_body = blocks.to_body();
286 print_mir_graphviz("mir_goto_switchint", &mir_body);
287 /* Graphviz character plots created using: `graph-easy --as=boxart`:
300 ┌─────────────┐ ┌────────────────┐
301 │ bb4: Call │ ◀── │ bb2: SwitchInt │
302 └─────────────┘ └────────────────┘
306 ┌─────────────┐ ┌────────────────┐
307 │ bb6: Return │ │ bb3: Call │
308 └─────────────┘ └────────────────┘
319 macro_rules! assert_successors {
320 ($basic_coverage_blocks:ident, $i:ident, [$($successor:ident),*]) => {
321 let mut successors = $basic_coverage_blocks.successors[$i].clone();
322 successors.sort_unstable();
323 assert_eq!(successors, vec![$($successor),*]);
328 fn test_covgraph_goto_switchint() {
329 let mir_body = goto_switchint();
331 eprintln!("basic_blocks = {}", debug_basic_blocks(&mir_body));
333 let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
334 print_coverage_graphviz("covgraph_goto_switchint ", &mir_body, &basic_coverage_blocks);
336 ┌──────────────┐ ┌─────────────────┐
337 │ bcb2: Return │ ◀── │ bcb0: SwitchInt │
338 └──────────────┘ └─────────────────┘
347 basic_coverage_blocks.num_nodes(),
349 "basic_coverage_blocks: {:?}",
350 basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
357 assert_successors!(basic_coverage_blocks, bcb0, [bcb1, bcb2]);
358 assert_successors!(basic_coverage_blocks, bcb1, []);
359 assert_successors!(basic_coverage_blocks, bcb2, []);
362 /// Create a mock `Body` with a loop.
363 fn switchint_then_loop_else_return<'a>() -> Body<'a> {
364 let mut blocks = MockBlocks::new();
365 let start = blocks.call(None);
366 let switchint = blocks.switchint(Some(start));
367 let then_call = blocks.call(None);
368 blocks.set_branch(switchint, 0, then_call);
369 let backedge_goto = blocks.goto(Some(then_call));
370 blocks.link(backedge_goto, switchint);
371 let else_return = blocks.return_(None);
372 blocks.set_branch(switchint, 1, else_return);
374 let mir_body = blocks.to_body();
375 print_mir_graphviz("mir_switchint_then_loop_else_return", &mir_body);
383 ┌─────────────┐ ┌────────────────┐
384 │ bb4: Return │ ◀── │ bb1: SwitchInt │ ◀┐
385 └─────────────┘ └────────────────┘ │
403 fn test_covgraph_switchint_then_loop_else_return() {
404 let mir_body = switchint_then_loop_else_return();
405 let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
406 print_coverage_graphviz(
407 "covgraph_switchint_then_loop_else_return",
409 &basic_coverage_blocks,
418 ┌────────────┐ ┌─────────────────┐
419 │ bcb3: Goto │ ◀── │ bcb1: SwitchInt │ ◀┐
420 └────────────┘ └─────────────────┘ │
424 │ ┌─────────────────┐ │
426 │ └─────────────────┘ │
428 └─────────────────────────────────────┘
431 basic_coverage_blocks.num_nodes(),
433 "basic_coverage_blocks: {:?}",
434 basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
442 assert_successors!(basic_coverage_blocks, bcb0, [bcb1]);
443 assert_successors!(basic_coverage_blocks, bcb1, [bcb2, bcb3]);
444 assert_successors!(basic_coverage_blocks, bcb2, []);
445 assert_successors!(basic_coverage_blocks, bcb3, [bcb1]);
448 /// Create a mock `Body` with nested loops.
449 fn switchint_loop_then_inner_loop_else_break<'a>() -> Body<'a> {
450 let mut blocks = MockBlocks::new();
451 let start = blocks.call(None);
452 let switchint = blocks.switchint(Some(start));
453 let then_call = blocks.call(None);
454 blocks.set_branch(switchint, 0, then_call);
455 let else_return = blocks.return_(None);
456 blocks.set_branch(switchint, 1, else_return);
458 let inner_start = blocks.call(Some(then_call));
459 let inner_switchint = blocks.switchint(Some(inner_start));
460 let inner_then_call = blocks.call(None);
461 blocks.set_branch(inner_switchint, 0, inner_then_call);
462 let inner_backedge_goto = blocks.goto(Some(inner_then_call));
463 blocks.link(inner_backedge_goto, inner_switchint);
464 let inner_else_break_goto = blocks.goto(None);
465 blocks.set_branch(inner_switchint, 1, inner_else_break_goto);
467 let backedge_goto = blocks.goto(Some(inner_else_break_goto));
468 blocks.link(backedge_goto, switchint);
470 let mir_body = blocks.to_body();
471 print_mir_graphviz("mir_switchint_loop_then_inner_loop_else_break", &mir_body);
479 ┌─────────────┐ ┌────────────────┐
480 │ bb3: Return │ ◀── │ bb1: SwitchInt │ ◀─────┐
481 └─────────────┘ └────────────────┘ │
497 ┌─────────────┐ ┌────────────────┐ │
498 │ bb8: Goto │ ◀── │ bb5: SwitchInt │ ◀┐ │
499 └─────────────┘ └────────────────┘ │ │
503 ┌─────────────┐ ┌────────────────┐ │ │
504 │ bb9: Goto │ ─┐ │ bb6: Call │ │ │
505 └─────────────┘ │ └────────────────┘ │ │
509 │ ┌────────────────┐ │ │
511 │ └────────────────┘ │
513 └───────────────────────────┘
519 fn test_covgraph_switchint_loop_then_inner_loop_else_break() {
520 let mir_body = switchint_loop_then_inner_loop_else_break();
521 let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
522 print_coverage_graphviz(
523 "covgraph_switchint_loop_then_inner_loop_else_break",
525 &basic_coverage_blocks,
534 ┌──────────────┐ ┌─────────────────┐
535 │ bcb2: Return │ ◀── │ bcb1: SwitchInt │ ◀┐
536 └──────────────┘ └─────────────────┘ │
540 ┌─────────────────┐ │
542 └─────────────────┘ │
546 ┌──────────────┐ ┌─────────────────┐ │
547 │ bcb6: Goto │ ◀── │ bcb4: SwitchInt │ ◀┼────┐
548 └──────────────┘ └─────────────────┘ │ │
552 │ ┌─────────────────┐ │ │
553 │ │ bcb5: Goto │ ─┘ │
554 │ └─────────────────┘ │
556 └────────────────────────────────────────────┘
559 basic_coverage_blocks.num_nodes(),
561 "basic_coverage_blocks: {:?}",
562 basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
573 assert_successors!(basic_coverage_blocks, bcb0, [bcb1]);
574 assert_successors!(basic_coverage_blocks, bcb1, [bcb2, bcb3]);
575 assert_successors!(basic_coverage_blocks, bcb2, []);
576 assert_successors!(basic_coverage_blocks, bcb3, [bcb4]);
577 assert_successors!(basic_coverage_blocks, bcb4, [bcb5, bcb6]);
578 assert_successors!(basic_coverage_blocks, bcb5, [bcb1]);
579 assert_successors!(basic_coverage_blocks, bcb6, [bcb4]);
583 fn test_find_loop_backedges_none() {
584 let mir_body = goto_switchint();
585 let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
588 "basic_coverage_blocks = {:?}",
589 basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
591 eprintln!("successors = {:?}", basic_coverage_blocks.successors);
593 let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
595 backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
603 fn test_find_loop_backedges_one() {
604 let mir_body = switchint_then_loop_else_return();
605 let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
606 let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
608 backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
617 assert_eq!(backedges[bcb1], vec![bcb3]);
621 fn test_find_loop_backedges_two() {
622 let mir_body = switchint_loop_then_inner_loop_else_break();
623 let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
624 let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
626 backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
637 assert_eq!(backedges[bcb1], vec![bcb5]);
638 assert_eq!(backedges[bcb4], vec![bcb6]);
642 fn test_traverse_coverage_with_loops() {
643 let mir_body = switchint_loop_then_inner_loop_else_break();
644 let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
645 let mut traversed_in_order = Vec::new();
646 let mut traversal = graph::TraverseCoverageGraphWithLoops::new(&basic_coverage_blocks);
647 while let Some(bcb) = traversal.next(&basic_coverage_blocks) {
648 traversed_in_order.push(bcb);
653 // bcb0 is visited first. Then bcb1 starts the first loop, and all remaining nodes, *except*
654 // bcb6 are inside the first loop.
656 *traversed_in_order.last().expect("should have elements"),
658 "bcb6 should not be visited until all nodes inside the first loop have been visited"
662 fn synthesize_body_span_from_terminators(mir_body: &Body<'_>) -> Span {
663 let mut some_span: Option<Span> = None;
664 for (_, data) in mir_body.basic_blocks().iter_enumerated() {
665 let term_span = data.terminator().source_info.span;
666 if let Some(span) = some_span.as_mut() {
667 *span = span.to(term_span);
669 some_span = Some(term_span)
672 some_span.expect("body must have at least one BasicBlock")
676 fn test_make_bcb_counters() {
677 rustc_span::create_default_session_globals_then(|| {
678 let mir_body = goto_switchint();
679 let body_span = synthesize_body_span_from_terminators(&mir_body);
680 let mut basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
681 let mut coverage_spans = Vec::new();
682 for (bcb, data) in basic_coverage_blocks.iter_enumerated() {
683 if let Some(span) = spans::filtered_terminator_span(data.terminator(&mir_body)) {
684 coverage_spans.push(spans::CoverageSpan::for_terminator(
685 spans::function_source_span(span, body_span),
692 let mut coverage_counters = counters::CoverageCounters::new(0);
693 let intermediate_expressions = coverage_counters
694 .make_bcb_counters(&mut basic_coverage_blocks, &coverage_spans)
695 .expect("should be Ok");
696 assert_eq!(intermediate_expressions.len(), 0);
700 1, // coincidentally, bcb1 has a `Counter` with id = 1
701 match basic_coverage_blocks[bcb1].counter().expect("should have a counter") {
702 CoverageKind::Counter { id, .. } => id,
703 _ => panic!("expected a Counter"),
710 2, // coincidentally, bcb2 has a `Counter` with id = 2
711 match basic_coverage_blocks[bcb2].counter().expect("should have a counter") {
712 CoverageKind::Counter { id, .. } => id,
713 _ => panic!("expected a Counter"),