
This avoids the awkwardness of having to create it in the pass's constructor, and then mutate it later to actually create the counters.
657 lines
28 KiB
Rust
657 lines
28 KiB
Rust
//! This crate hosts a selection of "unit tests" for components of the `InstrumentCoverage` MIR
|
|
//! pass.
|
|
//!
|
|
//! ```shell
|
|
//! ./x.py test --keep-stage 1 compiler/rustc_mir --test-args '--show-output coverage'
|
|
//! ```
|
|
//!
|
|
//! The tests construct a few "mock" objects, as needed, to support the `InstrumentCoverage`
|
|
//! functions and algorithms. Mocked objects include instances of `mir::Body`; including
|
|
//! `Terminator`s of various `kind`s, and `Span` objects. Some functions used by or used on
|
|
//! real, runtime versions of these mocked-up objects have constraints (such as cross-thread
|
|
//! limitations) and deep dependencies on other elements of the full Rust compiler (which is
|
|
//! *not* constructed or mocked for these tests).
|
|
//!
|
|
//! Of particular note, attempting to simply print elements of the `mir::Body` with default
|
|
//! `Debug` formatting can fail because some `Debug` format implementations require the
|
|
//! `TyCtxt`, obtained via a static global variable that is *not* set for these tests.
|
|
//! Initializing the global type context is prohibitively complex for the scope and scale of these
|
|
//! tests (essentially requiring initializing the entire compiler).
|
|
//!
|
|
//! Also note, some basic features of `Span` also rely on the `Span`s own "session globals", which
|
|
//! are unrelated to the `TyCtxt` global. Without initializing the `Span` session globals, some
|
|
//! basic, coverage-specific features would be impossible to test, but thankfully initializing these
|
|
//! globals is comparatively simpler. The easiest way is to wrap the test in a closure argument
|
|
//! to: `rustc_span::create_default_session_globals_then(|| { test_here(); })`.
|
|
|
|
use super::counters;
|
|
use super::graph::{self, BasicCoverageBlock};
|
|
|
|
use itertools::Itertools;
|
|
use rustc_data_structures::graph::WithNumNodes;
|
|
use rustc_data_structures::graph::WithSuccessors;
|
|
use rustc_index::{Idx, IndexVec};
|
|
use rustc_middle::mir::*;
|
|
use rustc_middle::ty;
|
|
use rustc_span::{BytePos, Pos, Span, DUMMY_SP};
|
|
|
|
fn bcb(index: u32) -> BasicCoverageBlock {
|
|
BasicCoverageBlock::from_u32(index)
|
|
}
|
|
|
|
// All `TEMP_BLOCK` targets should be replaced before calling `to_body() -> mir::Body`.
|
|
const TEMP_BLOCK: BasicBlock = BasicBlock::MAX;
|
|
|
|
struct MockBlocks<'tcx> {
|
|
blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
|
|
dummy_place: Place<'tcx>,
|
|
next_local: usize,
|
|
}
|
|
|
|
impl<'tcx> MockBlocks<'tcx> {
|
|
fn new() -> Self {
|
|
Self {
|
|
blocks: IndexVec::new(),
|
|
dummy_place: Place { local: RETURN_PLACE, projection: ty::List::empty() },
|
|
next_local: 0,
|
|
}
|
|
}
|
|
|
|
fn new_temp(&mut self) -> Local {
|
|
let index = self.next_local;
|
|
self.next_local += 1;
|
|
Local::new(index)
|
|
}
|
|
|
|
fn push(&mut self, kind: TerminatorKind<'tcx>) -> BasicBlock {
|
|
let next_lo = if let Some(last) = self.blocks.last_index() {
|
|
self.blocks[last].terminator().source_info.span.hi()
|
|
} else {
|
|
BytePos(1)
|
|
};
|
|
let next_hi = next_lo + BytePos(1);
|
|
self.blocks.push(BasicBlockData {
|
|
statements: vec![],
|
|
terminator: Some(Terminator {
|
|
source_info: SourceInfo::outermost(Span::with_root_ctxt(next_lo, next_hi)),
|
|
kind,
|
|
}),
|
|
is_cleanup: false,
|
|
})
|
|
}
|
|
|
|
fn link(&mut self, from_block: BasicBlock, to_block: BasicBlock) {
|
|
match self.blocks[from_block].terminator_mut().kind {
|
|
TerminatorKind::Assert { ref mut target, .. }
|
|
| TerminatorKind::Call { target: Some(ref mut target), .. }
|
|
| TerminatorKind::Drop { ref mut target, .. }
|
|
| TerminatorKind::FalseEdge { real_target: ref mut target, .. }
|
|
| TerminatorKind::FalseUnwind { real_target: ref mut target, .. }
|
|
| TerminatorKind::Goto { ref mut target }
|
|
| TerminatorKind::InlineAsm { destination: Some(ref mut target), .. }
|
|
| TerminatorKind::Yield { resume: ref mut target, .. } => *target = to_block,
|
|
ref invalid => bug!("Invalid from_block: {:?}", invalid),
|
|
}
|
|
}
|
|
|
|
fn add_block_from(
|
|
&mut self,
|
|
some_from_block: Option<BasicBlock>,
|
|
to_kind: TerminatorKind<'tcx>,
|
|
) -> BasicBlock {
|
|
let new_block = self.push(to_kind);
|
|
if let Some(from_block) = some_from_block {
|
|
self.link(from_block, new_block);
|
|
}
|
|
new_block
|
|
}
|
|
|
|
fn set_branch(&mut self, switchint: BasicBlock, branch_index: usize, to_block: BasicBlock) {
|
|
match self.blocks[switchint].terminator_mut().kind {
|
|
TerminatorKind::SwitchInt { ref mut targets, .. } => {
|
|
let mut branches = targets.iter().collect::<Vec<_>>();
|
|
let otherwise = if branch_index == branches.len() {
|
|
to_block
|
|
} else {
|
|
let old_otherwise = targets.otherwise();
|
|
if branch_index > branches.len() {
|
|
branches.push((branches.len() as u128, old_otherwise));
|
|
while branches.len() < branch_index {
|
|
branches.push((branches.len() as u128, TEMP_BLOCK));
|
|
}
|
|
to_block
|
|
} else {
|
|
branches[branch_index] = (branch_index as u128, to_block);
|
|
old_otherwise
|
|
}
|
|
};
|
|
*targets = SwitchTargets::new(branches.into_iter(), otherwise);
|
|
}
|
|
ref invalid => bug!("Invalid BasicBlock kind or no to_block: {:?}", invalid),
|
|
}
|
|
}
|
|
|
|
fn call(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
|
|
self.add_block_from(
|
|
some_from_block,
|
|
TerminatorKind::Call {
|
|
func: Operand::Copy(self.dummy_place.clone()),
|
|
args: vec![],
|
|
destination: self.dummy_place.clone(),
|
|
target: Some(TEMP_BLOCK),
|
|
unwind: UnwindAction::Continue,
|
|
call_source: CallSource::Misc,
|
|
fn_span: DUMMY_SP,
|
|
},
|
|
)
|
|
}
|
|
|
|
fn goto(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
|
|
self.add_block_from(some_from_block, TerminatorKind::Goto { target: TEMP_BLOCK })
|
|
}
|
|
|
|
fn switchint(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
|
|
let switchint_kind = TerminatorKind::SwitchInt {
|
|
discr: Operand::Move(Place::from(self.new_temp())),
|
|
targets: SwitchTargets::static_if(0, TEMP_BLOCK, TEMP_BLOCK),
|
|
};
|
|
self.add_block_from(some_from_block, switchint_kind)
|
|
}
|
|
|
|
fn return_(&mut self, some_from_block: Option<BasicBlock>) -> BasicBlock {
|
|
self.add_block_from(some_from_block, TerminatorKind::Return)
|
|
}
|
|
|
|
fn to_body(self) -> Body<'tcx> {
|
|
Body::new_cfg_only(self.blocks)
|
|
}
|
|
}
|
|
|
|
fn debug_basic_blocks(mir_body: &Body<'_>) -> String {
|
|
format!(
|
|
"{:?}",
|
|
mir_body
|
|
.basic_blocks
|
|
.iter_enumerated()
|
|
.map(|(bb, data)| {
|
|
let term = &data.terminator();
|
|
let kind = &term.kind;
|
|
let span = term.source_info.span;
|
|
let sp = format!("(span:{},{})", span.lo().to_u32(), span.hi().to_u32());
|
|
match kind {
|
|
TerminatorKind::Assert { target, .. }
|
|
| TerminatorKind::Call { target: Some(target), .. }
|
|
| TerminatorKind::Drop { target, .. }
|
|
| TerminatorKind::FalseEdge { real_target: target, .. }
|
|
| TerminatorKind::FalseUnwind { real_target: target, .. }
|
|
| TerminatorKind::Goto { target }
|
|
| TerminatorKind::InlineAsm { destination: Some(target), .. }
|
|
| TerminatorKind::Yield { resume: target, .. } => {
|
|
format!("{}{:?}:{} -> {:?}", sp, bb, kind.name(), target)
|
|
}
|
|
TerminatorKind::SwitchInt { targets, .. } => {
|
|
format!("{}{:?}:{} -> {:?}", sp, bb, kind.name(), targets)
|
|
}
|
|
_ => format!("{}{:?}:{}", sp, bb, kind.name()),
|
|
}
|
|
})
|
|
.collect::<Vec<_>>()
|
|
)
|
|
}
|
|
|
|
static PRINT_GRAPHS: bool = false;
|
|
|
|
fn print_mir_graphviz(name: &str, mir_body: &Body<'_>) {
|
|
if PRINT_GRAPHS {
|
|
println!(
|
|
"digraph {} {{\n{}\n}}",
|
|
name,
|
|
mir_body
|
|
.basic_blocks
|
|
.iter_enumerated()
|
|
.map(|(bb, data)| {
|
|
format!(
|
|
" {:?} [label=\"{:?}: {}\"];\n{}",
|
|
bb,
|
|
bb,
|
|
data.terminator().kind.name(),
|
|
mir_body
|
|
.basic_blocks
|
|
.successors(bb)
|
|
.map(|successor| { format!(" {:?} -> {:?};", bb, successor) })
|
|
.join("\n")
|
|
)
|
|
})
|
|
.join("\n")
|
|
);
|
|
}
|
|
}
|
|
|
|
fn print_coverage_graphviz(
|
|
name: &str,
|
|
mir_body: &Body<'_>,
|
|
basic_coverage_blocks: &graph::CoverageGraph,
|
|
) {
|
|
if PRINT_GRAPHS {
|
|
println!(
|
|
"digraph {} {{\n{}\n}}",
|
|
name,
|
|
basic_coverage_blocks
|
|
.iter_enumerated()
|
|
.map(|(bcb, bcb_data)| {
|
|
format!(
|
|
" {:?} [label=\"{:?}: {}\"];\n{}",
|
|
bcb,
|
|
bcb,
|
|
mir_body[bcb_data.last_bb()].terminator().kind.name(),
|
|
basic_coverage_blocks
|
|
.successors(bcb)
|
|
.map(|successor| { format!(" {:?} -> {:?};", bcb, successor) })
|
|
.join("\n")
|
|
)
|
|
})
|
|
.join("\n")
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Create a mock `Body` with a simple flow.
|
|
fn goto_switchint<'a>() -> Body<'a> {
|
|
let mut blocks = MockBlocks::new();
|
|
let start = blocks.call(None);
|
|
let goto = blocks.goto(Some(start));
|
|
let switchint = blocks.switchint(Some(goto));
|
|
let then_call = blocks.call(None);
|
|
let else_call = blocks.call(None);
|
|
blocks.set_branch(switchint, 0, then_call);
|
|
blocks.set_branch(switchint, 1, else_call);
|
|
blocks.return_(Some(then_call));
|
|
blocks.return_(Some(else_call));
|
|
|
|
let mir_body = blocks.to_body();
|
|
print_mir_graphviz("mir_goto_switchint", &mir_body);
|
|
/* Graphviz character plots created using: `graph-easy --as=boxart`:
|
|
┌────────────────┐
|
|
│ bb0: Call │
|
|
└────────────────┘
|
|
│
|
|
│
|
|
▼
|
|
┌────────────────┐
|
|
│ bb1: Goto │
|
|
└────────────────┘
|
|
│
|
|
│
|
|
▼
|
|
┌─────────────┐ ┌────────────────┐
|
|
│ bb4: Call │ ◀── │ bb2: SwitchInt │
|
|
└─────────────┘ └────────────────┘
|
|
│ │
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────┐ ┌────────────────┐
|
|
│ bb6: Return │ │ bb3: Call │
|
|
└─────────────┘ └────────────────┘
|
|
│
|
|
│
|
|
▼
|
|
┌────────────────┐
|
|
│ bb5: Return │
|
|
└────────────────┘
|
|
*/
|
|
mir_body
|
|
}
|
|
|
|
#[track_caller]
|
|
fn assert_successors(
|
|
basic_coverage_blocks: &graph::CoverageGraph,
|
|
bcb: BasicCoverageBlock,
|
|
expected_successors: &[BasicCoverageBlock],
|
|
) {
|
|
let mut successors = basic_coverage_blocks.successors[bcb].clone();
|
|
successors.sort_unstable();
|
|
assert_eq!(successors, expected_successors);
|
|
}
|
|
|
|
#[test]
|
|
fn test_covgraph_goto_switchint() {
|
|
let mir_body = goto_switchint();
|
|
if false {
|
|
eprintln!("basic_blocks = {}", debug_basic_blocks(&mir_body));
|
|
}
|
|
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
|
|
print_coverage_graphviz("covgraph_goto_switchint ", &mir_body, &basic_coverage_blocks);
|
|
/*
|
|
┌──────────────┐ ┌─────────────────┐
|
|
│ bcb2: Return │ ◀── │ bcb0: SwitchInt │
|
|
└──────────────┘ └─────────────────┘
|
|
│
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ bcb1: Return │
|
|
└─────────────────┘
|
|
*/
|
|
assert_eq!(
|
|
basic_coverage_blocks.num_nodes(),
|
|
3,
|
|
"basic_coverage_blocks: {:?}",
|
|
basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
|
|
);
|
|
|
|
assert_successors(&basic_coverage_blocks, bcb(0), &[bcb(1), bcb(2)]);
|
|
assert_successors(&basic_coverage_blocks, bcb(1), &[]);
|
|
assert_successors(&basic_coverage_blocks, bcb(2), &[]);
|
|
}
|
|
|
|
/// Create a mock `Body` with a loop.
|
|
fn switchint_then_loop_else_return<'a>() -> Body<'a> {
|
|
let mut blocks = MockBlocks::new();
|
|
let start = blocks.call(None);
|
|
let switchint = blocks.switchint(Some(start));
|
|
let then_call = blocks.call(None);
|
|
blocks.set_branch(switchint, 0, then_call);
|
|
let backedge_goto = blocks.goto(Some(then_call));
|
|
blocks.link(backedge_goto, switchint);
|
|
let else_return = blocks.return_(None);
|
|
blocks.set_branch(switchint, 1, else_return);
|
|
|
|
let mir_body = blocks.to_body();
|
|
print_mir_graphviz("mir_switchint_then_loop_else_return", &mir_body);
|
|
/*
|
|
┌────────────────┐
|
|
│ bb0: Call │
|
|
└────────────────┘
|
|
│
|
|
│
|
|
▼
|
|
┌─────────────┐ ┌────────────────┐
|
|
│ bb4: Return │ ◀── │ bb1: SwitchInt │ ◀┐
|
|
└─────────────┘ └────────────────┘ │
|
|
│ │
|
|
│ │
|
|
▼ │
|
|
┌────────────────┐ │
|
|
│ bb2: Call │ │
|
|
└────────────────┘ │
|
|
│ │
|
|
│ │
|
|
▼ │
|
|
┌────────────────┐ │
|
|
│ bb3: Goto │ ─┘
|
|
└────────────────┘
|
|
*/
|
|
mir_body
|
|
}
|
|
|
|
#[test]
|
|
fn test_covgraph_switchint_then_loop_else_return() {
|
|
let mir_body = switchint_then_loop_else_return();
|
|
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
|
|
print_coverage_graphviz(
|
|
"covgraph_switchint_then_loop_else_return",
|
|
&mir_body,
|
|
&basic_coverage_blocks,
|
|
);
|
|
/*
|
|
┌─────────────────┐
|
|
│ bcb0: Call │
|
|
└─────────────────┘
|
|
│
|
|
│
|
|
▼
|
|
┌────────────┐ ┌─────────────────┐
|
|
│ bcb3: Goto │ ◀── │ bcb1: SwitchInt │ ◀┐
|
|
└────────────┘ └─────────────────┘ │
|
|
│ │ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────┐ │
|
|
│ │ bcb2: Return │ │
|
|
│ └─────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────┘
|
|
*/
|
|
assert_eq!(
|
|
basic_coverage_blocks.num_nodes(),
|
|
4,
|
|
"basic_coverage_blocks: {:?}",
|
|
basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
|
|
);
|
|
|
|
assert_successors(&basic_coverage_blocks, bcb(0), &[bcb(1)]);
|
|
assert_successors(&basic_coverage_blocks, bcb(1), &[bcb(2), bcb(3)]);
|
|
assert_successors(&basic_coverage_blocks, bcb(2), &[]);
|
|
assert_successors(&basic_coverage_blocks, bcb(3), &[bcb(1)]);
|
|
}
|
|
|
|
/// Create a mock `Body` with nested loops.
|
|
fn switchint_loop_then_inner_loop_else_break<'a>() -> Body<'a> {
|
|
let mut blocks = MockBlocks::new();
|
|
let start = blocks.call(None);
|
|
let switchint = blocks.switchint(Some(start));
|
|
let then_call = blocks.call(None);
|
|
blocks.set_branch(switchint, 0, then_call);
|
|
let else_return = blocks.return_(None);
|
|
blocks.set_branch(switchint, 1, else_return);
|
|
|
|
let inner_start = blocks.call(Some(then_call));
|
|
let inner_switchint = blocks.switchint(Some(inner_start));
|
|
let inner_then_call = blocks.call(None);
|
|
blocks.set_branch(inner_switchint, 0, inner_then_call);
|
|
let inner_backedge_goto = blocks.goto(Some(inner_then_call));
|
|
blocks.link(inner_backedge_goto, inner_switchint);
|
|
let inner_else_break_goto = blocks.goto(None);
|
|
blocks.set_branch(inner_switchint, 1, inner_else_break_goto);
|
|
|
|
let backedge_goto = blocks.goto(Some(inner_else_break_goto));
|
|
blocks.link(backedge_goto, switchint);
|
|
|
|
let mir_body = blocks.to_body();
|
|
print_mir_graphviz("mir_switchint_loop_then_inner_loop_else_break", &mir_body);
|
|
/*
|
|
┌────────────────┐
|
|
│ bb0: Call │
|
|
└────────────────┘
|
|
│
|
|
│
|
|
▼
|
|
┌─────────────┐ ┌────────────────┐
|
|
│ bb3: Return │ ◀── │ bb1: SwitchInt │ ◀─────┐
|
|
└─────────────┘ └────────────────┘ │
|
|
│ │
|
|
│ │
|
|
▼ │
|
|
┌────────────────┐ │
|
|
│ bb2: Call │ │
|
|
└────────────────┘ │
|
|
│ │
|
|
│ │
|
|
▼ │
|
|
┌────────────────┐ │
|
|
│ bb4: Call │ │
|
|
└────────────────┘ │
|
|
│ │
|
|
│ │
|
|
▼ │
|
|
┌─────────────┐ ┌────────────────┐ │
|
|
│ bb8: Goto │ ◀── │ bb5: SwitchInt │ ◀┐ │
|
|
└─────────────┘ └────────────────┘ │ │
|
|
│ │ │ │
|
|
│ │ │ │
|
|
▼ ▼ │ │
|
|
┌─────────────┐ ┌────────────────┐ │ │
|
|
│ bb9: Goto │ ─┐ │ bb6: Call │ │ │
|
|
└─────────────┘ │ └────────────────┘ │ │
|
|
│ │ │ │
|
|
│ │ │ │
|
|
│ ▼ │ │
|
|
│ ┌────────────────┐ │ │
|
|
│ │ bb7: Goto │ ─┘ │
|
|
│ └────────────────┘ │
|
|
│ │
|
|
└───────────────────────────┘
|
|
*/
|
|
mir_body
|
|
}
|
|
|
|
#[test]
|
|
fn test_covgraph_switchint_loop_then_inner_loop_else_break() {
|
|
let mir_body = switchint_loop_then_inner_loop_else_break();
|
|
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
|
|
print_coverage_graphviz(
|
|
"covgraph_switchint_loop_then_inner_loop_else_break",
|
|
&mir_body,
|
|
&basic_coverage_blocks,
|
|
);
|
|
/*
|
|
┌─────────────────┐
|
|
│ bcb0: Call │
|
|
└─────────────────┘
|
|
│
|
|
│
|
|
▼
|
|
┌──────────────┐ ┌─────────────────┐
|
|
│ bcb2: Return │ ◀── │ bcb1: SwitchInt │ ◀┐
|
|
└──────────────┘ └─────────────────┘ │
|
|
│ │
|
|
│ │
|
|
▼ │
|
|
┌─────────────────┐ │
|
|
│ bcb3: Call │ │
|
|
└─────────────────┘ │
|
|
│ │
|
|
│ │
|
|
▼ │
|
|
┌──────────────┐ ┌─────────────────┐ │
|
|
│ bcb6: Goto │ ◀── │ bcb4: SwitchInt │ ◀┼────┐
|
|
└──────────────┘ └─────────────────┘ │ │
|
|
│ │ │ │
|
|
│ │ │ │
|
|
│ ▼ │ │
|
|
│ ┌─────────────────┐ │ │
|
|
│ │ bcb5: Goto │ ─┘ │
|
|
│ └─────────────────┘ │
|
|
│ │
|
|
└────────────────────────────────────────────┘
|
|
*/
|
|
assert_eq!(
|
|
basic_coverage_blocks.num_nodes(),
|
|
7,
|
|
"basic_coverage_blocks: {:?}",
|
|
basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
|
|
);
|
|
|
|
assert_successors(&basic_coverage_blocks, bcb(0), &[bcb(1)]);
|
|
assert_successors(&basic_coverage_blocks, bcb(1), &[bcb(2), bcb(3)]);
|
|
assert_successors(&basic_coverage_blocks, bcb(2), &[]);
|
|
assert_successors(&basic_coverage_blocks, bcb(3), &[bcb(4)]);
|
|
assert_successors(&basic_coverage_blocks, bcb(4), &[bcb(5), bcb(6)]);
|
|
assert_successors(&basic_coverage_blocks, bcb(5), &[bcb(1)]);
|
|
assert_successors(&basic_coverage_blocks, bcb(6), &[bcb(4)]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_loop_backedges_none() {
|
|
let mir_body = goto_switchint();
|
|
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
|
|
if false {
|
|
eprintln!(
|
|
"basic_coverage_blocks = {:?}",
|
|
basic_coverage_blocks.iter_enumerated().collect::<Vec<_>>()
|
|
);
|
|
eprintln!("successors = {:?}", basic_coverage_blocks.successors);
|
|
}
|
|
let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
|
|
assert_eq!(
|
|
backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
|
|
0,
|
|
"backedges: {:?}",
|
|
backedges
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_loop_backedges_one() {
|
|
let mir_body = switchint_then_loop_else_return();
|
|
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
|
|
let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
|
|
assert_eq!(
|
|
backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
|
|
1,
|
|
"backedges: {:?}",
|
|
backedges
|
|
);
|
|
|
|
assert_eq!(backedges[bcb(1)], &[bcb(3)]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_loop_backedges_two() {
|
|
let mir_body = switchint_loop_then_inner_loop_else_break();
|
|
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
|
|
let backedges = graph::find_loop_backedges(&basic_coverage_blocks);
|
|
assert_eq!(
|
|
backedges.iter_enumerated().map(|(_bcb, backedges)| backedges.len()).sum::<usize>(),
|
|
2,
|
|
"backedges: {:?}",
|
|
backedges
|
|
);
|
|
|
|
assert_eq!(backedges[bcb(1)], &[bcb(5)]);
|
|
assert_eq!(backedges[bcb(4)], &[bcb(6)]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_traverse_coverage_with_loops() {
|
|
let mir_body = switchint_loop_then_inner_loop_else_break();
|
|
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
|
|
let mut traversed_in_order = Vec::new();
|
|
let mut traversal = graph::TraverseCoverageGraphWithLoops::new(&basic_coverage_blocks);
|
|
while let Some(bcb) = traversal.next() {
|
|
traversed_in_order.push(bcb);
|
|
}
|
|
|
|
// bcb0 is visited first. Then bcb1 starts the first loop, and all remaining nodes, *except*
|
|
// bcb6 are inside the first loop.
|
|
assert_eq!(
|
|
*traversed_in_order.last().expect("should have elements"),
|
|
bcb(6),
|
|
"bcb6 should not be visited until all nodes inside the first loop have been visited"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_make_bcb_counters() {
|
|
rustc_span::create_default_session_globals_then(|| {
|
|
let mir_body = goto_switchint();
|
|
let basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
|
|
// Historically this test would use `spans` internals to set up fake
|
|
// coverage spans for BCBs 1 and 2. Now we skip that step and just tell
|
|
// BCB counter construction that those BCBs have spans.
|
|
let bcb_has_coverage_spans = |bcb: BasicCoverageBlock| (1..=2).contains(&bcb.as_usize());
|
|
let coverage_counters = counters::CoverageCounters::make_bcb_counters(
|
|
&basic_coverage_blocks,
|
|
bcb_has_coverage_spans,
|
|
);
|
|
assert_eq!(coverage_counters.num_expressions(), 0);
|
|
|
|
assert_eq!(
|
|
0, // bcb1 has a `Counter` with id = 0
|
|
match coverage_counters.bcb_counter(bcb(1)).expect("should have a counter") {
|
|
counters::BcbCounter::Counter { id, .. } => id,
|
|
_ => panic!("expected a Counter"),
|
|
}
|
|
.as_u32()
|
|
);
|
|
|
|
assert_eq!(
|
|
1, // bcb2 has a `Counter` with id = 1
|
|
match coverage_counters.bcb_counter(bcb(2)).expect("should have a counter") {
|
|
counters::BcbCounter::Counter { id, .. } => id,
|
|
_ => panic!("expected a Counter"),
|
|
}
|
|
.as_u32()
|
|
);
|
|
});
|
|
}
|