Skip to main content

googletest/matchers/
contains_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
15use crate::{
16    description::Description,
17    matcher::{Matcher, MatcherBase, MatcherResult},
18};
19use std::fmt::Debug;
20
21/// Matches an [`IntoIterator`] type whose elements contain a value matched by
22/// `inner`.
23///
24/// By default, this matches a container with any number of elements matched
25/// by `inner`. Use the method [`ContainsMatcher::times`] to constrain the
26/// matched containers to a specific number of matching elements.
27///
28/// ```
29/// # use googletest::prelude::*;
30/// # fn should_pass() -> Result<()> {
31/// verify_that!(["Some value"], contains(eq("Some value")))?;  // Passes
32/// verify_that!(vec!["Some value"], contains(eq(&"Some value")))?;  // Passes
33/// #     Ok(())
34/// # }
35/// # fn should_fail_1() -> Result<()> {
36/// verify_that!([] as [&String; 0], contains(eq("Some value")))?;   // Fails
37/// #     Ok(())
38/// # }
39/// # fn should_fail_2() -> Result<()> {
40/// verify_that!(["Some value"], contains(eq("Some other value")))?;   // Fails
41/// #     Ok(())
42/// # }
43/// # should_pass().unwrap();
44/// # should_fail_1().unwrap_err();
45/// # should_fail_2().unwrap_err();
46/// ```
47pub fn contains<InnerMatcherT>(inner: InnerMatcherT) -> ContainsMatcher<InnerMatcherT> {
48    ContainsMatcher { inner, count: None }
49}
50
51/// A matcher which matches a container containing one or more elements a given
52/// inner [`Matcher`] matches.
53#[derive(MatcherBase)]
54pub struct ContainsMatcher<InnerMatcherT> {
55    inner: InnerMatcherT,
56    count: Option<Box<dyn Matcher<usize>>>,
57}
58
59impl<InnerMatcherT> ContainsMatcher<InnerMatcherT> {
60    /// Configures this instance to match containers which contain a number of
61    /// matching items matched by `count`.
62    ///
63    /// For example, to assert that exactly three matching items must be
64    /// present, use:
65    ///
66    /// ```ignore
67    /// contains(...).times(eq(3))
68    /// ```
69    ///
70    /// One can also use `times(eq(0))` to test for the *absence* of an item
71    /// matching the expected value.
72    pub fn times(mut self, count: impl Matcher<usize> + 'static) -> Self {
73        self.count = Some(Box::new(count));
74        self
75    }
76}
77
78// TODO(hovinen): Revisit the trait bounds to see whether this can be made more
79//  flexible. Namely, the following doesn't compile currently:
80//
81//      let matcher = contains(eq(&42));
82//      let val = 42;
83//      let _ = matcher.matches(&vec![&val]);
84//
85//  because val is dropped before matcher but the trait bound requires that
86//  the argument to matches outlive the matcher. It works fine if one defines
87//  val before matcher.
88impl<T: Debug + Copy, InnerMatcherT: Matcher<T>, ContainerT: Debug + Copy> Matcher<ContainerT>
89    for ContainsMatcher<InnerMatcherT>
90where
91    ContainerT: IntoIterator<Item = T>,
92{
93    fn matches(&self, actual: ContainerT) -> MatcherResult {
94        if let Some(count) = &self.count {
95            count.matches(self.count_matches(actual))
96        } else {
97            for v in actual.into_iter() {
98                if self.inner.matches(v).into() {
99                    return MatcherResult::Match;
100                }
101            }
102            MatcherResult::NoMatch
103        }
104    }
105
106    fn explain_match(&self, actual: ContainerT) -> Description {
107        let count = self.count_matches(actual);
108        match (count, &self.count) {
109            (_, Some(_)) => format!("which contains {count} matching elements").into(),
110            (0, None) => "which does not contain a matching element".into(),
111            (_, None) => "which contains a matching element".into(),
112        }
113    }
114
115    fn describe(&self, matcher_result: MatcherResult) -> Description {
116        match (matcher_result, &self.count) {
117            (MatcherResult::Match, Some(count)) => format!(
118                "contains n elements which {}\n  where n {}",
119                self.inner.describe(MatcherResult::Match),
120                count.describe(MatcherResult::Match)
121            )
122            .into(),
123            (MatcherResult::NoMatch, Some(count)) => format!(
124                "doesn't contain n elements which {}\n  where n {}",
125                self.inner.describe(MatcherResult::Match),
126                count.describe(MatcherResult::Match)
127            )
128            .into(),
129            (MatcherResult::Match, None) => format!(
130                "contains at least one element which {}",
131                self.inner.describe(MatcherResult::Match)
132            )
133            .into(),
134            (MatcherResult::NoMatch, None) => {
135                format!("contains no element which {}", self.inner.describe(MatcherResult::Match))
136                    .into()
137            }
138        }
139    }
140}
141
142impl<InnerMatcherT> ContainsMatcher<InnerMatcherT> {
143    fn count_matches<T: Debug + Copy, ContainerT>(&self, actual: ContainerT) -> usize
144    where
145        ContainerT: IntoIterator<Item = T>,
146        InnerMatcherT: Matcher<T>,
147    {
148        let mut count = 0;
149        for v in actual.into_iter() {
150            if self.inner.matches(v).into() {
151                count += 1;
152            }
153        }
154        count
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use crate::matcher::MatcherResult;
161    use crate::prelude::*;
162    use crate::Result;
163
164    #[test]
165    fn contains_matches_singleton_slice_with_value() -> Result<()> {
166        let matcher = contains(eq(&1));
167
168        let result = matcher.matches(&vec![1]);
169
170        verify_that!(result, eq(MatcherResult::Match))
171    }
172
173    #[test]
174    fn contains_matches_singleton_vec_with_value() -> Result<()> {
175        let matcher = contains(eq(&1));
176
177        let result = matcher.matches(&vec![1]);
178
179        verify_that!(result, eq(MatcherResult::Match))
180    }
181
182    #[test]
183    fn contains_matches_two_element_slice_with_value() -> Result<()> {
184        let matcher = contains(eq(&1));
185
186        let result = matcher.matches(&[0, 1]);
187
188        verify_that!(result, eq(MatcherResult::Match))
189    }
190
191    #[test]
192    fn contains_does_not_match_singleton_slice_with_wrong_value() -> Result<()> {
193        let matcher = contains(eq(&1));
194
195        let result = matcher.matches(&[0]);
196
197        verify_that!(result, eq(MatcherResult::NoMatch))
198    }
199
200    #[test]
201    fn contains_does_not_match_empty_slice() -> Result<()> {
202        let matcher = contains(eq(&1));
203
204        let result = matcher.matches(&[1; 0]);
205
206        verify_that!(result, eq(MatcherResult::NoMatch))
207    }
208
209    #[test]
210    fn contains_matches_slice_with_repeated_value() -> Result<()> {
211        let matcher = contains(eq(&1)).times(eq(2));
212
213        let result = matcher.matches(&[1, 1]);
214
215        verify_that!(result, eq(MatcherResult::Match))
216    }
217
218    #[test]
219    fn contains_does_not_match_slice_with_too_few_of_value() -> Result<()> {
220        let matcher = contains(eq(&1)).times(eq(2));
221
222        let result = matcher.matches(&[0, 1]);
223
224        verify_that!(result, eq(MatcherResult::NoMatch))
225    }
226
227    #[test]
228    fn contains_does_not_match_slice_with_too_many_of_value() -> Result<()> {
229        let matcher = contains(eq(&1)).times(eq(1));
230
231        let result = matcher.matches(&[1, 1]);
232
233        verify_that!(result, eq(MatcherResult::NoMatch))
234    }
235
236    #[test]
237    fn contains_formats_without_multiplicity_by_default() -> Result<()> {
238        let matcher = contains(eq(&1));
239
240        verify_that!(
241            Matcher::<&Vec<i32>>::describe(&matcher, MatcherResult::Match),
242            displays_as(eq("contains at least one element which is equal to 1"))
243        )
244    }
245
246    #[test]
247    fn contains_formats_with_multiplicity_when_specified() -> Result<()> {
248        let matcher = contains(eq(&1)).times(eq(2));
249
250        verify_that!(
251            Matcher::<&Vec<i32>>::describe(&matcher, MatcherResult::Match),
252            displays_as(eq("contains n elements which is equal to 1\n  where n is equal to 2"))
253        )
254    }
255
256    #[test]
257    fn contains_mismatch_shows_number_of_times_element_was_found() -> Result<()> {
258        verify_that!(
259            contains(eq(&3)).times(eq(1)).explain_match(&vec![1, 2, 3, 3]),
260            displays_as(eq("which contains 2 matching elements"))
261        )
262    }
263
264    #[test]
265    fn contains_mismatch_shows_when_matches() -> Result<()> {
266        verify_that!(
267            contains(eq(&3)).explain_match(&vec![1, 2, 3, 3]),
268            displays_as(eq("which contains a matching element"))
269        )
270    }
271
272    #[test]
273    fn contains_mismatch_shows_when_no_matches() -> Result<()> {
274        verify_that!(
275            contains(eq(&3)).explain_match(&vec![1, 2]),
276            displays_as(eq("which does not contain a matching element"))
277        )
278    }
279}