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 `check!` 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 //! * Names of marks must be globally unique. 64 //! 65 //! # Implementation Details 66 //! 67 //! Each coverage mark is an `AtomicUsize` counter. [`hit!`] increments 68 //! this counter, [`check!`] returns a guard object which checks that 69 //! the mark was incremented. 70 //! Each counter is stored as a thread-local, allowing for accurate per-thread 71 //! counting. 72 73 #![cfg_attr(nightly_docs, deny(broken_intra_doc_links))] 74 #![cfg_attr(nightly_docs, feature(doc_cfg))] 75 76 /// Hit a mark with a specified name. 77 /// 78 /// # Example 79 /// 80 /// ``` 81 /// fn safe_divide(dividend: u32, divisor: u32) -> u32 { 82 /// if divisor == 0 { 83 /// cov_mark::hit!(save_divide_zero); 84 /// return 0; 85 /// } 86 /// dividend / divisor 87 /// } 88 /// ``` 89 #[macro_export] 90 macro_rules! hit { 91 ($ident:ident) => { 92 $crate::__rt::hit(stringify!($ident)) 93 }; 94 } 95 96 /// Checks that a specified mark was hit. 97 /// 98 /// # Example 99 /// 100 /// ``` 101 /// #[test] 102 /// fn test_safe_divide_by_zero() { 103 /// cov_mark::check!(save_divide_zero); 104 /// assert_eq!(safe_divide(92, 0), 0); 105 /// } 106 /// # fn safe_divide(dividend: u32, divisor: u32) -> u32 { 107 /// # if divisor == 0 { 108 /// # cov_mark::hit!(save_divide_zero); 109 /// # return 0; 110 /// # } 111 /// # dividend / divisor 112 /// # } 113 /// ``` 114 #[macro_export] 115 macro_rules! check { 116 ($ident:ident) => { 117 let _guard = $crate::__rt::Guard::new(stringify!($ident), None); 118 }; 119 } 120 121 /// Checks that a specified mark was hit exactly the specified number of times. 122 /// 123 /// # Example 124 /// 125 /// ``` 126 /// struct CoveredDropper; 127 /// impl Drop for CoveredDropper { 128 /// fn drop(&mut self) { 129 /// cov_mark::hit!(covered_dropper_drops); 130 /// } 131 /// } 132 /// 133 /// #[test] 134 /// fn drop_count_test() { 135 /// cov_mark::check_count!(covered_dropper_drops, 2); 136 /// let _covered_dropper1 = CoveredDropper; 137 /// let _covered_dropper2 = CoveredDropper; 138 /// } 139 /// ``` 140 #[macro_export] 141 macro_rules! check_count { 142 ($ident:ident, $count: literal) => { 143 let _guard = $crate::__rt::Guard::new(stringify!($ident), Some($count)); 144 }; 145 } 146 147 #[doc(hidden)] 148 #[cfg(feature = "enable")] 149 pub mod __rt { 150 use std::{ 151 cell::{Cell, RefCell}, 152 rc::Rc, 153 sync::atomic::{AtomicUsize, Ordering::Relaxed}, 154 }; 155 156 /// Even with 157 /// https://github.com/rust-lang/rust/commit/641d3b09f41b441f2c2618de32983ad3d13ea3f8, 158 /// a `thread_local` generates significantly more verbose assembly on x86 159 /// than atomic, so we'll use atomic for the fast path 160 static LEVEL: AtomicUsize = AtomicUsize::new(0); 161 162 thread_local! { 163 static ACTIVE: RefCell<Vec<Rc<GuardInner>>> = Default::default(); 164 } 165 166 #[inline(always)] hit(key: &'static str)167 pub fn hit(key: &'static str) { 168 if LEVEL.load(Relaxed) > 0 { 169 hit_cold(key); 170 } 171 172 #[cold] 173 fn hit_cold(key: &'static str) { 174 ACTIVE.with(|it| it.borrow().iter().for_each(|g| g.hit(key))) 175 } 176 } 177 178 struct GuardInner { 179 mark: &'static str, 180 hits: Cell<usize>, 181 expected_hits: Option<usize>, 182 } 183 184 pub struct Guard { 185 inner: Rc<GuardInner>, 186 } 187 188 impl GuardInner { hit(&self, key: &'static str)189 fn hit(&self, key: &'static str) { 190 if key == self.mark { 191 self.hits.set(self.hits.get().saturating_add(1)) 192 } 193 } 194 } 195 196 impl Guard { new(mark: &'static str, expected_hits: Option<usize>) -> Guard197 pub fn new(mark: &'static str, expected_hits: Option<usize>) -> Guard { 198 let inner = GuardInner { 199 mark, 200 hits: Cell::new(0), 201 expected_hits, 202 }; 203 let inner = Rc::new(inner); 204 LEVEL.fetch_add(1, Relaxed); 205 ACTIVE.with(|it| it.borrow_mut().push(Rc::clone(&inner))); 206 Guard { inner } 207 } 208 } 209 210 impl Drop for Guard { drop(&mut self)211 fn drop(&mut self) { 212 LEVEL.fetch_sub(1, Relaxed); 213 let last = ACTIVE.with(|it| it.borrow_mut().pop()); 214 215 if std::thread::panicking() { 216 return; 217 } 218 219 let last = last.unwrap(); 220 assert!(Rc::ptr_eq(&last, &self.inner)); 221 let hit_count = last.hits.get(); 222 match last.expected_hits { 223 Some(hits) => assert!( 224 hit_count == hits, 225 "mark was hit {} times, expected {}", 226 hit_count, 227 hits 228 ), 229 None => assert!(hit_count > 0, "mark was not hit"), 230 } 231 } 232 } 233 } 234 235 #[doc(hidden)] 236 #[cfg(not(feature = "enable"))] 237 pub mod __rt { 238 #[inline(always)] hit(_: &'static str)239 pub fn hit(_: &'static str) {} 240 241 #[non_exhaustive] 242 pub struct Guard; 243 244 impl Guard { new(_: &'static str, _: Option<usize>) -> Guard245 pub fn new(_: &'static str, _: Option<usize>) -> Guard { 246 Guard 247 } 248 } 249 } 250