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