aboutsummaryrefslogtreecommitdiff
path: root/crates/test_utils/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/test_utils/src')
-rw-r--r--crates/test_utils/src/lib.rs9
-rw-r--r--crates/test_utils/src/marks.rs80
2 files changed, 87 insertions, 2 deletions
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index 6489033dd..35a679aea 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -1,5 +1,10 @@
1use std::fs; 1#[macro_use]
2use std::path::{Path, PathBuf}; 2pub mod marks;
3
4use std::{
5 fs,
6 path::{Path, PathBuf}
7};
3 8
4use text_unit::{TextRange, TextUnit}; 9use text_unit::{TextRange, TextUnit};
5use serde_json::Value; 10use serde_json::Value;
diff --git a/crates/test_utils/src/marks.rs b/crates/test_utils/src/marks.rs
new file mode 100644
index 000000000..79ffedf69
--- /dev/null
+++ b/crates/test_utils/src/marks.rs
@@ -0,0 +1,80 @@
1//! This module implements manually tracked test coverage, which 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//! ```no-run
8//! #[test]
9//! fn test_foo() {
10//! covers!(test_foo);
11//! }
12//! ```
13//!
14//! and in the code under test you write
15//!
16//! ```no-run
17//! fn foo() {
18//! if some_condition() {
19//! tested_by!(test_foo);
20//! }
21//! }
22//! ```
23//!
24//! This module then checks that executing the test indeed covers the specified
25//! function. This is useful if you come back to the `foo` function ten years
26//! later and wonder where the test are: now you can grep for `test_foo`.
27use std::sync::atomic::{AtomicUsize, Ordering};
28
29#[macro_export]
30macro_rules! tested_by {
31 ($ident:ident) => {
32 #[cfg(test)]
33 {
34 // sic! use call-site crate
35 crate::marks::$ident.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
36 }
37 };
38}
39
40#[macro_export]
41macro_rules! covers {
42 ($ident:ident) => {
43 // sic! use call-site crate
44 let _checker = $crate::marks::MarkChecker::new(&crate::marks::$ident);
45 };
46}
47
48#[macro_export]
49macro_rules! mark {
50 ($ident:ident) => {
51 #[allow(bad_style)]
52 pub(crate) static $ident: std::sync::atomic::AtomicUsize =
53 std::sync::atomic::AtomicUsize::new(0);
54 };
55}
56
57pub struct MarkChecker {
58 mark: &'static AtomicUsize,
59 value_on_entry: usize,
60}
61
62impl MarkChecker {
63 pub fn new(mark: &'static AtomicUsize) -> MarkChecker {
64 let value_on_entry = mark.load(Ordering::SeqCst);
65 MarkChecker {
66 mark,
67 value_on_entry,
68 }
69 }
70}
71
72impl Drop for MarkChecker {
73 fn drop(&mut self) {
74 if std::thread::panicking() {
75 return;
76 }
77 let value_on_exit = self.mark.load(Ordering::SeqCst);
78 assert!(value_on_exit > self.value_on_entry, "mark was not hit")
79 }
80}