Skip to content

Commit 7919b91

Browse files
authored
Merge pull request #113 from pgerber/is_serial
Add is_locked_serially() to check if we are in a #[serial] context
2 parents 870d278 + 7a5387e commit 7919b91

File tree

3 files changed

+130
-0
lines changed

3 files changed

+130
-0
lines changed

serial_test/src/code_lock.rs

+124
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ impl UniqueReentrantMutex {
3434
pub fn is_locked(&self) -> bool {
3535
self.locks.is_locked()
3636
}
37+
38+
pub fn is_locked_by_current_thread(&self) -> bool {
39+
self.locks.is_locked_by_current_thread()
40+
}
3741
}
3842

3943
#[inline]
@@ -44,6 +48,63 @@ pub(crate) fn global_locks() -> &'static HashMap<String, UniqueReentrantMutex> {
4448
LOCKS.get_or_init(HashMap::new)
4549
}
4650

51+
/// Check if the current thread is holding a serial lock
52+
///
53+
/// Can be used to assert that a piece of code can only be called
54+
/// from a test marked `#[serial]`.
55+
///
56+
/// Example, with `#[serial]`:
57+
///
58+
/// ```
59+
/// use serial_test::{is_locked_serially, serial};
60+
///
61+
/// fn do_something_in_need_of_serialization() {
62+
/// assert!(is_locked_serially(None));
63+
///
64+
/// // ...
65+
/// }
66+
///
67+
/// #[test]
68+
/// # fn unused() {}
69+
/// #[serial]
70+
/// fn main() {
71+
/// do_something_in_need_of_serialization();
72+
/// }
73+
/// ```
74+
///
75+
/// Example, missing `#[serial]`:
76+
///
77+
/// ```should_panic
78+
/// use serial_test::{is_locked_serially, serial};
79+
///
80+
/// #[test]
81+
/// # fn unused() {}
82+
/// // #[serial] // <-- missing
83+
/// fn main() {
84+
/// assert!(is_locked_serially(None));
85+
/// }
86+
/// ```
87+
///
88+
/// Example, `#[test(some_key)]`:
89+
///
90+
/// ```
91+
/// use serial_test::{is_locked_serially, serial};
92+
///
93+
/// #[test]
94+
/// # fn unused() {}
95+
/// #[serial(some_key)]
96+
/// fn main() {
97+
/// assert!(is_locked_serially(Some("some_key")));
98+
/// assert!(!is_locked_serially(None));
99+
/// }
100+
/// ```
101+
pub fn is_locked_serially(name: Option<&str>) -> bool {
102+
global_locks()
103+
.get(name.unwrap_or_default())
104+
.map(|lock| lock.get().is_locked_by_current_thread())
105+
.unwrap_or_default()
106+
}
107+
47108
static MUTEX_ID: AtomicU32 = AtomicU32::new(1);
48109

49110
impl UniqueReentrantMutex {
@@ -68,3 +129,66 @@ pub(crate) fn check_new_key(name: &str) {
68129
Entry::Vacant(v) => v.insert_entry(UniqueReentrantMutex::new_mutex(name)),
69130
};
70131
}
132+
133+
#[cfg(test)]
134+
mod tests {
135+
use super::*;
136+
use crate::{local_parallel_core, local_serial_core};
137+
138+
const NAME1: &str = "NAME1";
139+
const NAME2: &str = "NAME2";
140+
141+
#[test]
142+
fn assert_serially_locked_without_name() {
143+
local_serial_core(vec![""], None, || {
144+
assert!(is_locked_serially(None));
145+
assert!(!is_locked_serially(Some("no_such_name")));
146+
});
147+
}
148+
149+
#[test]
150+
fn assert_serially_locked_with_multiple_names() {
151+
local_serial_core(vec![NAME1, NAME2], None, || {
152+
assert!(is_locked_serially(Some(NAME1)));
153+
assert!(is_locked_serially(Some(NAME2)));
154+
assert!(!is_locked_serially(Some("no_such_name")));
155+
assert!(!is_locked_serially(None));
156+
});
157+
}
158+
159+
#[test]
160+
fn assert_serially_locked_when_actually_locked_parallel() {
161+
local_parallel_core(vec![NAME1, NAME2], None, || {
162+
assert!(!is_locked_serially(Some(NAME1)));
163+
assert!(!is_locked_serially(Some(NAME2)));
164+
assert!(!is_locked_serially(Some("no_such_name")));
165+
assert!(!is_locked_serially(None));
166+
});
167+
}
168+
169+
#[test]
170+
fn assert_serially_locked_outside_serial_lock() {
171+
assert!(!is_locked_serially(Some(NAME1)));
172+
assert!(!is_locked_serially(Some(NAME2)));
173+
assert!(!is_locked_serially(None));
174+
175+
local_serial_core(vec![NAME1], None, || {
176+
// ...
177+
});
178+
179+
assert!(!is_locked_serially(Some(NAME1)));
180+
assert!(!is_locked_serially(Some(NAME2)));
181+
assert!(!is_locked_serially(None));
182+
}
183+
184+
#[test]
185+
fn assert_serially_locked_in_different_thread() {
186+
local_serial_core(vec![NAME1, NAME2], None, || {
187+
std::thread::spawn(|| {
188+
assert!(!is_locked_serially(Some(NAME2)));
189+
})
190+
.join()
191+
.unwrap();
192+
});
193+
}
194+
}

serial_test/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,5 @@ pub use serial_test_derive::{parallel, serial};
112112

113113
#[cfg(feature = "file_locks")]
114114
pub use serial_test_derive::{file_parallel, file_serial};
115+
116+
pub use code_lock::is_locked_serially;

serial_test/src/rwlock.rs

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ impl Locks {
5454
self.arc.serial.is_locked()
5555
}
5656

57+
pub fn is_locked_by_current_thread(&self) -> bool {
58+
self.arc.serial.is_owned_by_current_thread()
59+
}
60+
5761
pub fn serial(&self) -> MutexGuardWrapper {
5862
#[cfg(feature = "logging")]
5963
debug!("Get serial lock '{}'", self.name);

0 commit comments

Comments
 (0)