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