diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5a61329 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,324 @@ +//! Provides a macro, `assert_matches!`, which tests whether a value +//! matches a given pattern, causing a panic if the match fails. +//! +//! See the macro [`assert_matches!`] documentation for more information. +//! +//! Also provides a debug-only counterpart, [`debug_assert_matches!`]. +//! +//! See the macro [`debug_assert_matches!`] documentation for more information +//! about this macro. +//! +//! [`assert_matches!`]: macro.assert_matches.html +//! [`debug_assert_matches!`]: macro.debug_assert_matches.html + +#![deny(missing_docs)] +#![cfg_attr(not(test), no_std)] + +/// Asserts that an expression matches a given pattern. +/// +/// A guard expression may be supplied to add further restrictions to the +/// expected value of the expression. +/// +/// A `match` arm may be supplied to perform additional assertions or to yield +/// a value from the macro invocation. +/// +/// # Examples +/// +/// ``` +/// #[macro_use] extern crate assert_matches; +/// +/// #[derive(Debug)] +/// enum Foo { +/// A(i32), +/// B(&'static str), +/// } +/// +/// # fn main() { +/// let a = Foo::A(1); +/// +/// // Assert that `a` matches the pattern `Foo::A(_)`. +/// assert_matches!(a, Foo::A(_)); +/// +/// // Assert that `a` matches the pattern and +/// // that the contained value meets the condition `i > 0`. +/// assert_matches!(a, Foo::A(i) if i > 0); +/// +/// let b = Foo::B("foobar"); +/// +/// // Assert that `b` matches the pattern `Foo::B(_)`. +/// assert_matches!(b, Foo::B(s) => { +/// // Perform additional assertions on the variable binding `s`. +/// assert!(s.starts_with("foo")); +/// assert!(s.ends_with("bar")); +/// }); +/// +/// // Assert that `b` matches the pattern and yield the string `s`. +/// let s = assert_matches!(b, Foo::B(s) => s); +/// +/// // Perform an assertion on the value `s`. +/// assert_eq!(s, "foobar"); +/// # } +/// ``` +#[macro_export] +macro_rules! assert_matches { + ( $e:expr , $($pat:pat)|+ ) => { + match $e { + $($pat)|+ => (), + ref e => panic!("assertion failed: `{:?}` does not match `{}`", + e, stringify!($($pat)|+)) + } + }; + ( $e:expr , $($pat:pat)|+ if $cond:expr ) => { + match $e { + $($pat)|+ if $cond => (), + ref e => panic!("assertion failed: `{:?}` does not match `{}`", + e, stringify!($($pat)|+ if $cond)) + } + }; + ( $e:expr , $($pat:pat)|+ => $arm:expr ) => { + match $e { + $($pat)|+ => $arm, + ref e => panic!("assertion failed: `{:?}` does not match `{}`", + e, stringify!($($pat)|+)) + } + }; + ( $e:expr , $($pat:pat)|+ if $cond:expr => $arm:expr ) => { + match $e { + $($pat)|+ if $cond => $arm, + ref e => panic!("assertion failed: `{:?}` does not match `{}`", + e, stringify!($($pat)|+ if $cond)) + } + }; + ( $e:expr , $($pat:pat)|+ , $($arg:tt)* ) => { + match $e { + $($pat)|+ => (), + ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}", + e, stringify!($($pat)|+), format_args!($($arg)*)) + } + }; + ( $e:expr , $($pat:pat)|+ if $cond:expr , $($arg:tt)* ) => { + match $e { + $($pat)|+ if $cond => (), + ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}", + e, stringify!($($pat)|+ if $cond), format_args!($($arg)*)) + } + }; + ( $e:expr , $($pat:pat)|+ => $arm:expr , $($arg:tt)* ) => { + match $e { + $($pat)|+ => $arm, + ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}", + e, stringify!($($pat)|+), format_args!($($arg)*)) + } + }; + ( $e:expr , $($pat:pat)|+ if $cond:expr => $arm:expr , $($arg:tt)* ) => { + match $e { + $($pat)|+ if $cond => $arm, + ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}", + e, stringify!($($pat)|+ if $cond), format_args!($($arg)*)) + } + }; +} + +/// Asserts that an expression matches a given pattern. +/// +/// Unlike [`assert_matches!`], `debug_assert_matches!` statements are only enabled +/// in non-optimized builds by default. An optimized build will omit all +/// `debug_assert_matches!` statements unless `-C debug-assertions` is passed +/// to the compiler. +/// +/// See the macro [`assert_matches!`] documentation for more information. +/// +/// [`assert_matches!`]: macro.assert_matches.html +#[macro_export(local_inner_macros)] +macro_rules! debug_assert_matches { + ( $($tt:tt)* ) => { { + if _assert_matches_cfg!(debug_assertions) { + assert_matches!($($tt)*); + } + } } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! _assert_matches_cfg { + ( $($tt:tt)* ) => { cfg!($($tt)*) } +} + +#[cfg(test)] +mod test { + use std::panic::{catch_unwind, UnwindSafe}; + + #[derive(Debug)] + enum Foo { + A(i32), + B(&'static str), + C(&'static str), + } + + #[test] + fn test_assert_succeed() { + let a = Foo::A(123); + + assert_matches!(a, Foo::A(_)); + assert_matches!(a, Foo::A(123)); + assert_matches!(a, Foo::A(i) if i == 123); + assert_matches!(a, Foo::A(42) | Foo::A(123)); + + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(_)); + assert_matches!(b, Foo::B("foo")); + assert_matches!(b, Foo::B(s) if s == "foo"); + assert_matches!(b, Foo::B(s) => assert_eq!(s, "foo")); + assert_matches!(b, Foo::B(s) => { assert_eq!(s, "foo"); assert!(true) }); + assert_matches!(b, Foo::B(s) if s == "foo" => assert_eq!(s, "foo")); + assert_matches!(b, Foo::B(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(true) }); + + let c = Foo::C("foo"); + + assert_matches!(c, Foo::B(_) | Foo::C(_)); + assert_matches!(c, Foo::B("foo") | Foo::C("foo")); + assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo"); + assert_matches!(c, Foo::B(s) | Foo::C(s) => assert_eq!(s, "foo")); + assert_matches!(c, Foo::B(s) | Foo::C(s) => { assert_eq!(s, "foo"); assert!(true) }); + assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo" => assert_eq!(s, "foo")); + assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(true) }); + } + + #[test] + #[should_panic] + fn test_assert_panic_0() { + let a = Foo::A(123); + + assert_matches!(a, Foo::B(_)); + } + + #[test] + #[should_panic] + fn test_assert_panic_1() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B("bar")); + } + + #[test] + #[should_panic] + fn test_assert_panic_2() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) if s == "bar"); + } + + #[test] + #[should_panic] + fn test_assert_panic_3() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) => assert_eq!(s, "bar")); + } + + #[test] + #[should_panic] + fn test_assert_panic_4() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) if s == "bar" => assert_eq!(s, "foo")); + } + + #[test] + #[should_panic] + fn test_assert_panic_5() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) if s == "foo" => assert_eq!(s, "bar")); + } + + #[test] + #[should_panic] + fn test_assert_panic_6() { + let b = Foo::B("foo"); + + assert_matches!(b, Foo::B(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(false) }); + } + + #[test] + fn test_assert_no_move() { + let b = &mut Foo::A(0); + assert_matches!(*b, Foo::A(0)); + } + + #[test] + fn assert_with_message() { + let a = Foo::A(0); + + assert_matches!(a, Foo::A(_), "o noes"); + assert_matches!(a, Foo::A(n) if n == 0, "o noes"); + assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes"); + assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes"); + assert_matches!(a, Foo::A(n) if n == 0 => assert_eq!(n, 0), "o noes"); + assert_matches!(a, Foo::A(n) if n == 0 => { assert_eq!(n, 0); assert!(n < 1) }, "o noes"); + assert_matches!(a, Foo::A(_), "o noes {:?}", a); + assert_matches!(a, Foo::A(n) if n == 0, "o noes {:?}", a); + assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes {:?}", a); + assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes {:?}", a); + assert_matches!(a, Foo::A(_), "o noes {value:?}", value=a); + assert_matches!(a, Foo::A(n) if n == 0, "o noes {value:?}", value=a); + assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes {value:?}", value=a); + assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes {value:?}", value=a); + assert_matches!(a, Foo::A(n) if n == 0 => assert_eq!(n, 0), "o noes {value:?}", value=a); + } + + fn panic_message<F>(f: F) -> String + where F: FnOnce() + UnwindSafe { + let err = catch_unwind(f) + .expect_err("function did not panic"); + + *err.downcast::<String>() + .expect("function panicked with non-String value") + } + + #[test] + fn test_panic_message() { + let a = Foo::A(1); + + // expr, pat + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(_)); + }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`"#); + + // expr, pat if cond + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(s) if s == "foo"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`"#); + + // expr, pat => arm + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(_) => {}); + }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`"#); + + // expr, pat if cond => arm + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(s) if s == "foo" => {}); + }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`"#); + + // expr, pat, args + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(_), "msg"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`: msg"#); + + // expr, pat if cond, args + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(s) if s == "foo", "msg"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`: msg"#); + + // expr, pat => arm, args + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(_) => {}, "msg"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`: msg"#); + + // expr, pat if cond => arm, args + assert_eq!(panic_message(|| { + assert_matches!(a, Foo::B(s) if s == "foo" => {}, "msg"); + }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`: msg"#); + } +} |