Skip to content

Commit 9c92629

Browse files
Merge pull request #247 from google:create-matcher-char-count
PiperOrigin-RevId: 546268810
2 parents c3ba28f + bdb26d3 commit 9c92629

File tree

4 files changed

+174
-4
lines changed

4 files changed

+174
-4
lines changed

googletest/crate_docs.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ The following matchers are provided in GoogleTest Rust:
8181
| [`all!`] | Anything matched by all given matchers. |
8282
| [`anything`] | Any input. |
8383
| [`approx_eq`] | A floating point number within a standard tolerance of the argument. |
84+
| [`char_count`] | A string with a Unicode scalar count matching the argument. |
8485
| [`container_eq`] | Same as [`eq`], but for containers (with a better mismatch description). |
8586
| [`contains`] | A container containing an element matched by the given matcher. |
8687
| [`contains_each!`] | A container containing distinct elements each of the arguments match. |
@@ -122,6 +123,7 @@ The following matchers are provided in GoogleTest Rust:
122123

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

googletest/src/matchers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
1717
pub mod all_matcher;
1818
pub mod anything_matcher;
19+
pub mod char_count_matcher;
1920
pub mod conjunction_matcher;
2021
pub mod container_eq_matcher;
2122
pub mod contains_matcher;
@@ -55,6 +56,7 @@ pub mod tuple_matcher;
5556
pub mod unordered_elements_are_matcher;
5657

5758
pub use anything_matcher::anything;
59+
pub use char_count_matcher::char_count;
5860
pub use container_eq_matcher::container_eq;
5961
pub use contains_matcher::contains;
6062
pub use contains_regex_matcher::contains_regex;

googletest/src/matchers/str_matcher.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414

1515
use crate::{
1616
matcher::{Matcher, MatcherResult},
17-
matcher_support::{edit_distance, summarize_diff::{create_diff_reversed, create_diff}},
18-
matchers::{
19-
eq_deref_of_matcher::EqDerefOfMatcher,
20-
eq_matcher::{ EqMatcher},
17+
matcher_support::{
18+
edit_distance,
19+
summarize_diff::{create_diff, create_diff_reversed},
2120
},
21+
matchers::{eq_deref_of_matcher::EqDerefOfMatcher, eq_matcher::EqMatcher},
2222
};
2323
use std::borrow::Cow;
2424
use std::fmt::Debug;

0 commit comments

Comments
 (0)