Skip to main content

googletest/matchers/
subset_of_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 a container all of whose items are in the given container
22/// `superset`.
23///
24/// The element type `ElementT` must implement `PartialEq` to allow element
25/// comparison.
26///
27/// `ActualT` and `ExpectedT` can each be any container which
28/// implements `IntoIterator`. For instance, `T` can be a common container like
29/// `&Vec` or arrays. They need not be the same container type.
30///
31/// ```
32/// # use googletest::prelude::*;
33/// # use std::collections::HashSet;
34/// # fn should_pass_1() -> Result<()> {
35/// let value = vec![1, 2, 3];
36/// verify_that!(value, subset_of([&1, &2, &3, &4]))?;  // Passes
37/// let array_value = [1, 2, 3];
38/// verify_that!(array_value, subset_of([1, 2, 3, 4]))?;  // Passes
39/// #     Ok(())
40/// # }
41/// # fn should_fail() -> Result<()> {
42/// # let value = vec![1, 2, 3];
43/// verify_that!(value, subset_of([&1, &2]))?;  // Fails: 3 is not in the superset
44/// #     Ok(())
45/// # }
46/// # should_pass_1().unwrap();
47/// # should_fail().unwrap_err();
48///
49/// # fn should_pass_2() -> Result<()> {
50/// let value: HashSet<i32> = [1, 2, 3].into();
51/// verify_that!(value, subset_of([&1, &2, &3]))?;  // Passes
52/// #     Ok(())
53/// # }
54/// # should_pass_2().unwrap();
55/// ```
56///
57/// Item multiplicity in both the actual and expected containers is ignored:
58///
59/// ```
60/// # use googletest::prelude::*;
61/// # fn should_pass() -> Result<()> {
62/// let value: Vec<i32> = vec![0, 0, 1];
63/// verify_that!(value, subset_of([&0, &1]))?;  // Passes
64/// verify_that!(value, subset_of([&0, &1, &1]))?;  // Passes
65/// #     Ok(())
66/// # }
67/// # should_pass().unwrap();
68/// ```
69///
70/// A note on performance: This matcher uses a naive algorithm with a worst-case
71/// runtime proportional to the *product* of the sizes of the actual and
72/// expected containers as well as the time to check equality of each pair of
73/// items. It should not be used on especially large containers.
74pub fn subset_of<ExpectedT>(superset: ExpectedT) -> SubsetOfMatcher<ExpectedT> {
75    SubsetOfMatcher { superset }
76}
77
78#[derive(MatcherBase)]
79pub struct SubsetOfMatcher<ExpectedT> {
80    superset: ExpectedT,
81}
82
83impl<ElementT: Debug + PartialEq + Copy, ActualT: Debug + Copy, ExpectedT: Debug> Matcher<ActualT>
84    for SubsetOfMatcher<ExpectedT>
85where
86    ActualT: IntoIterator<Item = ElementT>,
87    for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
88{
89    fn matches(&self, actual: ActualT) -> MatcherResult {
90        for actual_item in actual {
91            if self.expected_is_missing(&actual_item) {
92                return MatcherResult::NoMatch;
93            }
94        }
95        MatcherResult::Match
96    }
97
98    fn explain_match(&self, actual: ActualT) -> Description {
99        let unexpected_elements = actual
100            .into_iter()
101            .enumerate()
102            .filter(|item| self.expected_is_missing(&item.1))
103            .map(|(idx, actual_item)| format!("{actual_item:#?} at #{idx}"))
104            .collect::<Vec<_>>();
105
106        match unexpected_elements.len() {
107            0 => "which no element is unexpected".into(),
108            1 => format!("whose element {} is unexpected", &unexpected_elements[0]).into(),
109            _ => format!("whose elements {} are unexpected", unexpected_elements.join(", ")).into(),
110        }
111    }
112
113    fn describe(&self, matcher_result: MatcherResult) -> Description {
114        match matcher_result {
115            MatcherResult::Match => format!("is a subset of {:#?}", self.superset).into(),
116            MatcherResult::NoMatch => format!("isn't a subset of {:#?}", self.superset).into(),
117        }
118    }
119}
120
121impl<ElementT: PartialEq, ExpectedT> SubsetOfMatcher<ExpectedT>
122where
123    for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
124{
125    fn expected_is_missing(&self, needle: &ElementT) -> bool {
126        !self.superset.into_iter().any(|item| item == needle)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use crate::prelude::*;
133    use crate::Result;
134    use indoc::indoc;
135    use std::collections::HashSet;
136
137    #[test]
138    fn subset_of_matches_empty_vec() -> Result<()> {
139        let value: Vec<i32> = vec![];
140        verify_that!(value, subset_of([]))
141    }
142
143    #[test]
144    fn subset_of_matches_vec_with_one_element() -> Result<()> {
145        let value = vec![1];
146        verify_that!(value, subset_of([&1]))
147    }
148
149    #[test]
150    fn subset_of_matches_vec_with_two_elements() -> Result<()> {
151        let value = vec![1, 2];
152        verify_that!(value, subset_of([&1, &2]))
153    }
154
155    #[test]
156    fn subset_of_matches_vec_when_expected_has_excess_element() -> Result<()> {
157        let value = vec![1, 2];
158        verify_that!(value, subset_of([&1, &2, &3]))
159    }
160
161    #[test]
162    fn subset_of_matches_vec_when_expected_has_excess_element_first() -> Result<()> {
163        let value = vec![1, 2];
164        verify_that!(value, subset_of([&3, &1, &2]))
165    }
166
167    #[test]
168    fn subset_of_matches_slice_with_one_element() -> Result<()> {
169        let value = &[1];
170        verify_that!(value, subset_of([&1]))
171    }
172
173    #[test]
174    fn subset_of_matches_hash_set_with_one_element() -> Result<()> {
175        let value: HashSet<i32> = [1].into();
176        verify_that!(value, subset_of([&1]))
177    }
178
179    #[test]
180    fn subset_of_does_not_match_when_first_element_does_not_match() -> Result<()> {
181        let value = vec![0];
182        verify_that!(value, not(subset_of([&1])))
183    }
184
185    #[test]
186    fn subset_of_does_not_match_when_second_element_does_not_match() -> Result<()> {
187        let value = vec![2, 0];
188        verify_that!(value, not(subset_of([&2])))
189    }
190
191    #[test]
192    fn subset_of_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
193        let result = verify_that!(vec![0, 2, 3], subset_of([&1, &2, &3]));
194
195        verify_that!(
196            result,
197            err(displays_as(contains_substring(indoc!(
198                "
199                    Value of: vec![0, 2, 3]
200                    Expected: is a subset of [
201                        1,
202                        2,
203                        3,
204                    ]
205                    Actual: [0, 2, 3],
206                      whose element 0 at #0 is unexpected
207                "
208            ))))
209        )
210    }
211
212    #[test]
213    fn subset_of_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
214        let result = verify_that!(vec![1, 0, 3], subset_of([&1, &2, &3]));
215
216        verify_that!(
217            result,
218            err(displays_as(contains_substring(indoc!(
219                "
220                    Value of: vec![1, 0, 3]
221                    Expected: is a subset of [
222                        1,
223                        2,
224                        3,
225                    ]
226                    Actual: [1, 0, 3],
227                      whose element 0 at #1 is unexpected
228                "
229            ))))
230        )
231    }
232
233    #[test]
234    fn subset_of_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
235        let result = verify_that!(vec![0, 0, 3], subset_of([&1, &2, &3]));
236
237        verify_that!(
238            result,
239            err(displays_as(contains_substring(indoc!(
240                "
241                    Value of: vec![0, 0, 3]
242                    Expected: is a subset of [
243                        1,
244                        2,
245                        3,
246                    ]
247                    Actual: [0, 0, 3],
248                      whose elements 0 at #0, 0 at #1 are unexpected
249                "
250            ))))
251        )
252    }
253}