1 //! # cov-mark 2 //! 3 //! This library at its core provides two macros, [`hit!`] and [`check!`], 4 //! which can be used to verify that a certain test exercises a certain code 5 //! path. 6 //! 7 //! Here's a short example: 8 //! 9 //! ``` 10 //! fn parse_date(s: &str) -> Option<(u32, u32, u32)> { 11 //! if 10 != s.len() { 12 //! // By using `cov_mark::hit!` 13 //! // we signal which test exercises this code. 14 //! cov_mark::hit!(short_date); 15 //! return None; 16 //! } 17 //! 18 //! if "-" != &s[4..5] || "-" != &s[7..8] { 19 //! cov_mark::hit!(bad_dashes); 20 //! return None; 21 //! } 22 //! // ... 23 //! # unimplemented!() 24 //! } 25 //! 26 //! #[test] 27 //! fn test_parse_date() { 28 //! { 29 //! // `cov_mark::check!` creates a guard object 30 //! // that verifies that by the end of the scope we've 31 //! // executed the corresponding `cov_mark::hit`. 32 //! cov_mark::check!(short_date); 33 //! assert!(parse_date("92").is_none()); 34 //! } 35 //! 36 //! // This will fail. Although the test looks like 37 //! // it exercises the second condition, it does not. 38 //! // The call to `covers!` call catches this bug in the test. 39 //! // { 40 //! // cov_mark::check!(bad_dashes);; 41 //! // assert!(parse_date("27.2.2013").is_none()); 42 //! // } 43 //! 44 //! { 45 //! cov_mark::check!(bad_dashes); 46 //! assert!(parse_date("27.02.2013").is_none()); 47 //! } 48 //! } 49 //! 50 //! # fn main() {} 51 //! ``` 52 //! 53 //! Here's why coverage marks are useful: 54 //! 55 //! * Verifying that something doesn't happen for the *right* reason. 56 //! * Finding the test that exercises the code (grep for `check!(mark_name)`). 57 //! * Finding the code that the test is supposed to check (grep for `hit!(mark_name)`). 58 //! * Making sure that code and tests don't diverge during refactorings. 59 //! * (If used pervasively) Verifying that each branch has a corresponding test. 60 //! 61 //! # Limitations 62 //! 63 //! * In the presence of threads, [`check!`] may falsely pass, if the 64 //! mark is hit by an unrelated thread, unless the `thread-local` feature is 65 //! enabled. 66 //! * Names of marks must be globally unique. 67 //! * [`check!`] can't be used in integration tests. 68 //! 69 //! # Implementation Details 70 //! 71 //! Each coverage mark is an `AtomicUsize` counter. [`hit!`] increments 72 //! this counter, [`check!`] returns a guard object which checks that 73 //! the mark was incremented. When the `thread-local` feature is enabled, 74 //! each counter is stored as a thread-local, allowing for more accurate 75 //! counting. 76 //! 77 //! Counters are declared using `#[no_mangle]` attribute, so that [`hit!`] and 78 //! [`check!`] both can find the mark without the need to declare it in a common 79 //! module. Aren't the linkers ~~horrible~~ wonderful? 80 //! 81 //! # Safety 82 //! 83 //! Technically, the [`hit!`] macro in this crate is unsound: it uses `extern "C" 84 //! #[no_mangle]` symbol, which *could* clash with an existing symbol and cause 85 //! UB. For example, `cov_mark::hit!(main)` may segfault. That said: 86 //! 87 //! * If there's no existing symbol, the result is a linker error. 88 //! * If there exists corresponding `cov_mark::check!`, the result is a linker 89 //! error. 90 //! * Code inside `cov_mark::hit!` is hidden under `#[cfg(test)]`. 91 //! 92 //! It is believed that it is practically impossible to cause UB by accident 93 //! when using this crate. For this reason, the `hit` macro hides unsafety 94 //! inside. 95 96 #![cfg_attr(nightly_docs, deny(broken_intra_doc_links))] 97 #![cfg_attr(nightly_docs, feature(doc_cfg))] 98 99 /// Hit a mark with a specified name. 100 /// 101 /// # Example 102 /// 103 /// ``` 104 /// fn safe_divide(dividend: u32, divisor: u32) -> u32 { 105 /// if divisor == 0 { 106 /// cov_mark::hit!(save_divide_zero); 107 /// return 0; 108 /// } 109 /// dividend / divisor 110 /// } 111 /// ``` 112 #[macro_export] 113 macro_rules! hit { 114 ($ident:ident) => { 115 $crate::__rt::hit(stringify!($ident)) 116 }; 117 } 118 119 /// Checks that a specified mark was hit. 120 /// 121 /// # Example 122 /// 123 /// ``` 124 /// #[test] 125 /// fn test_safe_divide_by_zero() { 126 /// cov_mark::check!(save_divide_zero); 127 /// assert_eq!(safe_divide(92, 0), 0); 128 /// } 129 /// # fn safe_divide(dividend: u32, divisor: u32) -> u32 { 130 /// # if divisor == 0 { 131 /// # cov_mark::hit!(save_divide_zero); 132 /// # return 0; 133 /// # } 134 /// # dividend / divisor 135 /// # } 136 /// ``` 137 #[macro_export] 138 macro_rules! check { 139 ($ident:ident) => { 140 let _guard = $crate::__rt::Guard::new(stringify!($ident), None); 141 }; 142 } 143 144 /// Checks that a specified mark was hit exactly the specified number of times. 145 /// 146 /// # Example 147 /// 148 /// ``` 149 /// struct CoveredDropper; 150 /// impl Drop for CoveredDropper { 151 /// fn drop(&mut self) { 152 /// cov_mark::hit!(covered_dropper_drops); 153 /// } 154 /// } 155 /// 156 /// #[test] 157 /// fn drop_count_test() { 158 /// cov_mark::check_count!(covered_dropper_drops, 2); 159 /// let _covered_dropper1 = CoveredDropper; 160 /// let _covered_dropper2 = CoveredDropper; 161 /// } 162 /// ``` 163 #[macro_export] 164 macro_rules! check_count { 165 ($ident:ident, $count: literal) => { 166 let _guard = $crate::__rt::Guard::new(stringify!($ident), Some($count)); 167 }; 168 } 169 170 #[doc(hidden)] 171 #[cfg(feature = "enable")] 172 pub mod __rt { 173 use std::{ 174 cell::{Cell, RefCell}, 175 rc::Rc, 176 sync::atomic::{AtomicUsize, Ordering::Relaxed}, 177 }; 178 179 /// Even with 180 /// https://github.com/rust-lang/rust/commit/641d3b09f41b441f2c2618de32983ad3d13ea3f8, 181 /// a `thread_local` generates significantly more verbose assembly on x86 182 /// than atomic, so we'll use atomic for the fast path 183 static LEVEL: AtomicUsize = AtomicUsize::new(0); 184 185 thread_local! { 186 static ACTIVE: RefCell<Vec<Rc<GuardInner>>> = Default::default(); 187 } 188 189 #[inline(always)] hit(key: &'static str)190 pub fn hit(key: &'static str) { 191 if LEVEL.load(Relaxed) > 0 { 192 hit_cold(key); 193 } 194 195 #[cold] 196 fn hit_cold(key: &'static str) -> () { 197 ACTIVE.with(|it| it.borrow().iter().for_each(|g| g.hit(key))) 198 } 199 } 200 201 struct GuardInner { 202 mark: &'static str, 203 hits: Cell<usize>, 204 expected_hits: Option<usize>, 205 } 206 207 pub struct Guard { 208 inner: Rc<GuardInner>, 209 } 210 211 impl GuardInner { hit(&self, key: &'static str)212 fn hit(&self, key: &'static str) { 213 if key == self.mark { 214 self.hits.set(self.hits.get().saturating_add(1)) 215 } 216 } 217 } 218 219 impl Guard { new(mark: &'static str, expected_hits: Option<usize>) -> Guard220 pub fn new(mark: &'static str, expected_hits: Option<usize>) -> Guard { 221 let inner = GuardInner { 222 mark, 223 hits: Cell::new(0), 224 expected_hits, 225 }; 226 let inner = Rc::new(inner); 227 LEVEL.fetch_add(1, Relaxed); 228 ACTIVE.with(|it| it.borrow_mut().push(Rc::clone(&inner))); 229 Guard { inner } 230 } 231 } 232 233 impl Drop for Guard { drop(&mut self)234 fn drop(&mut self) { 235 LEVEL.fetch_sub(1, Relaxed); 236 let last = ACTIVE.with(|it| it.borrow_mut().pop()); 237 238 if std::thread::panicking() { 239 return; 240 } 241 242 let last = last.unwrap(); 243 assert!(Rc::ptr_eq(&last, &self.inner)); 244 let hit_count = last.hits.get(); 245 match last.expected_hits { 246 Some(hits) => assert!( 247 hit_count == hits, 248 "mark was hit {} times, expected {}", 249 hit_count, 250 hits 251 ), 252 None => assert!(hit_count > 0, "mark was not hit"), 253 } 254 } 255 } 256 } 257 258 #[doc(hidden)] 259 #[cfg(not(feature = "enable"))] 260 pub mod __rt { 261 #[inline(always)] hit(_: &'static str)262 pub fn hit(_: &'static str) {} 263 264 #[non_exhaustive] 265 pub struct Guard; 266 267 impl Guard { new(_: &'static str, _: Option<usize>) -> Guard268 pub fn new(_: &'static str, _: Option<usize>) -> Guard { 269 Guard 270 } 271 } 272 } 273