aboutsummaryrefslogtreecommitdiff
path: root/crates/test_utils
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-05-19 23:19:59 +0100
committerAleksey Kladov <[email protected]>2020-05-20 12:00:50 +0100
commitd18d1c05949eaa890e7bb75710816a61b09a93dd (patch)
treebe16530829a82a839a02bef0983bbc520e074579 /crates/test_utils
parent4d3fd62f897da50db1b203b86f45b9d2cd272b4d (diff)
Significantly more glorious marks
Diffstat (limited to 'crates/test_utils')
-rw-r--r--crates/test_utils/src/lib.rs2
-rw-r--r--crates/test_utils/src/mark.rs78
2 files changed, 80 insertions, 0 deletions
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index b1e3c328f..4e05d464f 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -8,6 +8,8 @@
8 8
9#[macro_use] 9#[macro_use]
10pub mod marks; 10pub mod marks;
11#[macro_use]
12pub mod mark;
11 13
12use std::{ 14use std::{
13 fs, 15 fs,
diff --git a/crates/test_utils/src/mark.rs b/crates/test_utils/src/mark.rs
new file mode 100644
index 000000000..7c309a894
--- /dev/null
+++ b/crates/test_utils/src/mark.rs
@@ -0,0 +1,78 @@
1//! This module implements manually tracked test coverage, which is useful for
2//! quickly finding a test responsible for testing a particular bit of code.
3//!
4//! See <https://matklad.github.io/2018/06/18/a-trick-for-test-maintenance.html>
5//! for details, but the TL;DR is that you write your test as
6//!
7//! ```
8//! #[test]
9//! fn test_foo() {
10//! mark::check!(test_foo);
11//! }
12//! ```
13//!
14//! and in the code under test you write
15//!
16//! ```
17//! # use test_utils::mark;
18//! # fn some_condition() -> bool { true }
19//! fn foo() {
20//! if some_condition() {
21//! mark::hit!(test_foo);
22//! }
23//! }
24//! ```
25//!
26//! This module then checks that executing the test indeed covers the specified
27//! function. This is useful if you come back to the `foo` function ten years
28//! later and wonder where the test are: now you can grep for `test_foo`.
29use std::sync::atomic::{AtomicUsize, Ordering};
30
31#[macro_export]
32macro_rules! _hit {
33 ($ident:ident) => {{
34 #[cfg(test)]
35 {
36 extern "C" {
37 #[no_mangle]
38 static $ident: std::sync::atomic::AtomicUsize;
39 }
40 unsafe {
41 $ident.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
42 }
43 }
44 }};
45}
46pub use _hit as hit;
47
48#[macro_export]
49macro_rules! _check {
50 ($ident:ident) => {
51 #[no_mangle]
52 static $ident: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
53 let _checker = $crate::mark::MarkChecker::new(&$ident);
54 };
55}
56pub use _check as check;
57
58pub struct MarkChecker {
59 mark: &'static AtomicUsize,
60 value_on_entry: usize,
61}
62
63impl MarkChecker {
64 pub fn new(mark: &'static AtomicUsize) -> MarkChecker {
65 let value_on_entry = mark.load(Ordering::SeqCst);
66 MarkChecker { mark, value_on_entry }
67 }
68}
69
70impl Drop for MarkChecker {
71 fn drop(&mut self) {
72 if std::thread::panicking() {
73 return;
74 }
75 let value_on_exit = self.mark.load(Ordering::SeqCst);
76 assert!(value_on_exit > self.value_on_entry, "mark was not hit")
77 }
78}