test_helpers_async/lib.rs
1//
2//! Some setup and teardown macro helpers to mimic [Jest's setup and teardown](https://jestjs.io/docs/setup-teardown)
3//! functionality. Also includes a `skip` macro that mimics the [skip](https://jestjs.io/docs/api#testskipname-fn)
4//! functionality in Jest.
5//!
6//! There are currently five macros provided: `after_all`,
7//! `after_each`, `before_all`, `before_each`, and `skip`. I would like to implement `only` to
8//! match [Jest's only](https://jestjs.io/docs/api#testonlyname-fn-timeout) functionality. I'm
9//! unsure of a great way to do that currently, however.
10//!
11//! ## Getting Started
12//! Using these macros is fairly simple. The four after/before functions all require a function
13//! with the same name as the attribute and are only valid when applied to a mod. They are all used
14//! like in the below example. Replace `before_each` with whichever method you want to use. The
15//! code in the matching function will be inserted into every fn in the containing mod that has an
16//! attribute with the word "test" in it. This is to allow for use with not just normal `#[test]`
17//! attributes, but also other flavors like `#[tokio::test]` and `#[test_case(0)]`.
18//! ```
19//! #[cfg(test)]
20//! use test_env_helpers::*;
21//!
22//! #[before_each]
23//! #[cfg(test)]
24//! mod my_tests{
25//! fn before_each(){println!("I'm in every test!")}
26//! #[test]
27//! fn test_1(){}
28//! #[test]
29//! fn test_2(){}
30//! #[test]
31//! fn test_3(){}
32//! }
33//! ```
34//!
35//! The `skip` macro is valid on either a mod or an individual test and will remove the mod or test
36//! it is applied to. You can use it to skip tests that aren't working correctly or that you don't
37//! want to run for some reason.
38//!
39//! ```
40//! #[cfg(test)]
41//! use test_env_helpers::*;
42//!
43//! #[cfg(test)]
44//! mod my_tests{
45//! #[skip]
46//! #[test]
47//! fn broken_test(){panic!("I'm hella broke")}
48//! #[skip]
49//! mod broken_mod{
50//! #[test]
51//! fn i_will_not_be_run(){panic!("I get skipped too")}
52//! }
53//! #[test]
54//! fn test_2(){}
55//! #[test]
56//! fn test_3(){}
57//! }
58//! ```
59
60extern crate proc_macro;
61mod utils;
62
63use crate::utils::traverse_use_item;
64
65use proc_macro::TokenStream;
66use quote::quote;
67use syn::Stmt;
68use syn::Item;
69use syn::parse_quote;
70use syn::parse_macro_input;
71
72/// Will run the code in the matching `after_all` function exactly once when all of the tests have
73/// run. This works by counting the number of `#[test]` attributes and decrementing a counter at
74/// the beginning of every test. Once the counter reaches 0, it will run the code in `after_all`.
75/// It uses [std::sync::Once](https://doc.rust-lang.org/std/sync/struct.Once.html) internally
76/// to ensure that the code is run at maximum one time.
77///
78/// ```
79/// #[cfg(test)]
80/// use test_env_helpers::*;
81///
82/// #[after_all]
83/// #[cfg(test)]
84/// mod my_tests{
85/// fn after_all(){println!("I only get run once at the very end")}
86/// #[test]
87/// fn test_1(){}
88/// #[test]
89/// fn test_2(){}
90/// #[test]
91/// fn test_3(){}
92/// }
93/// ```
94#[proc_macro_attribute]
95pub fn after_all_async(_metadata: TokenStream, input: TokenStream) -> TokenStream {
96 let input: Item = match parse_macro_input!(input as Item) {
97 Item::Mod(mut m) => {
98 let (brace, items) = m.content.unwrap();
99 let (after_all_fn, everything_else): (Vec<Item>, Vec<Item>) =
100 items.into_iter().partition(|t| match t {
101 Item::Fn(f) => f.sig.ident == "after_all_async",
102 _ => false,
103 });
104 let after_all_fn_block = if after_all_fn.len() != 1 {
105 panic!("The `after_all` macro attribute requires a single function named `after_all` in the body of the module it is called on.")
106 } else {
107 match after_all_fn.into_iter().next().unwrap() {
108 Item::Fn(f) => f.block,
109 _ => unreachable!(),
110 }
111 };
112 let after_all_if: Stmt = parse_quote! {
113 if REMAINING_TESTS.fetch_sub(1, Ordering::SeqCst) == 1 {
114 AFTER_ALL.call_once(|| {
115 #after_all_fn_block
116 });
117 }
118 };
119 let resume: Stmt = parse_quote! {
120 if let Err(err) = result {
121 panic::resume_unwind(err);
122 }
123 };
124 let mut count: usize = 0;
125 let mut has_once: bool = false;
126 let mut has_atomic_usize: bool = false;
127 let mut has_ordering: bool = false;
128 let mut has_panic: bool = false;
129
130 let mut e: Vec<Item> = everything_else
131 .into_iter()
132 .map(|t| match t {
133 Item::Fn(mut f) => {
134 let test_count = f
135 .attrs
136 .iter()
137 .filter(|attr| {
138 attr.path
139 .segments
140 .iter()
141 .any(|segment| segment.ident.to_string().contains("test"))
142 })
143 .count();
144 if test_count > 0 {
145 count += test_count;
146 f.block.stmts.push(after_all_if.clone());
147 Item::Fn(f)
148 } else {
149 Item::Fn(f)
150 }
151 }
152 Item::Use(use_stmt) => {
153 if traverse_use_item(&use_stmt.tree, vec!["std", "sync", "Once"]).is_some()
154 {
155 has_once = true;
156 }
157 if traverse_use_item(
158 &use_stmt.tree,
159 vec!["std", "sync", "atomic", "AtomicUsize"],
160 )
161 .is_some()
162 {
163 has_atomic_usize = true;
164 }
165 if traverse_use_item(
166 &use_stmt.tree,
167 vec!["std", "sync", "atomic", "Ordering"],
168 )
169 .is_some()
170 {
171 has_ordering = true;
172 }
173 if traverse_use_item(
174 &use_stmt.tree,
175 vec!["std", "panic"],
176 )
177 .is_some() {
178 has_panic = true;
179 }
180 Item::Use(use_stmt)
181 }
182 el => el,
183 })
184 .collect();
185
186 let use_once: Item = parse_quote!(
187 use std::sync::Once;
188 );
189 let use_atomic_usize: Item = parse_quote!(
190 use std::sync::atomic::AtomicUsize;
191 );
192 let use_ordering: Item = parse_quote!(
193 use std::sync::atomic::Ordering;
194 );
195 let use_panic: Item = parse_quote!(
196 use std::panic;
197 );
198 let static_once: Item = parse_quote!(
199 static AFTER_ALL: Once = Once::new();
200 );
201 let static_count: Item = parse_quote!(
202 static REMAINING_TESTS: AtomicUsize = AtomicUsize::new(#count);
203 );
204
205 let mut once_content = vec![];
206
207 if !has_once {
208 once_content.push(use_once);
209 }
210 if !has_atomic_usize {
211 once_content.push(use_atomic_usize);
212 }
213 if !has_ordering {
214 once_content.push(use_ordering);
215 }
216 if !has_panic {
217 once_content.push(use_panic);
218 }
219 once_content.append(&mut vec![static_once, static_count]);
220 once_content.append(&mut e);
221
222 m.content = Some((brace, once_content));
223 Item::Mod(m)
224 }
225 _ => {
226 panic!("The `after_all` macro attribute is only valid when called on a module.")
227 }
228 };
229 TokenStream::from(quote! (#input))
230}
231
232
233/// Will run the code in the matching `after_all` function exactly once when all of the tests have
234/// run. This works by counting the number of `#[test]` attributes and decrementing a counter at
235/// the beginning of every test. Once the counter reaches 0, it will run the code in `after_all`.
236/// It uses [std::sync::Once](https://doc.rust-lang.org/std/sync/struct.Once.html) internally
237/// to ensure that the code is run at maximum one time.
238///
239/// ```
240/// #[cfg(test)]
241/// use test_env_helpers::*;
242///
243/// #[after_all]
244/// #[cfg(test)]
245/// mod my_tests{
246/// fn after_all(){println!("I only get run once at the very end")}
247/// #[test]
248/// fn test_1(){}
249/// #[test]
250/// fn test_2(){}
251/// #[test]
252/// fn test_3(){}
253/// }
254/// ```
255#[proc_macro_attribute]
256pub fn after_all(_metadata: TokenStream, input: TokenStream) -> TokenStream {
257 let input: Item = match parse_macro_input!(input as Item) {
258 Item::Mod(mut m) => {
259 let (brace, items) = m.content.unwrap();
260 let (after_all_fn, everything_else): (Vec<Item>, Vec<Item>) =
261 items.into_iter().partition(|t| match t {
262 Item::Fn(f) => f.sig.ident == "after_all",
263 _ => false,
264 });
265 let after_all_fn_block = if after_all_fn.len() != 1 {
266 panic!("The `after_all` macro attribute requires a single function named `after_all` in the body of the module it is called on.")
267 } else {
268 match after_all_fn.into_iter().next().unwrap() {
269 Item::Fn(f) => f.block,
270 _ => unreachable!(),
271 }
272 };
273 let after_all_if: Stmt = parse_quote! {
274 if REMAINING_TESTS.fetch_sub(1, Ordering::SeqCst) == 1 {
275 AFTER_ALL.call_once(|| {
276 #after_all_fn_block
277 });
278 }
279 };
280 let resume: Stmt = parse_quote! {
281 if let Err(err) = result {
282 panic::resume_unwind(err);
283 }
284 };
285 let mut count: usize = 0;
286 let mut has_once: bool = false;
287 let mut has_atomic_usize: bool = false;
288 let mut has_ordering: bool = false;
289 let mut has_panic: bool = false;
290
291 let mut e: Vec<Item> = everything_else
292 .into_iter()
293 .map(|t| match t {
294 Item::Fn(mut f) => {
295 let test_count = f
296 .attrs
297 .iter()
298 .filter(|attr| {
299 attr.path
300 .segments
301 .iter()
302 .any(|segment| segment.ident.to_string().contains("test"))
303 })
304 .count();
305 if test_count > 0 {
306 count += test_count;
307 let block = f.block.clone();
308 let catch_unwind: Stmt = parse_quote! {
309 let result = panic::catch_unwind(|| {
310 #block
311 });
312 };
313 f.block.stmts = vec![catch_unwind, after_all_if.clone(), resume.clone()];
314 Item::Fn(f)
315 } else {
316 Item::Fn(f)
317 }
318 }
319 Item::Use(use_stmt) => {
320 if traverse_use_item(&use_stmt.tree, vec!["std", "sync", "Once"]).is_some()
321 {
322 has_once = true;
323 }
324 if traverse_use_item(
325 &use_stmt.tree,
326 vec!["std", "sync", "atomic", "AtomicUsize"],
327 )
328 .is_some()
329 {
330 has_atomic_usize = true;
331 }
332 if traverse_use_item(
333 &use_stmt.tree,
334 vec!["std", "sync", "atomic", "Ordering"],
335 )
336 .is_some()
337 {
338 has_ordering = true;
339 }
340 if traverse_use_item(
341 &use_stmt.tree,
342 vec!["std", "panic"],
343 )
344 .is_some() {
345 has_panic = true;
346 }
347 Item::Use(use_stmt)
348 }
349 el => el,
350 })
351 .collect();
352
353 let use_once: Item = parse_quote!(
354 use std::sync::Once;
355 );
356 let use_atomic_usize: Item = parse_quote!(
357 use std::sync::atomic::AtomicUsize;
358 );
359 let use_ordering: Item = parse_quote!(
360 use std::sync::atomic::Ordering;
361 );
362 let use_panic: Item = parse_quote!(
363 use std::panic;
364 );
365 let static_once: Item = parse_quote!(
366 static AFTER_ALL: Once = Once::new();
367 );
368 let static_count: Item = parse_quote!(
369 static REMAINING_TESTS: AtomicUsize = AtomicUsize::new(#count);
370 );
371
372 let mut once_content = vec![];
373
374 if !has_once {
375 once_content.push(use_once);
376 }
377 if !has_atomic_usize {
378 once_content.push(use_atomic_usize);
379 }
380 if !has_ordering {
381 once_content.push(use_ordering);
382 }
383 if !has_panic {
384 once_content.push(use_panic);
385 }
386 once_content.append(&mut vec![static_once, static_count]);
387 once_content.append(&mut e);
388
389 m.content = Some((brace, once_content));
390 Item::Mod(m)
391 }
392 _ => {
393 panic!("The `after_all` macro attribute is only valid when called on a module.")
394 }
395 };
396 TokenStream::from(quote! (#input))
397}
398
399// /// Will run the code in the matching `after_all` function exactly once when all of the tests have
400// /// run. This works by counting the number of `#[test]` attributes and decrementing a counter at
401// /// the beginning of every test. Once the counter reaches 0, it will run the code in `after_all`.
402// /// It uses [std::sync::Once](https://doc.rust-lang.org/std/sync/struct.Once.html) internally
403// /// to ensure that the code is run at maximum one time.
404// ///
405// /// ```
406// /// #[cfg(test)]
407// /// use test_env_helpers::*;
408// ///
409// /// #[after_all]
410// /// #[cfg(test)]
411// /// mod my_tests{
412// /// fn after_all(){println!("I only get run once at the very end")}
413// /// #[test]
414// /// fn test_1(){}
415// /// #[test]
416// /// fn test_2(){}
417// /// #[test]
418// /// fn test_3(){}
419// /// }
420// /// ```
421// #[proc_macro_attribute]
422// pub fn after_all_async(_metadata: TokenStream, input: TokenStream) -> TokenStream {
423// let input: Item = match parse_macro_input!(input as Item) {
424// Item::Mod(mut m) => {
425// let (brace, items) = m.content.unwrap();
426// let (after_all_fn, everything_else): (Vec<Item>, Vec<Item>) =
427// items.into_iter().partition(|t| match t {
428// Item::Fn(f) => f.sig.ident == "after_all_async",
429// _ => false,
430// });
431// let after_all_fn_block = if after_all_fn.len() != 1 {
432// panic!("The `after_all_async` macro attribute requires a single function named `after_all_async` in the body of the module it is called on.")
433// } else {
434// match after_all_fn.into_iter().next().unwrap() {
435// Item::Fn(f) => f.block,
436// _ => unreachable!(),
437// }
438// };
439// let after_all_if: Stmt = parse_quote! {
440// if REMAINING_TESTS.fetch_sub(1, Ordering::SeqCst) == 1 {
441// AFTER_ALL.call_once(|| {
442// #after_all_fn_block
443// });
444// }
445// };
446// let resume: Stmt = parse_quote! {
447// if let Err(err) = result {
448// panic::resume_unwind(err);
449// }
450// };
451// let mut count: usize = 0;
452// let mut has_once: bool = false;
453// let mut has_atomic_usize: bool = false;
454// let mut has_ordering: bool = false;
455// let mut has_panic: bool = false;
456// let mut has_future_ext: bool = false;
457
458// let mut e: Vec<Item> = everything_else
459// .into_iter()
460// .map(|t| match t {
461// Item::Fn(mut f) => {
462// let test_count = f
463// .attrs
464// .iter()
465// .filter(|attr| {
466// attr.path
467// .segments
468// .iter()
469// .any(|segment| segment.ident.to_string().contains("test"))
470// })
471// .count();
472// if test_count > 0 {
473// count += test_count;
474// let block = f.block.clone();
475// let catch_unwind: Stmt = parse_quote! {
476// let run = async {
477// #block
478// };
479// let result = run.catch_unwind().await;
480// };
481// f.block.stmts = vec![catch_unwind, after_all_if.clone(), resume.clone()];
482// Item::Fn(f)
483// } else {
484// Item::Fn(f)
485// }
486// }
487// Item::Use(use_stmt) => {
488// if traverse_use_item(&use_stmt.tree, vec!["std", "sync", "Once"]).is_some()
489// {
490// has_once = true;
491// }
492// if traverse_use_item(
493// &use_stmt.tree,
494// vec!["std", "sync", "atomic", "AtomicUsize"],
495// )
496// .is_some()
497// {
498// has_atomic_usize = true;
499// }
500// if traverse_use_item(
501// &use_stmt.tree,
502// vec!["std", "sync", "atomic", "Ordering"],
503// )
504// .is_some()
505// {
506// has_ordering = true;
507// }
508// if traverse_use_item(
509// &use_stmt.tree,
510// vec!["std", "panic"],
511// )
512// .is_some() {
513// has_panic = true;
514// }
515// Item::Use(use_stmt)
516// }
517// el => el,
518// })
519// .collect();
520
521// let use_once: Item = parse_quote!(
522// use std::sync::Once;
523// );
524// let use_atomic_usize: Item = parse_quote!(
525// use std::sync::atomic::AtomicUsize;
526// );
527// let use_ordering: Item = parse_quote!(
528// use std::sync::atomic::Ordering;
529// );
530// let use_panic: Item = parse_quote!(
531// use std::panic;
532// );
533// let use_future_ext: Item = parse_quote!(
534// use futures::FutureExt;
535// );
536// let static_once: Item = parse_quote!(
537// static AFTER_ALL: Once = Once::new();
538// );
539// let static_count: Item = parse_quote!(
540// static REMAINING_TESTS: AtomicUsize = AtomicUsize::new(#count);
541// );
542
543// let mut once_content = vec![];
544
545// if !has_once {
546// once_content.push(use_once);
547// }
548// if !has_atomic_usize {
549// once_content.push(use_atomic_usize);
550// }
551// if !has_ordering {
552// once_content.push(use_ordering);
553// }
554// if !has_panic {
555// once_content.push(use_panic);
556// }
557// if !has_future_ext {
558// once_content.push(use_future_ext);
559// }
560// once_content.append(&mut vec![static_once, static_count]);
561// once_content.append(&mut e);
562
563// m.content = Some((brace, once_content));
564// Item::Mod(m)
565// }
566// _ => {
567// panic!("The `after_all_async` macro attribute is only valid when called on a module.")
568// }
569// };
570// TokenStream::from(quote! (#input))
571// }
572
573/// Will run the code in the matching `after_each` function at the end of every `#[test]` function.
574/// Useful if you want to cleanup after a test or reset some external state. If the test panics,
575/// this code will not be run. If you need something that is infallible, you should use
576/// `before_each` instead.
577/// ```
578/// #[cfg(test)]
579/// use test_env_helpers::*;
580///
581/// #[after_each]
582/// #[cfg(test)]
583/// mod my_tests{
584/// fn after_each(){println!("I get run at the very end of each function")}
585/// #[test]
586/// fn test_1(){}
587/// #[test]
588/// fn test_2(){}
589/// #[test]
590/// fn test_3(){}
591/// }
592/// ```
593#[proc_macro_attribute]
594pub fn after_each(_metadata: TokenStream, input: TokenStream) -> TokenStream {
595 let input: Item = match parse_macro_input!(input as Item) {
596 Item::Mod(mut m) => {
597 let (brace, items) = m.content.unwrap();
598 let (after_each_fn, everything_else): (Vec<Item>, Vec<Item>) =
599 items.into_iter().partition(|t| match t {
600 Item::Fn(f) => f.sig.ident == "after_each",
601 _ => false,
602 });
603 let after_each_fn_block = if after_each_fn.len() != 1 {
604 panic!("The `after_each` macro attribute requires a single function named `after_each` in the body of the module it is called on.")
605 } else {
606 match after_each_fn.into_iter().next().unwrap() {
607 Item::Fn(f) => f.block,
608 _ => unreachable!(),
609 }
610 };
611
612 let e: Vec<Item> = everything_else
613 .into_iter()
614 .map(|t| match t {
615 Item::Fn(mut f) => {
616 if f.attrs.iter().any(|attr| {
617 attr.path
618 .segments
619 .iter()
620 .any(|segment| segment.ident.to_string().contains("test"))
621 }) {
622 f.block.stmts.append(&mut after_each_fn_block.stmts.clone());
623 Item::Fn(f)
624 } else {
625 Item::Fn(f)
626 }
627 }
628 e => e,
629 })
630 .collect();
631 m.content = Some((brace, e));
632 Item::Mod(m)
633 }
634
635 _ => {
636 panic!("The `after_each` macro attribute is only valid when called on a module.")
637 }
638 };
639 TokenStream::from(quote! {#input})
640}
641
642/// Will run the code in the matching `before_all` function exactly once at the very beginning of a
643/// test run. It uses [std::sync::Once](https://doc.rust-lang.org/std/sync/struct.Once.html) internally
644/// to ensure that the code is run at maximum one time. Useful for setting up some external state
645/// that will be reused in multiple tests.
646/// ```
647/// #[cfg(test)]
648/// use test_env_helpers::*;
649///
650/// #[before_all]
651/// #[cfg(test)]
652/// mod my_tests{
653/// fn before_all(){println!("I get run at the very beginning of the test suite")}
654/// #[test]
655/// fn test_1(){}
656/// #[test]
657/// fn test_2(){}
658/// #[test]
659/// fn test_3(){}
660/// }
661/// ```
662#[proc_macro_attribute]
663pub fn before_all(_metadata: TokenStream, input: TokenStream) -> TokenStream {
664 let input: Item = match parse_macro_input!(input as Item) {
665 Item::Mod(mut m) => {
666 let (brace, items) = m.content.unwrap();
667 let (before_all_fn, everything_else): (Vec<Item>, Vec<Item>) =
668 items.into_iter().partition(|t| match t {
669 Item::Fn(f) => f.sig.ident == "before_all",
670 _ => false,
671 });
672 let before_all_fn_block = if before_all_fn.len() != 1 {
673 panic!("The `before_all` macro attribute requires a single function named `before_all` in the body of the module it is called on.")
674 } else {
675 match before_all_fn.into_iter().next().unwrap() {
676 Item::Fn(f) => f.block,
677 _ => unreachable!(),
678 }
679 };
680 let q: Stmt = parse_quote! {
681 BEFORE_ALL.call_once(|| {
682 #before_all_fn_block
683 });
684 };
685
686 let mut has_once: bool = false;
687 let mut e: Vec<Item> = everything_else
688 .into_iter()
689 .map(|t| match t {
690 Item::Fn(mut f) => {
691 if f.attrs.iter().any(|attr| {
692 attr.path
693 .segments
694 .iter()
695 .any(|segment| segment.ident.to_string().contains("test"))
696 }) {
697 let mut stmts = vec![q.clone()];
698 stmts.append(&mut f.block.stmts);
699 f.block.stmts = stmts;
700 Item::Fn(f)
701 } else {
702 Item::Fn(f)
703 }
704 }
705 Item::Use(use_stmt) => {
706 if traverse_use_item(&use_stmt.tree, vec!["std", "sync", "Once"]).is_some()
707 {
708 has_once = true;
709 }
710 Item::Use(use_stmt)
711 }
712 e => e,
713 })
714 .collect();
715 let use_once: Item = parse_quote!(
716 use std::sync::Once;
717 );
718 let static_once: Item = parse_quote!(
719 static BEFORE_ALL: Once = Once::new();
720 );
721
722 let mut once_content = vec![];
723 if !has_once {
724 once_content.push(use_once);
725 }
726 once_content.push(static_once);
727 once_content.append(&mut e);
728
729 m.content = Some((brace, once_content));
730 Item::Mod(m)
731 }
732
733 _ => {
734 panic!("The `before_all` macro attribute is only valid when called on a module.")
735 }
736 };
737 TokenStream::from(quote! (#input))
738}
739
740/// Will run the code in the matching `before_each` function at the beginning of every test. Useful
741/// to reset state to ensure that a test has a clean slate.
742/// ```
743/// #[cfg(test)]
744/// use test_env_helpers::*;
745///
746/// #[before_each]
747/// #[cfg(test)]
748/// mod my_tests{
749/// fn before_each(){println!("I get run at the very beginning of every test")}
750/// #[test]
751/// fn test_1(){}
752/// #[test]
753/// fn test_2(){}
754/// #[test]
755/// fn test_3(){}
756/// }
757/// ```
758///
759/// Can be used to reduce the amount of boilerplate setup code that needs to be copied into each test.
760/// For example, if you need to ensure that tests in a single test suite are not run in parallel, this can
761/// easily be done with a [Mutex](https://doc.rust-lang.org/std/sync/struct.Mutex.html).
762/// However, remembering to copy and paste the code to acquire a lock on the `Mutex` in every test
763/// is tedious and error prone.
764/// ```
765/// #[cfg(test)]
766/// mod without_before_each{
767/// lazy_static! {
768/// static ref MTX: Mutex<()> = Mutex::new(());
769/// }
770/// #[test]
771/// fn test_1(){let _m = MTX.lock();}
772/// #[test]
773/// fn test_2(){let _m = MTX.lock();}
774/// #[test]
775/// fn test_3(){let _m = MTX.lock();}
776/// }
777/// ```
778/// Using `before_each` removes the need to copy and paste so much and makes making changes easier
779/// because they only need to be made in a single location instead of once for every test.
780/// ```
781/// #[cfg(test)]
782/// use test_env_helpers::*;
783///
784/// #[before_each]
785/// #[cfg(test)]
786/// mod with_before_each{
787/// lazy_static! {
788/// static ref MTX: Mutex<()> = Mutex::new(());
789/// }
790/// fn before_each(){let _m = MTX.lock();}
791/// #[test]
792/// fn test_1(){}
793/// #[test]
794/// fn test_2(){}
795/// #[test]
796/// fn test_3(){}
797/// }
798/// ```
799#[proc_macro_attribute]
800pub fn before_each(_metadata: TokenStream, input: TokenStream) -> TokenStream {
801 let input: Item = match parse_macro_input!(input as Item) {
802 Item::Mod(mut m) => {
803 let (brace, items) = m.content.unwrap();
804 let (before_each_fn, everything_else): (Vec<Item>, Vec<Item>) =
805 items.into_iter().partition(|t| match t {
806 Item::Fn(f) => f.sig.ident == "before_each",
807 _ => false,
808 });
809 let before_each_fn_block = if before_each_fn.len() != 1 {
810 panic!("The `before_each` macro attribute requires a single function named `before_each` in the body of the module it is called on.")
811 } else {
812 match before_each_fn.into_iter().next().unwrap() {
813 Item::Fn(f) => f.block,
814 _ => unreachable!(),
815 }
816 };
817
818 let e: Vec<Item> = everything_else
819 .into_iter()
820 .map(|t| match t {
821 Item::Fn(mut f) => {
822 if f.attrs.iter().any(|attr| {
823 attr.path
824 .segments
825 .iter()
826 .any(|segment| segment.ident.to_string().contains("test"))
827 }) {
828 let mut b = before_each_fn_block.stmts.clone();
829 b.append(&mut f.block.stmts);
830 f.block.stmts = b;
831 Item::Fn(f)
832 } else {
833 Item::Fn(f)
834 }
835 }
836 e => e,
837 })
838 .collect();
839 m.content = Some((brace, e));
840 Item::Mod(m)
841 }
842
843 _ => {
844 panic!("The `before_each` macro attribute is only valid when called on a module.")
845 }
846 };
847 TokenStream::from(quote! {#input})
848}
849
850/// Will skip running the code it is applied on. You can use it to skip tests that aren't working
851/// correctly or that you don't want to run for some reason. There are no checks to make sure it's
852/// applied to a `#[test]` or mod. It will remove whatever it is applied to from the final AST.
853///
854/// ```
855/// #[cfg(test)]
856/// use test_env_helpers::*;
857///
858/// #[cfg(test)]
859/// mod my_tests{
860/// #[skip]
861/// #[test]
862/// fn broken_test(){panic!("I'm hella broke")}
863/// #[skip]
864/// mod broken_mod{
865/// #[test]
866/// fn i_will_not_be_run(){panic!("I get skipped too")}
867/// }
868/// #[test]
869/// fn test_2(){}
870/// #[test]
871/// fn test_3(){}
872/// }
873/// ```
874#[proc_macro_attribute]
875pub fn skip(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
876 TokenStream::from(quote! {})
877}