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}