@@ -34,6 +34,10 @@ impl UniqueReentrantMutex {
34
34
pub fn is_locked ( & self ) -> bool {
35
35
self . locks . is_locked ( )
36
36
}
37
+
38
+ pub fn is_locked_by_current_thread ( & self ) -> bool {
39
+ self . locks . is_locked_by_current_thread ( )
40
+ }
37
41
}
38
42
39
43
#[ inline]
@@ -44,6 +48,63 @@ pub(crate) fn global_locks() -> &'static HashMap<String, UniqueReentrantMutex> {
44
48
LOCKS . get_or_init ( HashMap :: new)
45
49
}
46
50
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
+
47
108
static MUTEX_ID : AtomicU32 = AtomicU32 :: new ( 1 ) ;
48
109
49
110
impl UniqueReentrantMutex {
@@ -68,3 +129,66 @@ pub(crate) fn check_new_key(name: &str) {
68
129
Entry :: Vacant ( v) => v. insert_entry ( UniqueReentrantMutex :: new_mutex ( name) ) ,
69
130
} ;
70
131
}
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
+ }
0 commit comments