Skip to main content

googletest/matchers/
char_count_matcher.rs

1// Copyright 2023 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 string whose number of Unicode scalars matches `expected`.
22///
23/// In other words, the argument must match the output of
24/// [`actual_string.chars().count()`][std::str::Chars].
25///
26/// This can have surprising effects when what appears to be a single character
27/// is composed of multiple Unicode scalars. See [Rust documentation on
28/// character
29/// representation](https://doc.rust-lang.org/std/primitive.char.html#representation)
30/// for more information.
31///
32/// This matches against owned strings and string slices.
33///
34/// ```
35/// # use googletest::prelude::*;
36/// # fn should_pass() -> Result<()> {
37/// let string_slice = "A string";
38/// verify_that!(string_slice, char_count(eq(8)))?;
39/// let non_ascii_string_slice = "Ä ſtřiɲğ";
40/// verify_that!(non_ascii_string_slice, char_count(eq(8)))?;
41/// let owned_string = String::from("A string");
42/// verify_that!(owned_string, char_count(eq(8)))?;
43/// #     Ok(())
44/// # }
45/// # should_pass().unwrap();
46/// ```
47///
48/// The parameter `expected` can be any integer numeric matcher.
49///
50/// ```
51/// # use googletest::prelude::*;
52/// # fn should_pass() -> Result<()> {
53/// let string_slice = "A string";
54/// verify_that!(string_slice, char_count(gt(4)))?;
55/// #     Ok(())
56/// # }
57/// # should_pass().unwrap();
58/// ```
59pub fn char_count<E: Matcher<usize>>(expected: E) -> CharLenMatcher<E> {
60    CharLenMatcher { expected }
61}
62
63#[derive(MatcherBase)]
64pub struct CharLenMatcher<E> {
65    expected: E,
66}
67
68impl<T: Debug + Copy + AsRef<str>, E: Matcher<usize>> Matcher<T> for CharLenMatcher<E> {
69    fn matches(&self, actual: T) -> MatcherResult {
70        self.expected.matches(actual.as_ref().chars().count())
71    }
72
73    fn describe(&self, matcher_result: MatcherResult) -> Description {
74        match matcher_result {
75            MatcherResult::Match => format!(
76                "has character count, which {}",
77                self.expected.describe(MatcherResult::Match)
78            )
79            .into(),
80            MatcherResult::NoMatch => format!(
81                "has character count, which {}",
82                self.expected.describe(MatcherResult::NoMatch)
83            )
84            .into(),
85        }
86    }
87
88    fn explain_match(&self, actual: T) -> Description {
89        let actual_size = actual.as_ref().chars().count();
90        format!(
91            "which has character count {}, {}",
92            actual_size,
93            self.expected.explain_match(actual_size)
94        )
95        .into()
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use crate::description::Description;
102    use crate::matcher::MatcherResult;
103    use crate::prelude::*;
104    use crate::Result;
105    use indoc::indoc;
106    use std::fmt::Debug;
107
108    #[test]
109    fn char_count_matches_string_slice() -> Result<()> {
110        let value = "abcd";
111        verify_that!(value, char_count(eq(4)))
112    }
113
114    #[test]
115    fn char_count_matches_owned_string() -> Result<()> {
116        let value = String::from("abcd");
117        verify_that!(value, char_count(eq(4)))
118    }
119
120    #[test]
121    fn char_count_counts_non_ascii_characters_correctly() -> Result<()> {
122        let value = "äöüß";
123        verify_that!(value, char_count(eq(4)))
124    }
125
126    #[test]
127    fn char_count_explains_match() -> Result<()> {
128        #[derive(MatcherBase)]
129        struct TestMatcher;
130
131        impl<T: Debug + Copy> Matcher<T> for TestMatcher {
132            fn matches(&self, _: T) -> MatcherResult {
133                false.into()
134            }
135
136            fn describe(&self, _: MatcherResult) -> Description {
137                "called described".into()
138            }
139
140            fn explain_match(&self, _: T) -> Description {
141                "called explain_match".into()
142            }
143        }
144        verify_that!(
145            char_count(TestMatcher).explain_match("A string"),
146            displays_as(eq("which has character count 8, called explain_match"))
147        )
148    }
149
150    #[test]
151    fn char_count_has_correct_failure_message() -> Result<()> {
152        let result = verify_that!("äöüß", char_count(eq(3)));
153        verify_that!(
154            result,
155            err(displays_as(contains_substring(indoc!(
156                r#"
157                Value of: "äöüß"
158                Expected: has character count, which is equal to 3
159                Actual: "äöüß",
160                  which has character count 4, which isn't equal to 3"#
161            ))))
162        )
163    }
164}