Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions googletest/crate_docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ The following matchers are provided in GoogleTest Rust:
| [`all!`] | Anything matched by all given matchers. |
| [`anything`] | Any input. |
| [`approx_eq`] | A floating point number within a standard tolerance of the argument. |
| [`char_count`] | A string which a Unicode scalar count matching the argument. |
| [`container_eq`] | Same as [`eq`], but for containers (with a better mismatch description). |
| [`contains`] | A container containing an element matched by the given matcher. |
| [`contains_each!`] | A container containing distinct elements each of the arguments match. |
Expand Down Expand Up @@ -122,6 +123,7 @@ The following matchers are provided in GoogleTest Rust:

[`anything`]: matchers::anything
[`approx_eq`]: matchers::approx_eq
[`char_count`]: matchers::char_count
[`container_eq`]: matchers::container_eq
[`contains`]: matchers::contains
[`contains_regex`]: matchers::contains_regex
Expand Down
161 changes: 161 additions & 0 deletions googletest/src/matchers/char_count_matcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::matcher::{Matcher, MatcherResult};
use std::{fmt::Debug, marker::PhantomData};

/// Matches a string whose number of Unicode scalars matches `expected`.
///
/// In other words, the argument must match the output of [`actual_string.chars().count()`][std::str::Chars].
///
/// This can have surprising effects when what appears to be a single character is composed of multiple Unicode scalars. See [Rust documentation on character representation](https://doc.rust-lang.org/std/primitive.char.html#representation) for more information.
///
/// This matches against owned strings and string slices.
///
/// ```
/// # use googletest::prelude::*;
/// # fn should_pass() -> Result<()> {
/// let string_slice = "A string";
/// verify_that!(string_slice, char_count(eq(8)))?;
/// let non_ascii_string_slice = "Ä ſtřiɲğ";
/// verify_that!(non_ascii_string_slice, char_count(eq(8)))?;
/// let owned_string = String::from("A string");
/// verify_that!(owned_string, char_count(eq(8)))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
///
/// The parameter `expected` can be any integer numeric matcher.
///
/// ```
/// # use googletest::prelude::*;
/// # fn should_pass() -> Result<()> {
/// let string_slice = "A string";
/// verify_that!(string_slice, char_count(gt(4)))?;
/// # Ok(())
/// # }
/// # should_pass().unwrap();
/// ```
pub fn char_count<T: Debug + ?Sized + AsRef<str>, E: Matcher<ActualT = usize>>(
expected: E,
) -> impl Matcher<ActualT = T> {
CharLenMatcher { expected, phantom: Default::default() }
}

struct CharLenMatcher<T: ?Sized, E> {
expected: E,
phantom: PhantomData<T>,
}

impl<T: Debug + ?Sized + AsRef<str>, E: Matcher<ActualT = usize>> Matcher for CharLenMatcher<T, E> {
type ActualT = T;

fn matches(&self, actual: &T) -> MatcherResult {
self.expected.matches(&actual.as_ref().chars().count())
}

fn describe(&self, matcher_result: MatcherResult) -> String {
match matcher_result {
MatcherResult::Matches => {
format!(
"has character count, which {}",
self.expected.describe(MatcherResult::Matches)
)
}
MatcherResult::DoesNotMatch => {
format!(
"has character count, which {}",
self.expected.describe(MatcherResult::DoesNotMatch)
)
}
}
}

fn explain_match(&self, actual: &T) -> String {
let actual_size = actual.as_ref().chars().count();
format!(
"which has character count {}, {}",
actual_size,
self.expected.explain_match(&actual_size)
)
}
}

#[cfg(test)]
mod tests {
use super::char_count;
use crate::matcher::{Matcher, MatcherResult};
use crate::prelude::*;
use indoc::indoc;
use std::fmt::Debug;
use std::marker::PhantomData;

#[test]
fn char_count_matches_string_slice() -> Result<()> {
let value = "abcd";
verify_that!(value, char_count(eq(4)))
}

#[test]
fn char_count_matches_owned_string() -> Result<()> {
let value = String::from("abcd");
verify_that!(value, char_count(eq(4)))
}

#[test]
fn char_count_counts_non_ascii_characters_correctly() -> Result<()> {
let value = "äöüß";
verify_that!(value, char_count(eq(4)))
}

#[test]
fn char_count_explains_match() -> Result<()> {
struct TestMatcher<T>(PhantomData<T>);
impl<T: Debug> Matcher for TestMatcher<T> {
type ActualT = T;

fn matches(&self, _: &T) -> MatcherResult {
false.into()
}

fn describe(&self, _: MatcherResult) -> String {
"called described".into()
}

fn explain_match(&self, _: &T) -> String {
"called explain_match".into()
}
}
verify_that!(
char_count(TestMatcher(Default::default())).explain_match(&"A string"),
displays_as(eq("which has character count 8, called explain_match"))
)
}

#[test]
fn char_count_has_correct_failure_message() -> Result<()> {
let result = verify_that!("äöüß", char_count(eq(3)));
verify_that!(
result,
err(displays_as(contains_substring(indoc!(
r#"
Value of: "äöüß"
Expected: has character count, which is equal to 3
Actual: "äöüß",
which has character count 4, which isn't equal to 3"#
))))
)
}
}
2 changes: 2 additions & 0 deletions googletest/src/matchers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

pub mod all_matcher;
pub mod anything_matcher;
pub mod char_count_matcher;
pub mod conjunction_matcher;
pub mod container_eq_matcher;
pub mod contains_matcher;
Expand Down Expand Up @@ -55,6 +56,7 @@ pub mod tuple_matcher;
pub mod unordered_elements_are_matcher;

pub use anything_matcher::anything;
pub use char_count_matcher::char_count;
pub use container_eq_matcher::container_eq;
pub use contains_matcher::contains;
pub use contains_regex_matcher::contains_regex;
Expand Down
8 changes: 4 additions & 4 deletions googletest/src/matchers/str_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

use crate::{
matcher::{Matcher, MatcherResult},
matcher_support::{edit_distance, summarize_diff::{create_diff_reversed, create_diff}},
matchers::{
eq_deref_of_matcher::EqDerefOfMatcher,
eq_matcher::{ EqMatcher},
matcher_support::{
edit_distance,
summarize_diff::{create_diff, create_diff_reversed},
},
matchers::{eq_deref_of_matcher::EqDerefOfMatcher, eq_matcher::EqMatcher},
};
use std::borrow::Cow;
use std::fmt::Debug;
Expand Down