diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/cov-mark/src/lib.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/cov-mark/src/lib.rs')
-rw-r--r-- | vendor/cov-mark/src/lib.rs | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/vendor/cov-mark/src/lib.rs b/vendor/cov-mark/src/lib.rs new file mode 100644 index 000000000..a2ee0cad1 --- /dev/null +++ b/vendor/cov-mark/src/lib.rs @@ -0,0 +1,272 @@ +//! # cov-mark +//! +//! This library at its core provides two macros, [`hit!`] and [`check!`], +//! which can be used to verify that a certain test exercises a certain code +//! path. +//! +//! Here's a short example: +//! +//! ``` +//! fn parse_date(s: &str) -> Option<(u32, u32, u32)> { +//! if 10 != s.len() { +//! // By using `cov_mark::hit!` +//! // we signal which test exercises this code. +//! cov_mark::hit!(short_date); +//! return None; +//! } +//! +//! if "-" != &s[4..5] || "-" != &s[7..8] { +//! cov_mark::hit!(bad_dashes); +//! return None; +//! } +//! // ... +//! # unimplemented!() +//! } +//! +//! #[test] +//! fn test_parse_date() { +//! { +//! // `cov_mark::check!` creates a guard object +//! // that verifies that by the end of the scope we've +//! // executed the corresponding `cov_mark::hit`. +//! cov_mark::check!(short_date); +//! assert!(parse_date("92").is_none()); +//! } +//! +//! // This will fail. Although the test looks like +//! // it exercises the second condition, it does not. +//! // The call to `covers!` call catches this bug in the test. +//! // { +//! // cov_mark::check!(bad_dashes);; +//! // assert!(parse_date("27.2.2013").is_none()); +//! // } +//! +//! { +//! cov_mark::check!(bad_dashes); +//! assert!(parse_date("27.02.2013").is_none()); +//! } +//! } +//! +//! # fn main() {} +//! ``` +//! +//! Here's why coverage marks are useful: +//! +//! * Verifying that something doesn't happen for the *right* reason. +//! * Finding the test that exercises the code (grep for `check!(mark_name)`). +//! * Finding the code that the test is supposed to check (grep for `hit!(mark_name)`). +//! * Making sure that code and tests don't diverge during refactorings. +//! * (If used pervasively) Verifying that each branch has a corresponding test. +//! +//! # Limitations +//! +//! * In the presence of threads, [`check!`] may falsely pass, if the +//! mark is hit by an unrelated thread, unless the `thread-local` feature is +//! enabled. +//! * Names of marks must be globally unique. +//! * [`check!`] can't be used in integration tests. +//! +//! # Implementation Details +//! +//! Each coverage mark is an `AtomicUsize` counter. [`hit!`] increments +//! this counter, [`check!`] returns a guard object which checks that +//! the mark was incremented. When the `thread-local` feature is enabled, +//! each counter is stored as a thread-local, allowing for more accurate +//! counting. +//! +//! Counters are declared using `#[no_mangle]` attribute, so that [`hit!`] and +//! [`check!`] both can find the mark without the need to declare it in a common +//! module. Aren't the linkers ~~horrible~~ wonderful? +//! +//! # Safety +//! +//! Technically, the [`hit!`] macro in this crate is unsound: it uses `extern "C" +//! #[no_mangle]` symbol, which *could* clash with an existing symbol and cause +//! UB. For example, `cov_mark::hit!(main)` may segfault. That said: +//! +//! * If there's no existing symbol, the result is a linker error. +//! * If there exists corresponding `cov_mark::check!`, the result is a linker +//! error. +//! * Code inside `cov_mark::hit!` is hidden under `#[cfg(test)]`. +//! +//! It is believed that it is practically impossible to cause UB by accident +//! when using this crate. For this reason, the `hit` macro hides unsafety +//! inside. + +#![cfg_attr(nightly_docs, deny(broken_intra_doc_links))] +#![cfg_attr(nightly_docs, feature(doc_cfg))] + +/// Hit a mark with a specified name. +/// +/// # Example +/// +/// ``` +/// fn safe_divide(dividend: u32, divisor: u32) -> u32 { +/// if divisor == 0 { +/// cov_mark::hit!(save_divide_zero); +/// return 0; +/// } +/// dividend / divisor +/// } +/// ``` +#[macro_export] +macro_rules! hit { + ($ident:ident) => { + $crate::__rt::hit(stringify!($ident)) + }; +} + +/// Checks that a specified mark was hit. +/// +/// # Example +/// +/// ``` +/// #[test] +/// fn test_safe_divide_by_zero() { +/// cov_mark::check!(save_divide_zero); +/// assert_eq!(safe_divide(92, 0), 0); +/// } +/// # fn safe_divide(dividend: u32, divisor: u32) -> u32 { +/// # if divisor == 0 { +/// # cov_mark::hit!(save_divide_zero); +/// # return 0; +/// # } +/// # dividend / divisor +/// # } +/// ``` +#[macro_export] +macro_rules! check { + ($ident:ident) => { + let _guard = $crate::__rt::Guard::new(stringify!($ident), None); + }; +} + +/// Checks that a specified mark was hit exactly the specified number of times. +/// +/// # Example +/// +/// ``` +/// struct CoveredDropper; +/// impl Drop for CoveredDropper { +/// fn drop(&mut self) { +/// cov_mark::hit!(covered_dropper_drops); +/// } +/// } +/// +/// #[test] +/// fn drop_count_test() { +/// cov_mark::check_count!(covered_dropper_drops, 2); +/// let _covered_dropper1 = CoveredDropper; +/// let _covered_dropper2 = CoveredDropper; +/// } +/// ``` +#[macro_export] +macro_rules! check_count { + ($ident:ident, $count: literal) => { + let _guard = $crate::__rt::Guard::new(stringify!($ident), Some($count)); + }; +} + +#[doc(hidden)] +#[cfg(feature = "enable")] +pub mod __rt { + use std::{ + cell::{Cell, RefCell}, + rc::Rc, + sync::atomic::{AtomicUsize, Ordering::Relaxed}, + }; + + /// Even with + /// https://github.com/rust-lang/rust/commit/641d3b09f41b441f2c2618de32983ad3d13ea3f8, + /// a `thread_local` generates significantly more verbose assembly on x86 + /// than atomic, so we'll use atomic for the fast path + static LEVEL: AtomicUsize = AtomicUsize::new(0); + + thread_local! { + static ACTIVE: RefCell<Vec<Rc<GuardInner>>> = Default::default(); + } + + #[inline(always)] + pub fn hit(key: &'static str) { + if LEVEL.load(Relaxed) > 0 { + hit_cold(key); + } + + #[cold] + fn hit_cold(key: &'static str) -> () { + ACTIVE.with(|it| it.borrow().iter().for_each(|g| g.hit(key))) + } + } + + struct GuardInner { + mark: &'static str, + hits: Cell<usize>, + expected_hits: Option<usize>, + } + + pub struct Guard { + inner: Rc<GuardInner>, + } + + impl GuardInner { + fn hit(&self, key: &'static str) { + if key == self.mark { + self.hits.set(self.hits.get().saturating_add(1)) + } + } + } + + impl Guard { + pub fn new(mark: &'static str, expected_hits: Option<usize>) -> Guard { + let inner = GuardInner { + mark, + hits: Cell::new(0), + expected_hits, + }; + let inner = Rc::new(inner); + LEVEL.fetch_add(1, Relaxed); + ACTIVE.with(|it| it.borrow_mut().push(Rc::clone(&inner))); + Guard { inner } + } + } + + impl Drop for Guard { + fn drop(&mut self) { + LEVEL.fetch_sub(1, Relaxed); + let last = ACTIVE.with(|it| it.borrow_mut().pop()); + + if std::thread::panicking() { + return; + } + + let last = last.unwrap(); + assert!(Rc::ptr_eq(&last, &self.inner)); + let hit_count = last.hits.get(); + match last.expected_hits { + Some(hits) => assert!( + hit_count == hits, + "mark was hit {} times, expected {}", + hit_count, + hits + ), + None => assert!(hit_count > 0, "mark was not hit"), + } + } + } +} + +#[doc(hidden)] +#[cfg(not(feature = "enable"))] +pub mod __rt { + #[inline(always)] + pub fn hit(_: &'static str) {} + + #[non_exhaustive] + pub struct Guard; + + impl Guard { + pub fn new(_: &'static str, _: Option<usize>) -> Guard { + Guard + } + } +} |