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)] 115 macro_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)] 147 pub 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> { new(matchers: Vec<MatcherT>) -> Self163 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 { matches(&self, actual: ContainerT) -> MatcherResult173 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 explain_match(&self, actual: ContainerT) -> Description187 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 describe(&self, matcher_result: MatcherResult) -> Description218 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 } 233