diff options
Diffstat (limited to 'tests/coverage')
104 files changed, 5849 insertions, 0 deletions
diff --git a/tests/coverage/abort.coverage b/tests/coverage/abort.coverage new file mode 100644 index 00000000000..ceef6386780 --- /dev/null +++ b/tests/coverage/abort.coverage @@ -0,0 +1,69 @@ + LL| |#![feature(c_unwind)] + LL| |#![allow(unused_assignments)] + LL| | + LL| 12|extern "C" fn might_abort(should_abort: bool) { + LL| 12| if should_abort { + LL| 0| println!("aborting..."); + LL| 0| panic!("panics and aborts"); + LL| 12| } else { + LL| 12| println!("Don't Panic"); + LL| 12| } + LL| 12|} + LL| | + LL| 1|fn main() -> Result<(), u8> { + LL| 1| let mut countdown = 10; + LL| 11| while countdown > 0 { + LL| 10| if countdown < 5 { + LL| 4| might_abort(false); + LL| 6| } + LL| | // See discussion (below the `Notes` section) on coverage results for the closing brace. + LL| 10| if countdown < 5 { might_abort(false); } // Counts for different regions on one line. + ^4 ^6 + LL| | // For the following example, the closing brace is the last character on the line. + LL| | // This shows the character after the closing brace is highlighted, even if that next + LL| | // character is a newline. + LL| 10| if countdown < 5 { might_abort(false); } + ^4 ^6 + LL| 10| countdown -= 1; + LL| | } + LL| 1| Ok(()) + LL| 1|} + LL| | + LL| |// Notes: + LL| |// 1. Compare this program and its coverage results to those of the similar tests + LL| |// `panic_unwind.rs` and `try_error_result.rs`. + LL| |// 2. This test confirms the coverage generated when a program includes `UnwindAction::Terminate`. + LL| |// 3. The test does not invoke the abort. By executing to a successful completion, the coverage + LL| |// results show where the program did and did not execute. + LL| |// 4. If the program actually aborted, the coverage counters would not be saved (which "works as + LL| |// intended"). Coverage results would show no executed coverage regions. + LL| |// 6. If `should_abort` is `true` and the program aborts, the program exits with a `132` status + LL| |// (on Linux at least). + LL| | + LL| |/* + LL| | + LL| |Expect the following coverage results: + LL| | + LL| |```text + LL| | 16| 11| while countdown > 0 { + LL| | 17| 10| if countdown < 5 { + LL| | 18| 4| might_abort(false); + LL| | 19| 6| } + LL| |``` + LL| | + LL| |This is actually correct. + LL| | + LL| |The condition `countdown < 5` executed 10 times (10 loop iterations). + LL| | + LL| |It evaluated to `true` 4 times, and executed the `might_abort()` call. + LL| | + LL| |It skipped the body of the `might_abort()` call 6 times. If an `if` does not include an explicit + LL| |`else`, the coverage implementation injects a counter, at the character immediately after the `if`s + LL| |closing brace, to count the "implicit" `else`. This is the only way to capture the coverage of the + LL| |non-true condition. + LL| | + LL| |As another example of why this is important, say the condition was `countdown < 50`, which is always + LL| |`true`. In that case, we wouldn't have a test for what happens if `might_abort()` is not called. + LL| |The closing brace would have a count of `0`, highlighting the missed coverage. + LL| |*/ + diff --git a/tests/coverage/abort.rs b/tests/coverage/abort.rs new file mode 100644 index 00000000000..98264bdc1af --- /dev/null +++ b/tests/coverage/abort.rs @@ -0,0 +1,66 @@ +#![feature(c_unwind)] +#![allow(unused_assignments)] + +extern "C" fn might_abort(should_abort: bool) { + if should_abort { + println!("aborting..."); + panic!("panics and aborts"); + } else { + println!("Don't Panic"); + } +} + +fn main() -> Result<(), u8> { + let mut countdown = 10; + while countdown > 0 { + if countdown < 5 { + might_abort(false); + } + // See discussion (below the `Notes` section) on coverage results for the closing brace. + if countdown < 5 { might_abort(false); } // Counts for different regions on one line. + // For the following example, the closing brace is the last character on the line. + // This shows the character after the closing brace is highlighted, even if that next + // character is a newline. + if countdown < 5 { might_abort(false); } + countdown -= 1; + } + Ok(()) +} + +// Notes: +// 1. Compare this program and its coverage results to those of the similar tests +// `panic_unwind.rs` and `try_error_result.rs`. +// 2. This test confirms the coverage generated when a program includes `UnwindAction::Terminate`. +// 3. The test does not invoke the abort. By executing to a successful completion, the coverage +// results show where the program did and did not execute. +// 4. If the program actually aborted, the coverage counters would not be saved (which "works as +// intended"). Coverage results would show no executed coverage regions. +// 6. If `should_abort` is `true` and the program aborts, the program exits with a `132` status +// (on Linux at least). + +/* + +Expect the following coverage results: + +```text + 16| 11| while countdown > 0 { + 17| 10| if countdown < 5 { + 18| 4| might_abort(false); + 19| 6| } +``` + +This is actually correct. + +The condition `countdown < 5` executed 10 times (10 loop iterations). + +It evaluated to `true` 4 times, and executed the `might_abort()` call. + +It skipped the body of the `might_abort()` call 6 times. If an `if` does not include an explicit +`else`, the coverage implementation injects a counter, at the character immediately after the `if`s +closing brace, to count the "implicit" `else`. This is the only way to capture the coverage of the +non-true condition. + +As another example of why this is important, say the condition was `countdown < 50`, which is always +`true`. In that case, we wouldn't have a test for what happens if `might_abort()` is not called. +The closing brace would have a count of `0`, highlighting the missed coverage. +*/ diff --git a/tests/coverage/assert.coverage b/tests/coverage/assert.coverage new file mode 100644 index 00000000000..8b997724c4a --- /dev/null +++ b/tests/coverage/assert.coverage @@ -0,0 +1,34 @@ + LL| |#![allow(unused_assignments)] + LL| |// failure-status: 101 + LL| | + LL| 4|fn might_fail_assert(one_plus_one: u32) { + LL| 4| println!("does 1 + 1 = {}?", one_plus_one); + LL| 4| assert_eq!(1 + 1, one_plus_one, "the argument was wrong"); + ^1 + LL| 3|} + LL| | + LL| 1|fn main() -> Result<(), u8> { + LL| 1| let mut countdown = 10; + LL| 11| while countdown > 0 { + LL| 11| if countdown == 1 { + LL| 1| might_fail_assert(3); + LL| 10| } else if countdown < 5 { + LL| 3| might_fail_assert(2); + LL| 6| } + LL| 10| countdown -= 1; + LL| | } + LL| 0| Ok(()) + LL| 0|} + LL| | + LL| |// Notes: + LL| |// 1. Compare this program and its coverage results to those of the very similar test + LL| |// `panic_unwind.rs`, and similar tests `abort.rs` and `try_error_result.rs`. + LL| |// 2. This test confirms the coverage generated when a program passes or fails an `assert!()` or + LL| |// related `assert_*!()` macro. + LL| |// 3. Notably, the `assert` macros *do not* generate `TerminatorKind::Assert`. The macros produce + LL| |// conditional expressions, `TerminatorKind::SwitchInt` branches, and a possible call to + LL| |// `begin_panic_fmt()` (that begins a panic unwind, if the assertion test fails). + LL| |// 4. `TerminatoKind::Assert` is, however, also present in the MIR generated for this test + LL| |// (and in many other coverage tests). The `Assert` terminator is typically generated by the + LL| |// Rust compiler to check for runtime failures, such as numeric overflows. + diff --git a/tests/coverage/assert.rs b/tests/coverage/assert.rs new file mode 100644 index 00000000000..85e6662a6ad --- /dev/null +++ b/tests/coverage/assert.rs @@ -0,0 +1,32 @@ +#![allow(unused_assignments)] +// failure-status: 101 + +fn might_fail_assert(one_plus_one: u32) { + println!("does 1 + 1 = {}?", one_plus_one); + assert_eq!(1 + 1, one_plus_one, "the argument was wrong"); +} + +fn main() -> Result<(), u8> { + let mut countdown = 10; + while countdown > 0 { + if countdown == 1 { + might_fail_assert(3); + } else if countdown < 5 { + might_fail_assert(2); + } + countdown -= 1; + } + Ok(()) +} + +// Notes: +// 1. Compare this program and its coverage results to those of the very similar test +// `panic_unwind.rs`, and similar tests `abort.rs` and `try_error_result.rs`. +// 2. This test confirms the coverage generated when a program passes or fails an `assert!()` or +// related `assert_*!()` macro. +// 3. Notably, the `assert` macros *do not* generate `TerminatorKind::Assert`. The macros produce +// conditional expressions, `TerminatorKind::SwitchInt` branches, and a possible call to +// `begin_panic_fmt()` (that begins a panic unwind, if the assertion test fails). +// 4. `TerminatoKind::Assert` is, however, also present in the MIR generated for this test +// (and in many other coverage tests). The `Assert` terminator is typically generated by the +// Rust compiler to check for runtime failures, such as numeric overflows. diff --git a/tests/coverage/async.coverage b/tests/coverage/async.coverage new file mode 100644 index 00000000000..07bc16c2d92 --- /dev/null +++ b/tests/coverage/async.coverage @@ -0,0 +1,139 @@ + LL| |#![allow(unused_assignments, dead_code)] + LL| | + LL| |// compile-flags: --edition=2018 -C opt-level=1 + LL| | + LL| 1|async fn c(x: u8) -> u8 { + LL| 1| if x == 8 { + LL| 1| 1 + LL| | } else { + LL| 0| 0 + LL| | } + LL| 1|} + LL| | + LL| 0|async fn d() -> u8 { 1 } + LL| | + LL| 0|async fn e() -> u8 { 1 } // unused function; executor does not block on `g()` + LL| | + LL| 1|async fn f() -> u8 { 1 } + LL| | + LL| 0|async fn foo() -> [bool; 10] { [false; 10] } // unused function; executor does not block on `h()` + LL| | + LL| 1|pub async fn g(x: u8) { + LL| 0| match x { + LL| 0| y if e().await == y => (), + LL| 0| y if f().await == y => (), + LL| 0| _ => (), + LL| | } + LL| 0|} + LL| | + LL| 1|async fn h(x: usize) { // The function signature is counted when called, but the body is not + LL| 0| // executed (not awaited) so the open brace has a `0` count (at least when + LL| 0| // displayed with `llvm-cov show` in color-mode). + LL| 0| match x { + LL| 0| y if foo().await[y] => (), + LL| 0| _ => (), + LL| | } + LL| 0|} + LL| | + LL| 1|async fn i(x: u8) { // line coverage is 1, but there are 2 regions: + LL| 1| // (a) the function signature, counted when the function is called; and + LL| 1| // (b) the open brace for the function body, counted once when the body is + LL| 1| // executed asynchronously. + LL| 1| match x { + LL| 1| y if c(x).await == y + 1 => { d().await; } + ^0 ^0 ^0 ^0 + LL| 1| y if f().await == y + 1 => (), + ^0 ^0 ^0 + LL| 1| _ => (), + LL| | } + LL| 1|} + LL| | + LL| 1|fn j(x: u8) { + LL| 1| // non-async versions of `c()`, `d()`, and `f()` to make it similar to async `i()`. + LL| 1| fn c(x: u8) -> u8 { + LL| 1| if x == 8 { + LL| 1| 1 // This line appears covered, but the 1-character expression span covering the `1` + ^0 + LL| 1| // is not executed. (`llvm-cov show` displays a `^0` below the `1` ). This is because + LL| 1| // `fn j()` executes the open brace for the function body, followed by the function's + LL| 1| // first executable statement, `match x`. Inner function declarations are not + LL| 1| // "visible" to the MIR for `j()`, so the code region counts all lines between the + LL| 1| // open brace and the first statement as executed, which is, in a sense, true. + LL| 1| // `llvm-cov show` overcomes this kind of situation by showing the actual counts + LL| 1| // of the enclosed coverages, (that is, the `1` expression was not executed, and + LL| 1| // accurately displays a `0`). + LL| 1| } else { + LL| 1| 0 + LL| 1| } + LL| 1| } + LL| 1| fn d() -> u8 { 1 } // inner function is defined in-line, but the function is not executed + ^0 + LL| 1| fn f() -> u8 { 1 } + LL| 1| match x { + LL| 1| y if c(x) == y + 1 => { d(); } + ^0 ^0 + LL| 1| y if f() == y + 1 => (), + ^0 ^0 + LL| 1| _ => (), + LL| | } + LL| 1|} + LL| | + LL| 0|fn k(x: u8) { // unused function + LL| 0| match x { + LL| 0| 1 => (), + LL| 0| 2 => (), + LL| 0| _ => (), + LL| | } + LL| 0|} + LL| | + LL| 1|fn l(x: u8) { + LL| 1| match x { + LL| 0| 1 => (), + LL| 0| 2 => (), + LL| 1| _ => (), + LL| | } + LL| 1|} + LL| | + LL| 1|async fn m(x: u8) -> u8 { x - 1 } + ^0 + LL| | + LL| 1|fn main() { + LL| 1| let _ = g(10); + LL| 1| let _ = h(9); + LL| 1| let mut future = Box::pin(i(8)); + LL| 1| j(7); + LL| 1| l(6); + LL| 1| let _ = m(5); + LL| 1| executor::block_on(future.as_mut()); + LL| 1|} + LL| | + LL| |mod executor { + LL| | use core::{ + LL| | future::Future, + LL| | pin::Pin, + LL| | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + LL| | }; + LL| | + LL| 1| pub fn block_on<F: Future>(mut future: F) -> F::Output { + LL| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + LL| 1| use std::hint::unreachable_unchecked; + LL| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new( + LL| 1| |_| unsafe { unreachable_unchecked() }, // clone + ^0 + LL| 1| |_| unsafe { unreachable_unchecked() }, // wake + ^0 + LL| 1| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + ^0 + LL| 1| |_| (), + LL| 1| ); + LL| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + LL| 1| let mut context = Context::from_waker(&waker); + LL| | + LL| | loop { + LL| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + LL| 1| break val; + LL| 0| } + LL| | } + LL| 1| } + LL| |} + diff --git a/tests/coverage/async.rs b/tests/coverage/async.rs new file mode 100644 index 00000000000..efd9e62d64e --- /dev/null +++ b/tests/coverage/async.rs @@ -0,0 +1,128 @@ +#![allow(unused_assignments, dead_code)] + +// compile-flags: --edition=2018 -C opt-level=1 + +async fn c(x: u8) -> u8 { + if x == 8 { + 1 + } else { + 0 + } +} + +async fn d() -> u8 { 1 } + +async fn e() -> u8 { 1 } // unused function; executor does not block on `g()` + +async fn f() -> u8 { 1 } + +async fn foo() -> [bool; 10] { [false; 10] } // unused function; executor does not block on `h()` + +pub async fn g(x: u8) { + match x { + y if e().await == y => (), + y if f().await == y => (), + _ => (), + } +} + +async fn h(x: usize) { // The function signature is counted when called, but the body is not + // executed (not awaited) so the open brace has a `0` count (at least when + // displayed with `llvm-cov show` in color-mode). + match x { + y if foo().await[y] => (), + _ => (), + } +} + +async fn i(x: u8) { // line coverage is 1, but there are 2 regions: + // (a) the function signature, counted when the function is called; and + // (b) the open brace for the function body, counted once when the body is + // executed asynchronously. + match x { + y if c(x).await == y + 1 => { d().await; } + y if f().await == y + 1 => (), + _ => (), + } +} + +fn j(x: u8) { + // non-async versions of `c()`, `d()`, and `f()` to make it similar to async `i()`. + fn c(x: u8) -> u8 { + if x == 8 { + 1 // This line appears covered, but the 1-character expression span covering the `1` + // is not executed. (`llvm-cov show` displays a `^0` below the `1` ). This is because + // `fn j()` executes the open brace for the function body, followed by the function's + // first executable statement, `match x`. Inner function declarations are not + // "visible" to the MIR for `j()`, so the code region counts all lines between the + // open brace and the first statement as executed, which is, in a sense, true. + // `llvm-cov show` overcomes this kind of situation by showing the actual counts + // of the enclosed coverages, (that is, the `1` expression was not executed, and + // accurately displays a `0`). + } else { + 0 + } + } + fn d() -> u8 { 1 } // inner function is defined in-line, but the function is not executed + fn f() -> u8 { 1 } + match x { + y if c(x) == y + 1 => { d(); } + y if f() == y + 1 => (), + _ => (), + } +} + +fn k(x: u8) { // unused function + match x { + 1 => (), + 2 => (), + _ => (), + } +} + +fn l(x: u8) { + match x { + 1 => (), + 2 => (), + _ => (), + } +} + +async fn m(x: u8) -> u8 { x - 1 } + +fn main() { + let _ = g(10); + let _ = h(9); + let mut future = Box::pin(i(8)); + j(7); + l(6); + let _ = m(5); + executor::block_on(future.as_mut()); +} + +mod executor { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + pub fn block_on<F: Future>(mut future: F) -> F::Output { + let mut future = unsafe { Pin::new_unchecked(&mut future) }; + use std::hint::unreachable_unchecked; + static VTABLE: RawWakerVTable = RawWakerVTable::new( + |_| unsafe { unreachable_unchecked() }, // clone + |_| unsafe { unreachable_unchecked() }, // wake + |_| unsafe { unreachable_unchecked() }, // wake_by_ref + |_| (), + ); + let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + let mut context = Context::from_waker(&waker); + + loop { + if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + break val; + } + } + } +} diff --git a/tests/coverage/async2.coverage b/tests/coverage/async2.coverage new file mode 100644 index 00000000000..fcb0a3aed64 --- /dev/null +++ b/tests/coverage/async2.coverage @@ -0,0 +1,104 @@ + LL| |// compile-flags: --edition=2018 + LL| | + LL| 1|fn non_async_func() { + LL| 1| println!("non_async_func was covered"); + LL| 1| let b = true; + LL| 1| if b { + LL| 1| println!("non_async_func println in block"); + LL| 1| } + ^0 + LL| 1|} + LL| | + LL| 1|async fn async_func() { + LL| 1| println!("async_func was covered"); + LL| 1| let b = true; + LL| 1| if b { + LL| 1| println!("async_func println in block"); + LL| 1| } + ^0 + LL| 1|} + LL| | + LL| 1|async fn async_func_just_println() { + LL| 1| println!("async_func_just_println was covered"); + LL| 1|} + LL| | + LL| 1|fn main() { + LL| 1| println!("codecovsample::main"); + LL| 1| + LL| 1| non_async_func(); + LL| 1| + LL| 1| executor::block_on(async_func()); + LL| 1| executor::block_on(async_func_just_println()); + LL| 1|} + LL| | + LL| |mod executor { + LL| | use core::{ + LL| | future::Future, + LL| | pin::Pin, + LL| | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + LL| | }; + LL| | + LL| 2| pub fn block_on<F: Future>(mut future: F) -> F::Output { + LL| 2| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + LL| 2| use std::hint::unreachable_unchecked; + LL| 2| static VTABLE: RawWakerVTable = RawWakerVTable::new( + LL| 2| |_| unsafe { unreachable_unchecked() }, // clone + ^0 + LL| 2| |_| unsafe { unreachable_unchecked() }, // wake + ^0 + LL| 2| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + ^0 + LL| 2| |_| (), + LL| 2| ); + LL| 2| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + LL| 2| let mut context = Context::from_waker(&waker); + LL| | + LL| | loop { + LL| 2| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + LL| 2| break val; + LL| 0| } + LL| | } + LL| 2| } + ------------------ + | async2::executor::block_on::<async2::async_func::{closure#0}>: + | LL| 1| pub fn block_on<F: Future>(mut future: F) -> F::Output { + | LL| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + | LL| 1| use std::hint::unreachable_unchecked; + | LL| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new( + | LL| 1| |_| unsafe { unreachable_unchecked() }, // clone + | LL| 1| |_| unsafe { unreachable_unchecked() }, // wake + | LL| 1| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + | LL| 1| |_| (), + | LL| 1| ); + | LL| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + | LL| 1| let mut context = Context::from_waker(&waker); + | LL| | + | LL| | loop { + | LL| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + | LL| 1| break val; + | LL| 0| } + | LL| | } + | LL| 1| } + ------------------ + | async2::executor::block_on::<async2::async_func_just_println::{closure#0}>: + | LL| 1| pub fn block_on<F: Future>(mut future: F) -> F::Output { + | LL| 1| let mut future = unsafe { Pin::new_unchecked(&mut future) }; + | LL| 1| use std::hint::unreachable_unchecked; + | LL| 1| static VTABLE: RawWakerVTable = RawWakerVTable::new( + | LL| 1| |_| unsafe { unreachable_unchecked() }, // clone + | LL| 1| |_| unsafe { unreachable_unchecked() }, // wake + | LL| 1| |_| unsafe { unreachable_unchecked() }, // wake_by_ref + | LL| 1| |_| (), + | LL| 1| ); + | LL| 1| let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + | LL| 1| let mut context = Context::from_waker(&waker); + | LL| | + | LL| | loop { + | LL| 1| if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + | LL| 1| break val; + | LL| 0| } + | LL| | } + | LL| 1| } + ------------------ + LL| |} + diff --git a/tests/coverage/async2.rs b/tests/coverage/async2.rs new file mode 100644 index 00000000000..2884ff297af --- /dev/null +++ b/tests/coverage/async2.rs @@ -0,0 +1,57 @@ +// compile-flags: --edition=2018 + +fn non_async_func() { + println!("non_async_func was covered"); + let b = true; + if b { + println!("non_async_func println in block"); + } +} + +async fn async_func() { + println!("async_func was covered"); + let b = true; + if b { + println!("async_func println in block"); + } +} + +async fn async_func_just_println() { + println!("async_func_just_println was covered"); +} + +fn main() { + println!("codecovsample::main"); + + non_async_func(); + + executor::block_on(async_func()); + executor::block_on(async_func_just_println()); +} + +mod executor { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + pub fn block_on<F: Future>(mut future: F) -> F::Output { + let mut future = unsafe { Pin::new_unchecked(&mut future) }; + use std::hint::unreachable_unchecked; + static VTABLE: RawWakerVTable = RawWakerVTable::new( + |_| unsafe { unreachable_unchecked() }, // clone + |_| unsafe { unreachable_unchecked() }, // wake + |_| unsafe { unreachable_unchecked() }, // wake_by_ref + |_| (), + ); + let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + let mut context = Context::from_waker(&waker); + + loop { + if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + break val; + } + } + } +} diff --git a/tests/coverage/auxiliary/inline_always_with_dead_code.rs b/tests/coverage/auxiliary/inline_always_with_dead_code.rs new file mode 100644 index 00000000000..9dc50dae25a --- /dev/null +++ b/tests/coverage/auxiliary/inline_always_with_dead_code.rs @@ -0,0 +1,22 @@ +// compile-flags: -Cinstrument-coverage -Ccodegen-units=4 -Copt-level=0 + +#![allow(dead_code)] + +mod foo { + #[inline(always)] + pub fn called() {} + + fn uncalled() {} +} + +pub mod bar { + pub fn call_me() { + super::foo::called(); + } +} + +pub mod baz { + pub fn call_me() { + super::foo::called(); + } +} diff --git a/tests/coverage/auxiliary/unused_mod_helper.rs b/tests/coverage/auxiliary/unused_mod_helper.rs new file mode 100644 index 00000000000..88c5dac65cb --- /dev/null +++ b/tests/coverage/auxiliary/unused_mod_helper.rs @@ -0,0 +1,4 @@ +#[allow(dead_code)] +pub fn never_called_function() { + println!("I am never called"); +} diff --git a/tests/coverage/auxiliary/used_crate.rs b/tests/coverage/auxiliary/used_crate.rs new file mode 100644 index 00000000000..c086ef21e1a --- /dev/null +++ b/tests/coverage/auxiliary/used_crate.rs @@ -0,0 +1,103 @@ +#![allow(unused_assignments, unused_variables)] +// Verify that coverage works with optimizations: +// compile-flags: -C opt-level=3 + +use std::fmt::Debug; + +pub fn used_function() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + let mut countdown = 0; + if is_true { + countdown = 10; + } + use_this_lib_crate(); +} + +pub fn used_only_from_bin_crate_generic_function<T: Debug>(arg: T) { + println!("used_only_from_bin_crate_generic_function with {:?}", arg); +} +// Expect for above function: `Unexecuted instantiation` (see below) +pub fn used_only_from_this_lib_crate_generic_function<T: Debug>(arg: T) { + println!("used_only_from_this_lib_crate_generic_function with {:?}", arg); +} + +pub fn used_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + println!("used_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); +} + +pub fn used_with_same_type_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + println!("used_with_same_type_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); +} + +pub fn unused_generic_function<T: Debug>(arg: T) { + println!("unused_generic_function with {:?}", arg); +} + +pub fn unused_function() { + let is_true = std::env::args().len() == 1; + let mut countdown = 2; + if !is_true { + countdown = 20; + } +} + +#[allow(dead_code)] +fn unused_private_function() { + let is_true = std::env::args().len() == 1; + let mut countdown = 2; + if !is_true { + countdown = 20; + } +} + +fn use_this_lib_crate() { + used_from_bin_crate_and_lib_crate_generic_function("used from library used_crate.rs"); + used_with_same_type_from_bin_crate_and_lib_crate_generic_function( + "used from library used_crate.rs", + ); + let some_vec = vec![5, 6, 7, 8]; + used_only_from_this_lib_crate_generic_function(some_vec); + used_only_from_this_lib_crate_generic_function("used ONLY from library used_crate.rs"); +} + +// FIXME(#79651): "Unexecuted instantiation" errors appear in coverage results, +// for example: +// +// | Unexecuted instantiation: used_crate::used_only_from_bin_crate_generic_function::<_> +// +// These notices appear when `llvm-cov` shows instantiations. This may be a +// default option, but it can be suppressed with: +// +// ```shell +// $ `llvm-cov show --show-instantiations=0 ...` +// ``` +// +// The notice is triggered because the function is unused by the library itself, +// and when the library is compiled, a synthetic function is generated, so +// unused function coverage can be reported. Coverage can be skipped for unused +// generic functions with: +// +// ```shell +// $ `rustc -Zunstable-options -C instrument-coverage=except-unused-generics ...` +// ``` +// +// Even though this function is used by `uses_crate.rs` (and +// counted), with substitutions for `T`, those instantiations are only generated +// when the generic function is actually used (from the binary, not from this +// library crate). So the test result shows coverage for all instantiated +// versions and their generic type substitutions, plus the `Unexecuted +// instantiation` message for the non-substituted version. This is valid, but +// unfortunately a little confusing. +// +// The library crate has its own coverage map, and the only way to show unused +// coverage of a generic function is to include the generic function in the +// coverage map, marked as an "unused function". If the library were used by +// another binary that never used this generic function, then it would be valid +// to show the unused generic, with unknown substitution (`_`). +// +// The alternative is to exclude all generics from being included in the "unused +// functions" list, which would then omit coverage results for +// `unused_generic_function<T>()`, below. diff --git a/tests/coverage/auxiliary/used_inline_crate.rs b/tests/coverage/auxiliary/used_inline_crate.rs new file mode 100644 index 00000000000..e8929de6b36 --- /dev/null +++ b/tests/coverage/auxiliary/used_inline_crate.rs @@ -0,0 +1,85 @@ +#![allow(unused_assignments, unused_variables)] +// Verify that coverage works with optimizations: +// compile-flags: -C opt-level=3 + +use std::fmt::Debug; + +pub fn used_function() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + let mut countdown = 0; + if is_true { + countdown = 10; + } + use_this_lib_crate(); +} + +#[inline(always)] +pub fn used_inline_function() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + let mut countdown = 0; + if is_true { + countdown = 10; + } + use_this_lib_crate(); +} + +#[inline(always)] +pub fn used_only_from_bin_crate_generic_function<T: Debug>(arg: T) { + println!("used_only_from_bin_crate_generic_function with {:?}", arg); +} +// Expect for above function: `Unexecuted instantiation` (see notes in `used_crate.rs`) + +#[inline(always)] +pub fn used_only_from_this_lib_crate_generic_function<T: Debug>(arg: T) { + println!("used_only_from_this_lib_crate_generic_function with {:?}", arg); +} + +#[inline(always)] +pub fn used_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + println!("used_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); +} + +#[inline(always)] +pub fn used_with_same_type_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + println!("used_with_same_type_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); +} + +#[inline(always)] +pub fn unused_generic_function<T: Debug>(arg: T) { + println!("unused_generic_function with {:?}", arg); +} + +#[inline(always)] +pub fn unused_function() { + let is_true = std::env::args().len() == 1; + let mut countdown = 2; + if !is_true { + countdown = 20; + } +} + +#[inline(always)] +#[allow(dead_code)] +fn unused_private_function() { + let is_true = std::env::args().len() == 1; + let mut countdown = 2; + if !is_true { + countdown = 20; + } +} + +fn use_this_lib_crate() { + used_from_bin_crate_and_lib_crate_generic_function("used from library used_crate.rs"); + used_with_same_type_from_bin_crate_and_lib_crate_generic_function( + "used from library used_crate.rs", + ); + let some_vec = vec![5, 6, 7, 8]; + used_only_from_this_lib_crate_generic_function(some_vec); + used_only_from_this_lib_crate_generic_function("used ONLY from library used_crate.rs"); +} diff --git a/tests/coverage/bad_counter_ids.coverage b/tests/coverage/bad_counter_ids.coverage new file mode 100644 index 00000000000..d69ebf160ea --- /dev/null +++ b/tests/coverage/bad_counter_ids.coverage @@ -0,0 +1,69 @@ + LL| |#![feature(coverage_attribute)] + LL| |// compile-flags: --edition=2021 -Copt-level=0 -Zmir-opt-level=3 + LL| | + LL| |// Regression test for <https://github.com/rust-lang/rust/issues/117012>. + LL| |// + LL| |// If some coverage counters were removed by MIR optimizations, we need to take + LL| |// care not to refer to those counter IDs in coverage mappings, and instead + LL| |// replace them with a constant zero value. If we don't, `llvm-cov` might see + LL| |// a too-large counter ID and silently discard the entire function from its + LL| |// coverage reports. + LL| | + LL| 8|#[derive(Debug, PartialEq, Eq)] + LL| |struct Foo(u32); + LL| | + LL| 1|fn eq_good() { + LL| 1| println!("a"); + LL| 1| assert_eq!(Foo(1), Foo(1)); + LL| 1|} + LL| | + LL| 1|fn eq_good_message() { + LL| 1| println!("b"); + LL| 1| assert_eq!(Foo(1), Foo(1), "message b"); + ^0 + LL| 1|} + LL| | + LL| 1|fn ne_good() { + LL| 1| println!("c"); + LL| 1| assert_ne!(Foo(1), Foo(3)); + LL| 1|} + LL| | + LL| 1|fn ne_good_message() { + LL| 1| println!("d"); + LL| 1| assert_ne!(Foo(1), Foo(3), "message d"); + ^0 + LL| 1|} + LL| | + LL| 1|fn eq_bad() { + LL| 1| println!("e"); + LL| 1| assert_eq!(Foo(1), Foo(3)); + LL| 0|} + LL| | + LL| 1|fn eq_bad_message() { + LL| 1| println!("f"); + LL| 1| assert_eq!(Foo(1), Foo(3), "message f"); + LL| 0|} + LL| | + LL| 1|fn ne_bad() { + LL| 1| println!("g"); + LL| 1| assert_ne!(Foo(1), Foo(1)); + LL| 0|} + LL| | + LL| 1|fn ne_bad_message() { + LL| 1| println!("h"); + LL| 1| assert_ne!(Foo(1), Foo(1), "message h"); + LL| 0|} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | eq_good(); + LL| | eq_good_message(); + LL| | ne_good(); + LL| | ne_good_message(); + LL| | + LL| | assert!(std::panic::catch_unwind(eq_bad).is_err()); + LL| | assert!(std::panic::catch_unwind(eq_bad_message).is_err()); + LL| | assert!(std::panic::catch_unwind(ne_bad).is_err()); + LL| | assert!(std::panic::catch_unwind(ne_bad_message).is_err()); + LL| |} + diff --git a/tests/coverage/bad_counter_ids.rs b/tests/coverage/bad_counter_ids.rs new file mode 100644 index 00000000000..ef5460102b7 --- /dev/null +++ b/tests/coverage/bad_counter_ids.rs @@ -0,0 +1,66 @@ +#![feature(coverage_attribute)] +// compile-flags: --edition=2021 -Copt-level=0 -Zmir-opt-level=3 + +// Regression test for <https://github.com/rust-lang/rust/issues/117012>. +// +// If some coverage counters were removed by MIR optimizations, we need to take +// care not to refer to those counter IDs in coverage mappings, and instead +// replace them with a constant zero value. If we don't, `llvm-cov` might see +// a too-large counter ID and silently discard the entire function from its +// coverage reports. + +#[derive(Debug, PartialEq, Eq)] +struct Foo(u32); + +fn eq_good() { + println!("a"); + assert_eq!(Foo(1), Foo(1)); +} + +fn eq_good_message() { + println!("b"); + assert_eq!(Foo(1), Foo(1), "message b"); +} + +fn ne_good() { + println!("c"); + assert_ne!(Foo(1), Foo(3)); +} + +fn ne_good_message() { + println!("d"); + assert_ne!(Foo(1), Foo(3), "message d"); +} + +fn eq_bad() { + println!("e"); + assert_eq!(Foo(1), Foo(3)); +} + +fn eq_bad_message() { + println!("f"); + assert_eq!(Foo(1), Foo(3), "message f"); +} + +fn ne_bad() { + println!("g"); + assert_ne!(Foo(1), Foo(1)); +} + +fn ne_bad_message() { + println!("h"); + assert_ne!(Foo(1), Foo(1), "message h"); +} + +#[coverage(off)] +fn main() { + eq_good(); + eq_good_message(); + ne_good(); + ne_good_message(); + + assert!(std::panic::catch_unwind(eq_bad).is_err()); + assert!(std::panic::catch_unwind(eq_bad_message).is_err()); + assert!(std::panic::catch_unwind(ne_bad).is_err()); + assert!(std::panic::catch_unwind(ne_bad_message).is_err()); +} diff --git a/tests/coverage/closure.coverage b/tests/coverage/closure.coverage new file mode 100644 index 00000000000..67014f792c8 --- /dev/null +++ b/tests/coverage/closure.coverage @@ -0,0 +1,227 @@ + LL| |#![allow(unused_assignments, unused_variables)] + LL| |// compile-flags: -C opt-level=2 + LL| | + LL| |// This test used to be sensitive to certain coverage-specific hacks in + LL| |// `rustc_middle/mir/mono.rs`, but those hacks were later cleaned up by + LL| |// <https://github.com/rust-lang/rust/pull/83666>. + LL| | + LL| 1|fn main() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| let is_false = !is_true; + LL| 1| + LL| 1| let mut some_string = Some(String::from("the string content")); + LL| 1| println!( + LL| 1| "The string or alt: {}" + LL| 1| , + LL| 1| some_string + LL| 1| . + LL| 1| unwrap_or_else + LL| 1| ( + LL| 1| || + LL| 0| { + LL| 0| let mut countdown = 0; + LL| 0| if is_false { + LL| 0| countdown = 10; + LL| 0| } + LL| 0| "alt string 1".to_owned() + LL| 1| } + LL| 1| ) + LL| 1| ); + LL| 1| + LL| 1| some_string = Some(String::from("the string content")); + LL| 1| let + LL| 1| a + LL| | = + LL| | || + LL| 0| { + LL| 0| let mut countdown = 0; + LL| 0| if is_false { + LL| 0| countdown = 10; + LL| 0| } + LL| 0| "alt string 2".to_owned() + LL| 0| }; + LL| 1| println!( + LL| 1| "The string or alt: {}" + LL| 1| , + LL| 1| some_string + LL| 1| . + LL| 1| unwrap_or_else + LL| 1| ( + LL| 1| a + LL| 1| ) + LL| 1| ); + LL| 1| + LL| 1| some_string = None; + LL| 1| println!( + LL| 1| "The string or alt: {}" + LL| 1| , + LL| 1| some_string + LL| 1| . + LL| 1| unwrap_or_else + LL| 1| ( + LL| 1| || + LL| 1| { + LL| 1| let mut countdown = 0; + LL| 1| if is_false { + LL| 0| countdown = 10; + LL| 1| } + LL| 1| "alt string 3".to_owned() + LL| 1| } + LL| 1| ) + LL| 1| ); + LL| 1| + LL| 1| some_string = None; + LL| 1| let + LL| 1| a + LL| | = + LL| | || + LL| 1| { + LL| 1| let mut countdown = 0; + LL| 1| if is_false { + LL| 0| countdown = 10; + LL| 1| } + LL| 1| "alt string 4".to_owned() + LL| 1| }; + LL| 1| println!( + LL| 1| "The string or alt: {}" + LL| 1| , + LL| 1| some_string + LL| 1| . + LL| 1| unwrap_or_else + LL| 1| ( + LL| 1| a + LL| 1| ) + LL| 1| ); + LL| 1| + LL| 1| let + LL| 1| quote_closure + LL| | = + LL| | |val| + LL| 5| { + LL| 5| let mut countdown = 0; + LL| 5| if is_false { + LL| 0| countdown = 10; + LL| 5| } + LL| 5| format!("'{}'", val) + LL| 5| }; + LL| 1| println!( + LL| 1| "Repeated, quoted string: {:?}" + LL| 1| , + LL| 1| std::iter::repeat("repeat me") + LL| 1| .take(5) + LL| 1| .map + LL| 1| ( + LL| 1| quote_closure + LL| 1| ) + LL| 1| .collect::<Vec<_>>() + LL| 1| ); + LL| 1| + LL| 1| let + LL| 1| _unused_closure + LL| | = + LL| | | + LL| | mut countdown + LL| | | + LL| 0| { + LL| 0| if is_false { + LL| 0| countdown = 10; + LL| 0| } + LL| 0| "closure should be unused".to_owned() + LL| 0| }; + LL| | + LL| 1| let mut countdown = 10; + LL| 1| let _short_unused_closure = | _unused_arg: u8 | countdown += 1; + ^0 + LL| | + LL| | + LL| 1| let short_used_covered_closure_macro = | used_arg: u8 | println!("called"); + LL| 1| let short_used_not_covered_closure_macro = | used_arg: u8 | println!("not called"); + ^0 + LL| 1| let _short_unused_closure_macro = | _unused_arg: u8 | println!("not called"); + ^0 + LL| | + LL| | + LL| | + LL| | + LL| 1| let _short_unused_closure_block = | _unused_arg: u8 | { println!("not called") }; + ^0 + LL| | + LL| 1| let _shortish_unused_closure = | _unused_arg: u8 | { + LL| 0| println!("not called") + LL| 0| }; + LL| | + LL| 1| let _as_short_unused_closure = | + LL| | _unused_arg: u8 + LL| 0| | { println!("not called") }; + LL| | + LL| 1| let _almost_as_short_unused_closure = | + LL| | _unused_arg: u8 + LL| 0| | { println!("not called") } + LL| | ; + LL| | + LL| | + LL| | + LL| | + LL| | + LL| 1| let _short_unused_closure_line_break_no_block = | _unused_arg: u8 | + LL| 0|println!("not called") + LL| | ; + LL| | + LL| 1| let _short_unused_closure_line_break_no_block2 = + LL| | | _unused_arg: u8 | + LL| 0| println!( + LL| 0| "not called" + LL| 0| ) + LL| | ; + LL| | + LL| 1| let short_used_not_covered_closure_line_break_no_block_embedded_branch = + LL| | | _unused_arg: u8 | + LL| 0| println!( + LL| 0| "not called: {}", + LL| 0| if is_true { "check" } else { "me" } + LL| 0| ) + LL| | ; + LL| | + LL| 1| let short_used_not_covered_closure_line_break_block_embedded_branch = + LL| | | _unused_arg: u8 | + LL| 0| { + LL| 0| println!( + LL| 0| "not called: {}", + LL| 0| if is_true { "check" } else { "me" } + LL| | ) + LL| 0| } + LL| | ; + LL| | + LL| 1| let short_used_covered_closure_line_break_no_block_embedded_branch = + LL| | | _unused_arg: u8 | + LL| 1| println!( + LL| 1| "not called: {}", + LL| 1| if is_true { "check" } else { "me" } + ^0 + LL| 1| ) + LL| | ; + LL| | + LL| 1| let short_used_covered_closure_line_break_block_embedded_branch = + LL| | | _unused_arg: u8 | + LL| 1| { + LL| 1| println!( + LL| 1| "not called: {}", + LL| 1| if is_true { "check" } else { "me" } + ^0 + LL| | ) + LL| 1| } + LL| | ; + LL| | + LL| 1| if is_false { + LL| 0| short_used_not_covered_closure_macro(0); + LL| 0| short_used_not_covered_closure_line_break_no_block_embedded_branch(0); + LL| 0| short_used_not_covered_closure_line_break_block_embedded_branch(0); + LL| 1| } + LL| 1| short_used_covered_closure_macro(0); + LL| 1| short_used_covered_closure_line_break_no_block_embedded_branch(0); + LL| 1| short_used_covered_closure_line_break_block_embedded_branch(0); + LL| 1|} + diff --git a/tests/coverage/closure.rs b/tests/coverage/closure.rs new file mode 100644 index 00000000000..16a2c4e33bd --- /dev/null +++ b/tests/coverage/closure.rs @@ -0,0 +1,220 @@ +#![allow(unused_assignments, unused_variables)] +// compile-flags: -C opt-level=2 + +// This test used to be sensitive to certain coverage-specific hacks in +// `rustc_middle/mir/mono.rs`, but those hacks were later cleaned up by +// <https://github.com/rust-lang/rust/pull/83666>. + +fn main() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + let is_false = !is_true; + + let mut some_string = Some(String::from("the string content")); + println!( + "The string or alt: {}" + , + some_string + . + unwrap_or_else + ( + || + { + let mut countdown = 0; + if is_false { + countdown = 10; + } + "alt string 1".to_owned() + } + ) + ); + + some_string = Some(String::from("the string content")); + let + a + = + || + { + let mut countdown = 0; + if is_false { + countdown = 10; + } + "alt string 2".to_owned() + }; + println!( + "The string or alt: {}" + , + some_string + . + unwrap_or_else + ( + a + ) + ); + + some_string = None; + println!( + "The string or alt: {}" + , + some_string + . + unwrap_or_else + ( + || + { + let mut countdown = 0; + if is_false { + countdown = 10; + } + "alt string 3".to_owned() + } + ) + ); + + some_string = None; + let + a + = + || + { + let mut countdown = 0; + if is_false { + countdown = 10; + } + "alt string 4".to_owned() + }; + println!( + "The string or alt: {}" + , + some_string + . + unwrap_or_else + ( + a + ) + ); + + let + quote_closure + = + |val| + { + let mut countdown = 0; + if is_false { + countdown = 10; + } + format!("'{}'", val) + }; + println!( + "Repeated, quoted string: {:?}" + , + std::iter::repeat("repeat me") + .take(5) + .map + ( + quote_closure + ) + .collect::<Vec<_>>() + ); + + let + _unused_closure + = + | + mut countdown + | + { + if is_false { + countdown = 10; + } + "closure should be unused".to_owned() + }; + + let mut countdown = 10; + let _short_unused_closure = | _unused_arg: u8 | countdown += 1; + + + let short_used_covered_closure_macro = | used_arg: u8 | println!("called"); + let short_used_not_covered_closure_macro = | used_arg: u8 | println!("not called"); + let _short_unused_closure_macro = | _unused_arg: u8 | println!("not called"); + + + + + let _short_unused_closure_block = | _unused_arg: u8 | { println!("not called") }; + + let _shortish_unused_closure = | _unused_arg: u8 | { + println!("not called") + }; + + let _as_short_unused_closure = | + _unused_arg: u8 + | { println!("not called") }; + + let _almost_as_short_unused_closure = | + _unused_arg: u8 + | { println!("not called") } + ; + + + + + + let _short_unused_closure_line_break_no_block = | _unused_arg: u8 | +println!("not called") + ; + + let _short_unused_closure_line_break_no_block2 = + | _unused_arg: u8 | + println!( + "not called" + ) + ; + + let short_used_not_covered_closure_line_break_no_block_embedded_branch = + | _unused_arg: u8 | + println!( + "not called: {}", + if is_true { "check" } else { "me" } + ) + ; + + let short_used_not_covered_closure_line_break_block_embedded_branch = + | _unused_arg: u8 | + { + println!( + "not called: {}", + if is_true { "check" } else { "me" } + ) + } + ; + + let short_used_covered_closure_line_break_no_block_embedded_branch = + | _unused_arg: u8 | + println!( + "not called: {}", + if is_true { "check" } else { "me" } + ) + ; + + let short_used_covered_closure_line_break_block_embedded_branch = + | _unused_arg: u8 | + { + println!( + "not called: {}", + if is_true { "check" } else { "me" } + ) + } + ; + + if is_false { + short_used_not_covered_closure_macro(0); + short_used_not_covered_closure_line_break_no_block_embedded_branch(0); + short_used_not_covered_closure_line_break_block_embedded_branch(0); + } + short_used_covered_closure_macro(0); + short_used_covered_closure_line_break_no_block_embedded_branch(0); + short_used_covered_closure_line_break_block_embedded_branch(0); +} diff --git a/tests/coverage/closure_bug.coverage b/tests/coverage/closure_bug.coverage new file mode 100644 index 00000000000..f3299834bce --- /dev/null +++ b/tests/coverage/closure_bug.coverage @@ -0,0 +1,53 @@ + LL| |// Regression test for #115930. + LL| |// All of these closures are identical, and should produce identical output in + LL| |// the coverage report. However, an unstable sort was causing them to be treated + LL| |// inconsistently when preparing coverage spans. + LL| | + LL| 1|fn main() { + LL| 1| let truthy = std::env::args().len() == 1; + LL| 1| + LL| 1| let a + LL| | = + LL| | | + LL| | | + LL| 2| if truthy { true } else { false }; + ^0 + LL| | + LL| 1| a(); + LL| 1| if truthy { a(); } + ^0 + LL| | + LL| 1| let b + LL| | = + LL| | | + LL| | | + LL| 2| if truthy { true } else { false }; + ^0 + LL| | + LL| 1| b(); + LL| 1| if truthy { b(); } + ^0 + LL| | + LL| 1| let c + LL| | = + LL| | | + LL| | | + LL| 2| if truthy { true } else { false }; + ^0 + LL| | + LL| 1| c(); + LL| 1| if truthy { c(); } + ^0 + LL| | + LL| 1| let d + LL| | = + LL| | | + LL| | | + LL| 2| if truthy { true } else { false }; + ^0 + LL| | + LL| 1| d(); + LL| 1| if truthy { d(); } + ^0 + LL| 1|} + diff --git a/tests/coverage/closure_bug.rs b/tests/coverage/closure_bug.rs new file mode 100644 index 00000000000..739bc5f0b51 --- /dev/null +++ b/tests/coverage/closure_bug.rs @@ -0,0 +1,44 @@ +// Regression test for #115930. +// All of these closures are identical, and should produce identical output in +// the coverage report. However, an unstable sort was causing them to be treated +// inconsistently when preparing coverage spans. + +fn main() { + let truthy = std::env::args().len() == 1; + + let a + = + | + | + if truthy { true } else { false }; + + a(); + if truthy { a(); } + + let b + = + | + | + if truthy { true } else { false }; + + b(); + if truthy { b(); } + + let c + = + | + | + if truthy { true } else { false }; + + c(); + if truthy { c(); } + + let d + = + | + | + if truthy { true } else { false }; + + d(); + if truthy { d(); } +} diff --git a/tests/coverage/closure_macro.coverage b/tests/coverage/closure_macro.coverage new file mode 100644 index 00000000000..0f2c917e090 --- /dev/null +++ b/tests/coverage/closure_macro.coverage @@ -0,0 +1,42 @@ + LL| |// compile-flags: --edition=2018 + LL| |#![feature(coverage_attribute)] + LL| | + LL| |macro_rules! bail { + LL| | ($msg:literal $(,)?) => { + LL| | if $msg.len() > 0 { + LL| | println!("no msg"); + LL| | } else { + LL| | println!($msg); + LL| | } + LL| | return Err(String::from($msg)); + LL| | }; + LL| |} + LL| | + LL| |macro_rules! on_error { + LL| | ($value:expr, $error_message:expr) => { + LL| | $value.or_else(|e| { // FIXME(85000): no coverage in closure macros + LL| | let message = format!($error_message, e); + LL| | if message.len() > 0 { + LL| | println!("{}", message); + LL| | Ok(String::from("ok")) + LL| | } else { + LL| | bail!("error"); + LL| | } + LL| | }) + LL| | }; + LL| |} + LL| | + LL| 1|fn load_configuration_files() -> Result<String, String> { + LL| 1| Ok(String::from("config")) + LL| 1|} + LL| | + LL| 1|pub fn main() -> Result<(), String> { + LL| 1| println!("Starting service"); + LL| 1| let config = on_error!(load_configuration_files(), "Error loading configs: {}")?; + ^0 + LL| | + LL| 1| let startup_delay_duration = String::from("arg"); + LL| 1| let _ = (config, startup_delay_duration); + LL| 1| Ok(()) + LL| 1|} + diff --git a/tests/coverage/closure_macro.rs b/tests/coverage/closure_macro.rs new file mode 100644 index 00000000000..9b289141c2e --- /dev/null +++ b/tests/coverage/closure_macro.rs @@ -0,0 +1,40 @@ +// compile-flags: --edition=2018 +#![feature(coverage_attribute)] + +macro_rules! bail { + ($msg:literal $(,)?) => { + if $msg.len() > 0 { + println!("no msg"); + } else { + println!($msg); + } + return Err(String::from($msg)); + }; +} + +macro_rules! on_error { + ($value:expr, $error_message:expr) => { + $value.or_else(|e| { // FIXME(85000): no coverage in closure macros + let message = format!($error_message, e); + if message.len() > 0 { + println!("{}", message); + Ok(String::from("ok")) + } else { + bail!("error"); + } + }) + }; +} + +fn load_configuration_files() -> Result<String, String> { + Ok(String::from("config")) +} + +pub fn main() -> Result<(), String> { + println!("Starting service"); + let config = on_error!(load_configuration_files(), "Error loading configs: {}")?; + + let startup_delay_duration = String::from("arg"); + let _ = (config, startup_delay_duration); + Ok(()) +} diff --git a/tests/coverage/closure_macro_async.coverage b/tests/coverage/closure_macro_async.coverage new file mode 100644 index 00000000000..74247f1bc6f --- /dev/null +++ b/tests/coverage/closure_macro_async.coverage @@ -0,0 +1,79 @@ + LL| |// compile-flags: --edition=2018 + LL| |#![feature(coverage_attribute)] + LL| | + LL| |macro_rules! bail { + LL| | ($msg:literal $(,)?) => { + LL| | if $msg.len() > 0 { + LL| | println!("no msg"); + LL| | } else { + LL| | println!($msg); + LL| | } + LL| | return Err(String::from($msg)); + LL| | }; + LL| |} + LL| | + LL| |macro_rules! on_error { + LL| | ($value:expr, $error_message:expr) => { + LL| | $value.or_else(|e| { // FIXME(85000): no coverage in closure macros + LL| | let message = format!($error_message, e); + LL| | if message.len() > 0 { + LL| | println!("{}", message); + LL| | Ok(String::from("ok")) + LL| | } else { + LL| | bail!("error"); + LL| | } + LL| | }) + LL| | }; + LL| |} + LL| | + LL| 1|fn load_configuration_files() -> Result<String, String> { + LL| 1| Ok(String::from("config")) + LL| 1|} + LL| | + LL| 1|pub async fn test() -> Result<(), String> { + LL| 1| println!("Starting service"); + LL| 1| let config = on_error!(load_configuration_files(), "Error loading configs: {}")?; + ^0 + LL| | + LL| 1| let startup_delay_duration = String::from("arg"); + LL| 1| let _ = (config, startup_delay_duration); + LL| 1| Ok(()) + LL| 1|} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | executor::block_on(test()).unwrap(); + LL| |} + LL| | + LL| |mod executor { + LL| | use core::{ + LL| | future::Future, + LL| | pin::Pin, + LL| | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + LL| | }; + LL| | + LL| | #[coverage(off)] + LL| | pub fn block_on<F: Future>(mut future: F) -> F::Output { + LL| | let mut future = unsafe { Pin::new_unchecked(&mut future) }; + LL| | use std::hint::unreachable_unchecked; + LL| | static VTABLE: RawWakerVTable = RawWakerVTable::new( + LL| | #[coverage(off)] + LL| | |_| unsafe { unreachable_unchecked() }, // clone + LL| | #[coverage(off)] + LL| | |_| unsafe { unreachable_unchecked() }, // wake + LL| | #[coverage(off)] + LL| | |_| unsafe { unreachable_unchecked() }, // wake_by_ref + LL| | #[coverage(off)] + LL| | |_| (), + LL| | ); + LL| | let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + LL| | let mut context = Context::from_waker(&waker); + LL| | + LL| | loop { + LL| | if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + LL| | break val; + LL| | } + LL| | } + LL| | } + LL| |} + diff --git a/tests/coverage/closure_macro_async.rs b/tests/coverage/closure_macro_async.rs new file mode 100644 index 00000000000..b4275599e59 --- /dev/null +++ b/tests/coverage/closure_macro_async.rs @@ -0,0 +1,77 @@ +// compile-flags: --edition=2018 +#![feature(coverage_attribute)] + +macro_rules! bail { + ($msg:literal $(,)?) => { + if $msg.len() > 0 { + println!("no msg"); + } else { + println!($msg); + } + return Err(String::from($msg)); + }; +} + +macro_rules! on_error { + ($value:expr, $error_message:expr) => { + $value.or_else(|e| { // FIXME(85000): no coverage in closure macros + let message = format!($error_message, e); + if message.len() > 0 { + println!("{}", message); + Ok(String::from("ok")) + } else { + bail!("error"); + } + }) + }; +} + +fn load_configuration_files() -> Result<String, String> { + Ok(String::from("config")) +} + +pub async fn test() -> Result<(), String> { + println!("Starting service"); + let config = on_error!(load_configuration_files(), "Error loading configs: {}")?; + + let startup_delay_duration = String::from("arg"); + let _ = (config, startup_delay_duration); + Ok(()) +} + +#[coverage(off)] +fn main() { + executor::block_on(test()).unwrap(); +} + +mod executor { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + #[coverage(off)] + pub fn block_on<F: Future>(mut future: F) -> F::Output { + let mut future = unsafe { Pin::new_unchecked(&mut future) }; + use std::hint::unreachable_unchecked; + static VTABLE: RawWakerVTable = RawWakerVTable::new( + #[coverage(off)] + |_| unsafe { unreachable_unchecked() }, // clone + #[coverage(off)] + |_| unsafe { unreachable_unchecked() }, // wake + #[coverage(off)] + |_| unsafe { unreachable_unchecked() }, // wake_by_ref + #[coverage(off)] + |_| (), + ); + let waker = unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }; + let mut context = Context::from_waker(&waker); + + loop { + if let Poll::Ready(val) = future.as_mut().poll(&mut context) { + break val; + } + } + } +} diff --git a/tests/coverage/conditions.coverage b/tests/coverage/conditions.coverage new file mode 100644 index 00000000000..473335ff641 --- /dev/null +++ b/tests/coverage/conditions.coverage @@ -0,0 +1,93 @@ + LL| |#![allow(unused_assignments, unused_variables)] + LL| | + LL| 1|fn main() { + LL| 1| let mut countdown = 0; + LL| 1| if true { + LL| 1| countdown = 10; + LL| 1| } + ^0 + LL| | + LL| | const B: u32 = 100; + LL| 1| let x = if countdown > 7 { + LL| 1| countdown -= 4; + LL| 1| B + LL| 0| } else if countdown > 2 { + LL| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + LL| 0| countdown = 0; + LL| 0| } + LL| 0| countdown -= 5; + LL| 0| countdown + LL| | } else { + LL| 0| return; + LL| | }; + LL| | + LL| 1| let mut countdown = 0; + LL| 1| if true { + LL| 1| countdown = 10; + LL| 1| } + ^0 + LL| | + LL| 1| if countdown > 7 { + LL| 1| countdown -= 4; + LL| 1| } else if countdown > 2 { + ^0 + LL| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + LL| 0| countdown = 0; + LL| 0| } + LL| 0| countdown -= 5; + LL| | } else { + LL| 0| return; + LL| | } + LL| | + LL| 1| if true { + LL| 1| let mut countdown = 0; + LL| 1| if true { + LL| 1| countdown = 10; + LL| 1| } + ^0 + LL| | + LL| 1| if countdown > 7 { + LL| 1| countdown -= 4; + LL| 1| } + LL| 0| else if countdown > 2 { + LL| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + LL| 0| countdown = 0; + LL| 0| } + LL| 0| countdown -= 5; + LL| | } else { + LL| 0| return; + LL| | } + LL| 0| } + LL| | + LL| 1| let mut countdown = 0; + LL| 1| if true { + LL| 1| countdown = 1; + LL| 1| } + ^0 + LL| | + LL| 1| let z = if countdown > 7 { + ^0 + LL| 0| countdown -= 4; + LL| 1| } else if countdown > 2 { + LL| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + LL| 0| countdown = 0; + LL| 0| } + LL| 0| countdown -= 5; + LL| | } else { + LL| 1| let should_be_reachable = countdown; + LL| 1| println!("reached"); + LL| 1| return; + LL| | }; + LL| | + LL| 0| let w = if countdown > 7 { + LL| 0| countdown -= 4; + LL| 0| } else if countdown > 2 { + LL| 0| if countdown < 1 || countdown > 5 || countdown != 9 { + LL| 0| countdown = 0; + LL| 0| } + LL| 0| countdown -= 5; + LL| | } else { + LL| 0| return; + LL| | }; + LL| 1|} + diff --git a/tests/coverage/conditions.rs b/tests/coverage/conditions.rs new file mode 100644 index 00000000000..fa7f2a116c2 --- /dev/null +++ b/tests/coverage/conditions.rs @@ -0,0 +1,86 @@ +#![allow(unused_assignments, unused_variables)] + +fn main() { + let mut countdown = 0; + if true { + countdown = 10; + } + + const B: u32 = 100; + let x = if countdown > 7 { + countdown -= 4; + B + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + countdown + } else { + return; + }; + + let mut countdown = 0; + if true { + countdown = 10; + } + + if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + } + + if true { + let mut countdown = 0; + if true { + countdown = 10; + } + + if countdown > 7 { + countdown -= 4; + } + else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + } + } + + let mut countdown = 0; + if true { + countdown = 1; + } + + let z = if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + let should_be_reachable = countdown; + println!("reached"); + return; + }; + + let w = if countdown > 7 { + countdown -= 4; + } else if countdown > 2 { + if countdown < 1 || countdown > 5 || countdown != 9 { + countdown = 0; + } + countdown -= 5; + } else { + return; + }; +} diff --git a/tests/coverage/continue.coverage b/tests/coverage/continue.coverage new file mode 100644 index 00000000000..4916cac0038 --- /dev/null +++ b/tests/coverage/continue.coverage @@ -0,0 +1,70 @@ + LL| |#![allow(unused_assignments, unused_variables)] + LL| | + LL| 1|fn main() { + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| let mut x = 0; + LL| 11| for _ in 0..10 { + LL| 10| match is_true { + LL| | true => { + LL| 10| continue; + LL| | } + LL| 0| _ => { + LL| 0| x = 1; + LL| 0| } + LL| 0| } + LL| 0| x = 3; + LL| | } + LL| 11| for _ in 0..10 { + LL| 10| match is_true { + LL| 0| false => { + LL| 0| x = 1; + LL| 0| } + LL| | _ => { + LL| 10| continue; + LL| | } + LL| | } + LL| 0| x = 3; + LL| | } + LL| 11| for _ in 0..10 { + LL| 10| match is_true { + LL| 10| true => { + LL| 10| x = 1; + LL| 10| } + LL| | _ => { + LL| 0| continue; + LL| | } + LL| | } + LL| 10| x = 3; + LL| | } + LL| 11| for _ in 0..10 { + LL| 10| if is_true { + LL| 10| continue; + LL| 0| } + LL| 0| x = 3; + LL| | } + LL| 11| for _ in 0..10 { + LL| 10| match is_true { + LL| 0| false => { + LL| 0| x = 1; + LL| 0| } + LL| 10| _ => { + LL| 10| let _ = x; + LL| 10| } + LL| | } + LL| 10| x = 3; + LL| | } + LL| 1| for _ in 0..10 { + LL| 1| match is_true { + LL| 0| false => { + LL| 0| x = 1; + LL| 0| } + LL| | _ => { + LL| 1| break; + LL| | } + LL| | } + LL| 0| x = 3; + LL| | } + LL| 1| let _ = x; + LL| 1|} + diff --git a/tests/coverage/continue.rs b/tests/coverage/continue.rs new file mode 100644 index 00000000000..624aa98341b --- /dev/null +++ b/tests/coverage/continue.rs @@ -0,0 +1,69 @@ +#![allow(unused_assignments, unused_variables)] + +fn main() { + let is_true = std::env::args().len() == 1; + + let mut x = 0; + for _ in 0..10 { + match is_true { + true => { + continue; + } + _ => { + x = 1; + } + } + x = 3; + } + for _ in 0..10 { + match is_true { + false => { + x = 1; + } + _ => { + continue; + } + } + x = 3; + } + for _ in 0..10 { + match is_true { + true => { + x = 1; + } + _ => { + continue; + } + } + x = 3; + } + for _ in 0..10 { + if is_true { + continue; + } + x = 3; + } + for _ in 0..10 { + match is_true { + false => { + x = 1; + } + _ => { + let _ = x; + } + } + x = 3; + } + for _ in 0..10 { + match is_true { + false => { + x = 1; + } + _ => { + break; + } + } + x = 3; + } + let _ = x; +} diff --git a/tests/coverage/coroutine.coverage b/tests/coverage/coroutine.coverage new file mode 100644 index 00000000000..3a9791a0dbd --- /dev/null +++ b/tests/coverage/coroutine.coverage @@ -0,0 +1,32 @@ + LL| |#![feature(coroutines, coroutine_trait)] + LL| | + LL| |use std::ops::{Coroutine, CoroutineState}; + LL| |use std::pin::Pin; + LL| | + LL| |// The following implementation of a function called from a `yield` statement + LL| |// (apparently requiring the Result and the `String` type or constructor) + LL| |// creates conditions where the `coroutine::StateTransform` MIR transform will + LL| |// drop all `Counter` `Coverage` statements from a MIR. `simplify.rs` has logic + LL| |// to handle this condition, and still report dead block coverage. + LL| 1|fn get_u32(val: bool) -> Result<u32, String> { + LL| 1| if val { Ok(1) } else { Err(String::from("some error")) } + ^0 + LL| 1|} + LL| | + LL| 1|fn main() { + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| let mut coroutine = || { + LL| 1| yield get_u32(is_true); + LL| 1| return "foo"; + LL| 1| }; + LL| | + LL| 1| match Pin::new(&mut coroutine).resume(()) { + LL| 1| CoroutineState::Yielded(Ok(1)) => {} + LL| 0| _ => panic!("unexpected return from resume"), + LL| | } + LL| 1| match Pin::new(&mut coroutine).resume(()) { + LL| 1| CoroutineState::Complete("foo") => {} + LL| 0| _ => panic!("unexpected return from resume"), + LL| | } + LL| 1|} + diff --git a/tests/coverage/coroutine.rs b/tests/coverage/coroutine.rs new file mode 100644 index 00000000000..86d19af6f4f --- /dev/null +++ b/tests/coverage/coroutine.rs @@ -0,0 +1,30 @@ +#![feature(coroutines, coroutine_trait)] + +use std::ops::{Coroutine, CoroutineState}; +use std::pin::Pin; + +// The following implementation of a function called from a `yield` statement +// (apparently requiring the Result and the `String` type or constructor) +// creates conditions where the `coroutine::StateTransform` MIR transform will +// drop all `Counter` `Coverage` statements from a MIR. `simplify.rs` has logic +// to handle this condition, and still report dead block coverage. +fn get_u32(val: bool) -> Result<u32, String> { + if val { Ok(1) } else { Err(String::from("some error")) } +} + +fn main() { + let is_true = std::env::args().len() == 1; + let mut coroutine = || { + yield get_u32(is_true); + return "foo"; + }; + + match Pin::new(&mut coroutine).resume(()) { + CoroutineState::Yielded(Ok(1)) => {} + _ => panic!("unexpected return from resume"), + } + match Pin::new(&mut coroutine).resume(()) { + CoroutineState::Complete("foo") => {} + _ => panic!("unexpected return from resume"), + } +} diff --git a/tests/coverage/dead_code.coverage b/tests/coverage/dead_code.coverage new file mode 100644 index 00000000000..c4ee9f23f08 --- /dev/null +++ b/tests/coverage/dead_code.coverage @@ -0,0 +1,39 @@ + LL| |#![allow(dead_code, unused_assignments, unused_variables)] + LL| | + LL| 0|pub fn unused_pub_fn_not_in_library() { + LL| 0| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 0| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 0| // dependent conditions. + LL| 0| let is_true = std::env::args().len() == 1; + LL| 0| + LL| 0| let mut countdown = 0; + LL| 0| if is_true { + LL| 0| countdown = 10; + LL| 0| } + LL| 0|} + LL| | + LL| 0|fn unused_fn() { + LL| 0| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 0| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 0| // dependent conditions. + LL| 0| let is_true = std::env::args().len() == 1; + LL| 0| + LL| 0| let mut countdown = 0; + LL| 0| if is_true { + LL| 0| countdown = 10; + LL| 0| } + LL| 0|} + LL| | + LL| 1|fn main() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| let mut countdown = 0; + LL| 1| if is_true { + LL| 1| countdown = 10; + LL| 1| } + ^0 + LL| 1|} + diff --git a/tests/coverage/dead_code.rs b/tests/coverage/dead_code.rs new file mode 100644 index 00000000000..3492712a6f9 --- /dev/null +++ b/tests/coverage/dead_code.rs @@ -0,0 +1,37 @@ +#![allow(dead_code, unused_assignments, unused_variables)] + +pub fn unused_pub_fn_not_in_library() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + + let mut countdown = 0; + if is_true { + countdown = 10; + } +} + +fn unused_fn() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + + let mut countdown = 0; + if is_true { + countdown = 10; + } +} + +fn main() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + + let mut countdown = 0; + if is_true { + countdown = 10; + } +} diff --git a/tests/coverage/drop_trait.coverage b/tests/coverage/drop_trait.coverage new file mode 100644 index 00000000000..2c9439a93b1 --- /dev/null +++ b/tests/coverage/drop_trait.coverage @@ -0,0 +1,34 @@ + LL| |#![allow(unused_assignments)] + LL| |// failure-status: 1 + LL| | + LL| |struct Firework { + LL| | strength: i32, + LL| |} + LL| | + LL| |impl Drop for Firework { + LL| 2| fn drop(&mut self) { + LL| 2| println!("BOOM times {}!!!", self.strength); + LL| 2| } + LL| |} + LL| | + LL| 1|fn main() -> Result<(), u8> { + LL| 1| let _firecracker = Firework { strength: 1 }; + LL| 1| + LL| 1| let _tnt = Firework { strength: 100 }; + LL| 1| + LL| 1| if true { + LL| 1| println!("Exiting with error..."); + LL| 1| return Err(1); + LL| 0| } + LL| 0| + LL| 0| let _ = Firework { strength: 1000 }; + LL| 0| + LL| 0| Ok(()) + LL| 1|} + LL| | + LL| |// Expected program output: + LL| |// Exiting with error... + LL| |// BOOM times 100!!! + LL| |// BOOM times 1!!! + LL| |// Error: 1 + diff --git a/tests/coverage/drop_trait.rs b/tests/coverage/drop_trait.rs new file mode 100644 index 00000000000..7b062719c6b --- /dev/null +++ b/tests/coverage/drop_trait.rs @@ -0,0 +1,33 @@ +#![allow(unused_assignments)] +// failure-status: 1 + +struct Firework { + strength: i32, +} + +impl Drop for Firework { + fn drop(&mut self) { + println!("BOOM times {}!!!", self.strength); + } +} + +fn main() -> Result<(), u8> { + let _firecracker = Firework { strength: 1 }; + + let _tnt = Firework { strength: 100 }; + + if true { + println!("Exiting with error..."); + return Err(1); + } + + let _ = Firework { strength: 1000 }; + + Ok(()) +} + +// Expected program output: +// Exiting with error... +// BOOM times 100!!! +// BOOM times 1!!! +// Error: 1 diff --git a/tests/coverage/fn_sig_into_try.coverage b/tests/coverage/fn_sig_into_try.coverage new file mode 100644 index 00000000000..f1ddb1da780 --- /dev/null +++ b/tests/coverage/fn_sig_into_try.coverage @@ -0,0 +1,45 @@ + LL| |#![feature(coverage_attribute)] + LL| |// compile-flags: --edition=2021 + LL| | + LL| |// Regression test for inconsistent handling of function signature spans that + LL| |// are followed by code using the `?` operator. + LL| |// + LL| |// For each of these similar functions, the line containing the function + LL| |// signature should be handled in the same way. + LL| | + LL| 1|fn a() -> Option<i32> + LL| 1|{ + LL| 1| Some(7i32); + LL| 1| Some(0) + LL| 1|} + LL| | + LL| 1|fn b() -> Option<i32> + LL| 1|{ + LL| 1| Some(7i32)?; + ^0 + LL| 1| Some(0) + LL| 1|} + LL| | + LL| 1|fn c() -> Option<i32> + LL| 1|{ + LL| 1| let _ = Some(7i32)?; + ^0 + LL| 1| Some(0) + LL| 1|} + LL| | + LL| 1|fn d() -> Option<i32> + LL| 1|{ + LL| 1| let _: () = (); + LL| 1| Some(7i32)?; + ^0 + LL| 1| Some(0) + LL| 1|} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | a(); + LL| | b(); + LL| | c(); + LL| | d(); + LL| |} + diff --git a/tests/coverage/fn_sig_into_try.rs b/tests/coverage/fn_sig_into_try.rs new file mode 100644 index 00000000000..92850c8a188 --- /dev/null +++ b/tests/coverage/fn_sig_into_try.rs @@ -0,0 +1,41 @@ +#![feature(coverage_attribute)] +// compile-flags: --edition=2021 + +// Regression test for inconsistent handling of function signature spans that +// are followed by code using the `?` operator. +// +// For each of these similar functions, the line containing the function +// signature should be handled in the same way. + +fn a() -> Option<i32> +{ + Some(7i32); + Some(0) +} + +fn b() -> Option<i32> +{ + Some(7i32)?; + Some(0) +} + +fn c() -> Option<i32> +{ + let _ = Some(7i32)?; + Some(0) +} + +fn d() -> Option<i32> +{ + let _: () = (); + Some(7i32)?; + Some(0) +} + +#[coverage(off)] +fn main() { + a(); + b(); + c(); + d(); +} diff --git a/tests/coverage/generics.coverage b/tests/coverage/generics.coverage new file mode 100644 index 00000000000..09839183566 --- /dev/null +++ b/tests/coverage/generics.coverage @@ -0,0 +1,67 @@ + LL| |#![allow(unused_assignments)] + LL| |// failure-status: 1 + LL| | + LL| |struct Firework<T> where T: Copy + std::fmt::Display { + LL| | strength: T, + LL| |} + LL| | + LL| |impl<T> Firework<T> where T: Copy + std::fmt::Display { + LL| | #[inline(always)] + LL| 3| fn set_strength(&mut self, new_strength: T) { + LL| 3| self.strength = new_strength; + LL| 3| } + ------------------ + | <generics::Firework<f64>>::set_strength: + | LL| 2| fn set_strength(&mut self, new_strength: T) { + | LL| 2| self.strength = new_strength; + | LL| 2| } + ------------------ + | <generics::Firework<i32>>::set_strength: + | LL| 1| fn set_strength(&mut self, new_strength: T) { + | LL| 1| self.strength = new_strength; + | LL| 1| } + ------------------ + LL| |} + LL| | + LL| |impl<T> Drop for Firework<T> where T: Copy + std::fmt::Display { + LL| | #[inline(always)] + LL| 2| fn drop(&mut self) { + LL| 2| println!("BOOM times {}!!!", self.strength); + LL| 2| } + ------------------ + | <generics::Firework<f64> as core::ops::drop::Drop>::drop: + | LL| 1| fn drop(&mut self) { + | LL| 1| println!("BOOM times {}!!!", self.strength); + | LL| 1| } + ------------------ + | <generics::Firework<i32> as core::ops::drop::Drop>::drop: + | LL| 1| fn drop(&mut self) { + | LL| 1| println!("BOOM times {}!!!", self.strength); + | LL| 1| } + ------------------ + LL| |} + LL| | + LL| 1|fn main() -> Result<(), u8> { + LL| 1| let mut firecracker = Firework { strength: 1 }; + LL| 1| firecracker.set_strength(2); + LL| 1| + LL| 1| let mut tnt = Firework { strength: 100.1 }; + LL| 1| tnt.set_strength(200.1); + LL| 1| tnt.set_strength(300.3); + LL| 1| + LL| 1| if true { + LL| 1| println!("Exiting with error..."); + LL| 1| return Err(1); + LL| 0| } + LL| 0| + LL| 0| let _ = Firework { strength: 1000 }; + LL| 0| + LL| 0| Ok(()) + LL| 1|} + LL| | + LL| |// Expected program output: + LL| |// Exiting with error... + LL| |// BOOM times 100!!! + LL| |// BOOM times 1!!! + LL| |// Error: 1 + diff --git a/tests/coverage/generics.rs b/tests/coverage/generics.rs new file mode 100644 index 00000000000..bf4c2d8d685 --- /dev/null +++ b/tests/coverage/generics.rs @@ -0,0 +1,44 @@ +#![allow(unused_assignments)] +// failure-status: 1 + +struct Firework<T> where T: Copy + std::fmt::Display { + strength: T, +} + +impl<T> Firework<T> where T: Copy + std::fmt::Display { + #[inline(always)] + fn set_strength(&mut self, new_strength: T) { + self.strength = new_strength; + } +} + +impl<T> Drop for Firework<T> where T: Copy + std::fmt::Display { + #[inline(always)] + fn drop(&mut self) { + println!("BOOM times {}!!!", self.strength); + } +} + +fn main() -> Result<(), u8> { + let mut firecracker = Firework { strength: 1 }; + firecracker.set_strength(2); + + let mut tnt = Firework { strength: 100.1 }; + tnt.set_strength(200.1); + tnt.set_strength(300.3); + + if true { + println!("Exiting with error..."); + return Err(1); + } + + let _ = Firework { strength: 1000 }; + + Ok(()) +} + +// Expected program output: +// Exiting with error... +// BOOM times 100!!! +// BOOM times 1!!! +// Error: 1 diff --git a/tests/coverage/if.coverage b/tests/coverage/if.coverage new file mode 100644 index 00000000000..2e6845190aa --- /dev/null +++ b/tests/coverage/if.coverage @@ -0,0 +1,30 @@ + LL| |#![allow(unused_assignments, unused_variables)] + LL| | + LL| 1|fn main() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let + LL| 1| is_true + LL| 1| = + LL| 1| std::env::args().len() + LL| 1| == + LL| 1| 1 + LL| 1| ; + LL| 1| let + LL| 1| mut + LL| 1| countdown + LL| 1| = + LL| 1| 0 + LL| 1| ; + LL| 1| if + LL| 1| is_true + LL| 1| { + LL| 1| countdown + LL| 1| = + LL| 1| 10 + LL| 1| ; + LL| 1| } + ^0 + LL| 1|} + diff --git a/tests/coverage/if.rs b/tests/coverage/if.rs new file mode 100644 index 00000000000..8ad5042ff7b --- /dev/null +++ b/tests/coverage/if.rs @@ -0,0 +1,28 @@ +#![allow(unused_assignments, unused_variables)] + +fn main() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let + is_true + = + std::env::args().len() + == + 1 + ; + let + mut + countdown + = + 0 + ; + if + is_true + { + countdown + = + 10 + ; + } +} diff --git a/tests/coverage/if_else.coverage b/tests/coverage/if_else.coverage new file mode 100644 index 00000000000..0274401f004 --- /dev/null +++ b/tests/coverage/if_else.coverage @@ -0,0 +1,41 @@ + LL| |#![allow(unused_assignments, unused_variables)] + LL| | + LL| 1|fn main() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| let mut countdown = 0; + LL| 1| if + LL| 1| is_true + LL| 1| { + LL| 1| countdown + LL| 1| = + LL| 1| 10 + LL| 1| ; + LL| 1| } + LL| | else // Note coverage region difference without semicolon + LL| | { + LL| 0| countdown + LL| 0| = + LL| 0| 100 + LL| | } + LL| | + LL| | if + LL| 1| is_true + LL| 1| { + LL| 1| countdown + LL| 1| = + LL| 1| 10 + LL| 1| ; + LL| 1| } + LL| | else + LL| 0| { + LL| 0| countdown + LL| 0| = + LL| 0| 100 + LL| 0| ; + LL| 0| } + LL| 1|} + diff --git a/tests/coverage/if_else.rs b/tests/coverage/if_else.rs new file mode 100644 index 00000000000..3244e1e3afd --- /dev/null +++ b/tests/coverage/if_else.rs @@ -0,0 +1,40 @@ +#![allow(unused_assignments, unused_variables)] + +fn main() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + + let mut countdown = 0; + if + is_true + { + countdown + = + 10 + ; + } + else // Note coverage region difference without semicolon + { + countdown + = + 100 + } + + if + is_true + { + countdown + = + 10 + ; + } + else + { + countdown + = + 100 + ; + } +} diff --git a/tests/coverage/inline-dead.coverage b/tests/coverage/inline-dead.coverage new file mode 100644 index 00000000000..de96aa17acd --- /dev/null +++ b/tests/coverage/inline-dead.coverage @@ -0,0 +1,28 @@ + LL| |// Regression test for issue #98833. + LL| |// compile-flags: -Zinline-mir -Cdebug-assertions=off + LL| | + LL| 1|fn main() { + LL| 1| println!("{}", live::<false>()); + LL| 1| + LL| 1| let f = |x: bool| { + LL| | debug_assert!( + LL| 0| x + LL| | ); + LL| 1| }; + LL| 1| f(false); + LL| 1|} + LL| | + LL| |#[inline] + LL| 1|fn live<const B: bool>() -> u32 { + LL| 1| if B { + LL| 0| dead() + LL| | } else { + LL| 1| 0 + LL| | } + LL| 1|} + LL| | + LL| |#[inline] + LL| 0|fn dead() -> u32 { + LL| 0| 42 + LL| 0|} + diff --git a/tests/coverage/inline-dead.rs b/tests/coverage/inline-dead.rs new file mode 100644 index 00000000000..854fa062967 --- /dev/null +++ b/tests/coverage/inline-dead.rs @@ -0,0 +1,27 @@ +// Regression test for issue #98833. +// compile-flags: -Zinline-mir -Cdebug-assertions=off + +fn main() { + println!("{}", live::<false>()); + + let f = |x: bool| { + debug_assert!( + x + ); + }; + f(false); +} + +#[inline] +fn live<const B: bool>() -> u32 { + if B { + dead() + } else { + 0 + } +} + +#[inline] +fn dead() -> u32 { + 42 +} diff --git a/tests/coverage/inline.coverage b/tests/coverage/inline.coverage new file mode 100644 index 00000000000..6efd9a0830b --- /dev/null +++ b/tests/coverage/inline.coverage @@ -0,0 +1,54 @@ + LL| |// compile-flags: -Zinline-mir + LL| | + LL| |use std::fmt::Display; + LL| | + LL| 1|fn main() { + LL| 1| permutations(&['a', 'b', 'c']); + LL| 1|} + LL| | + LL| |#[inline(always)] + LL| 1|fn permutations<T: Copy + Display>(xs: &[T]) { + LL| 1| let mut ys = xs.to_owned(); + LL| 1| permutate(&mut ys, 0); + LL| 1|} + LL| | + LL| 16|fn permutate<T: Copy + Display>(xs: &mut [T], k: usize) { + LL| 16| let n = length(xs); + LL| 16| if k == n { + LL| 6| display(xs); + LL| 10| } else if k < n { + LL| 15| for i in k..n { + ^10 + LL| 15| swap(xs, i, k); + LL| 15| permutate(xs, k + 1); + LL| 15| swap(xs, i, k); + LL| 15| } + LL| 0| } else { + LL| 0| error(); + LL| 0| } + LL| 16|} + LL| | + LL| 16|fn length<T>(xs: &[T]) -> usize { + LL| 16| xs.len() + LL| 16|} + LL| | + LL| |#[inline] + LL| 30|fn swap<T: Copy>(xs: &mut [T], i: usize, j: usize) { + LL| 30| let t = xs[i]; + LL| 30| xs[i] = xs[j]; + LL| 30| xs[j] = t; + LL| 30|} + LL| | + LL| 6|fn display<T: Display>(xs: &[T]) { + LL| 24| for x in xs { + ^18 + LL| 18| print!("{}", x); + LL| 18| } + LL| 6| println!(); + LL| 6|} + LL| | + LL| |#[inline(always)] + LL| 0|fn error() { + LL| 0| panic!("error"); + LL| 0|} + diff --git a/tests/coverage/inline.rs b/tests/coverage/inline.rs new file mode 100644 index 00000000000..9cfab9ddbad --- /dev/null +++ b/tests/coverage/inline.rs @@ -0,0 +1,51 @@ +// compile-flags: -Zinline-mir + +use std::fmt::Display; + +fn main() { + permutations(&['a', 'b', 'c']); +} + +#[inline(always)] +fn permutations<T: Copy + Display>(xs: &[T]) { + let mut ys = xs.to_owned(); + permutate(&mut ys, 0); +} + +fn permutate<T: Copy + Display>(xs: &mut [T], k: usize) { + let n = length(xs); + if k == n { + display(xs); + } else if k < n { + for i in k..n { + swap(xs, i, k); + permutate(xs, k + 1); + swap(xs, i, k); + } + } else { + error(); + } +} + +fn length<T>(xs: &[T]) -> usize { + xs.len() +} + +#[inline] +fn swap<T: Copy>(xs: &mut [T], i: usize, j: usize) { + let t = xs[i]; + xs[i] = xs[j]; + xs[j] = t; +} + +fn display<T: Display>(xs: &[T]) { + for x in xs { + print!("{}", x); + } + println!(); +} + +#[inline(always)] +fn error() { + panic!("error"); +} diff --git a/tests/coverage/inner_items.coverage b/tests/coverage/inner_items.coverage new file mode 100644 index 00000000000..65493bcd9db --- /dev/null +++ b/tests/coverage/inner_items.coverage @@ -0,0 +1,60 @@ + LL| |#![allow(unused_assignments, unused_variables, dead_code)] + LL| | + LL| 1|fn main() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| let mut countdown = 0; + LL| 1| if is_true { + LL| 1| countdown = 10; + LL| 1| } + ^0 + LL| | + LL| | mod in_mod { + LL| | const IN_MOD_CONST: u32 = 1000; + LL| | } + LL| | + LL| 3| fn in_func(a: u32) { + LL| 3| let b = 1; + LL| 3| let c = a + b; + LL| 3| println!("c = {}", c) + LL| 3| } + LL| | + LL| | struct InStruct { + LL| | in_struct_field: u32, + LL| | } + LL| | + LL| | const IN_CONST: u32 = 1234; + LL| | + LL| | trait InTrait { + LL| | fn trait_func(&mut self, incr: u32); + LL| | + LL| 1| fn default_trait_func(&mut self) { + LL| 1| in_func(IN_CONST); + LL| 1| self.trait_func(IN_CONST); + LL| 1| } + LL| | } + LL| | + LL| | impl InTrait for InStruct { + LL| 1| fn trait_func(&mut self, incr: u32) { + LL| 1| self.in_struct_field += incr; + LL| 1| in_func(self.in_struct_field); + LL| 1| } + LL| | } + LL| | + LL| | type InType = String; + LL| | + LL| 1| if is_true { + LL| 1| in_func(countdown); + LL| 1| } + ^0 + LL| | + LL| 1| let mut val = InStruct { + LL| 1| in_struct_field: 101, + LL| 1| }; + LL| 1| + LL| 1| val.default_trait_func(); + LL| 1|} + diff --git a/tests/coverage/inner_items.rs b/tests/coverage/inner_items.rs new file mode 100644 index 00000000000..bcb62b3031c --- /dev/null +++ b/tests/coverage/inner_items.rs @@ -0,0 +1,57 @@ +#![allow(unused_assignments, unused_variables, dead_code)] + +fn main() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + + let mut countdown = 0; + if is_true { + countdown = 10; + } + + mod in_mod { + const IN_MOD_CONST: u32 = 1000; + } + + fn in_func(a: u32) { + let b = 1; + let c = a + b; + println!("c = {}", c) + } + + struct InStruct { + in_struct_field: u32, + } + + const IN_CONST: u32 = 1234; + + trait InTrait { + fn trait_func(&mut self, incr: u32); + + fn default_trait_func(&mut self) { + in_func(IN_CONST); + self.trait_func(IN_CONST); + } + } + + impl InTrait for InStruct { + fn trait_func(&mut self, incr: u32) { + self.in_struct_field += incr; + in_func(self.in_struct_field); + } + } + + type InType = String; + + if is_true { + in_func(countdown); + } + + let mut val = InStruct { + in_struct_field: 101, + }; + + val.default_trait_func(); +} diff --git a/tests/coverage/issue-83601.coverage b/tests/coverage/issue-83601.coverage new file mode 100644 index 00000000000..7995332cad3 --- /dev/null +++ b/tests/coverage/issue-83601.coverage @@ -0,0 +1,16 @@ + LL| |// Shows that rust-lang/rust/83601 is resolved + LL| | + LL| 3|#[derive(Debug, PartialEq, Eq)] + ^2 + LL| |struct Foo(u32); + LL| | + LL| 1|fn main() { + LL| 1| let bar = Foo(1); + LL| 1| assert_eq!(bar, Foo(1)); + LL| 1| let baz = Foo(0); + LL| 1| assert_ne!(baz, Foo(1)); + LL| 1| println!("{:?}", Foo(1)); + LL| 1| println!("{:?}", bar); + LL| 1| println!("{:?}", baz); + LL| 1|} + diff --git a/tests/coverage/issue-83601.rs b/tests/coverage/issue-83601.rs new file mode 100644 index 00000000000..0b72a81947c --- /dev/null +++ b/tests/coverage/issue-83601.rs @@ -0,0 +1,14 @@ +// Shows that rust-lang/rust/83601 is resolved + +#[derive(Debug, PartialEq, Eq)] +struct Foo(u32); + +fn main() { + let bar = Foo(1); + assert_eq!(bar, Foo(1)); + let baz = Foo(0); + assert_ne!(baz, Foo(1)); + println!("{:?}", Foo(1)); + println!("{:?}", bar); + println!("{:?}", baz); +} diff --git a/tests/coverage/issue-84561.coverage b/tests/coverage/issue-84561.coverage new file mode 100644 index 00000000000..e693866e277 --- /dev/null +++ b/tests/coverage/issue-84561.coverage @@ -0,0 +1,189 @@ + LL| |// This demonstrated Issue #84561: function-like macros produce unintuitive coverage results. + LL| | + LL| |// failure-status: 101 + LL| 21|#[derive(PartialEq, Eq)] + LL| |struct Foo(u32); + LL| 1|fn test3() { + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| let bar = Foo(1); + LL| 1| assert_eq!(bar, Foo(1)); + LL| 1| let baz = Foo(0); + LL| 1| assert_ne!(baz, Foo(1)); + LL| 1| println!("{:?}", Foo(1)); + LL| 1| println!("{:?}", bar); + LL| 1| println!("{:?}", baz); + LL| 1| + LL| 1| assert_eq!(Foo(1), Foo(1)); + LL| 1| assert_ne!(Foo(0), Foo(1)); + LL| 1| assert_eq!(Foo(2), Foo(2)); + LL| 1| let bar = Foo(0); + LL| 1| assert_ne!(bar, Foo(3)); + LL| 1| assert_ne!(Foo(0), Foo(4)); + LL| 1| assert_eq!(Foo(3), Foo(3), "with a message"); + ^0 + LL| 1| println!("{:?}", bar); + LL| 1| println!("{:?}", Foo(1)); + LL| 1| + LL| 1| assert_ne!(Foo(0), Foo(5), "{}", if is_true { "true message" } else { "false message" }); + ^0 ^0 ^0 + LL| 1| assert_ne!( + LL| | Foo(0) + LL| | , + LL| | Foo(5) + LL| | , + LL| 0| "{}" + LL| 0| , + LL| 0| if + LL| 0| is_true + LL| | { + LL| 0| "true message" + LL| | } else { + LL| 0| "false message" + LL| | } + LL| | ); + LL| | + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| assert_eq!( + LL| 1| Foo(1), + LL| 1| Foo(1) + LL| 1| ); + LL| 1| assert_ne!( + LL| 1| Foo(0), + LL| 1| Foo(1) + LL| 1| ); + LL| 1| assert_eq!( + LL| 1| Foo(2), + LL| 1| Foo(2) + LL| 1| ); + LL| 1| let bar = Foo(1); + LL| 1| assert_ne!( + LL| 1| bar, + LL| 1| Foo(3) + LL| 1| ); + LL| 1| if is_true { + LL| 1| assert_ne!( + LL| 1| Foo(0), + LL| 1| Foo(4) + LL| 1| ); + LL| | } else { + LL| 0| assert_eq!( + LL| 0| Foo(3), + LL| 0| Foo(3) + LL| 0| ); + LL| | } + LL| 1| if is_true { + LL| 1| assert_ne!( + LL| | Foo(0), + LL| | Foo(4), + LL| 0| "with a message" + LL| | ); + LL| | } else { + LL| 0| assert_eq!( + LL| | Foo(3), + LL| | Foo(3), + LL| 0| "with a message" + LL| | ); + LL| | } + LL| 1| assert_ne!( + LL| 1| if is_true { + LL| 1| Foo(0) + LL| | } else { + LL| 0| Foo(1) + LL| | }, + LL| | Foo(5) + LL| | ); + LL| 1| assert_ne!( + LL| 1| Foo(5), + LL| 1| if is_true { + LL| 1| Foo(0) + LL| | } else { + LL| 0| Foo(1) + LL| | } + LL| | ); + LL| 1| assert_ne!( + LL| 1| if is_true { + LL| 1| assert_eq!( + LL| 1| Foo(3), + LL| 1| Foo(3) + LL| 1| ); + LL| 1| Foo(0) + LL| | } else { + LL| 0| assert_ne!( + LL| 0| if is_true { + LL| 0| Foo(0) + LL| | } else { + LL| 0| Foo(1) + LL| | }, + LL| | Foo(5) + LL| | ); + LL| 0| Foo(1) + LL| | }, + LL| | Foo(5), + LL| 0| "with a message" + LL| | ); + LL| 1| assert_eq!( + LL| | Foo(1), + LL| | Foo(3), + LL| 1| "this assert should fail" + LL| | ); + LL| 0| assert_eq!( + LL| | Foo(3), + LL| | Foo(3), + LL| 0| "this assert should not be reached" + LL| | ); + LL| 0|} + LL| | + LL| |impl std::fmt::Debug for Foo { + LL| 7| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + LL| 7| write!(f, "try and succeed")?; + ^0 + LL| 7| Ok(()) + LL| 7| } + LL| |} + LL| | + LL| |static mut DEBUG_LEVEL_ENABLED: bool = false; + LL| | + LL| |macro_rules! debug { + LL| | ($($arg:tt)+) => ( + LL| | if unsafe { DEBUG_LEVEL_ENABLED } { + LL| | println!($($arg)+); + LL| | } + LL| | ); + LL| |} + LL| | + LL| 1|fn test1() { + LL| 1| debug!("debug is enabled"); + ^0 + LL| 1| debug!("debug is enabled"); + ^0 + LL| 1| let _ = 0; + LL| 1| debug!("debug is enabled"); + ^0 + LL| 1| unsafe { + LL| 1| DEBUG_LEVEL_ENABLED = true; + LL| 1| } + LL| 1| debug!("debug is enabled"); + LL| 1|} + LL| | + LL| |macro_rules! call_debug { + LL| | ($($arg:tt)+) => ( + LL| 1| fn call_print(s: &str) { + LL| 1| print!("{}", s); + LL| 1| } + LL| | + LL| | call_print("called from call_debug: "); + LL| | debug!($($arg)+); + LL| | ); + LL| |} + LL| | + LL| 1|fn test2() { + LL| 1| call_debug!("debug is enabled"); + LL| 1|} + LL| | + LL| 1|fn main() { + LL| 1| test1(); + LL| 1| test2(); + LL| 1| test3(); + LL| 1|} + diff --git a/tests/coverage/issue-84561.rs b/tests/coverage/issue-84561.rs new file mode 100644 index 00000000000..facf5b5b4cf --- /dev/null +++ b/tests/coverage/issue-84561.rs @@ -0,0 +1,182 @@ +// This demonstrated Issue #84561: function-like macros produce unintuitive coverage results. + +// failure-status: 101 +#[derive(PartialEq, Eq)] +struct Foo(u32); +fn test3() { + let is_true = std::env::args().len() == 1; + let bar = Foo(1); + assert_eq!(bar, Foo(1)); + let baz = Foo(0); + assert_ne!(baz, Foo(1)); + println!("{:?}", Foo(1)); + println!("{:?}", bar); + println!("{:?}", baz); + + assert_eq!(Foo(1), Foo(1)); + assert_ne!(Foo(0), Foo(1)); + assert_eq!(Foo(2), Foo(2)); + let bar = Foo(0); + assert_ne!(bar, Foo(3)); + assert_ne!(Foo(0), Foo(4)); + assert_eq!(Foo(3), Foo(3), "with a message"); + println!("{:?}", bar); + 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; + + assert_eq!( + Foo(1), + Foo(1) + ); + assert_ne!( + Foo(0), + Foo(1) + ); + assert_eq!( + Foo(2), + Foo(2) + ); + let bar = Foo(1); + assert_ne!( + bar, + Foo(3) + ); + if is_true { + assert_ne!( + Foo(0), + Foo(4) + ); + } else { + assert_eq!( + 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!( + if is_true { + Foo(0) + } else { + Foo(1) + }, + Foo(5) + ); + assert_ne!( + Foo(5), + if is_true { + Foo(0) + } else { + Foo(1) + } + ); + assert_ne!( + if is_true { + assert_eq!( + Foo(3), + Foo(3) + ); + Foo(0) + } else { + assert_ne!( + if is_true { + Foo(0) + } else { + Foo(1) + }, + Foo(5) + ); + Foo(1) + }, + 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"); +} + +macro_rules! call_debug { + ($($arg:tt)+) => ( + fn call_print(s: &str) { + print!("{}", s); + } + + call_print("called from call_debug: "); + debug!($($arg)+); + ); +} + +fn test2() { + call_debug!("debug is enabled"); +} + +fn main() { + test1(); + test2(); + test3(); +} diff --git a/tests/coverage/issue-85461.coverage b/tests/coverage/issue-85461.coverage new file mode 100644 index 00000000000..cbc910664d0 --- /dev/null +++ b/tests/coverage/issue-85461.coverage @@ -0,0 +1,37 @@ +$DIR/auxiliary/inline_always_with_dead_code.rs: + LL| |// compile-flags: -Cinstrument-coverage -Ccodegen-units=4 -Copt-level=0 + LL| | + LL| |#![allow(dead_code)] + LL| | + LL| |mod foo { + LL| | #[inline(always)] + LL| 2| pub fn called() {} + LL| | + LL| 0| fn uncalled() {} + LL| |} + LL| | + LL| |pub mod bar { + LL| 1| pub fn call_me() { + LL| 1| super::foo::called(); + LL| 1| } + LL| |} + LL| | + LL| |pub mod baz { + LL| 1| pub fn call_me() { + LL| 1| super::foo::called(); + LL| 1| } + LL| |} + +$DIR/issue-85461.rs: + LL| |// Regression test for #85461: MSVC sometimes fail to link with dead code and #[inline(always)] + LL| | + LL| |// aux-build:inline_always_with_dead_code.rs + LL| |extern crate inline_always_with_dead_code; + LL| | + LL| |use inline_always_with_dead_code::{bar, baz}; + LL| | + LL| 1|fn main() { + LL| 1| bar::call_me(); + LL| 1| baz::call_me(); + LL| 1|} + diff --git a/tests/coverage/issue-85461.rs b/tests/coverage/issue-85461.rs new file mode 100644 index 00000000000..9d4c90a827e --- /dev/null +++ b/tests/coverage/issue-85461.rs @@ -0,0 +1,11 @@ +// Regression test for #85461: MSVC sometimes fail to link with dead code and #[inline(always)] + +// aux-build:inline_always_with_dead_code.rs +extern crate inline_always_with_dead_code; + +use inline_always_with_dead_code::{bar, baz}; + +fn main() { + bar::call_me(); + baz::call_me(); +} diff --git a/tests/coverage/issue-93054.coverage b/tests/coverage/issue-93054.coverage new file mode 100644 index 00000000000..15f225326a4 --- /dev/null +++ b/tests/coverage/issue-93054.coverage @@ -0,0 +1,31 @@ + LL| |#![allow(dead_code, unreachable_code)] + LL| | + LL| |// Regression test for #93054: Functions using uninhabited types often only have a single, + LL| |// unreachable basic block which doesn't get instrumented. This should not cause llvm-cov to fail. + LL| |// Since these kinds functions can't be invoked anyway, it's ok to not have coverage data for them. + LL| | + LL| |// compile-flags: --edition=2021 + LL| | + LL| |enum Never {} + LL| | + LL| |impl Never { + LL| | fn foo(self) { + LL| | match self {} + LL| | make().map(|never| match never {}); + LL| | } + LL| | + LL| | fn bar(&self) { + LL| | match *self {} + LL| | } + LL| |} + LL| | + LL| 0|async fn foo2(never: Never) { + LL| | match never {} + LL| |} + LL| | + LL| 0|fn make() -> Option<Never> { + LL| 0| None + LL| 0|} + LL| | + LL| 1|fn main() {} + diff --git a/tests/coverage/issue-93054.rs b/tests/coverage/issue-93054.rs new file mode 100644 index 00000000000..da546cfeef8 --- /dev/null +++ b/tests/coverage/issue-93054.rs @@ -0,0 +1,30 @@ +#![allow(dead_code, unreachable_code)] + +// Regression test for #93054: Functions using uninhabited types often only have a single, +// unreachable basic block which doesn't get instrumented. This should not cause llvm-cov to fail. +// Since these kinds functions can't be invoked anyway, it's ok to not have coverage data for them. + +// compile-flags: --edition=2021 + +enum Never {} + +impl Never { + fn foo(self) { + match self {} + make().map(|never| match never {}); + } + + fn bar(&self) { + match *self {} + } +} + +async fn foo2(never: Never) { + match never {} +} + +fn make() -> Option<Never> { + None +} + +fn main() {} diff --git a/tests/coverage/lazy_boolean.coverage b/tests/coverage/lazy_boolean.coverage new file mode 100644 index 00000000000..8f14082ef68 --- /dev/null +++ b/tests/coverage/lazy_boolean.coverage @@ -0,0 +1,64 @@ + LL| |#![allow(unused_assignments, unused_variables)] + LL| | + LL| 1|fn main() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| let (mut a, mut b, mut c) = (0, 0, 0); + LL| 1| if is_true { + LL| 1| a = 1; + LL| 1| b = 10; + LL| 1| c = 100; + LL| 1| } + ^0 + LL| | let + LL| 1| somebool + LL| | = + LL| 1| a < b + LL| | || + LL| 0| b < c + LL| | ; + LL| | let + LL| 1| somebool + LL| | = + LL| 1| b < a + LL| | || + LL| 1| b < c + LL| | ; + LL| 1| let somebool = a < b && b < c; + LL| 1| let somebool = b < a && b < c; + ^0 + LL| | + LL| | if + LL| | ! + LL| 1| is_true + LL| 0| { + LL| 0| a = 2 + LL| 0| ; + LL| 1| } + LL| | + LL| | if + LL| 1| is_true + LL| 1| { + LL| 1| b = 30 + LL| 1| ; + LL| 1| } + LL| | else + LL| 0| { + LL| 0| c = 400 + LL| 0| ; + LL| 0| } + LL| | + LL| 1| if !is_true { + LL| 0| a = 2; + LL| 1| } + LL| | + LL| 1| if is_true { + LL| 1| b = 30; + LL| 1| } else { + LL| 0| c = 400; + LL| 0| } + LL| 1|} + diff --git a/tests/coverage/lazy_boolean.rs b/tests/coverage/lazy_boolean.rs new file mode 100644 index 00000000000..bb6219e851c --- /dev/null +++ b/tests/coverage/lazy_boolean.rs @@ -0,0 +1,61 @@ +#![allow(unused_assignments, unused_variables)] + +fn main() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + + let (mut a, mut b, mut c) = (0, 0, 0); + if is_true { + a = 1; + b = 10; + c = 100; + } + let + somebool + = + a < b + || + b < c + ; + let + somebool + = + b < a + || + b < c + ; + let somebool = a < b && b < c; + let somebool = b < a && b < c; + + if + ! + is_true + { + a = 2 + ; + } + + if + is_true + { + b = 30 + ; + } + else + { + c = 400 + ; + } + + if !is_true { + a = 2; + } + + if is_true { + b = 30; + } else { + c = 400; + } +} diff --git a/tests/coverage/long_and_wide.coverage b/tests/coverage/long_and_wide.coverage new file mode 100644 index 00000000000..d7d29ca40cd --- /dev/null +++ b/tests/coverage/long_and_wide.coverage @@ -0,0 +1,151 @@ + LL| |// compile-flags: --edition=2021 + LL| |// ignore-tidy-linelength + LL| | + LL| |// This file deliberately contains line and column numbers larger than 127, + LL| |// to verify that `coverage-dump`'s ULEB128 parser can handle them. + LL| | + LL| 1|fn main() { + LL| 1| wide_function(); + LL| 1| long_function(); + LL| 1| far_function(); + LL| 1|} + LL| | + LL| |#[rustfmt::skip] + LL| 1|fn wide_function() { /* */ (); } + LL| | + LL| 1|fn long_function() { + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1| // + LL| 1|} + LL| | + LL| 1|fn far_function() {} + diff --git a/tests/coverage/long_and_wide.rs b/tests/coverage/long_and_wide.rs new file mode 100644 index 00000000000..a7cbcd48027 --- /dev/null +++ b/tests/coverage/long_and_wide.rs @@ -0,0 +1,150 @@ +// compile-flags: --edition=2021 +// ignore-tidy-linelength + +// This file deliberately contains line and column numbers larger than 127, +// to verify that `coverage-dump`'s ULEB128 parser can handle them. + +fn main() { + wide_function(); + long_function(); + far_function(); +} + +#[rustfmt::skip] +fn wide_function() { /* */ (); } + +fn long_function() { + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // +} + +fn far_function() {} diff --git a/tests/coverage/loop_break_value.coverage b/tests/coverage/loop_break_value.coverage new file mode 100644 index 00000000000..1f0630636dd --- /dev/null +++ b/tests/coverage/loop_break_value.coverage @@ -0,0 +1,14 @@ + LL| |#![allow(unused_assignments, unused_variables)] + LL| | + LL| 1|fn main() { + LL| 1| let result + LL| 1| = + LL| 1| loop + LL| 1| { + LL| 1| break + LL| 1| 10 + LL| 1| ; + LL| 1| } + LL| 1| ; + LL| 1|} + diff --git a/tests/coverage/loop_break_value.rs b/tests/coverage/loop_break_value.rs new file mode 100644 index 00000000000..dbc4fad7a23 --- /dev/null +++ b/tests/coverage/loop_break_value.rs @@ -0,0 +1,13 @@ +#![allow(unused_assignments, unused_variables)] + +fn main() { + let result + = + loop + { + break + 10 + ; + } + ; +} diff --git a/tests/coverage/loops_branches.coverage b/tests/coverage/loops_branches.coverage new file mode 100644 index 00000000000..8cd6f1be3f7 --- /dev/null +++ b/tests/coverage/loops_branches.coverage @@ -0,0 +1,67 @@ + LL| |#![allow(unused_assignments, unused_variables, while_true)] + LL| | + LL| |// This test confirms that (1) unexecuted infinite loops are handled correctly by the + LL| |// InstrumentCoverage MIR pass; and (2) Counter Expressions that subtract from zero can be dropped. + LL| | + LL| |struct DebugTest; + LL| | + LL| |impl std::fmt::Debug for DebugTest { + LL| 1| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + LL| 1| if true { + LL| 1| if false { + LL| 0| while true {} + LL| 1| } + LL| 1| write!(f, "cool")?; + ^0 + LL| 0| } else { + LL| 0| } + LL| | + LL| 11| for i in 0..10 { + ^10 + LL| 10| if true { + LL| 10| if false { + LL| 0| while true {} + LL| 10| } + LL| 10| write!(f, "cool")?; + ^0 + LL| 0| } else { + LL| 0| } + LL| | } + LL| 1| Ok(()) + LL| 1| } + LL| |} + LL| | + LL| |struct DisplayTest; + LL| | + LL| |impl std::fmt::Display for DisplayTest { + LL| 1| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + LL| 1| if false { + LL| 0| } else { + LL| 1| if false { + LL| 0| while true {} + LL| 1| } + LL| 1| write!(f, "cool")?; + ^0 + LL| | } + LL| 11| for i in 0..10 { + ^10 + LL| 10| if false { + LL| 0| } else { + LL| 10| if false { + LL| 0| while true {} + LL| 10| } + LL| 10| write!(f, "cool")?; + ^0 + LL| | } + LL| | } + LL| 1| Ok(()) + LL| 1| } + LL| |} + LL| | + LL| 1|fn main() { + LL| 1| let debug_test = DebugTest; + LL| 1| println!("{:?}", debug_test); + LL| 1| let display_test = DisplayTest; + LL| 1| println!("{}", display_test); + LL| 1|} + diff --git a/tests/coverage/loops_branches.rs b/tests/coverage/loops_branches.rs new file mode 100644 index 00000000000..f3a343bcc1f --- /dev/null +++ b/tests/coverage/loops_branches.rs @@ -0,0 +1,60 @@ +#![allow(unused_assignments, unused_variables, while_true)] + +// This test confirms that (1) unexecuted infinite loops are handled correctly by the +// InstrumentCoverage MIR pass; and (2) Counter Expressions that subtract from zero can be dropped. + +struct DebugTest; + +impl std::fmt::Debug for DebugTest { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if true { + if false { + while true {} + } + write!(f, "cool")?; + } else { + } + + for i in 0..10 { + if true { + if false { + while true {} + } + write!(f, "cool")?; + } else { + } + } + Ok(()) + } +} + +struct DisplayTest; + +impl std::fmt::Display for DisplayTest { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if false { + } else { + if false { + while true {} + } + write!(f, "cool")?; + } + for i in 0..10 { + if false { + } else { + if false { + while true {} + } + write!(f, "cool")?; + } + } + Ok(()) + } +} + +fn main() { + let debug_test = DebugTest; + println!("{:?}", debug_test); + let display_test = DisplayTest; + println!("{}", display_test); +} diff --git a/tests/coverage/match_or_pattern.coverage b/tests/coverage/match_or_pattern.coverage new file mode 100644 index 00000000000..94c7967215c --- /dev/null +++ b/tests/coverage/match_or_pattern.coverage @@ -0,0 +1,48 @@ + LL| 1|fn main() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| let mut a: u8 = 0; + LL| 1| let mut b: u8 = 0; + LL| 1| if is_true { + LL| 1| a = 2; + LL| 1| b = 0; + LL| 1| } + ^0 + LL| 1| match (a, b) { + LL| | // Or patterns generate MIR `SwitchInt` with multiple targets to the same `BasicBlock`. + LL| | // This test confirms a fix for Issue #79569. + LL| 0| (0 | 1, 2 | 3) => {} + LL| 1| _ => {} + LL| | } + LL| 1| if is_true { + LL| 1| a = 0; + LL| 1| b = 0; + LL| 1| } + ^0 + LL| 1| match (a, b) { + LL| 0| (0 | 1, 2 | 3) => {} + LL| 1| _ => {} + LL| | } + LL| 1| if is_true { + LL| 1| a = 2; + LL| 1| b = 2; + LL| 1| } + ^0 + LL| 1| match (a, b) { + LL| 0| (0 | 1, 2 | 3) => {} + LL| 1| _ => {} + LL| | } + LL| 1| if is_true { + LL| 1| a = 0; + LL| 1| b = 2; + LL| 1| } + ^0 + LL| 1| match (a, b) { + LL| 1| (0 | 1, 2 | 3) => {} + LL| 0| _ => {} + LL| | } + LL| 1|} + diff --git a/tests/coverage/match_or_pattern.rs b/tests/coverage/match_or_pattern.rs new file mode 100644 index 00000000000..ab7aee51d1b --- /dev/null +++ b/tests/coverage/match_or_pattern.rs @@ -0,0 +1,43 @@ +fn main() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + + let mut a: u8 = 0; + let mut b: u8 = 0; + if is_true { + a = 2; + b = 0; + } + match (a, b) { + // Or patterns generate MIR `SwitchInt` with multiple targets to the same `BasicBlock`. + // This test confirms a fix for Issue #79569. + (0 | 1, 2 | 3) => {} + _ => {} + } + if is_true { + a = 0; + b = 0; + } + match (a, b) { + (0 | 1, 2 | 3) => {} + _ => {} + } + if is_true { + a = 2; + b = 2; + } + match (a, b) { + (0 | 1, 2 | 3) => {} + _ => {} + } + if is_true { + a = 0; + b = 2; + } + match (a, b) { + (0 | 1, 2 | 3) => {} + _ => {} + } +} diff --git a/tests/coverage/nested_loops.coverage b/tests/coverage/nested_loops.coverage new file mode 100644 index 00000000000..143d0d26aa7 --- /dev/null +++ b/tests/coverage/nested_loops.coverage @@ -0,0 +1,26 @@ + LL| 1|fn main() { + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| let mut countdown = 10; + LL| | + LL| 1| 'outer: while countdown > 0 { + LL| 1| let mut a = 100; + LL| 1| let mut b = 100; + LL| 3| for _ in 0..50 { + LL| 3| if a < 30 { + LL| 0| break; + LL| 3| } + LL| 3| a -= 5; + LL| 3| b -= 5; + LL| 3| if b < 90 { + LL| 1| a -= 10; + LL| 1| if is_true { + LL| 1| break 'outer; + LL| 0| } else { + LL| 0| a -= 2; + LL| 0| } + LL| 2| } + LL| | } + LL| 0| countdown -= 1; + LL| | } + LL| 1|} + diff --git a/tests/coverage/nested_loops.rs b/tests/coverage/nested_loops.rs new file mode 100644 index 00000000000..4c7c7842796 --- /dev/null +++ b/tests/coverage/nested_loops.rs @@ -0,0 +1,25 @@ +fn main() { + let is_true = std::env::args().len() == 1; + let mut countdown = 10; + + 'outer: while countdown > 0 { + let mut a = 100; + let mut b = 100; + for _ in 0..50 { + if a < 30 { + break; + } + a -= 5; + b -= 5; + if b < 90 { + a -= 10; + if is_true { + break 'outer; + } else { + a -= 2; + } + } + } + countdown -= 1; + } +} diff --git a/tests/coverage/no_cov_crate.coverage b/tests/coverage/no_cov_crate.coverage new file mode 100644 index 00000000000..f5a0322bf3e --- /dev/null +++ b/tests/coverage/no_cov_crate.coverage @@ -0,0 +1,89 @@ + LL| |// Enables `coverage(off)` on the entire crate + LL| |#![feature(coverage_attribute)] + LL| | + LL| |#[coverage(off)] + LL| |fn do_not_add_coverage_1() { + LL| | println!("called but not covered"); + LL| |} + LL| | + LL| |fn do_not_add_coverage_2() { + LL| | #![coverage(off)] + LL| | println!("called but not covered"); + LL| |} + LL| | + LL| |#[coverage(off)] + LL| |#[allow(dead_code)] + LL| |fn do_not_add_coverage_not_called() { + LL| | println!("not called and not covered"); + LL| |} + LL| | + LL| 1|fn add_coverage_1() { + LL| 1| println!("called and covered"); + LL| 1|} + LL| | + LL| 1|fn add_coverage_2() { + LL| 1| println!("called and covered"); + LL| 1|} + LL| | + LL| |#[allow(dead_code)] + LL| 0|fn add_coverage_not_called() { + LL| 0| println!("not called but covered"); + LL| 0|} + LL| | + LL| |// FIXME: These test-cases illustrate confusing results of nested functions. + LL| |// See https://github.com/rust-lang/rust/issues/93319 + LL| |mod nested_fns { + LL| | #[coverage(off)] + LL| | pub fn outer_not_covered(is_true: bool) { + LL| 1| fn inner(is_true: bool) { + LL| 1| if is_true { + LL| 1| println!("called and covered"); + LL| 1| } else { + LL| 0| println!("absolutely not covered"); + LL| 0| } + LL| 1| } + LL| | println!("called but not covered"); + LL| | inner(is_true); + LL| | } + LL| | + LL| 1| pub fn outer(is_true: bool) { + LL| 1| println!("called and covered"); + LL| 1| inner_not_covered(is_true); + LL| 1| + LL| 1| #[coverage(off)] + LL| 1| fn inner_not_covered(is_true: bool) { + LL| 1| if is_true { + LL| 1| println!("called but not covered"); + LL| 1| } else { + LL| 1| println!("absolutely not covered"); + LL| 1| } + LL| 1| } + LL| 1| } + LL| | + LL| 1| pub fn outer_both_covered(is_true: bool) { + LL| 1| println!("called and covered"); + LL| 1| inner(is_true); + LL| 1| + LL| 1| fn inner(is_true: bool) { + LL| 1| if is_true { + LL| 1| println!("called and covered"); + LL| 1| } else { + LL| 0| println!("absolutely not covered"); + LL| 0| } + LL| 1| } + LL| 1| } + LL| |} + LL| | + LL| 1|fn main() { + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| do_not_add_coverage_1(); + LL| 1| do_not_add_coverage_2(); + LL| 1| add_coverage_1(); + LL| 1| add_coverage_2(); + LL| 1| + LL| 1| nested_fns::outer_not_covered(is_true); + LL| 1| nested_fns::outer(is_true); + LL| 1| nested_fns::outer_both_covered(is_true); + LL| 1|} + diff --git a/tests/coverage/no_cov_crate.rs b/tests/coverage/no_cov_crate.rs new file mode 100644 index 00000000000..e12e4bc55e3 --- /dev/null +++ b/tests/coverage/no_cov_crate.rs @@ -0,0 +1,88 @@ +// Enables `coverage(off)` on the entire crate +#![feature(coverage_attribute)] + +#[coverage(off)] +fn do_not_add_coverage_1() { + println!("called but not covered"); +} + +fn do_not_add_coverage_2() { + #![coverage(off)] + println!("called but not covered"); +} + +#[coverage(off)] +#[allow(dead_code)] +fn do_not_add_coverage_not_called() { + println!("not called and not covered"); +} + +fn add_coverage_1() { + println!("called and covered"); +} + +fn add_coverage_2() { + println!("called and covered"); +} + +#[allow(dead_code)] +fn add_coverage_not_called() { + println!("not called but covered"); +} + +// FIXME: These test-cases illustrate confusing results of nested functions. +// See https://github.com/rust-lang/rust/issues/93319 +mod nested_fns { + #[coverage(off)] + pub fn outer_not_covered(is_true: bool) { + fn inner(is_true: bool) { + if is_true { + println!("called and covered"); + } else { + println!("absolutely not covered"); + } + } + println!("called but not covered"); + inner(is_true); + } + + pub fn outer(is_true: bool) { + println!("called and covered"); + inner_not_covered(is_true); + + #[coverage(off)] + fn inner_not_covered(is_true: bool) { + if is_true { + println!("called but not covered"); + } else { + println!("absolutely not covered"); + } + } + } + + pub fn outer_both_covered(is_true: bool) { + println!("called and covered"); + inner(is_true); + + fn inner(is_true: bool) { + if is_true { + println!("called and covered"); + } else { + println!("absolutely not covered"); + } + } + } +} + +fn main() { + let is_true = std::env::args().len() == 1; + + do_not_add_coverage_1(); + do_not_add_coverage_2(); + add_coverage_1(); + add_coverage_2(); + + nested_fns::outer_not_covered(is_true); + nested_fns::outer(is_true); + nested_fns::outer_both_covered(is_true); +} diff --git a/tests/coverage/overflow.coverage b/tests/coverage/overflow.coverage new file mode 100644 index 00000000000..4f8dffc0c48 --- /dev/null +++ b/tests/coverage/overflow.coverage @@ -0,0 +1,65 @@ + LL| |#![allow(unused_assignments)] + LL| |// compile-flags: -Coverflow-checks=yes + LL| |// failure-status: 101 + LL| | + LL| 4|fn might_overflow(to_add: u32) -> u32 { + LL| 4| if to_add > 5 { + LL| 1| println!("this will probably overflow"); + LL| 3| } + LL| 4| let add_to = u32::MAX - 5; + LL| 4| println!("does {} + {} overflow?", add_to, to_add); + LL| 4| let result = to_add + add_to; + LL| 4| println!("continuing after overflow check"); + LL| 4| result + LL| 4|} + LL| | + LL| 1|fn main() -> Result<(), u8> { + LL| 1| let mut countdown = 10; + LL| 11| while countdown > 0 { + LL| 11| if countdown == 1 { + LL| 1| let result = might_overflow(10); + LL| 1| println!("Result: {}", result); + LL| 10| } else if countdown < 5 { + LL| 3| let result = might_overflow(1); + LL| 3| println!("Result: {}", result); + LL| 6| } + LL| 10| countdown -= 1; + LL| | } + LL| 0| Ok(()) + LL| 0|} + LL| | + LL| |// Notes: + LL| |// 1. Compare this program and its coverage results to those of the very similar test `assert.rs`, + LL| |// and similar tests `panic_unwind.rs`, abort.rs` and `try_error_result.rs`. + LL| |// 2. This test confirms the coverage generated when a program passes or fails a + LL| |// compiler-generated `TerminatorKind::Assert` (based on an overflow check, in this case). + LL| |// 3. Similar to how the coverage instrumentation handles `TerminatorKind::Call`, + LL| |// compiler-generated assertion failures are assumed to be a symptom of a program bug, not + LL| |// expected behavior. To simplify the coverage graphs and keep instrumented programs as + LL| |// small and fast as possible, `Assert` terminators are assumed to always succeed, and + LL| |// therefore are considered "non-branching" terminators. So, an `Assert` terminator does not + LL| |// get its own coverage counter. + LL| |// 4. After an unhandled panic or failed Assert, coverage results may not always be intuitive. + LL| |// In this test, the final count for the statements after the `if` block in `might_overflow()` + LL| |// is 4, even though the lines after `to_add + add_to` were executed only 3 times. Depending + LL| |// on the MIR graph and the structure of the code, this count could have been 3 (which might + LL| |// have been valid for the overflowed add `+`, but should have been 4 for the lines before + LL| |// the overflow. The reason for this potential uncertainty is, a `CounterKind` is incremented + LL| |// via StatementKind::Counter at the end of the block, but (as in the case in this test), + LL| |// a CounterKind::Expression is always evaluated. In this case, the expression was based on + LL| |// a `Counter` incremented as part of the evaluation of the `if` expression, which was + LL| |// executed, and counted, 4 times, before reaching the overflow add. + LL| | + LL| |// If the program did not overflow, the coverage for `might_overflow()` would look like this: + LL| |// + LL| |// 4| |fn might_overflow(to_add: u32) -> u32 { + LL| |// 5| 4| if to_add > 5 { + LL| |// 6| 0| println!("this will probably overflow"); + LL| |// 7| 4| } + LL| |// 8| 4| let add_to = u32::MAX - 5; + LL| |// 9| 4| println!("does {} + {} overflow?", add_to, to_add); + LL| |// 10| 4| let result = to_add + add_to; + LL| |// 11| 4| println!("continuing after overflow check"); + LL| |// 12| 4| result + LL| |// 13| 4|} + diff --git a/tests/coverage/overflow.rs b/tests/coverage/overflow.rs new file mode 100644 index 00000000000..1c40771b274 --- /dev/null +++ b/tests/coverage/overflow.rs @@ -0,0 +1,64 @@ +#![allow(unused_assignments)] +// compile-flags: -Coverflow-checks=yes +// failure-status: 101 + +fn might_overflow(to_add: u32) -> u32 { + if to_add > 5 { + println!("this will probably overflow"); + } + let add_to = u32::MAX - 5; + println!("does {} + {} overflow?", add_to, to_add); + let result = to_add + add_to; + println!("continuing after overflow check"); + result +} + +fn main() -> Result<(), u8> { + let mut countdown = 10; + while countdown > 0 { + if countdown == 1 { + let result = might_overflow(10); + println!("Result: {}", result); + } else if countdown < 5 { + let result = might_overflow(1); + println!("Result: {}", result); + } + countdown -= 1; + } + Ok(()) +} + +// Notes: +// 1. Compare this program and its coverage results to those of the very similar test `assert.rs`, +// and similar tests `panic_unwind.rs`, abort.rs` and `try_error_result.rs`. +// 2. This test confirms the coverage generated when a program passes or fails a +// compiler-generated `TerminatorKind::Assert` (based on an overflow check, in this case). +// 3. Similar to how the coverage instrumentation handles `TerminatorKind::Call`, +// compiler-generated assertion failures are assumed to be a symptom of a program bug, not +// expected behavior. To simplify the coverage graphs and keep instrumented programs as +// small and fast as possible, `Assert` terminators are assumed to always succeed, and +// therefore are considered "non-branching" terminators. So, an `Assert` terminator does not +// get its own coverage counter. +// 4. After an unhandled panic or failed Assert, coverage results may not always be intuitive. +// In this test, the final count for the statements after the `if` block in `might_overflow()` +// is 4, even though the lines after `to_add + add_to` were executed only 3 times. Depending +// on the MIR graph and the structure of the code, this count could have been 3 (which might +// have been valid for the overflowed add `+`, but should have been 4 for the lines before +// the overflow. The reason for this potential uncertainty is, a `CounterKind` is incremented +// via StatementKind::Counter at the end of the block, but (as in the case in this test), +// a CounterKind::Expression is always evaluated. In this case, the expression was based on +// a `Counter` incremented as part of the evaluation of the `if` expression, which was +// executed, and counted, 4 times, before reaching the overflow add. + +// If the program did not overflow, the coverage for `might_overflow()` would look like this: +// +// 4| |fn might_overflow(to_add: u32) -> u32 { +// 5| 4| if to_add > 5 { +// 6| 0| println!("this will probably overflow"); +// 7| 4| } +// 8| 4| let add_to = u32::MAX - 5; +// 9| 4| println!("does {} + {} overflow?", add_to, to_add); +// 10| 4| let result = to_add + add_to; +// 11| 4| println!("continuing after overflow check"); +// 12| 4| result +// 13| 4|} diff --git a/tests/coverage/panic_unwind.coverage b/tests/coverage/panic_unwind.coverage new file mode 100644 index 00000000000..2b0777ef215 --- /dev/null +++ b/tests/coverage/panic_unwind.coverage @@ -0,0 +1,32 @@ + LL| |#![allow(unused_assignments)] + LL| |// failure-status: 101 + LL| | + LL| 4|fn might_panic(should_panic: bool) { + LL| 4| if should_panic { + LL| 1| println!("panicking..."); + LL| 1| panic!("panics"); + LL| 3| } else { + LL| 3| println!("Don't Panic"); + LL| 3| } + LL| 3|} + LL| | + LL| 1|fn main() -> Result<(), u8> { + LL| 1| let mut countdown = 10; + LL| 11| while countdown > 0 { + LL| 11| if countdown == 1 { + LL| 1| might_panic(true); + LL| 10| } else if countdown < 5 { + LL| 3| might_panic(false); + LL| 6| } + LL| 10| countdown -= 1; + LL| | } + LL| 0| Ok(()) + LL| 0|} + LL| | + LL| |// Notes: + LL| |// 1. Compare this program and its coverage results to those of the similar tests `abort.rs` and + LL| |// `try_error_result.rs`. + LL| |// 2. Since the `panic_unwind.rs` test is allowed to unwind, it is also allowed to execute the + LL| |// normal program exit cleanup, including writing out the current values of the coverage + LL| |// counters. + diff --git a/tests/coverage/panic_unwind.rs b/tests/coverage/panic_unwind.rs new file mode 100644 index 00000000000..638d2eb6aaa --- /dev/null +++ b/tests/coverage/panic_unwind.rs @@ -0,0 +1,31 @@ +#![allow(unused_assignments)] +// failure-status: 101 + +fn might_panic(should_panic: bool) { + if should_panic { + println!("panicking..."); + panic!("panics"); + } else { + println!("Don't Panic"); + } +} + +fn main() -> Result<(), u8> { + let mut countdown = 10; + while countdown > 0 { + if countdown == 1 { + might_panic(true); + } else if countdown < 5 { + might_panic(false); + } + countdown -= 1; + } + Ok(()) +} + +// Notes: +// 1. Compare this program and its coverage results to those of the similar tests `abort.rs` and +// `try_error_result.rs`. +// 2. Since the `panic_unwind.rs` test is allowed to unwind, it is also allowed to execute the +// normal program exit cleanup, including writing out the current values of the coverage +// counters. diff --git a/tests/coverage/partial_eq.coverage b/tests/coverage/partial_eq.coverage new file mode 100644 index 00000000000..c6d9ad6cf27 --- /dev/null +++ b/tests/coverage/partial_eq.coverage @@ -0,0 +1,48 @@ + LL| |// This test confirms an earlier problem was resolved, supporting the MIR graph generated by the + LL| |// structure of this test. + LL| | + LL| 2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + ^0 ^0 ^0 ^1 ^1 ^0^0 + LL| |pub struct Version { + LL| | major: usize, + LL| | minor: usize, + LL| | patch: usize, + LL| |} + LL| | + LL| |impl Version { + LL| 2| pub fn new(major: usize, minor: usize, patch: usize) -> Self { + LL| 2| Self { + LL| 2| major, + LL| 2| minor, + LL| 2| patch, + LL| 2| } + LL| 2| } + LL| |} + LL| | + LL| 1|fn main() { + LL| 1| let version_3_2_1 = Version::new(3, 2, 1); + LL| 1| let version_3_3_0 = Version::new(3, 3, 0); + LL| 1| + LL| 1| println!("{:?} < {:?} = {}", version_3_2_1, version_3_3_0, version_3_2_1 < version_3_3_0); + LL| 1|} + LL| | + LL| |/* + LL| | + LL| |This test verifies a bug was fixed that otherwise generated this error: + LL| | + LL| |thread 'rustc' panicked at 'No counters provided the source_hash for function: + LL| | Instance { + LL| | def: Item(WithOptConstParam { + LL| | did: DefId(0:101 ~ autocfg[c44a]::version::{impl#2}::partial_cmp), + LL| | const_param_did: None + LL| | }), + LL| | args: [] + LL| | }' + LL| |The `PartialOrd` derived by `Version` happened to generate a MIR that generated coverage + LL| |without a code region associated with any `Counter`. Code regions were associated with at least + LL| |one expression, which is allowed, but the `function_source_hash` was only passed to the codegen + LL| |(coverage mapgen) phase from a `Counter`s code region. A new method was added to pass the + LL| |`function_source_hash` without a code region, if necessary. + LL| | + LL| |*/ + diff --git a/tests/coverage/partial_eq.rs b/tests/coverage/partial_eq.rs new file mode 100644 index 00000000000..dd8b42c18ce --- /dev/null +++ b/tests/coverage/partial_eq.rs @@ -0,0 +1,46 @@ +// This test confirms an earlier problem was resolved, supporting the MIR graph generated by the +// structure of this test. + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Version { + major: usize, + minor: usize, + patch: usize, +} + +impl Version { + pub fn new(major: usize, minor: usize, patch: usize) -> Self { + Self { + major, + minor, + patch, + } + } +} + +fn main() { + let version_3_2_1 = Version::new(3, 2, 1); + let version_3_3_0 = Version::new(3, 3, 0); + + println!("{:?} < {:?} = {}", version_3_2_1, version_3_3_0, version_3_2_1 < version_3_3_0); +} + +/* + +This test verifies a bug was fixed that otherwise generated this error: + +thread 'rustc' panicked at 'No counters provided the source_hash for function: + Instance { + def: Item(WithOptConstParam { + did: DefId(0:101 ~ autocfg[c44a]::version::{impl#2}::partial_cmp), + const_param_did: None + }), + args: [] + }' +The `PartialOrd` derived by `Version` happened to generate a MIR that generated coverage +without a code region associated with any `Counter`. Code regions were associated with at least +one expression, which is allowed, but the `function_source_hash` was only passed to the codegen +(coverage mapgen) phase from a `Counter`s code region. A new method was added to pass the +`function_source_hash` without a code region, if necessary. + +*/ diff --git a/tests/coverage/simple_loop.coverage b/tests/coverage/simple_loop.coverage new file mode 100644 index 00000000000..691c6cd1e7d --- /dev/null +++ b/tests/coverage/simple_loop.coverage @@ -0,0 +1,37 @@ + LL| |#![allow(unused_assignments)] + LL| | + LL| 1|fn main() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| let mut countdown = 0; + LL| 1| + LL| 1| if + LL| 1| is_true + LL| 1| { + LL| 1| countdown + LL| 1| = + LL| 1| 10 + LL| 1| ; + LL| 1| } + ^0 + LL| | + LL| | loop + LL| | { + LL| | if + LL| 11| countdown + LL| 11| == + LL| 11| 0 + LL| | { + LL| 1| break + LL| | ; + LL| 10| } + LL| 10| countdown + LL| 10| -= + LL| 10| 1 + LL| | ; + LL| | } + LL| 1|} + diff --git a/tests/coverage/simple_loop.rs b/tests/coverage/simple_loop.rs new file mode 100644 index 00000000000..6f7f23475b8 --- /dev/null +++ b/tests/coverage/simple_loop.rs @@ -0,0 +1,35 @@ +#![allow(unused_assignments)] + +fn main() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + + let mut countdown = 0; + + if + is_true + { + countdown + = + 10 + ; + } + + loop + { + if + countdown + == + 0 + { + break + ; + } + countdown + -= + 1 + ; + } +} diff --git a/tests/coverage/simple_match.coverage b/tests/coverage/simple_match.coverage new file mode 100644 index 00000000000..7f5dd3bb646 --- /dev/null +++ b/tests/coverage/simple_match.coverage @@ -0,0 +1,45 @@ + LL| |#![allow(unused_assignments, unused_variables)] + LL| | + LL| 1|fn main() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| + LL| 1| let mut countdown = 1; + LL| 1| if is_true { + LL| 1| countdown = 0; + LL| 1| } + ^0 + LL| | + LL| | for + LL| | _ + LL| | in + LL| 3| 0..2 + LL| | { + LL| | let z + LL| | ; + LL| | match + LL| 2| countdown + LL| | { + LL| 1| x + LL| | if + LL| 2| x + LL| 2| < + LL| 2| 1 + LL| | => + LL| 1| { + LL| 1| z = countdown + LL| 1| ; + LL| 1| let y = countdown + LL| 1| ; + LL| 1| countdown = 10 + LL| 1| ; + LL| 1| } + LL| | _ + LL| | => + LL| 1| {} + LL| | } + LL| | } + LL| 1|} + diff --git a/tests/coverage/simple_match.rs b/tests/coverage/simple_match.rs new file mode 100644 index 00000000000..be99e59a826 --- /dev/null +++ b/tests/coverage/simple_match.rs @@ -0,0 +1,43 @@ +#![allow(unused_assignments, unused_variables)] + +fn main() { + // Initialize test constants in a way that cannot be determined at compile time, to ensure + // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + // dependent conditions. + let is_true = std::env::args().len() == 1; + + let mut countdown = 1; + if is_true { + countdown = 0; + } + + for + _ + in + 0..2 + { + let z + ; + match + countdown + { + x + if + x + < + 1 + => + { + z = countdown + ; + let y = countdown + ; + countdown = 10 + ; + } + _ + => + {} + } + } +} diff --git a/tests/coverage/sort_groups.coverage b/tests/coverage/sort_groups.coverage new file mode 100644 index 00000000000..c70d7b3b282 --- /dev/null +++ b/tests/coverage/sort_groups.coverage @@ -0,0 +1,49 @@ + LL| |// compile-flags: --edition=2021 + LL| | + LL| |// Demonstrate that `sort_subviews.py` can sort instantiation groups into a + LL| |// predictable order, while preserving their heterogeneous contents. + LL| | + LL| 1|fn main() { + LL| 1| let cond = std::env::args().len() > 1; + LL| 1| generic_fn::<()>(cond); + LL| 1| generic_fn::<&'static str>(!cond); + LL| 1| if std::hint::black_box(false) { + LL| 0| generic_fn::<char>(cond); + LL| 1| } + LL| 1| generic_fn::<i32>(cond); + LL| 1| other_fn(); + LL| 1|} + LL| | + LL| 3|fn generic_fn<T>(cond: bool) { + LL| 3| if cond { + LL| 1| println!("{}", std::any::type_name::<T>()); + LL| 2| } + LL| 3|} + ------------------ + | Unexecuted instantiation: sort_groups::generic_fn::<char> + ------------------ + | sort_groups::generic_fn::<&str>: + | LL| 1|fn generic_fn<T>(cond: bool) { + | LL| 1| if cond { + | LL| 1| println!("{}", std::any::type_name::<T>()); + | LL| 1| } + | ^0 + | LL| 1|} + ------------------ + | sort_groups::generic_fn::<()>: + | LL| 1|fn generic_fn<T>(cond: bool) { + | LL| 1| if cond { + | LL| 0| println!("{}", std::any::type_name::<T>()); + | LL| 1| } + | LL| 1|} + ------------------ + | sort_groups::generic_fn::<i32>: + | LL| 1|fn generic_fn<T>(cond: bool) { + | LL| 1| if cond { + | LL| 0| println!("{}", std::any::type_name::<T>()); + | LL| 1| } + | LL| 1|} + ------------------ + LL| | + LL| 1|fn other_fn() {} + diff --git a/tests/coverage/sort_groups.rs b/tests/coverage/sort_groups.rs new file mode 100644 index 00000000000..5adbbc6a87d --- /dev/null +++ b/tests/coverage/sort_groups.rs @@ -0,0 +1,23 @@ +// compile-flags: --edition=2021 + +// Demonstrate that `sort_subviews.py` can sort instantiation groups into a +// predictable order, while preserving their heterogeneous contents. + +fn main() { + let cond = std::env::args().len() > 1; + generic_fn::<()>(cond); + generic_fn::<&'static str>(!cond); + if std::hint::black_box(false) { + generic_fn::<char>(cond); + } + generic_fn::<i32>(cond); + other_fn(); +} + +fn generic_fn<T>(cond: bool) { + if cond { + println!("{}", std::any::type_name::<T>()); + } +} + +fn other_fn() {} diff --git a/tests/coverage/test_harness.coverage b/tests/coverage/test_harness.coverage new file mode 100644 index 00000000000..ff6009f6fce --- /dev/null +++ b/tests/coverage/test_harness.coverage @@ -0,0 +1,11 @@ + LL| |// Verify that the entry point injected by the test harness doesn't cause + LL| |// weird artifacts in the coverage report (e.g. issue #10749). + LL| | + LL| |// compile-flags: --test + LL| | + LL| |#[allow(dead_code)] + LL| 0|fn unused() {} + LL| | + LL| 1|#[test] + LL| 1|fn my_test() {} + diff --git a/tests/coverage/test_harness.rs b/tests/coverage/test_harness.rs new file mode 100644 index 00000000000..12a755734c1 --- /dev/null +++ b/tests/coverage/test_harness.rs @@ -0,0 +1,10 @@ +// Verify that the entry point injected by the test harness doesn't cause +// weird artifacts in the coverage report (e.g. issue #10749). + +// compile-flags: --test + +#[allow(dead_code)] +fn unused() {} + +#[test] +fn my_test() {} diff --git a/tests/coverage/tight_inf_loop.coverage b/tests/coverage/tight_inf_loop.coverage new file mode 100644 index 00000000000..c15c76b3aba --- /dev/null +++ b/tests/coverage/tight_inf_loop.coverage @@ -0,0 +1,6 @@ + LL| 1|fn main() { + LL| 1| if false { + LL| 0| loop {} + LL| 1| } + LL| 1|} + diff --git a/tests/coverage/tight_inf_loop.rs b/tests/coverage/tight_inf_loop.rs new file mode 100644 index 00000000000..cef99027aaa --- /dev/null +++ b/tests/coverage/tight_inf_loop.rs @@ -0,0 +1,5 @@ +fn main() { + if false { + loop {} + } +} diff --git a/tests/coverage/trivial.coverage b/tests/coverage/trivial.coverage new file mode 100644 index 00000000000..4f417979ef9 --- /dev/null +++ b/tests/coverage/trivial.coverage @@ -0,0 +1,4 @@ + LL| |// compile-flags: --edition=2021 + LL| | + LL| 1|fn main() {} + diff --git a/tests/coverage/trivial.rs b/tests/coverage/trivial.rs new file mode 100644 index 00000000000..d0a9b44fb36 --- /dev/null +++ b/tests/coverage/trivial.rs @@ -0,0 +1,3 @@ +// compile-flags: --edition=2021 + +fn main() {} diff --git a/tests/coverage/try_error_result.coverage b/tests/coverage/try_error_result.coverage new file mode 100644 index 00000000000..5d48cbd62f2 --- /dev/null +++ b/tests/coverage/try_error_result.coverage @@ -0,0 +1,125 @@ + LL| |#![allow(unused_assignments)] + LL| |// failure-status: 1 + LL| | + LL| 6|fn call(return_error: bool) -> Result<(), ()> { + LL| 6| if return_error { + LL| 1| Err(()) + LL| | } else { + LL| 5| Ok(()) + LL| | } + LL| 6|} + LL| | + LL| 1|fn test1() -> Result<(), ()> { + LL| 1| let mut + LL| 1| countdown = 10 + LL| | ; + LL| | for + LL| | _ + LL| | in + LL| 6| 0..10 + LL| | { + LL| 6| countdown + LL| 6| -= 1 + LL| 6| ; + LL| 6| if + LL| 6| countdown < 5 + LL| | { + LL| 1| call(/*return_error=*/ true)?; + LL| 0| call(/*return_error=*/ false)?; + LL| | } + LL| | else + LL| | { + LL| 5| call(/*return_error=*/ false)?; + ^0 + LL| | } + LL| | } + LL| 0| Ok(()) + LL| 1|} + LL| | + LL| |struct Thing1; + LL| |impl Thing1 { + LL| 18| fn get_thing_2(&self, return_error: bool) -> Result<Thing2, ()> { + LL| 18| if return_error { + LL| 1| Err(()) + LL| | } else { + LL| 17| Ok(Thing2 {}) + LL| | } + LL| 18| } + LL| |} + LL| | + LL| |struct Thing2; + LL| |impl Thing2 { + LL| 17| fn call(&self, return_error: bool) -> Result<u32, ()> { + LL| 17| if return_error { + LL| 2| Err(()) + LL| | } else { + LL| 15| Ok(57) + LL| | } + LL| 17| } + LL| |} + LL| | + LL| 1|fn test2() -> Result<(), ()> { + LL| 1| let thing1 = Thing1{}; + LL| 1| let mut + LL| 1| countdown = 10 + LL| | ; + LL| | for + LL| | _ + LL| | in + LL| 6| 0..10 + LL| | { + LL| 6| countdown + LL| 6| -= 1 + LL| 6| ; + LL| 6| if + LL| 6| countdown < 5 + LL| | { + LL| 1| thing1.get_thing_2(/*err=*/ false)?.call(/*err=*/ true).expect_err("call should fail"); + ^0 + LL| 1| thing1 + LL| 1| . + LL| 1| get_thing_2(/*return_error=*/ false) + LL| 0| ? + LL| | . + LL| 1| call(/*return_error=*/ true) + LL| 1| . + LL| 1| expect_err( + LL| 1| "call should fail" + LL| 1| ); + LL| 1| let val = thing1.get_thing_2(/*return_error=*/ true)?.call(/*return_error=*/ true)?; + ^0 ^0 ^0 + LL| 0| assert_eq!(val, 57); + LL| 0| let val = thing1.get_thing_2(/*return_error=*/ true)?.call(/*return_error=*/ false)?; + LL| 0| assert_eq!(val, 57); + LL| | } + LL| | else + LL| | { + LL| 5| let val = thing1.get_thing_2(/*return_error=*/ false)?.call(/*return_error=*/ false)?; + ^0 ^0 + LL| 5| assert_eq!(val, 57); + LL| 5| let val = thing1 + LL| 5| .get_thing_2(/*return_error=*/ false)? + ^0 + LL| 5| .call(/*return_error=*/ false)?; + ^0 + LL| 5| assert_eq!(val, 57); + LL| 5| let val = thing1 + LL| 5| .get_thing_2(/*return_error=*/ false) + LL| 0| ? + LL| 5| .call(/*return_error=*/ false) + LL| 0| ? + LL| | ; + LL| 5| assert_eq!(val, 57); + LL| | } + LL| | } + LL| 0| Ok(()) + LL| 1|} + LL| | + LL| 1|fn main() -> Result<(), ()> { + LL| 1| test1().expect_err("test1 should fail"); + LL| 1| test2() + LL| 1| ? + LL| | ; + LL| 0| Ok(()) + LL| 1|} + diff --git a/tests/coverage/try_error_result.rs b/tests/coverage/try_error_result.rs new file mode 100644 index 00000000000..557cbf22bfa --- /dev/null +++ b/tests/coverage/try_error_result.rs @@ -0,0 +1,118 @@ +#![allow(unused_assignments)] +// failure-status: 1 + +fn call(return_error: bool) -> Result<(), ()> { + if return_error { + Err(()) + } else { + Ok(()) + } +} + +fn test1() -> Result<(), ()> { + let mut + countdown = 10 + ; + for + _ + in + 0..10 + { + countdown + -= 1 + ; + if + countdown < 5 + { + call(/*return_error=*/ true)?; + call(/*return_error=*/ false)?; + } + else + { + call(/*return_error=*/ false)?; + } + } + Ok(()) +} + +struct Thing1; +impl Thing1 { + fn get_thing_2(&self, return_error: bool) -> Result<Thing2, ()> { + if return_error { + Err(()) + } else { + Ok(Thing2 {}) + } + } +} + +struct Thing2; +impl Thing2 { + fn call(&self, return_error: bool) -> Result<u32, ()> { + if return_error { + Err(()) + } else { + Ok(57) + } + } +} + +fn test2() -> Result<(), ()> { + let thing1 = Thing1{}; + let mut + countdown = 10 + ; + for + _ + in + 0..10 + { + countdown + -= 1 + ; + if + countdown < 5 + { + thing1.get_thing_2(/*err=*/ false)?.call(/*err=*/ true).expect_err("call should fail"); + thing1 + . + get_thing_2(/*return_error=*/ false) + ? + . + call(/*return_error=*/ true) + . + expect_err( + "call should fail" + ); + let val = thing1.get_thing_2(/*return_error=*/ true)?.call(/*return_error=*/ true)?; + assert_eq!(val, 57); + let val = thing1.get_thing_2(/*return_error=*/ true)?.call(/*return_error=*/ false)?; + assert_eq!(val, 57); + } + else + { + let val = thing1.get_thing_2(/*return_error=*/ false)?.call(/*return_error=*/ false)?; + assert_eq!(val, 57); + let val = thing1 + .get_thing_2(/*return_error=*/ false)? + .call(/*return_error=*/ false)?; + assert_eq!(val, 57); + let val = thing1 + .get_thing_2(/*return_error=*/ false) + ? + .call(/*return_error=*/ false) + ? + ; + assert_eq!(val, 57); + } + } + Ok(()) +} + +fn main() -> Result<(), ()> { + test1().expect_err("test1 should fail"); + test2() + ? + ; + Ok(()) +} diff --git a/tests/coverage/unreachable.coverage b/tests/coverage/unreachable.coverage new file mode 100644 index 00000000000..fa0ac9ccfa1 --- /dev/null +++ b/tests/coverage/unreachable.coverage @@ -0,0 +1,38 @@ + LL| |#![feature(core_intrinsics)] + LL| |#![feature(coverage_attribute)] + LL| |// compile-flags: --edition=2021 + LL| | + LL| |// <https://github.com/rust-lang/rust/issues/116171> + LL| |// If we instrument a function for coverage, but all of its counter-increment + LL| |// statements are removed by MIR optimizations, LLVM will think it isn't + LL| |// instrumented and it will disappear from coverage maps and coverage reports. + LL| |// Most MIR opts won't cause this because they tend not to remove statements + LL| |// from bb0, but `UnreachablePropagation` can do so if it sees that bb0 ends + LL| |// with `TerminatorKind::Unreachable`. + LL| | + LL| |use std::hint::{black_box, unreachable_unchecked}; + LL| | + LL| 0|static UNREACHABLE_CLOSURE: fn() = || unsafe { unreachable_unchecked() }; + LL| | + LL| 0|fn unreachable_function() { + LL| 0| unsafe { unreachable_unchecked() } + LL| 0|} + LL| | + LL| |// Use an intrinsic to more reliably trigger unreachable-propagation. + LL| 0|fn unreachable_intrinsic() { + LL| 0| unsafe { std::intrinsics::unreachable() } + LL| 0|} + LL| | + LL| |#[coverage(off)] + LL| |fn main() { + LL| | if black_box(false) { + LL| | UNREACHABLE_CLOSURE(); + LL| | } + LL| | if black_box(false) { + LL| | unreachable_function(); + LL| | } + LL| | if black_box(false) { + LL| | unreachable_intrinsic(); + LL| | } + LL| |} + diff --git a/tests/coverage/unreachable.rs b/tests/coverage/unreachable.rs new file mode 100644 index 00000000000..6385bfa160d --- /dev/null +++ b/tests/coverage/unreachable.rs @@ -0,0 +1,37 @@ +#![feature(core_intrinsics)] +#![feature(coverage_attribute)] +// compile-flags: --edition=2021 + +// <https://github.com/rust-lang/rust/issues/116171> +// If we instrument a function for coverage, but all of its counter-increment +// statements are removed by MIR optimizations, LLVM will think it isn't +// instrumented and it will disappear from coverage maps and coverage reports. +// Most MIR opts won't cause this because they tend not to remove statements +// from bb0, but `UnreachablePropagation` can do so if it sees that bb0 ends +// with `TerminatorKind::Unreachable`. + +use std::hint::{black_box, unreachable_unchecked}; + +static UNREACHABLE_CLOSURE: fn() = || unsafe { unreachable_unchecked() }; + +fn unreachable_function() { + unsafe { unreachable_unchecked() } +} + +// Use an intrinsic to more reliably trigger unreachable-propagation. +fn unreachable_intrinsic() { + unsafe { std::intrinsics::unreachable() } +} + +#[coverage(off)] +fn main() { + if black_box(false) { + UNREACHABLE_CLOSURE(); + } + if black_box(false) { + unreachable_function(); + } + if black_box(false) { + unreachable_intrinsic(); + } +} diff --git a/tests/coverage/unused.coverage b/tests/coverage/unused.coverage new file mode 100644 index 00000000000..056ffeb021a --- /dev/null +++ b/tests/coverage/unused.coverage @@ -0,0 +1,64 @@ + LL| |#![allow(dead_code, unused_assignments, unused_must_use, unused_variables)] + LL| | + LL| 2|fn foo<T>(x: T) { + LL| 2| let mut i = 0; + LL| 22| while i < 10 { + LL| 20| i != 0 || i != 0; + ^2 + LL| 20| i += 1; + LL| | } + LL| 2|} + ------------------ + | unused::foo::<f32>: + | LL| 1|fn foo<T>(x: T) { + | LL| 1| let mut i = 0; + | LL| 11| while i < 10 { + | LL| 10| i != 0 || i != 0; + | ^1 + | LL| 10| i += 1; + | LL| | } + | LL| 1|} + ------------------ + | unused::foo::<u32>: + | LL| 1|fn foo<T>(x: T) { + | LL| 1| let mut i = 0; + | LL| 11| while i < 10 { + | LL| 10| i != 0 || i != 0; + | ^1 + | LL| 10| i += 1; + | LL| | } + | LL| 1|} + ------------------ + LL| | + LL| 0|fn unused_template_func<T>(x: T) { + LL| 0| let mut i = 0; + LL| 0| while i < 10 { + LL| 0| i != 0 || i != 0; + LL| 0| i += 1; + LL| | } + LL| 0|} + LL| | + LL| 0|fn unused_func(mut a: u32) { + LL| 0| if a != 0 { + LL| 0| a += 1; + LL| 0| } + LL| 0|} + LL| | + LL| 0|fn unused_func2(mut a: u32) { + LL| 0| if a != 0 { + LL| 0| a += 1; + LL| 0| } + LL| 0|} + LL| | + LL| 0|fn unused_func3(mut a: u32) { + LL| 0| if a != 0 { + LL| 0| a += 1; + LL| 0| } + LL| 0|} + LL| | + LL| 1|fn main() -> Result<(), u8> { + LL| 1| foo::<u32>(0); + LL| 1| foo::<f32>(0.0); + LL| 1| Ok(()) + LL| 1|} + diff --git a/tests/coverage/unused.rs b/tests/coverage/unused.rs new file mode 100644 index 00000000000..d985af13547 --- /dev/null +++ b/tests/coverage/unused.rs @@ -0,0 +1,41 @@ +#![allow(dead_code, unused_assignments, unused_must_use, unused_variables)] + +fn foo<T>(x: T) { + let mut i = 0; + while i < 10 { + i != 0 || i != 0; + i += 1; + } +} + +fn unused_template_func<T>(x: T) { + let mut i = 0; + while i < 10 { + i != 0 || i != 0; + i += 1; + } +} + +fn unused_func(mut a: u32) { + if a != 0 { + a += 1; + } +} + +fn unused_func2(mut a: u32) { + if a != 0 { + a += 1; + } +} + +fn unused_func3(mut a: u32) { + if a != 0 { + a += 1; + } +} + +fn main() -> Result<(), u8> { + foo::<u32>(0); + foo::<f32>(0.0); + Ok(()) +} diff --git a/tests/coverage/unused_mod.coverage b/tests/coverage/unused_mod.coverage new file mode 100644 index 00000000000..a8fa24ac6a7 --- /dev/null +++ b/tests/coverage/unused_mod.coverage @@ -0,0 +1,14 @@ +$DIR/auxiliary/unused_mod_helper.rs: + LL| |#[allow(dead_code)] + LL| 0|pub fn never_called_function() { + LL| 0| println!("I am never called"); + LL| 0|} + +$DIR/unused_mod.rs: + LL| |#[path = "auxiliary/unused_mod_helper.rs"] + LL| |mod unused_module; + LL| | + LL| 1|fn main() { + LL| 1| println!("hello world!"); + LL| 1|} + diff --git a/tests/coverage/unused_mod.rs b/tests/coverage/unused_mod.rs new file mode 100644 index 00000000000..6e62839c998 --- /dev/null +++ b/tests/coverage/unused_mod.rs @@ -0,0 +1,6 @@ +#[path = "auxiliary/unused_mod_helper.rs"] +mod unused_module; + +fn main() { + println!("hello world!"); +} diff --git a/tests/coverage/uses_crate.coverage b/tests/coverage/uses_crate.coverage new file mode 100644 index 00000000000..50d92102a10 --- /dev/null +++ b/tests/coverage/uses_crate.coverage @@ -0,0 +1,173 @@ +$DIR/auxiliary/used_crate.rs: + LL| |#![allow(unused_assignments, unused_variables)] + LL| |// Verify that coverage works with optimizations: + LL| |// compile-flags: -C opt-level=3 + LL| | + LL| |use std::fmt::Debug; + LL| | + LL| 1|pub fn used_function() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| let mut countdown = 0; + LL| 1| if is_true { + LL| 1| countdown = 10; + LL| 1| } + ^0 + LL| 1| use_this_lib_crate(); + LL| 1|} + LL| | + LL| 2|pub fn used_only_from_bin_crate_generic_function<T: Debug>(arg: T) { + LL| 2| println!("used_only_from_bin_crate_generic_function with {:?}", arg); + LL| 2|} + ------------------ + | Unexecuted instantiation: used_crate::used_only_from_bin_crate_generic_function::<_> + ------------------ + | used_crate::used_only_from_bin_crate_generic_function::<&alloc::vec::Vec<i32>>: + | LL| 1|pub fn used_only_from_bin_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_only_from_bin_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + | used_crate::used_only_from_bin_crate_generic_function::<&str>: + | LL| 1|pub fn used_only_from_bin_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_only_from_bin_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + LL| |// Expect for above function: `Unexecuted instantiation` (see below) + LL| 2|pub fn used_only_from_this_lib_crate_generic_function<T: Debug>(arg: T) { + LL| 2| println!("used_only_from_this_lib_crate_generic_function with {:?}", arg); + LL| 2|} + ------------------ + | used_crate::used_only_from_this_lib_crate_generic_function::<&str>: + | LL| 1|pub fn used_only_from_this_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_only_from_this_lib_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + | used_crate::used_only_from_this_lib_crate_generic_function::<alloc::vec::Vec<i32>>: + | LL| 1|pub fn used_only_from_this_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_only_from_this_lib_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + LL| | + LL| 2|pub fn used_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + LL| 2| println!("used_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + LL| 2|} + ------------------ + | used_crate::used_from_bin_crate_and_lib_crate_generic_function::<&str>: + | LL| 1|pub fn used_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + | used_crate::used_from_bin_crate_and_lib_crate_generic_function::<alloc::vec::Vec<i32>>: + | LL| 1|pub fn used_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + LL| | + LL| 2|pub fn used_with_same_type_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + LL| 2| println!("used_with_same_type_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + LL| 2|} + ------------------ + | used_crate::used_with_same_type_from_bin_crate_and_lib_crate_generic_function::<&str>: + | LL| 1|pub fn used_with_same_type_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_with_same_type_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + | used_crate::used_with_same_type_from_bin_crate_and_lib_crate_generic_function::<&str>: + | LL| 1|pub fn used_with_same_type_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_with_same_type_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + LL| | + LL| 0|pub fn unused_generic_function<T: Debug>(arg: T) { + LL| 0| println!("unused_generic_function with {:?}", arg); + LL| 0|} + LL| | + LL| 0|pub fn unused_function() { + LL| 0| let is_true = std::env::args().len() == 1; + LL| 0| let mut countdown = 2; + LL| 0| if !is_true { + LL| 0| countdown = 20; + LL| 0| } + LL| 0|} + LL| | + LL| |#[allow(dead_code)] + LL| 0|fn unused_private_function() { + LL| 0| let is_true = std::env::args().len() == 1; + LL| 0| let mut countdown = 2; + LL| 0| if !is_true { + LL| 0| countdown = 20; + LL| 0| } + LL| 0|} + LL| | + LL| 1|fn use_this_lib_crate() { + LL| 1| used_from_bin_crate_and_lib_crate_generic_function("used from library used_crate.rs"); + LL| 1| used_with_same_type_from_bin_crate_and_lib_crate_generic_function( + LL| 1| "used from library used_crate.rs", + LL| 1| ); + LL| 1| let some_vec = vec![5, 6, 7, 8]; + LL| 1| used_only_from_this_lib_crate_generic_function(some_vec); + LL| 1| used_only_from_this_lib_crate_generic_function("used ONLY from library used_crate.rs"); + LL| 1|} + LL| | + LL| |// FIXME(#79651): "Unexecuted instantiation" errors appear in coverage results, + LL| |// for example: + LL| |// + LL| |// | Unexecuted instantiation: used_crate::used_only_from_bin_crate_generic_function::<_> + LL| |// + LL| |// These notices appear when `llvm-cov` shows instantiations. This may be a + LL| |// default option, but it can be suppressed with: + LL| |// + LL| |// ```shell + LL| |// $ `llvm-cov show --show-instantiations=0 ...` + LL| |// ``` + LL| |// + LL| |// The notice is triggered because the function is unused by the library itself, + LL| |// and when the library is compiled, a synthetic function is generated, so + LL| |// unused function coverage can be reported. Coverage can be skipped for unused + LL| |// generic functions with: + LL| |// + LL| |// ```shell + LL| |// $ `rustc -Zunstable-options -C instrument-coverage=except-unused-generics ...` + LL| |// ``` + LL| |// + LL| |// Even though this function is used by `uses_crate.rs` (and + LL| |// counted), with substitutions for `T`, those instantiations are only generated + LL| |// when the generic function is actually used (from the binary, not from this + LL| |// library crate). So the test result shows coverage for all instantiated + LL| |// versions and their generic type substitutions, plus the `Unexecuted + LL| |// instantiation` message for the non-substituted version. This is valid, but + LL| |// unfortunately a little confusing. + LL| |// + LL| |// The library crate has its own coverage map, and the only way to show unused + LL| |// coverage of a generic function is to include the generic function in the + LL| |// coverage map, marked as an "unused function". If the library were used by + LL| |// another binary that never used this generic function, then it would be valid + LL| |// to show the unused generic, with unknown substitution (`_`). + LL| |// + LL| |// The alternative is to exclude all generics from being included in the "unused + LL| |// functions" list, which would then omit coverage results for + LL| |// `unused_generic_function<T>()`, below. + +$DIR/uses_crate.rs: + LL| |// This test was failing on Linux for a while due to #110393 somehow making + LL| |// the unused functions not instrumented, but it seems to be fine now. + LL| | + LL| |// Validates coverage now works with optimizations + LL| |// compile-flags: -C opt-level=3 + LL| | + LL| |#![allow(unused_assignments, unused_variables)] + LL| | + LL| |// aux-build:used_crate.rs + LL| |extern crate used_crate; + LL| | + LL| 1|fn main() { + LL| 1| used_crate::used_function(); + LL| 1| let some_vec = vec![1, 2, 3, 4]; + LL| 1| used_crate::used_only_from_bin_crate_generic_function(&some_vec); + LL| 1| used_crate::used_only_from_bin_crate_generic_function("used from bin uses_crate.rs"); + LL| 1| used_crate::used_from_bin_crate_and_lib_crate_generic_function(some_vec); + LL| 1| used_crate::used_with_same_type_from_bin_crate_and_lib_crate_generic_function("interesting?"); + LL| 1|} + diff --git a/tests/coverage/uses_crate.rs b/tests/coverage/uses_crate.rs new file mode 100644 index 00000000000..ab203ad781d --- /dev/null +++ b/tests/coverage/uses_crate.rs @@ -0,0 +1,19 @@ +// This test was failing on Linux for a while due to #110393 somehow making +// the unused functions not instrumented, but it seems to be fine now. + +// Validates coverage now works with optimizations +// compile-flags: -C opt-level=3 + +#![allow(unused_assignments, unused_variables)] + +// aux-build:used_crate.rs +extern crate used_crate; + +fn main() { + used_crate::used_function(); + let some_vec = vec![1, 2, 3, 4]; + used_crate::used_only_from_bin_crate_generic_function(&some_vec); + used_crate::used_only_from_bin_crate_generic_function("used from bin uses_crate.rs"); + used_crate::used_from_bin_crate_and_lib_crate_generic_function(some_vec); + used_crate::used_with_same_type_from_bin_crate_and_lib_crate_generic_function("interesting?"); +} diff --git a/tests/coverage/uses_inline_crate.coverage b/tests/coverage/uses_inline_crate.coverage new file mode 100644 index 00000000000..cc0e01ffde1 --- /dev/null +++ b/tests/coverage/uses_inline_crate.coverage @@ -0,0 +1,159 @@ +$DIR/auxiliary/used_inline_crate.rs: + LL| |#![allow(unused_assignments, unused_variables)] + LL| |// Verify that coverage works with optimizations: + LL| |// compile-flags: -C opt-level=3 + LL| | + LL| |use std::fmt::Debug; + LL| | + LL| 1|pub fn used_function() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| let mut countdown = 0; + LL| 1| if is_true { + LL| 1| countdown = 10; + LL| 1| } + ^0 + LL| 1| use_this_lib_crate(); + LL| 1|} + LL| | + LL| |#[inline(always)] + LL| 1|pub fn used_inline_function() { + LL| 1| // Initialize test constants in a way that cannot be determined at compile time, to ensure + LL| 1| // rustc and LLVM cannot optimize out statements (or coverage counters) downstream from + LL| 1| // dependent conditions. + LL| 1| let is_true = std::env::args().len() == 1; + LL| 1| let mut countdown = 0; + LL| 1| if is_true { + LL| 1| countdown = 10; + LL| 1| } + ^0 + LL| 1| use_this_lib_crate(); + LL| 1|} + LL| | + LL| |#[inline(always)] + LL| 2|pub fn used_only_from_bin_crate_generic_function<T: Debug>(arg: T) { + LL| 2| println!("used_only_from_bin_crate_generic_function with {:?}", arg); + LL| 2|} + ------------------ + | Unexecuted instantiation: used_inline_crate::used_only_from_bin_crate_generic_function::<_> + ------------------ + | used_inline_crate::used_only_from_bin_crate_generic_function::<&alloc::vec::Vec<i32>>: + | LL| 1|pub fn used_only_from_bin_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_only_from_bin_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + | used_inline_crate::used_only_from_bin_crate_generic_function::<&str>: + | LL| 1|pub fn used_only_from_bin_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_only_from_bin_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + LL| |// Expect for above function: `Unexecuted instantiation` (see notes in `used_crate.rs`) + LL| | + LL| |#[inline(always)] + LL| 4|pub fn used_only_from_this_lib_crate_generic_function<T: Debug>(arg: T) { + LL| 4| println!("used_only_from_this_lib_crate_generic_function with {:?}", arg); + LL| 4|} + ------------------ + | used_inline_crate::used_only_from_this_lib_crate_generic_function::<&str>: + | LL| 2|pub fn used_only_from_this_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 2| println!("used_only_from_this_lib_crate_generic_function with {:?}", arg); + | LL| 2|} + ------------------ + | used_inline_crate::used_only_from_this_lib_crate_generic_function::<alloc::vec::Vec<i32>>: + | LL| 2|pub fn used_only_from_this_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 2| println!("used_only_from_this_lib_crate_generic_function with {:?}", arg); + | LL| 2|} + ------------------ + LL| | + LL| |#[inline(always)] + LL| 3|pub fn used_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + LL| 3| println!("used_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + LL| 3|} + ------------------ + | used_inline_crate::used_from_bin_crate_and_lib_crate_generic_function::<&str>: + | LL| 2|pub fn used_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 2| println!("used_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + | LL| 2|} + ------------------ + | used_inline_crate::used_from_bin_crate_and_lib_crate_generic_function::<alloc::vec::Vec<i32>>: + | LL| 1|pub fn used_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + LL| | + LL| |#[inline(always)] + LL| 3|pub fn used_with_same_type_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + LL| 3| println!("used_with_same_type_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + LL| 3|} + ------------------ + | used_inline_crate::used_with_same_type_from_bin_crate_and_lib_crate_generic_function::<&str>: + | LL| 1|pub fn used_with_same_type_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 1| println!("used_with_same_type_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + | LL| 1|} + ------------------ + | used_inline_crate::used_with_same_type_from_bin_crate_and_lib_crate_generic_function::<&str>: + | LL| 2|pub fn used_with_same_type_from_bin_crate_and_lib_crate_generic_function<T: Debug>(arg: T) { + | LL| 2| println!("used_with_same_type_from_bin_crate_and_lib_crate_generic_function with {:?}", arg); + | LL| 2|} + ------------------ + LL| | + LL| |#[inline(always)] + LL| 0|pub fn unused_generic_function<T: Debug>(arg: T) { + LL| 0| println!("unused_generic_function with {:?}", arg); + LL| 0|} + LL| | + LL| |#[inline(always)] + LL| 0|pub fn unused_function() { + LL| 0| let is_true = std::env::args().len() == 1; + LL| 0| let mut countdown = 2; + LL| 0| if !is_true { + LL| 0| countdown = 20; + LL| 0| } + LL| 0|} + LL| | + LL| |#[inline(always)] + LL| |#[allow(dead_code)] + LL| 0|fn unused_private_function() { + LL| 0| let is_true = std::env::args().len() == 1; + LL| 0| let mut countdown = 2; + LL| 0| if !is_true { + LL| 0| countdown = 20; + LL| 0| } + LL| 0|} + LL| | + LL| 2|fn use_this_lib_crate() { + LL| 2| used_from_bin_crate_and_lib_crate_generic_function("used from library used_crate.rs"); + LL| 2| used_with_same_type_from_bin_crate_and_lib_crate_generic_function( + LL| 2| "used from library used_crate.rs", + LL| 2| ); + LL| 2| let some_vec = vec![5, 6, 7, 8]; + LL| 2| used_only_from_this_lib_crate_generic_function(some_vec); + LL| 2| used_only_from_this_lib_crate_generic_function("used ONLY from library used_crate.rs"); + LL| 2|} + +$DIR/uses_inline_crate.rs: + LL| |// This test was failing on Linux for a while due to #110393 somehow making + LL| |// the unused functions not instrumented, but it seems to be fine now. + LL| | + LL| |// Validates coverage now works with optimizations + LL| |// compile-flags: -C opt-level=3 + LL| | + LL| |#![allow(unused_assignments, unused_variables)] + LL| | + LL| |// aux-build:used_inline_crate.rs + LL| |extern crate used_inline_crate; + LL| | + LL| 1|fn main() { + LL| 1| used_inline_crate::used_function(); + LL| 1| used_inline_crate::used_inline_function(); + LL| 1| let some_vec = vec![1, 2, 3, 4]; + LL| 1| used_inline_crate::used_only_from_bin_crate_generic_function(&some_vec); + LL| 1| used_inline_crate::used_only_from_bin_crate_generic_function("used from bin uses_crate.rs"); + LL| 1| used_inline_crate::used_from_bin_crate_and_lib_crate_generic_function(some_vec); + LL| 1| used_inline_crate::used_with_same_type_from_bin_crate_and_lib_crate_generic_function( + LL| 1| "interesting?", + LL| 1| ); + LL| 1|} + diff --git a/tests/coverage/uses_inline_crate.rs b/tests/coverage/uses_inline_crate.rs new file mode 100644 index 00000000000..d7b4c3c057f --- /dev/null +++ b/tests/coverage/uses_inline_crate.rs @@ -0,0 +1,22 @@ +// This test was failing on Linux for a while due to #110393 somehow making +// the unused functions not instrumented, but it seems to be fine now. + +// Validates coverage now works with optimizations +// compile-flags: -C opt-level=3 + +#![allow(unused_assignments, unused_variables)] + +// aux-build:used_inline_crate.rs +extern crate used_inline_crate; + +fn main() { + used_inline_crate::used_function(); + used_inline_crate::used_inline_function(); + let some_vec = vec![1, 2, 3, 4]; + used_inline_crate::used_only_from_bin_crate_generic_function(&some_vec); + used_inline_crate::used_only_from_bin_crate_generic_function("used from bin uses_crate.rs"); + used_inline_crate::used_from_bin_crate_and_lib_crate_generic_function(some_vec); + used_inline_crate::used_with_same_type_from_bin_crate_and_lib_crate_generic_function( + "interesting?", + ); +} diff --git a/tests/coverage/while.coverage b/tests/coverage/while.coverage new file mode 100644 index 00000000000..c9d497651c9 --- /dev/null +++ b/tests/coverage/while.coverage @@ -0,0 +1,6 @@ + LL| 1|fn main() { + LL| 1| let num = 9; + LL| 1| while num >= 10 { + LL| 0| } + LL| 1|} + diff --git a/tests/coverage/while.rs b/tests/coverage/while.rs new file mode 100644 index 00000000000..781b90b3566 --- /dev/null +++ b/tests/coverage/while.rs @@ -0,0 +1,5 @@ +fn main() { + let num = 9; + while num >= 10 { + } +} diff --git a/tests/coverage/while_early_ret.coverage b/tests/coverage/while_early_ret.coverage new file mode 100644 index 00000000000..49d39d36603 --- /dev/null +++ b/tests/coverage/while_early_ret.coverage @@ -0,0 +1,43 @@ + LL| |#![allow(unused_assignments)] + LL| |// failure-status: 1 + LL| | + LL| 1|fn main() -> Result<(), u8> { + LL| 1| let mut countdown = 10; + LL| | while + LL| 7| countdown + LL| 7| > + LL| 7| 0 + LL| | { + LL| | if + LL| 7| countdown + LL| 7| < + LL| 7| 5 + LL| | { + LL| | return + LL| | if + LL| 1| countdown + LL| 1| > + LL| 1| 8 + LL| | { + LL| 0| Ok(()) + LL| | } + LL| | else + LL| | { + LL| 1| Err(1) + LL| | } + LL| | ; + LL| 6| } + LL| 6| countdown + LL| 6| -= + LL| 6| 1 + LL| | ; + LL| | } + LL| 0| Ok(()) + LL| 1|} + LL| | + LL| |// ISSUE(77553): Originally, this test had `Err(1)` on line 22 (instead of `Ok(())`) and + LL| |// `std::process::exit(2)` on line 26 (instead of `Err(1)`); and this worked as expected on Linux + LL| |// and MacOS. But on Windows (MSVC, at least), the call to `std::process::exit()` exits the program + LL| |// without saving the InstrProf coverage counters. The use of `std::process:exit()` is not critical + LL| |// to the coverage test for early returns, but this is a limitation that should be fixed. + diff --git a/tests/coverage/while_early_ret.rs b/tests/coverage/while_early_ret.rs new file mode 100644 index 00000000000..b2f0eee2cc0 --- /dev/null +++ b/tests/coverage/while_early_ret.rs @@ -0,0 +1,42 @@ +#![allow(unused_assignments)] +// failure-status: 1 + +fn main() -> Result<(), u8> { + let mut countdown = 10; + while + countdown + > + 0 + { + if + countdown + < + 5 + { + return + if + countdown + > + 8 + { + Ok(()) + } + else + { + Err(1) + } + ; + } + countdown + -= + 1 + ; + } + Ok(()) +} + +// ISSUE(77553): Originally, this test had `Err(1)` on line 22 (instead of `Ok(())`) and +// `std::process::exit(2)` on line 26 (instead of `Err(1)`); and this worked as expected on Linux +// and MacOS. But on Windows (MSVC, at least), the call to `std::process::exit()` exits the program +// without saving the InstrProf coverage counters. The use of `std::process:exit()` is not critical +// to the coverage test for early returns, but this is a limitation that should be fixed. diff --git a/tests/coverage/yield.coverage b/tests/coverage/yield.coverage new file mode 100644 index 00000000000..d7e455f211e --- /dev/null +++ b/tests/coverage/yield.coverage @@ -0,0 +1,38 @@ + LL| |#![feature(coroutines, coroutine_trait)] + LL| |#![allow(unused_assignments)] + LL| | + LL| |use std::ops::{Coroutine, CoroutineState}; + LL| |use std::pin::Pin; + LL| | + LL| 1|fn main() { + LL| 1| let mut coroutine = || { + LL| 1| yield 1; + LL| 1| return "foo"; + LL| 1| }; + LL| | + LL| 1| match Pin::new(&mut coroutine).resume(()) { + LL| 1| CoroutineState::Yielded(1) => {} + LL| 0| _ => panic!("unexpected value from resume"), + LL| | } + LL| 1| match Pin::new(&mut coroutine).resume(()) { + LL| 1| CoroutineState::Complete("foo") => {} + LL| 0| _ => panic!("unexpected value from resume"), + LL| | } + LL| | + LL| 1| let mut coroutine = || { + LL| 1| yield 1; + LL| 1| yield 2; + LL| 0| yield 3; + LL| 0| return "foo"; + LL| 0| }; + LL| | + LL| 1| match Pin::new(&mut coroutine).resume(()) { + LL| 1| CoroutineState::Yielded(1) => {} + LL| 0| _ => panic!("unexpected value from resume"), + LL| | } + LL| 1| match Pin::new(&mut coroutine).resume(()) { + LL| 1| CoroutineState::Yielded(2) => {} + LL| 0| _ => panic!("unexpected value from resume"), + LL| | } + LL| 1|} + diff --git a/tests/coverage/yield.rs b/tests/coverage/yield.rs new file mode 100644 index 00000000000..b7e2ba31b59 --- /dev/null +++ b/tests/coverage/yield.rs @@ -0,0 +1,37 @@ +#![feature(coroutines, coroutine_trait)] +#![allow(unused_assignments)] + +use std::ops::{Coroutine, CoroutineState}; +use std::pin::Pin; + +fn main() { + let mut coroutine = || { + yield 1; + return "foo"; + }; + + match Pin::new(&mut coroutine).resume(()) { + CoroutineState::Yielded(1) => {} + _ => panic!("unexpected value from resume"), + } + match Pin::new(&mut coroutine).resume(()) { + CoroutineState::Complete("foo") => {} + _ => panic!("unexpected value from resume"), + } + + let mut coroutine = || { + yield 1; + yield 2; + yield 3; + return "foo"; + }; + + match Pin::new(&mut coroutine).resume(()) { + CoroutineState::Yielded(1) => {} + _ => panic!("unexpected value from resume"), + } + match Pin::new(&mut coroutine).resume(()) { + CoroutineState::Yielded(2) => {} + _ => panic!("unexpected value from resume"), + } +} |
