googletest/matchers/pointwise_matcher.rs
1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// There are no visible documentation elements in this module; the declarative
16// macro is documented in the matcher module.
17#![doc(hidden)]
18
19/// Generates a matcher which matches a container each of whose elements match
20/// the given matcher name applied respectively to each element of the given
21/// container.
22///
23/// For example, the following matches a container of integers each of which
24/// does not exceed the given upper bounds:
25///
26/// ```
27/// # use googletest::prelude::*;
28/// # fn should_pass() -> Result<()> {
29/// let value = vec![1, 2, 3];
30/// verify_that!(value, pointwise!(le, [&1, &2, &3]))?; // Passes
31/// verify_that!(value, pointwise!(|e| points_to(le(e)), [1, 2, 3]))?; // Passes
32/// verify_that!(value, pointwise!(|e| points_to(le(e)), vec![1, 3, 3]))?; // Passes
33/// # Ok(())
34/// # }
35/// # fn should_fail() -> Result<()> {
36/// # let value = vec![1, 2, 3];
37/// verify_that!(value, pointwise!(|e| points_to(le(e)), [1, 1, 3]))?; // Fails
38/// # Ok(())
39/// # }
40/// # should_pass().unwrap();
41/// # should_fail().unwrap_err();
42/// ```
43///
44/// One can also use a closure which returns a matcher:
45///
46/// ```
47/// # use googletest::prelude::*;
48/// # fn should_pass() -> Result<()> {
49/// let value = vec![1.00001, 2.000001, 3.00001];
50/// verify_that!(value, pointwise!(|v| points_to(near(v, 0.001)), [1.0, 2.0, 3.0]))?;
51/// # Ok(())
52/// # }
53/// # should_pass().unwrap();
54/// ```
55///
56/// One can pass up to three containers to supply arguments to the function
57/// creating the matcher:
58///
59/// ```
60/// # use googletest::prelude::*;
61/// # fn should_pass() -> Result<()> {
62/// let value = vec![1.00001, 2.000001, 3.00001];
63/// verify_that!(value, pointwise!(|v, t| points_to(near(v, t)), [1.0, 2.0, 3.0], [0.001, 0.0001, 0.01]))?;
64/// verify_that!(
65/// value,
66/// pointwise!(
67/// |v, t, u| points_to(near(v, t * u)),
68/// [1.0, 2.0, 3.0],
69/// [0.001, 0.0001, 0.01],
70/// [0.5, 0.5, 1.0]
71/// )
72/// )?;
73/// # Ok(())
74/// # }
75/// # should_pass().unwrap();
76/// ```
77///
78/// When using `pointwise!` with multiple containers, the caller must ensure
79/// that all of the containers have the same size. This matcher does not check
80/// whether the sizes match.
81///
82/// The actual value must be a container such as a `&Vec`, an array, or a
83/// slice. More precisely, the actual value must implement [`IntoIterator`].
84///
85/// ```
86/// # use googletest::prelude::*;
87/// # fn should_pass() -> Result<()> {
88/// let value = vec![1, 2, 3];
89/// verify_that!(value, pointwise!(|i| points_to(le(i)), [1, 3, 3]))?; // Passes
90/// verify_that!([1, 2, 3], pointwise!(le, [1, 3, 3]))?; // Passes
91/// # Ok(())
92/// # }
93/// # should_pass().unwrap();
94/// ```
95///
96/// The second argument can be any value implementing `IntoIterator`, such as a
97/// `Vec` or an array. The container does not have to have the same type as the
98/// actual value, but the value type must be the same.
99///
100/// **Note for users of the [`Pointwise`] matcher in C++ GoogleTest:**
101///
102/// This macro differs from `Pointwise` in that the first parameter is not a
103/// matcher which matches a pair but rather the name of a function of one
104/// argument whose output is a matcher. This means that one can use standard
105/// matchers like `eq`, `le`, and so on with `pointwise!` but certain C++ tests
106/// using `Pointwise` will require some extra work to port.
107///
108/// [`IntoIterator`]: std::iter::IntoIterator
109/// [`Iterator`]: std::iter::Iterator
110/// [`Iterator::collect`]: std::iter::Iterator::collect
111/// [`Pointwise`]: https://google.github.io/googletest/reference/matchers.html#container-matchers
112/// [`Vec`]: std::vec::Vec
113#[macro_export]
114#[doc(hidden)]
115macro_rules! __pointwise {
116 ($matcher:expr, $container:expr) => {{
117 $crate::matchers::__internal_unstable_do_not_depend_on_these::PointwiseMatcher::new(
118 $container.into_iter().map($matcher).collect(),
119 )
120 }};
121
122 ($matcher:expr, $left_container:expr, $right_container:expr) => {{
123 $crate::matchers::__internal_unstable_do_not_depend_on_these::PointwiseMatcher::new(
124 $left_container
125 .into_iter()
126 .zip($right_container.into_iter())
127 .map(|(l, r)| $matcher(l, r))
128 .collect(),
129 )
130 }};
131
132 ($matcher:expr, $left_container:expr, $middle_container:expr, $right_container:expr) => {{
133 $crate::matchers::__internal_unstable_do_not_depend_on_these::PointwiseMatcher::new(
134 $left_container
135 .into_iter()
136 .zip($right_container.into_iter().zip($middle_container.into_iter()))
137 .map(|(l, (m, r))| $matcher(l, m, r))
138 .collect(),
139 )
140 }};
141}
142
143/// Module for use only by the procedural macros in this module.
144///
145/// **For internal use only. API stablility is not guaranteed!**
146#[doc(hidden)]
147pub mod internal {
148 use crate::description::Description;
149 use crate::matcher::{Matcher, MatcherBase, MatcherResult};
150 use crate::matcher_support::zipped_iterator::zip;
151 use std::fmt::Debug;
152
153 /// This struct is meant to be used only through the `pointwise` macro.
154 ///
155 /// **For internal use only. API stablility is not guaranteed!**
156 #[doc(hidden)]
157 #[derive(MatcherBase)]
158 pub struct PointwiseMatcher<MatcherT> {
159 matchers: Vec<MatcherT>,
160 }
161
162 impl<MatcherT> PointwiseMatcher<MatcherT> {
163 pub fn new(matchers: Vec<MatcherT>) -> Self {
164 Self { matchers }
165 }
166 }
167
168 impl<T: Debug + Copy, MatcherT: Matcher<T>, ContainerT: Copy + Debug> Matcher<ContainerT>
169 for PointwiseMatcher<MatcherT>
170 where
171 ContainerT: IntoIterator<Item = T>,
172 {
173 fn matches(&self, actual: ContainerT) -> MatcherResult {
174 let mut zipped_iterator = zip(actual.into_iter(), self.matchers.iter());
175 for (element, matcher) in zipped_iterator.by_ref() {
176 if matcher.matches(element).is_no_match() {
177 return MatcherResult::NoMatch;
178 }
179 }
180 if zipped_iterator.has_size_mismatch() {
181 MatcherResult::NoMatch
182 } else {
183 MatcherResult::Match
184 }
185 }
186
187 fn explain_match(&self, actual: ContainerT) -> Description {
188 // TODO(b/260819741) This code duplicates elements_are_matcher.rs. Consider
189 // extract as a separate library. (or implement pointwise! with
190 // elements_are)
191 let actual_iterator = actual.into_iter();
192 let mut zipped_iterator = zip(actual_iterator, self.matchers.iter());
193 let mut mismatches = Vec::new();
194 for (idx, (a, e)) in zipped_iterator.by_ref().enumerate() {
195 if e.matches(a).is_no_match() {
196 mismatches.push(format!("element #{idx} is {a:?}, {}", e.explain_match(a)));
197 }
198 }
199 if mismatches.is_empty() {
200 if !zipped_iterator.has_size_mismatch() {
201 "which matches all elements".into()
202 } else {
203 format!(
204 "which has size {} (expected {})",
205 zipped_iterator.left_size(),
206 self.matchers.len()
207 )
208 .into()
209 }
210 } else if mismatches.len() == 1 {
211 format!("where {}", mismatches[0]).into()
212 } else {
213 let mismatches = mismatches.into_iter().collect::<Description>();
214 format!("where:\n{}", mismatches.bullet_list().indent()).into()
215 }
216 }
217
218 fn describe(&self, matcher_result: MatcherResult) -> Description {
219 format!(
220 "{} elements satisfying respectively:\n{}",
221 if matcher_result.into() { "has" } else { "doesn't have" },
222 self.matchers
223 .iter()
224 .map(|m| m.describe(MatcherResult::Match))
225 .collect::<Description>()
226 .enumerate()
227 .indent()
228 )
229 .into()
230 }
231 }
232}