1
Fork 0

More improvements to macro coverage

This commit is contained in:
Rich Kadel 2021-04-26 16:27:54 -07:00
parent c26afb765c
commit f12795f8a0
7 changed files with 366 additions and 155 deletions

View file

@ -11,7 +11,7 @@ use rustc_middle::mir::{
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
use rustc_span::source_map::original_sp; use rustc_span::source_map::original_sp;
use rustc_span::{BytePos, ExpnKind, MacroKind, Span}; use rustc_span::{BytePos, ExpnKind, MacroKind, Span, Symbol};
use std::cmp::Ordering; use std::cmp::Ordering;
@ -67,7 +67,7 @@ impl CoverageStatement {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(super) struct CoverageSpan { pub(super) struct CoverageSpan {
pub span: Span, pub span: Span,
pub is_macro_expansion: bool, pub expn_span: Span,
pub bcb: BasicCoverageBlock, pub bcb: BasicCoverageBlock,
pub coverage_statements: Vec<CoverageStatement>, pub coverage_statements: Vec<CoverageStatement>,
pub is_closure: bool, pub is_closure: bool,
@ -75,12 +75,9 @@ pub(super) struct CoverageSpan {
impl CoverageSpan { impl CoverageSpan {
pub fn for_fn_sig(fn_sig_span: Span) -> Self { pub fn for_fn_sig(fn_sig_span: Span) -> Self {
// Whether the function signature is from a macro or not, it should not be treated like
// macro-expanded statements and terminators.
let is_macro_expansion = false;
Self { Self {
span: fn_sig_span, span: fn_sig_span,
is_macro_expansion, expn_span: fn_sig_span,
bcb: START_BCB, bcb: START_BCB,
coverage_statements: vec![], coverage_statements: vec![],
is_closure: false, is_closure: false,
@ -90,7 +87,7 @@ impl CoverageSpan {
pub fn for_statement( pub fn for_statement(
statement: &Statement<'tcx>, statement: &Statement<'tcx>,
span: Span, span: Span,
is_macro_expansion: bool, expn_span: Span,
bcb: BasicCoverageBlock, bcb: BasicCoverageBlock,
bb: BasicBlock, bb: BasicBlock,
stmt_index: usize, stmt_index: usize,
@ -105,7 +102,7 @@ impl CoverageSpan {
Self { Self {
span, span,
is_macro_expansion, expn_span,
bcb, bcb,
coverage_statements: vec![CoverageStatement::Statement(bb, span, stmt_index)], coverage_statements: vec![CoverageStatement::Statement(bb, span, stmt_index)],
is_closure, is_closure,
@ -114,13 +111,13 @@ impl CoverageSpan {
pub fn for_terminator( pub fn for_terminator(
span: Span, span: Span,
is_macro_expansion: bool, expn_span: Span,
bcb: BasicCoverageBlock, bcb: BasicCoverageBlock,
bb: BasicBlock, bb: BasicBlock,
) -> Self { ) -> Self {
Self { Self {
span, span,
is_macro_expansion, expn_span,
bcb, bcb,
coverage_statements: vec![CoverageStatement::Terminator(bb, span)], coverage_statements: vec![CoverageStatement::Terminator(bb, span)],
is_closure: false, is_closure: false,
@ -176,6 +173,34 @@ impl CoverageSpan {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n") .join("\n")
} }
/// If the span is part of a macro, and the macro is visible (expands directly to the given
/// body_span), returns the macro name symbol.
pub fn current_macro(&self) -> Option<Symbol> {
if let ExpnKind::Macro(MacroKind::Bang, current_macro) =
self.expn_span.ctxt().outer_expn_data().kind
{
return Some(current_macro);
}
None
}
/// If the span is part of a macro, and the macro is visible (expands directly to the given
/// body_span), returns the macro name symbol.
pub fn visible_macro(&self, body_span: Span) -> Option<Symbol> {
if let Some(current_macro) = self.current_macro() {
if self.expn_span.parent().unwrap_or_else(|| bug!("macro must have a parent")).ctxt()
== body_span.ctxt()
{
return Some(current_macro);
}
}
None
}
pub fn is_macro_expansion(&self) -> bool {
self.current_macro().is_some()
}
} }
/// Converts the initial set of `CoverageSpan`s (one per MIR `Statement` or `Terminator`) into a /// Converts the initial set of `CoverageSpan`s (one per MIR `Statement` or `Terminator`) into a
@ -219,6 +244,9 @@ pub struct CoverageSpans<'a, 'tcx> {
/// Assigned from `curr_original_span` from the previous iteration. /// Assigned from `curr_original_span` from the previous iteration.
prev_original_span: Span, prev_original_span: Span,
/// A copy of the expn_span from the prior iteration.
prev_expn_span: Option<Span>,
/// One or more `CoverageSpan`s with the same `Span` but different `BasicCoverageBlock`s, and /// One or more `CoverageSpan`s with the same `Span` but different `BasicCoverageBlock`s, and
/// no `BasicCoverageBlock` in this list dominates another `BasicCoverageBlock` in the list. /// no `BasicCoverageBlock` in this list dominates another `BasicCoverageBlock` in the list.
/// If a new `curr` span also fits this criteria (compared to an existing list of /// If a new `curr` span also fits this criteria (compared to an existing list of
@ -273,15 +301,13 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
curr_original_span: Span::with_root_ctxt(BytePos(0), BytePos(0)), curr_original_span: Span::with_root_ctxt(BytePos(0), BytePos(0)),
some_prev: None, some_prev: None,
prev_original_span: Span::with_root_ctxt(BytePos(0), BytePos(0)), prev_original_span: Span::with_root_ctxt(BytePos(0), BytePos(0)),
prev_expn_span: None,
pending_dups: Vec::new(), pending_dups: Vec::new(),
}; };
let sorted_spans = coverage_spans.mir_to_initial_sorted_coverage_spans(); let sorted_spans = coverage_spans.mir_to_initial_sorted_coverage_spans();
coverage_spans.sorted_spans_iter = Some(sorted_spans.into_iter()); coverage_spans.sorted_spans_iter = Some(sorted_spans.into_iter());
coverage_spans.some_prev = coverage_spans.sorted_spans_iter.as_mut().unwrap().next();
coverage_spans.prev_original_span =
coverage_spans.some_prev.as_ref().expect("at least one span").span;
coverage_spans.to_refined_spans() coverage_spans.to_refined_spans()
} }
@ -335,10 +361,14 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
/// de-duplicated `CoverageSpan`s. /// de-duplicated `CoverageSpan`s.
fn to_refined_spans(mut self) -> Vec<CoverageSpan> { fn to_refined_spans(mut self) -> Vec<CoverageSpan> {
while self.next_coverage_span() { while self.next_coverage_span() {
if self.curr().is_mergeable(self.prev()) { if self.some_prev.is_none() {
debug!(" initial span");
self.check_invoked_macro_name_span();
} else if self.curr().is_mergeable(self.prev()) {
debug!(" same bcb (and neither is a closure), merge with prev={:?}", self.prev()); debug!(" same bcb (and neither is a closure), merge with prev={:?}", self.prev());
let prev = self.take_prev(); let prev = self.take_prev();
self.curr_mut().merge_from(prev); self.curr_mut().merge_from(prev);
self.check_invoked_macro_name_span();
// Note that curr.span may now differ from curr_original_span // Note that curr.span may now differ from curr_original_span
} else if self.prev_ends_before_curr() { } else if self.prev_ends_before_curr() {
debug!( debug!(
@ -347,7 +377,8 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
self.prev() self.prev()
); );
let prev = self.take_prev(); let prev = self.take_prev();
self.refined_spans.push(prev); self.push_refined_span(prev);
self.check_invoked_macro_name_span();
} else if self.prev().is_closure { } else if self.prev().is_closure {
// drop any equal or overlapping span (`curr`) and keep `prev` to test again in the // drop any equal or overlapping span (`curr`) and keep `prev` to test again in the
// next iter // next iter
@ -362,7 +393,7 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
} else if self.prev_original_span == self.curr().span { } else if self.prev_original_span == self.curr().span {
// Note that this compares the new span to `prev_original_span`, which may not // Note that this compares the new span to `prev_original_span`, which may not
// be the full `prev.span` (if merged during the previous iteration). // be the full `prev.span` (if merged during the previous iteration).
if self.prev().is_macro_expansion && self.curr().is_macro_expansion { if self.prev().is_macro_expansion() && self.curr().is_macro_expansion() {
// Macros that expand to include branching (such as // Macros that expand to include branching (such as
// `assert_eq!()`, `assert_ne!()`, `info!()`, `debug!()`, or // `assert_eq!()`, `assert_ne!()`, `info!()`, `debug!()`, or
// `trace!()) typically generate callee spans with identical // `trace!()) typically generate callee spans with identical
@ -385,15 +416,16 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
} }
} else { } else {
self.cutoff_prev_at_overlapping_curr(); self.cutoff_prev_at_overlapping_curr();
self.check_invoked_macro_name_span();
} }
} }
debug!(" AT END, adding last prev={:?}", self.prev()); debug!(" AT END, adding last prev={:?}", self.prev());
let prev = self.take_prev(); let prev = self.take_prev();
let CoverageSpans { pending_dups, mut refined_spans, .. } = self; let pending_dups = self.pending_dups.split_off(0);
for dup in pending_dups { for dup in pending_dups {
debug!(" ...adding at least one pending dup={:?}", dup); debug!(" ...adding at least one pending dup={:?}", dup);
refined_spans.push(dup); self.push_refined_span(dup);
} }
// Async functions wrap a closure that implements the body to be executed. The enclosing // Async functions wrap a closure that implements the body to be executed. The enclosing
@ -403,21 +435,60 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
// excluded. The closure's `Return` is the only one that will be counted. This provides // excluded. The closure's `Return` is the only one that will be counted. This provides
// adequate coverage, and more intuitive counts. (Avoids double-counting the closing brace // adequate coverage, and more intuitive counts. (Avoids double-counting the closing brace
// of the function body.) // of the function body.)
let body_ends_with_closure = if let Some(last_covspan) = refined_spans.last() { let body_ends_with_closure = if let Some(last_covspan) = self.refined_spans.last() {
last_covspan.is_closure && last_covspan.span.hi() == self.body_span.hi() last_covspan.is_closure && last_covspan.span.hi() == self.body_span.hi()
} else { } else {
false false
}; };
if !body_ends_with_closure { if !body_ends_with_closure {
refined_spans.push(prev); self.push_refined_span(prev);
} }
// Remove `CoverageSpan`s derived from closures, originally added to ensure the coverage // Remove `CoverageSpan`s derived from closures, originally added to ensure the coverage
// regions for the current function leave room for the closure's own coverage regions // regions for the current function leave room for the closure's own coverage regions
// (injected separately, from the closure's own MIR). // (injected separately, from the closure's own MIR).
refined_spans.retain(|covspan| !covspan.is_closure); self.refined_spans.retain(|covspan| !covspan.is_closure);
refined_spans self.refined_spans
}
fn push_refined_span(&mut self, covspan: CoverageSpan) {
let len = self.refined_spans.len();
if len > 0 {
let last = &mut self.refined_spans[len - 1];
if last.is_mergeable(&covspan) {
debug!(
"merging new refined span with last refined span, last={:?}, covspan={:?}",
last, covspan
);
last.merge_from(covspan);
return;
}
}
self.refined_spans.push(covspan)
}
fn check_invoked_macro_name_span(&mut self) {
if let Some(visible_macro) = self.curr().visible_macro(self.body_span) {
if self.prev_expn_span.map_or(true, |prev_expn_span| {
self.curr().expn_span.ctxt() != prev_expn_span.ctxt()
}) {
let merged_prefix_len = self.curr_original_span.lo() - self.curr().span.lo();
let after_macro_bang = merged_prefix_len
+ BytePos(visible_macro.to_string().bytes().count() as u32 + 1);
let mut macro_name_cov = self.curr().clone();
self.curr_mut().span =
self.curr().span.with_lo(self.curr().span.lo() + after_macro_bang);
macro_name_cov.span =
macro_name_cov.span.with_hi(macro_name_cov.span.lo() + after_macro_bang);
debug!(
" and curr starts a new macro expansion, so add a new span just for \
the macro `{}!`, new span={:?}",
visible_macro, macro_name_cov
);
self.push_refined_span(macro_name_cov);
}
}
} }
// Generate a set of `CoverageSpan`s from the filtered set of `Statement`s and `Terminator`s of // Generate a set of `CoverageSpan`s from the filtered set of `Statement`s and `Terminator`s of
@ -440,22 +511,15 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
.enumerate() .enumerate()
.filter_map(move |(index, statement)| { .filter_map(move |(index, statement)| {
filtered_statement_span(statement, self.body_span).map( filtered_statement_span(statement, self.body_span).map(
|(span, is_macro_expansion)| { |(span, expn_span)| {
CoverageSpan::for_statement( CoverageSpan::for_statement(
statement, statement, span, expn_span, bcb, bb, index,
span,
is_macro_expansion,
bcb,
bb,
index,
) )
}, },
) )
}) })
.chain(filtered_terminator_span(data.terminator(), self.body_span).map( .chain(filtered_terminator_span(data.terminator(), self.body_span).map(
|(span, is_macro_expansion)| { |(span, expn_span)| CoverageSpan::for_terminator(span, expn_span, bcb, bb),
CoverageSpan::for_terminator(span, is_macro_expansion, bcb, bb)
},
)) ))
}) })
.collect() .collect()
@ -509,7 +573,7 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
let pending_dups = self.pending_dups.split_off(0); let pending_dups = self.pending_dups.split_off(0);
for dup in pending_dups.into_iter() { for dup in pending_dups.into_iter() {
debug!(" ...adding at least one pending={:?}", dup); debug!(" ...adding at least one pending={:?}", dup);
self.refined_spans.push(dup); self.push_refined_span(dup);
} }
} else { } else {
self.pending_dups.clear(); self.pending_dups.clear();
@ -521,12 +585,13 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
/// Advance `prev` to `curr` (if any), and `curr` to the next `CoverageSpan` in sorted order. /// Advance `prev` to `curr` (if any), and `curr` to the next `CoverageSpan` in sorted order.
fn next_coverage_span(&mut self) -> bool { fn next_coverage_span(&mut self) -> bool {
if let Some(curr) = self.some_curr.take() { if let Some(curr) = self.some_curr.take() {
self.prev_expn_span = Some(curr.expn_span);
self.some_prev = Some(curr); self.some_prev = Some(curr);
self.prev_original_span = self.curr_original_span; self.prev_original_span = self.curr_original_span;
} }
while let Some(curr) = self.sorted_spans_iter.as_mut().unwrap().next() { while let Some(curr) = self.sorted_spans_iter.as_mut().unwrap().next() {
debug!("FOR curr={:?}", curr); debug!("FOR curr={:?}", curr);
if self.prev_starts_after_next(&curr) { if self.some_prev.is_some() && self.prev_starts_after_next(&curr) {
debug!( debug!(
" prev.span starts after curr.span, so curr will be dropped (skipping past \ " prev.span starts after curr.span, so curr will be dropped (skipping past \
closure?); prev={:?}", closure?); prev={:?}",
@ -583,10 +648,10 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
for mut dup in pending_dups.iter().cloned() { for mut dup in pending_dups.iter().cloned() {
dup.span = dup.span.with_hi(left_cutoff); dup.span = dup.span.with_hi(left_cutoff);
debug!(" ...and at least one pre_closure dup={:?}", dup); debug!(" ...and at least one pre_closure dup={:?}", dup);
self.refined_spans.push(dup); self.push_refined_span(dup);
} }
} }
self.refined_spans.push(pre_closure); self.push_refined_span(pre_closure);
} }
if has_post_closure_span { if has_post_closure_span {
// Update prev.span to start after the closure (and discard curr) // Update prev.span to start after the closure (and discard curr)
@ -597,7 +662,7 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
} }
self.pending_dups.append(&mut pending_dups); self.pending_dups.append(&mut pending_dups);
let closure_covspan = self.take_curr(); let closure_covspan = self.take_curr();
self.refined_spans.push(closure_covspan); // since self.prev() was already updated self.push_refined_span(closure_covspan); // since self.prev() was already updated
} else { } else {
pending_dups.clear(); pending_dups.clear();
} }
@ -688,7 +753,7 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
} else { } else {
debug!(" ... adding modified prev={:?}", self.prev()); debug!(" ... adding modified prev={:?}", self.prev());
let prev = self.take_prev(); let prev = self.take_prev();
self.refined_spans.push(prev); self.push_refined_span(prev);
} }
} else { } else {
// with `pending_dups`, `prev` cannot have any statements that don't overlap // with `pending_dups`, `prev` cannot have any statements that don't overlap
@ -704,7 +769,7 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> {
pub(super) fn filtered_statement_span( pub(super) fn filtered_statement_span(
statement: &'a Statement<'tcx>, statement: &'a Statement<'tcx>,
body_span: Span, body_span: Span,
) -> Option<(Span, bool)> { ) -> Option<(Span, Span)> {
match statement.kind { match statement.kind {
// These statements have spans that are often outside the scope of the executed source code // These statements have spans that are often outside the scope of the executed source code
// for their parent `BasicBlock`. // for their parent `BasicBlock`.
@ -749,7 +814,7 @@ pub(super) fn filtered_statement_span(
pub(super) fn filtered_terminator_span( pub(super) fn filtered_terminator_span(
terminator: &'a Terminator<'tcx>, terminator: &'a Terminator<'tcx>,
body_span: Span, body_span: Span,
) -> Option<(Span, bool)> { ) -> Option<(Span, Span)> {
match terminator.kind { match terminator.kind {
// These terminators have spans that don't positively contribute to computing a reasonable // These terminators have spans that don't positively contribute to computing a reasonable
// span of actually executed source code. (For example, SwitchInt terminators extracted from // span of actually executed source code. (For example, SwitchInt terminators extracted from
@ -789,14 +854,10 @@ pub(super) fn filtered_terminator_span(
} }
} }
/// Returns the span within the function source body, and the given span, which will be different
/// if the given span is an expansion (macro, syntactic sugar, etc.).
#[inline] #[inline]
fn function_source_span(span: Span, body_span: Span) -> (Span, bool) { fn function_source_span(span: Span, body_span: Span) -> (Span, Span) {
let is_macro_expansion = span.ctxt() != body_span.ctxt() let original_span = original_sp(span, body_span).with_ctxt(body_span.ctxt());
&& if let ExpnKind::Macro(MacroKind::Bang, _) = span.ctxt().outer_expn_data().kind { (if body_span.contains(original_span) { original_span } else { body_span }, span)
true
} else {
false
};
let span = original_sp(span, body_span).with_ctxt(body_span.ctxt());
(if body_span.contains(span) { span } else { body_span }, is_macro_expansion)
} }

View file

@ -683,12 +683,12 @@ fn test_make_bcb_counters() {
let mut basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body); let mut basic_coverage_blocks = graph::CoverageGraph::from_mir(&mir_body);
let mut coverage_spans = Vec::new(); let mut coverage_spans = Vec::new();
for (bcb, data) in basic_coverage_blocks.iter_enumerated() { for (bcb, data) in basic_coverage_blocks.iter_enumerated() {
if let Some((span, is_macro_expansion)) = if let Some((span, expn_span)) =
spans::filtered_terminator_span(data.terminator(&mir_body), body_span) spans::filtered_terminator_span(data.terminator(&mir_body), body_span)
{ {
coverage_spans.push(spans::CoverageSpan::for_terminator( coverage_spans.push(spans::CoverageSpan::for_terminator(
span, span,
is_macro_expansion, expn_span,
bcb, bcb,
data.last_bb(), data.last_bb(),
)); ));

View file

@ -1,9 +1,9 @@
1| |#![allow(unused_assignments, unused_variables, dead_code)] 1| |#![allow(unused_assignments, unused_variables, dead_code)]
2| | 2| |
3| 1|fn main() { 3| 1|fn main() {
4| | // Initialize test constants in a way that cannot be determined at compile time, to ensure 4| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure
5| | // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from 5| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from
6| | // dependent conditions. 6| 1| // dependent conditions.
7| 1| let is_true = std::env::args().len() == 1; 7| 1| let is_true = std::env::args().len() == 1;
8| 1| 8| 1|
9| 1| let mut countdown = 0; 9| 1| let mut countdown = 0;

View file

@ -1,17 +1,17 @@
1| |// FIXME(#84561): function-like macros produce unintuitive coverage results. 1| |// This demonstrated Issue #84561: function-like macros produce unintuitive coverage results.
2| |// This test demonstrates some of the problems. 2| |
3| | 3| |// expect-exit-status-101
4| 18|#[derive(Debug, PartialEq, Eq)] 4| 21|#[derive(PartialEq, Eq)]
^5 ^0 ^0
------------------ ------------------
| <issue_84561::Foo as core::cmp::PartialEq>::eq: | <issue_84561::Foo as core::cmp::PartialEq>::eq:
| 4| 18|#[derive(Debug, PartialEq, Eq)] | 4| 21|#[derive(PartialEq, Eq)]
------------------ ------------------
| Unexecuted instantiation: <issue_84561::Foo as core::cmp::PartialEq>::ne | Unexecuted instantiation: <issue_84561::Foo as core::cmp::PartialEq>::ne
------------------ ------------------
5| |struct Foo(u32); 5| |struct Foo(u32);
6| | 6| 1|fn test2() {
7| 1|fn main() { 7| 1| let is_true = std::env::args().len() == 1;
8| 1| let bar = Foo(1); 8| 1| let bar = Foo(1);
9| 1| assert_eq!(bar, Foo(1)); 9| 1| assert_eq!(bar, Foo(1));
10| 1| let baz = Foo(0); 10| 1| let baz = Foo(0);
@ -23,80 +23,158 @@
16| 1| assert_eq!(Foo(1), Foo(1)); 16| 1| assert_eq!(Foo(1), Foo(1));
17| 1| assert_ne!(Foo(0), Foo(1)); 17| 1| assert_ne!(Foo(0), Foo(1));
18| 1| assert_eq!(Foo(2), Foo(2)); 18| 1| assert_eq!(Foo(2), Foo(2));
19| 1| let bar = Foo(1); 19| 1| let bar = Foo(0);
20| 1| assert_ne!(Foo(0), Foo(3)); 20| 1| assert_ne!(bar, Foo(3));
21| 1| assert_ne!(Foo(0), Foo(4)); 21| 1| assert_ne!(Foo(0), Foo(4));
22| 1| assert_eq!(Foo(3), Foo(3)); 22| 1| assert_eq!(Foo(3), Foo(3), "with a message");
23| 1| assert_ne!(Foo(0), Foo(5)); ^0
24| 1| println!("{:?}", bar); 23| 1| println!("{:?}", bar);
25| 1| println!("{:?}", Foo(1)); 24| 1| println!("{:?}", Foo(1));
26| 1| 25| 1|
27| 1| let is_true = std::env::args().len() == 1; 26| 1| assert_ne!(Foo(0), Foo(5), "{}", if is_true { "true message" } else { "false message" });
28| 1| ^0 ^0 ^0
29| 1| assert_eq!( 27| 1| assert_ne!(
30| 1| Foo(1), 28| | Foo(0)
31| 1| Foo(1) 29| | ,
32| 1| ); 30| | Foo(5)
33| 1| assert_ne!( 31| | ,
34| 1| Foo(0), 32| 0| "{}"
35| 1| Foo(1) 33| 0| ,
36| 1| ); 34| 0| if
37| 1| assert_eq!( 35| 0| is_true
38| 1| Foo(2), 36| | {
39| 1| Foo(2) 37| 0| "true message"
40| 1| ); 38| | } else {
41| 1| let bar = Foo(1 39| 0| "false message"
42| 1| ); 40| | }
43| 1| assert_ne!( 41| | );
44| 1| Foo(0), 42| |
45| 1| Foo(3) 43| 1| let is_true = std::env::args().len() == 1;
46| 1| ); 44| 1|
47| 1| if is_true { 45| 1| assert_eq!(
48| 1| assert_ne!( 46| 1| Foo(1),
49| 1| Foo(0), 47| 1| Foo(1)
50| 1| Foo(4) 48| 1| );
51| 1| ); 49| 1| assert_ne!(
52| | } else { 50| 1| Foo(0),
53| 0| assert_eq!( 51| 1| Foo(1)
54| 0| Foo(3), 52| 1| );
55| 0| Foo(3) 53| 1| assert_eq!(
56| 0| ); 54| 1| Foo(2),
57| | } 55| 1| Foo(2)
58| | assert_ne!( 56| 1| );
59| 1| if is_true { 57| 1| let bar = Foo(1);
60| 1| Foo(0) 58| 1| assert_ne!(
61| | } else { 59| 1| bar,
62| 0| Foo(1) 60| 1| Foo(3)
63| | }, 61| 1| );
64| | Foo(5) 62| 1| if is_true {
65| | ); 63| 1| assert_ne!(
66| 1| assert_ne!( 64| 1| Foo(0),
67| 1| Foo(5), 65| 1| Foo(4)
68| 1| if is_true { 66| 1| );
69| 1| Foo(0) 67| | } else {
70| | } else { 68| 0| assert_eq!(
71| 0| Foo(1) 69| 0| Foo(3),
70| 0| Foo(3)
71| 0| );
72| | } 72| | }
73| | ); 73| 1| if is_true {
74| | assert_ne!( 74| 1| assert_ne!(
75| 1| if is_true { 75| | Foo(0),
76| 1| assert_eq!( 76| | Foo(4),
77| 1| Foo(3), 77| 0| "with a message"
78| 1| Foo(3) 78| | );
79| 1| ); 79| | } else {
80| 1| Foo(0) 80| 0| assert_eq!(
81| | } else { 81| | Foo(3),
82| | assert_ne!( 82| | Foo(3),
83| 0| if is_true { 83| 0| "with a message"
84| 0| Foo(0) 84| | );
85| | } else { 85| | }
86| 0| Foo(1) 86| 1| assert_ne!(
87| | }, 87| 1| if is_true {
88| | Foo(5) 88| 1| Foo(0)
89| | ); 89| | } else {
90| 0| Foo(1) 90| 0| Foo(1)
91| | }, 91| | },
92| | Foo(5) 92| | Foo(5)
93| | ); 93| | );
94| 1|} 94| 1| assert_ne!(
95| 1| Foo(5),
96| 1| if is_true {
97| 1| Foo(0)
98| | } else {
99| 0| Foo(1)
100| | }
101| | );
102| 1| assert_ne!(
103| 1| if is_true {
104| 1| assert_eq!(
105| 1| Foo(3),
106| 1| Foo(3)
107| 1| );
108| 1| Foo(0)
109| | } else {
110| 0| assert_ne!(
111| 0| if is_true {
112| 0| Foo(0)
113| | } else {
114| 0| Foo(1)
115| | },
116| | Foo(5)
117| | );
118| 0| Foo(1)
119| | },
120| | Foo(5),
121| 0| "with a message"
122| | );
123| 1| assert_eq!(
124| | Foo(1),
125| | Foo(3),
126| 1| "this assert should fail"
127| | );
128| 0| assert_eq!(
129| | Foo(3),
130| | Foo(3),
131| 0| "this assert should not be reached"
132| | );
133| 0|}
134| |
135| |impl std::fmt::Debug for Foo {
136| | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
137| 7| write!(f, "try and succeed")?;
^0
138| 7| Ok(())
139| 7| }
140| |}
141| |
142| |static mut DEBUG_LEVEL_ENABLED: bool = false;
143| |
144| |macro_rules! debug {
145| | ($($arg:tt)+) => (
146| | if unsafe { DEBUG_LEVEL_ENABLED } {
147| | println!($($arg)+);
148| | }
149| | );
150| |}
151| |
152| 1|fn test1() {
153| 1| debug!("debug is enabled");
^0
154| 1| debug!("debug is enabled");
^0
155| 1| let _ = 0;
156| 1| debug!("debug is enabled");
^0
157| 1| unsafe {
158| 1| DEBUG_LEVEL_ENABLED = true;
159| 1| }
160| 1| debug!("debug is enabled");
161| 1|}
162| |
163| 1|fn main() {
164| 1| test1();
165| 1| test2();
166| 1|}

View file

@ -3,9 +3,9 @@
3| |use std::fmt::Debug; 3| |use std::fmt::Debug;
4| | 4| |
5| 1|pub fn used_function() { 5| 1|pub fn used_function() {
6| | // Initialize test constants in a way that cannot be determined at compile time, to ensure 6| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure
7| | // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from 7| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from
8| | // dependent conditions. 8| 1| // dependent conditions.
9| 1| let is_true = std::env::args().len() == 1; 9| 1| let is_true = std::env::args().len() == 1;
10| 1| let mut countdown = 0; 10| 1| let mut countdown = 0;
11| 1| if is_true { 11| 1| if is_true {

View file

@ -5,9 +5,9 @@
5| |use std::fmt::Debug; 5| |use std::fmt::Debug;
6| | 6| |
7| 1|pub fn used_function() { 7| 1|pub fn used_function() {
8| | // Initialize test constants in a way that cannot be determined at compile time, to ensure 8| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure
9| | // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from 9| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from
10| | // dependent conditions. 10| 1| // dependent conditions.
11| 1| let is_true = std::env::args().len() == 1; 11| 1| let is_true = std::env::args().len() == 1;
12| 1| let mut countdown = 0; 12| 1| let mut countdown = 0;
13| 1| if is_true { 13| 1| if is_true {
@ -19,9 +19,9 @@
18| | 18| |
19| |#[inline(always)] 19| |#[inline(always)]
20| 1|pub fn used_inline_function() { 20| 1|pub fn used_inline_function() {
21| | // Initialize test constants in a way that cannot be determined at compile time, to ensure 21| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure
22| | // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from 22| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from
23| | // dependent conditions. 23| 1| // dependent conditions.
24| 1| let is_true = std::env::args().len() == 1; 24| 1| let is_true = std::env::args().len() == 1;
25| 1| let mut countdown = 0; 25| 1| let mut countdown = 0;
26| 1| if is_true { 26| 1| if is_true {

View file

@ -1,10 +1,10 @@
// FIXME(#84561): function-like macros produce unintuitive coverage results. // This demonstrated Issue #84561: function-like macros produce unintuitive coverage results.
// This test demonstrates some of the problems.
#[derive(Debug, PartialEq, Eq)] // expect-exit-status-101
#[derive(PartialEq, Eq)]
struct Foo(u32); struct Foo(u32);
fn test2() {
fn main() { let is_true = std::env::args().len() == 1;
let bar = Foo(1); let bar = Foo(1);
assert_eq!(bar, Foo(1)); assert_eq!(bar, Foo(1));
let baz = Foo(0); let baz = Foo(0);
@ -16,14 +16,30 @@ fn main() {
assert_eq!(Foo(1), Foo(1)); assert_eq!(Foo(1), Foo(1));
assert_ne!(Foo(0), Foo(1)); assert_ne!(Foo(0), Foo(1));
assert_eq!(Foo(2), Foo(2)); assert_eq!(Foo(2), Foo(2));
let bar = Foo(1); let bar = Foo(0);
assert_ne!(Foo(0), Foo(3)); assert_ne!(bar, Foo(3));
assert_ne!(Foo(0), Foo(4)); assert_ne!(Foo(0), Foo(4));
assert_eq!(Foo(3), Foo(3)); assert_eq!(Foo(3), Foo(3), "with a message");
assert_ne!(Foo(0), Foo(5));
println!("{:?}", bar); println!("{:?}", bar);
println!("{:?}", Foo(1)); println!("{:?}", Foo(1));
assert_ne!(Foo(0), Foo(5), "{}", if is_true { "true message" } else { "false message" });
assert_ne!(
Foo(0)
,
Foo(5)
,
"{}"
,
if
is_true
{
"true message"
} else {
"false message"
}
);
let is_true = std::env::args().len() == 1; let is_true = std::env::args().len() == 1;
assert_eq!( assert_eq!(
@ -38,10 +54,9 @@ fn main() {
Foo(2), Foo(2),
Foo(2) Foo(2)
); );
let bar = Foo(1 let bar = Foo(1);
);
assert_ne!( assert_ne!(
Foo(0), bar,
Foo(3) Foo(3)
); );
if is_true { if is_true {
@ -55,6 +70,19 @@ fn main() {
Foo(3) Foo(3)
); );
} }
if is_true {
assert_ne!(
Foo(0),
Foo(4),
"with a message"
);
} else {
assert_eq!(
Foo(3),
Foo(3),
"with a message"
);
}
assert_ne!( assert_ne!(
if is_true { if is_true {
Foo(0) Foo(0)
@ -89,6 +117,50 @@ fn main() {
); );
Foo(1) Foo(1)
}, },
Foo(5) Foo(5),
"with a message"
);
assert_eq!(
Foo(1),
Foo(3),
"this assert should fail"
);
assert_eq!(
Foo(3),
Foo(3),
"this assert should not be reached"
); );
} }
impl std::fmt::Debug for Foo {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "try and succeed")?;
Ok(())
}
}
static mut DEBUG_LEVEL_ENABLED: bool = false;
macro_rules! debug {
($($arg:tt)+) => (
if unsafe { DEBUG_LEVEL_ENABLED } {
println!($($arg)+);
}
);
}
fn test1() {
debug!("debug is enabled");
debug!("debug is enabled");
let _ = 0;
debug!("debug is enabled");
unsafe {
DEBUG_LEVEL_ENABLED = true;
}
debug!("debug is enabled");
}
fn main() {
test1();
test2();
}