parameterized_test/lib.rs
1/*!
2A macro for defining tests which accept arguments
3
4# Example
5
6```rust
7use parameterized_test::create;
8
9#[cfg(test)]
10mod tests {
11 use super::*;
12
13 parameterized_test::create!{ even, n, { assert_eq!(n % 2, 0); } }
14 even! {
15 bad_case: 1, // this test case will fail
16 good_case: 2,
17 }
18}
19```
20*/
21
22// Helper to escape the $ character in the nested macro, see
23// https://github.com/rust-lang/rust/issues/35853#issuecomment-415993963
24#[doc(hidden)]
25#[macro_export]
26macro_rules! __with_dollar_sign {
27 ($($body:tt)*) => {
28 macro_rules! __with_dollar_sign_ { $($body)* }
29 __with_dollar_sign_!($);
30 }
31}
32
33// Reexport anyhow so the macro can reference it from within clients' crates - see #1 and
34// https://users.rust-lang.org/t/proc-macros-using-third-party-crate/42465/4
35#[cfg(feature="propagation")]
36pub use ::anyhow::Result as AnyhowResult;
37
38// Duplicate the create!() macro to optionally support Result.
39// https://stackoverflow.com/a/63011109/113632 suggests using another macro to reduce the amount of
40// duplication, but this macro is messy enough already I think the redundancy is easier to deal with
41#[cfg(feature="propagation")]
42#[macro_export]
43macro_rules! create {
44 ($name:ident, $args:pat, $body:tt) => {
45 $crate::__with_dollar_sign! {
46 ($d:tt) => {
47 macro_rules! $name {
48 ($d($d pname:ident: $d values:expr,)*) => {
49 mod $name {
50 #![ allow( unused_imports ) ]
51 use super::*;
52 $d(
53 #[test]
54 fn $d pname() -> $crate::AnyhowResult<()> {
55 // TODO(https://github.com/rust-lang/rust/issues/69517)
56 // although Rust 2018 supports Result-returning tests, the
57 // failure behavior is very poor. This helper function f() can
58 // be removed once Result tests are handled better. Demo:
59 // https://play.rust-lang.org/?gist=b1a4d7bf42c885f42598d872877f2504
60 fn f() -> $crate::AnyhowResult<()> {
61 let $args = $d values;
62 $body
63 Ok(())
64 }
65 f().unwrap();
66 Ok(())
67 }
68 )*
69 }}}}}}}
70
71#[cfg(not(feature="propagation"))]
72#[macro_export]
73macro_rules! create {
74 ($name:ident, $args:pat, $body:tt) => {
75 $crate::__with_dollar_sign! {
76 ($d:tt) => {
77 macro_rules! $name {
78 ($d($d pname:ident: $d values:expr,)*) => {
79 mod $name {
80 #![ allow( unused_imports ) ]
81 use super::*;
82 $d(
83 #[test]
84 fn $d pname() {
85 let $args = $d values;
86 $body
87 }
88 )*
89 }}}}}}}
90
91#[cfg(test)]
92mod tests {
93 create!{ basic, n, { assert!(n > 0); } }
94 basic! {
95 one: 1,
96 two: 2,
97 ten: 10,
98 }
99
100 create!{ multi_arg, (n, m), { assert!(n > m); } }
101 multi_arg! {
102 a: (10, 5),
103 b: (4, 0),
104 c: (1000, 500),
105 }
106
107 fn helper(arg: bool) -> bool { arg }
108
109 create!{ calls_helper, arg, { assert!(helper(arg)); } }
110 calls_helper! {
111 arg: true,
112 }
113
114 #[cfg(feature="propagation")]
115 create!{ propagation, n, { assert_eq!(n.to_string().parse::<i8>()? as i32, n); } }
116 #[cfg(feature="propagation")]
117 propagation! {
118 a: 100, // use a larger value, like 200, to see a parse error test failure
119 }
120}