blob: 106a5d702d6d0dbaa37cf2c1f9276c101ebe7a28 [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();
/// ```
///
/// **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 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"): ends_with("a suffix"),
/// }))
/// # .unwrap();
/// ```
///
/// If the method returns a reference, precede it with a `*`:
///
/// ```
/// # use googletest::prelude::*;
/// # #[derive(Debug)]
/// # struct MyStruct {
/// # a_field: String,
/// # }
/// #
/// impl MyStruct {
/// fn get_a_field_ref(&self) -> &String { &self.a_field }
/// }
///
/// # 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(eq("Something"), 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. Use the macro
/// [`tuple`] for that purpose.
///
/// Trailing commas are allowed (but not required) in both ordinary and tuple
/// structs.
///
/// Unfortunately, this matcher does *not* work with methods returning string
/// slices:
///
/// ```compile_fail
/// # use googletest::prelude::*;
/// # #[derive(Debug)]
/// pub struct MyStruct {
/// a_string: String,
/// }
/// impl MyStruct {
/// pub fn get_a_string(&self) -> &str { &self.a_string }
/// }
///
/// let value = MyStruct { a_string: "A string".into() };
/// verify_that!(value, matches_pattern!( MyStruct {
/// get_a_string(): eq("A string"), // Does not compile
/// }))
/// # .unwrap();
/// ```
#[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 {
(
[$($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))
)
};
(
[$($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))
)
};
(
[$($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))
)
};
(
[$($struct_name:tt)*],
{ $field_name:ident : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
all!(field!($($struct_name)*.$field_name, $matcher)),
[$($struct_name)*],
{ $($rest)* }
)
};
(
[$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
all!(property!($($struct_name)*.$property_name($($argument),*), $matcher)),
[$($struct_name)*],
{ $($rest)* }
)
};
(
[$($struct_name:tt)*],
{ * $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
all!(property!(* $($struct_name)*.$property_name($($argument),*), $matcher)),
[$($struct_name)*],
{ $($rest)* }
)
};
(
all!($($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)
))
};
(
all!($($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)
))
};
(
all!($($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)
))
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
{ $field_name:ident : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
field!($($struct_name)*.$field_name, $matcher)
),
[$($struct_name)*],
{ $($rest)* }
)
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
property!($($struct_name)*.$property_name($($argument),*), $matcher)
),
[$($struct_name)*],
{ $($rest)* }
)
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
{ * $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
property!(* $($struct_name)*.$property_name($($argument),*), $matcher)
),
[$($struct_name)*],
{ $($rest)* }
)
};
(
[$($struct_name:tt)*],
) => {
$crate::matchers::predicate(|v| matches!(v, $($struct_name)*))
.with_description(
concat!("is ", stringify!($($struct_name)*)),
concat!("is not ", stringify!($($struct_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))
)
};
(
[$($struct_name:tt)*],
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
all!(
field!($($struct_name)*.0, $matcher)
),
[$($struct_name)*],
1,
($($rest)*)
)
};
(
all!($($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.
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
1,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
field!($($struct_name)*.1, $matcher)
),
[$($struct_name)*],
2,
($($rest)*)
)
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
2,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
field!($($struct_name)*.2, $matcher)
),
[$($struct_name)*],
3,
($($rest)*)
)
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
3,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
field!($($struct_name)*.3, $matcher)
),
[$($struct_name)*],
4,
($($rest)*)
)
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
4,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
field!($($struct_name)*.4, $matcher)
),
[$($struct_name)*],
5,
($($rest)*)
)
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
5,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
field!($($struct_name)*.5, $matcher)
),
[$($struct_name)*],
6,
($($rest)*)
)
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
6,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
field!($($struct_name)*.6, $matcher)
),
[$($struct_name)*],
7,
($($rest)*)
)
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
7,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
field!($($struct_name)*.7, $matcher)
),
[$($struct_name)*],
8,
($($rest)*)
)
};
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
8,
($matcher:expr, $($rest:tt)*)
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
field!($($struct_name)*.8, $matcher)
),
[$($struct_name)*],
9,
($($rest)*)
)
};
([$($struct_name:tt)*], $first:tt $($rest:tt)*) => {
$crate::matches_pattern_internal!([$($struct_name)* $first], $($rest)*)
};
($first:tt $($rest:tt)*) => {{
#[allow(unused)]
use $crate::matchers::{all, field, property};
$crate::matches_pattern_internal!([$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)*) }
}