blob: 7dc9ce334d5719507fb3a54bd638a026c34a1bed [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
// macro is documented in the matchers module.
#![doc(hidden)]
/// Matches a value according to a pattern of matchers.
///
/// This takes as an argument a specification similar to a struct or enum
/// initialiser, where each value is a [`Matcher`][crate::matcher::Matcher]
/// which is applied to the corresponding field.
///
/// This can be used to match arbitrary combinations of fields on structures
/// using arbitrary matchers:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// struct MyStruct {
/// a_field: String,
/// another_field: String,
/// }
///
/// let my_struct = MyStruct {
/// a_field: "Something to believe in".into(),
/// another_field: "Something else".into()
/// };
/// verify_that!(my_struct, matches_pattern!(MyStruct {
/// a_field: starts_with("Something"),
/// another_field: ends_with("else"),
/// }))
/// # .unwrap();
/// ```
///
/// It is not required to include all named fields in the specification. Omitted
/// fields have no effect on the output of the matcher.
///
/// ```
/// # use googletest::prelude::*;
/// # #[derive(Debug)]
/// # struct MyStruct {
/// # a_field: String,
/// # another_field: String,
/// # }
/// #
/// # let my_struct = MyStruct {
/// # a_field: "Something to believe in".into(),
/// # another_field: "Something else".into()
/// # };
/// verify_that!(my_struct, matches_pattern!(MyStruct {
/// a_field: starts_with("Something"),
/// // another_field is missing, so it may be anything.
/// }))
/// # .unwrap();
/// ```
///
/// One can use it recursively to match nested structures:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// struct MyStruct {
/// a_nested_struct: MyInnerStruct,
/// }
///
/// #[derive(Debug)]
/// struct MyInnerStruct {
/// a_field: String,
/// }
///
/// let my_struct = MyStruct {
/// a_nested_struct: MyInnerStruct { a_field: "Something to believe in".into() },
/// };
/// verify_that!(my_struct, matches_pattern!(MyStruct {
/// a_nested_struct: matches_pattern!(MyInnerStruct {
/// a_field: starts_with("Something"),
/// }),
/// }))
/// # .unwrap();
/// ```
///
/// One can use the alias [`pat`][crate::matchers::pat] to make this less
/// verbose:
///
/// ```
/// # use googletest::prelude::*;
/// # #[derive(Debug)]
/// # struct MyStruct {
/// # a_nested_struct: MyInnerStruct,
/// # }
/// #
/// # #[derive(Debug)]
/// # struct MyInnerStruct {
/// # a_field: String,
/// # }
/// #
/// # let my_struct = MyStruct {
/// # a_nested_struct: MyInnerStruct { a_field: "Something to believe in".into() },
/// # };
/// verify_that!(my_struct, matches_pattern!(MyStruct {
/// a_nested_struct: pat!(MyInnerStruct {
/// a_field: starts_with("Something"),
/// }),
/// }))
/// # .unwrap();
/// ```
///
/// In addition to fields, one can match on the outputs of methods
/// ("properties"):
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// struct MyStruct {
/// a_field: String,
/// }
///
/// impl MyStruct {
/// fn get_a_field(&self) -> String { self.a_field.clone() }
/// }
///
/// let my_struct = MyStruct { a_field: "Something to believe in".into() };
/// verify_that!(my_struct, matches_pattern!(MyStruct {
/// get_a_field(): starts_with("Something"),
/// }))
/// # .unwrap();
/// ```
///
/// If an inner matcher is `eq(...)`, it can be omitted:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// struct MyStruct {
/// a_field: String,
/// another_field: String,
/// }
///
/// let my_struct = MyStruct {
/// a_field: "this".into(),
/// another_field: "that".into()
/// };
/// verify_that!(my_struct, matches_pattern!(MyStruct {
/// a_field: "this",
/// another_field: "that",
/// }))
/// # .unwrap();
/// ```
///
/// **Important**: The method should be pure function with a deterministic
/// output and no side effects. In particular, in the event of an assertion
/// failure, it will be invoked a second time, with the assertion failure output
/// reflecting the *second* invocation.
///
/// These may also include extra litteral parameters you pass in:
///
/// ```
/// # use googletest::prelude::*;
/// # #[derive(Debug)]
/// # struct MyStruct {
/// # a_field: String,
/// # }
/// #
/// impl MyStruct {
/// fn append_to_a_field(&self, suffix: &str) -> String { self.a_field.clone() + suffix }
/// }
///
/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
/// verify_that!(my_struct, matches_pattern!(&MyStruct {
/// append_to_a_field("a suffix"): ref ends_with("a suffix"),
/// }))
/// # .unwrap();
/// ```
///
/// You can precede both field and property matchers with a `ref` to match the
/// result by reference:
///
/// ```
/// # use googletest::prelude::*;
/// # #[derive(Debug)]
/// # struct MyStruct {
/// # a_field: String,
/// # }
/// #
/// impl MyStruct {
/// fn get_a_field_ref(&self) -> String { self.a_field.clone() }
/// }
///
/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
/// verify_that!(my_struct, matches_pattern!(&MyStruct {
/// get_a_field_ref(): ref starts_with("Something"),
/// }))
/// # .unwrap();
/// ```
///
/// Note that if the `actual` is of type `&ActualT` and the pattern type is
/// `ActualT`, this is automatically performed. This behavior is similar to the
/// reference binding mode in pattern matching.
///
/// ```
/// # use googletest::prelude::*;
/// # #[derive(Debug)]
/// # struct MyStruct {
/// # a_field: String,
/// # }
/// #
/// impl MyStruct {
/// fn get_a_field_ref(&self) -> String { self.a_field.clone() }
/// }
///
/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
/// verify_that!(my_struct, matches_pattern!(MyStruct {
/// get_a_field_ref(): starts_with("Something"),
/// }))
/// # .unwrap();
/// ```
///
/// One can also match tuple structs with up to 10 fields. In this case, all
/// fields must have matchers:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// struct MyTupleStruct(String, String);
///
/// let my_struct = MyTupleStruct("Something".into(), "Some other thing".into());
/// verify_that!(
/// my_struct,
/// matches_pattern!(&MyTupleStruct(ref eq("Something"), ref eq("Some other thing")))
/// )
/// # .unwrap();
/// ```
///
/// One can also match enum values:
///
/// ```
/// # use googletest::prelude::*;
/// #[derive(Debug)]
/// enum MyEnum {
/// A(u32),
/// B,
/// }
///
/// # fn should_pass() -> Result<()> {
/// verify_that!(MyEnum::A(123), matches_pattern!(&MyEnum::A(eq(123))))?; // Passes
/// # Ok(())
/// # }
/// # fn should_fail() -> Result<()> {
/// verify_that!(MyEnum::B, matches_pattern!(&MyEnum::A(eq(123))))?; // Fails - wrong enum variant
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// # should_fail().unwrap_err();
/// ```
///
/// This macro does not support plain (non-struct) tuples. But it should not be
/// necessary as tuple of matchers are matchers of tuple. In other words, if
/// `MatcherU: Matcher<U>` and `MatcherT: Matcher<T>`, then `(MatcherU,
/// MatcherT): Matcher<(U, T)>`.
///
/// Trailing commas are allowed (but not required) in both ordinary and tuple
/// structs.
///
/// Note that the default format (rustfmt) can format macros if the macro
/// argument is parseable Rust code. This is mostly true for this macro with two
/// exceptions:
/// * property matching
/// * `ref` keyword with named fields
///
/// An option for formatting large is to avoid these exceptions (by removing the
/// parenthesis of properties and the `ref` keywords), run `rustfmt` and add
/// them back.
#[macro_export]
#[doc(hidden)]
macro_rules! __matches_pattern {
($($t:tt)*) => { $crate::matches_pattern_internal!($($t)*) }
}
// Internal-only macro created so that the macro definition does not appear in
// generated documentation.
#[doc(hidden)]
#[macro_export]
macro_rules! matches_pattern_internal {
(
@name [$($struct_name:tt)*],
{ $field_name:ident : ref $matcher:expr $(,)? }
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(field!($($struct_name)*.$field_name, ref $matcher))
)
};
(
@name [$($struct_name:tt)*],
{ $field_name:ident : $matcher:expr $(,)? }
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(field!($($struct_name)*.$field_name, $matcher))
)
};
(
@name [$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : ref $matcher:expr $(,)? }
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(property!($($struct_name)*.$property_name($($argument),*), ref $matcher))
)
};
(
@name [$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(property!($($struct_name)*.$property_name($($argument),*), $matcher))
)
};
(
@name [$($struct_name:tt)*],
{ $field_name:ident : ref $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
@fields (field!($($struct_name)*.$field_name, ref $matcher)),
[$($struct_name)*],
{ $($rest)* }
)
};
(
@name [$($struct_name:tt)*],
{ $field_name:ident : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
@fields (field!($($struct_name)*.$field_name, $matcher)),
[$($struct_name)*],
{ $($rest)* }
)
};
(
@name [$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : ref $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
@fields (property!($($struct_name)*.$property_name($($argument),*), ref $matcher)),
[$($struct_name)*],
{ $($rest)* }
)
};
(
@name [$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
@fields (property!($($struct_name)*.$property_name($($argument),*), $matcher)),
[$($struct_name)*],
{ $($rest)* }
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
{ $field_name:ident : ref $matcher:expr $(,)? }
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(
$($processed)*,
field!($($struct_name)*.$field_name, ref $matcher)
))
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
{ $field_name:ident : $matcher:expr $(,)? }
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(
$($processed)*,
field!($($struct_name)*.$field_name, $matcher)
))
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : ref $matcher:expr $(,)? }
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(
$($processed)*,
property!($($struct_name)*.$property_name($($argument),*), ref $matcher)
))
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(
$($processed)*,
property!($($struct_name)*.$property_name($($argument),*), $matcher)
))
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
{ $field_name:ident : ref $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.$field_name, ref $matcher)
),
[$($struct_name)*],
{ $($rest)* }
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
{ $field_name:ident : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.$field_name, $matcher)
),
[$($struct_name)*],
{ $($rest)* }
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : ref $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)
),
[$($struct_name)*],
{ $($rest)* }
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
property!($($struct_name)*.$property_name($($argument),*), $matcher)
),
[$($struct_name)*],
{ $($rest)* }
)
};
(
@name [$($struct_name:tt)*],
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::pattern_only(
|v| matches!(v, $($struct_name)*),
concat!("is ", stringify!($($struct_name)*)),
concat!("is not ", stringify!($($struct_name)*)))
};
(
@name [$($struct_name:tt)*],
(ref $matcher:expr $(,)?)
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(field!($($struct_name)*.0, ref $matcher))
)
};
(
@name [$($struct_name:tt)*],
($matcher:expr $(,)?)
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(field!($($struct_name)*.0, $matcher))
)
};
(
@name [$($struct_name:tt)*],
(ref $matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
field!($($struct_name)*.0, ref $matcher)
),
[$($struct_name)*],
1,
($($rest)*)
)
};
(
@name [$($struct_name:tt)*],
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
field!($($struct_name)*.0, $matcher)
),
[$($struct_name)*],
1,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
$field:tt,
(ref $matcher:expr $(,)?)
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(
$($processed)*,
field!($($struct_name)*.$field, ref $matcher)
))
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
$field:tt,
($matcher:expr $(,)?)
) => {
$crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(
$($processed)*,
field!($($struct_name)*.$field, $matcher)
))
};
// We need to repeat this once for every supported field position, unfortunately. There appears
// to be no way in declarative macros to compute $field + 1 and have the result evaluated to a
// token which can be used as a tuple index.
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
1,
(ref $matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.1, ref $matcher)
),
[$($struct_name)*],
2,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
1,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.1, $matcher)
),
[$($struct_name)*],
2,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
2,
(ref $matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.2, ref $matcher)
),
[$($struct_name)*],
3,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
2,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.2, $matcher)
),
[$($struct_name)*],
3,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
3,
(ref $matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.3, ref $matcher)
),
[$($struct_name)*],
4,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
3,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.3, $matcher)
),
[$($struct_name)*],
4,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
4,
(ref $matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.4, ref $matcher)
),
[$($struct_name)*],
5,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
4,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.4, $matcher)
),
[$($struct_name)*],
5,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
5,
(ref $matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.5, ref $matcher)
),
[$($struct_name)*],
6,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
5,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.5, $matcher)
),
[$($struct_name)*],
6,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
6,
(ref $matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.6, ref $matcher)
),
[$($struct_name)*],
7,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
6,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.6, $matcher)
),
[$($struct_name)*],
7,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
7,
(ref $matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.7, ref $matcher)
),
[$($struct_name)*],
8,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
7,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.7, $matcher)
),
[$($struct_name)*],
8,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
8,
(ref $matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.8, ref $matcher)
),
[$($struct_name)*],
9,
($($rest)*)
)
};
(
@fields ($($processed:tt)*),
[$($struct_name:tt)*],
8,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
@fields (
$($processed)*,
field!($($struct_name)*.8, $matcher)
),
[$($struct_name)*],
9,
($($rest)*)
)
};
(@name [$($struct_name:tt)*], $first:tt $($rest:tt)*) => {
$crate::matches_pattern_internal!(@name [$($struct_name)* $first], $($rest)*)
};
($first:tt $($rest:tt)*) => {{
#[allow(unused)]
use $crate::matchers::{all, field, property};
$crate::matches_pattern_internal!(@name [$first], $($rest)*)
}};
}
/// An alias for [`matches_pattern`][crate::matchers::matches_pattern!].
#[macro_export]
#[doc(hidden)]
macro_rules! __pat {
($($t:tt)*) => { $crate::matches_pattern_internal!($($t)*) }
}
#[doc(hidden)]
pub mod internal {
use crate::matcher::{Matcher, MatcherBase};
use std::fmt::Debug;
// Specialized implementation of the `predicate` matcher to support ref binding
// mode for `matches_pattern`.
pub fn pattern_only<T>(
matcher_function: fn(&T) -> bool,
match_description: &'static str,
no_match_description: &'static str,
) -> PatternOnlyMatcher<T> {
PatternOnlyMatcher { matcher_function, match_description, no_match_description }
}
#[derive(MatcherBase)]
#[doc(hidden)]
pub struct PatternOnlyMatcher<T> {
matcher_function: fn(&T) -> bool,
match_description: &'static str,
no_match_description: &'static str,
}
impl<'a, T: Debug> Matcher<&'a T> for PatternOnlyMatcher<T> {
fn matches(&self, actual: &'a T) -> crate::matcher::MatcherResult {
(self.matcher_function)(actual).into()
}
fn describe(
&self,
matcher_result: crate::matcher::MatcherResult,
) -> crate::description::Description {
match matcher_result {
crate::matcher::MatcherResult::Match => self.match_description.into(),
crate::matcher::MatcherResult::NoMatch => self.no_match_description.into(),
}
}
}
impl<T: Debug + Copy> Matcher<T> for PatternOnlyMatcher<T> {
fn matches(&self, actual: T) -> crate::matcher::MatcherResult {
(self.matcher_function)(&actual).into()
}
fn describe(
&self,
matcher_result: crate::matcher::MatcherResult,
) -> crate::description::Description {
match matcher_result {
crate::matcher::MatcherResult::Match => self.match_description.into(),
crate::matcher::MatcherResult::NoMatch => self.no_match_description.into(),
}
}
}
}