blob: 5ee0f22beed7c60732f6da8c4033244f37a6e0a3 [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 at the top level.
#![doc(hidden)]
/// Generates a matcher which matches a container each of whose elements match
/// the given matcher name applied respectively to each element of the given
/// container.
///
/// For example, the following matches a container of integers each of which
/// does not exceed the given upper bounds:
///
/// ```
/// # use googletest::prelude::*;
/// # fn should_pass() -> Result<()> {
/// let value = vec![1, 2, 3];
/// verify_that!(value, pointwise!(le, [1, 3, 3]))?; // Passes
/// verify_that!(value, pointwise!(le, vec![1, 3, 3]))?; // Passes
/// # Ok(())
/// # }
/// # fn should_fail() -> Result<()> {
/// # let value = vec![1, 2, 3];
/// verify_that!(value, pointwise!(le, [1, 1, 3]))?; // Fails
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// # should_fail().unwrap_err();
/// ```
///
/// One can also use a closure which returns a matcher:
///
/// ```
/// # use googletest::prelude::*;
/// # fn should_pass() -> Result<()> {
/// let value = vec![1.00001, 2.000001, 3.00001];
/// verify_that!(value, pointwise!(|v| near(v, 0.001), [1.0, 2.0, 3.0]))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// One can pass up to three containers to supply arguments to the function
/// creating the matcher:
///
/// ```
/// # use googletest::prelude::*;
/// # fn should_pass() -> Result<()> {
/// let value = vec![1.00001, 2.000001, 3.00001];
/// verify_that!(value, pointwise!(|v, t| near(v, t), [1.0, 2.0, 3.0], [0.001, 0.0001, 0.01]))?;
/// verify_that!(value, pointwise!(near, [1.0, 2.0, 3.0], [0.001, 0.0001, 0.01]))?; // Same as above
/// verify_that!(
/// value,
/// pointwise!(
/// |v, t, u| near(v, t * u),
/// [1.0, 2.0, 3.0],
/// [0.001, 0.0001, 0.01],
/// [0.5, 0.5, 1.0]
/// )
/// )?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// When using `pointwise!` with multiple containers, the caller must ensure
/// that all of the containers have the same size. This matcher does not check
/// whether the sizes match.
///
/// The actual value must be a container implementing [`IntoIterator`]. This
/// includes standard containers, slices (when dereferenced) and arrays.
///
/// ```
/// # use googletest::prelude::*;
/// # fn should_pass() -> Result<()> {
/// let value = vec![1, 2, 3];
/// verify_that!(*value.as_slice(), pointwise!(le, [1, 3, 3]))?; // Passes
/// verify_that!([1, 2, 3], pointwise!(le, [1, 3, 3]))?; // Passes
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// This matcher does not support matching directly against an [`Iterator`]. To
/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`]
/// first.
///
/// The second argument can be any value implementing `IntoIterator`, such as a
/// `Vec` or an array. The container does not have to have the same type as the
/// actual value, but the value type must be the same.
///
/// **Note for users of the [`Pointwise`] matcher in C++ GoogleTest:**
///
/// This macro differs from `Pointwise` in that the first parameter is not a
/// matcher which matches a pair but rather the name of a function of one
/// argument whose output is a matcher. This means that one can use standard
/// matchers like `eq`, `le`, and so on with `pointwise!` but certain C++ tests
/// using `Pointwise` will require some extra work to port.
///
/// [`IntoIterator`]: std::iter::IntoIterator
/// [`Iterator`]: std::iter::Iterator
/// [`Iterator::collect`]: std::iter::Iterator::collect
/// [`Pointwise`]: https://google.github.io/googletest/reference/matchers.html#container-matchers
/// [`Vec`]: std::vec::Vec
#[macro_export]
macro_rules! pointwise {
($matcher:expr, $container:expr) => {{
use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
PointwiseMatcher::new($container.into_iter().map($matcher).collect())
}};
($matcher:expr, $left_container:expr, $right_container:expr) => {{
use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
PointwiseMatcher::new(
$left_container
.into_iter()
.zip($right_container.into_iter())
.map(|(l, r)| $matcher(l, r))
.collect(),
)
}};
($matcher:expr, $left_container:expr, $middle_container:expr, $right_container:expr) => {{
use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
PointwiseMatcher::new(
$left_container
.into_iter()
.zip($right_container.into_iter().zip($middle_container.into_iter()))
.map(|(l, (m, r))| $matcher(l, m, r))
.collect(),
)
}};
}
/// Module for use only by the procedural macros in this module.
///
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub mod internal {
use crate::matcher::{Matcher, MatcherResult};
use crate::matcher_support::description::Description;
use crate::matcher_support::zipped_iterator::zip;
use std::{fmt::Debug, marker::PhantomData};
/// This struct is meant to be used only through the `pointwise` macro.
///
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub struct PointwiseMatcher<ContainerT: ?Sized, MatcherT> {
matchers: Vec<MatcherT>,
phantom: PhantomData<ContainerT>,
}
impl<ContainerT: ?Sized, MatcherT> PointwiseMatcher<ContainerT, MatcherT> {
pub fn new(matchers: Vec<MatcherT>) -> Self {
Self { matchers, phantom: Default::default() }
}
}
impl<T: Debug, MatcherT: Matcher<ActualT = T>, ContainerT: ?Sized + Debug> Matcher
for PointwiseMatcher<ContainerT, MatcherT>
where
for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
{
type ActualT = ContainerT;
fn matches(&self, actual: &ContainerT) -> MatcherResult {
let mut zipped_iterator = zip(actual.into_iter(), self.matchers.iter());
for (element, matcher) in zipped_iterator.by_ref() {
if matcher.matches(element).is_no_match() {
return MatcherResult::NoMatch;
}
}
if zipped_iterator.has_size_mismatch() {
MatcherResult::NoMatch
} else {
MatcherResult::Match
}
}
fn explain_match(&self, actual: &ContainerT) -> String {
// TODO(b/260819741) This code duplicates elements_are_matcher.rs. Consider
// extract as a separate library. (or implement pointwise! with
// elements_are)
let actual_iterator = actual.into_iter();
let mut zipped_iterator = zip(actual_iterator, self.matchers.iter());
let mut mismatches = Vec::new();
for (idx, (a, e)) in zipped_iterator.by_ref().enumerate() {
if e.matches(a).is_no_match() {
mismatches.push(format!("element #{idx} is {a:?}, {}", e.explain_match(a)));
}
}
if mismatches.is_empty() {
if !zipped_iterator.has_size_mismatch() {
"which matches all elements".to_string()
} else {
format!(
"which has size {} (expected {})",
zipped_iterator.left_size(),
self.matchers.len()
)
}
} else if mismatches.len() == 1 {
format!("where {}", mismatches[0])
} else {
let mismatches = mismatches.into_iter().collect::<Description>();
format!("where:\n{}", mismatches.bullet_list().indent())
}
}
fn describe(&self, matcher_result: MatcherResult) -> String {
format!(
"{} elements satisfying respectively:\n{}",
if matcher_result.into() { "has" } else { "doesn't have" },
self.matchers
.iter()
.map(|m| m.describe(MatcherResult::Match))
.collect::<Description>()
.enumerate()
.indent()
)
}
}
}