build(rust): vendor googletest crate
This commit was merged in pull request #76.
This commit is contained in:
1 parent
a6fe84190e
commit
6887b104d6
576 files changed
+284290
-2
No files matched your search
@@ -0,0 +1,148 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// There are no visible documentation elements in this module; the declarative
|
||||
// macro is documented in the matcher module.
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Matches a value which all of the given matchers match.
|
||||
///
|
||||
/// Each argument is a [`Matcher`][crate::matcher::Matcher] which matches
|
||||
/// against the actual value.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!("A string", all!(starts_with("A"), ends_with("string")))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!("A string", all!(starts_with("A"), ends_with("not a string")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// Using this macro is equivalent to using the
|
||||
/// [`and`][crate::matcher::MatcherBase::and] method:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(10, gt(9).and(lt(11)))?; // Also passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Assertion failure messages are not guaranteed to be identical, however.
|
||||
///
|
||||
/// If an inner matcher is `eq(...)`, it can be omitted:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
///
|
||||
/// verify_that!(123, all![123, lt(1000), gt(100)])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __all {
|
||||
($(,)?) => {{
|
||||
$crate::matchers::anything()
|
||||
}} ;
|
||||
($matcher:expr $(,)?) => {{
|
||||
use $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq;
|
||||
auto_eq!($matcher)
|
||||
}};
|
||||
($head:expr, $head2:expr $(,)?) => {{
|
||||
use $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq;
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::ConjunctionMatcher::new(auto_eq!($head), auto_eq!($head2))
|
||||
}};
|
||||
($head:expr, $head2:expr, $($tail:expr),+ $(,)?) => {{
|
||||
use $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq;
|
||||
$crate::__all![
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::ConjunctionMatcher::new(auto_eq!($head), auto_eq!($head2)),
|
||||
$($tail),+
|
||||
]
|
||||
}}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn description_shows_more_than_one_matcher() -> Result<()> {
|
||||
let first_matcher = starts_with("A");
|
||||
let second_matcher = ends_with("string");
|
||||
let matcher = all!(first_matcher, second_matcher);
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&String>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq(indoc!(
|
||||
"
|
||||
has all the following properties:
|
||||
* starts with prefix \"A\"
|
||||
* ends with suffix \"string\""
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn description_shows_one_matcher_directly() -> Result<()> {
|
||||
let first_matcher = starts_with("A");
|
||||
let matcher = all!(first_matcher);
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&String>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("starts with prefix \"A\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()>
|
||||
{
|
||||
let first_matcher = starts_with("Another");
|
||||
let second_matcher = ends_with("string");
|
||||
let matcher = all!(first_matcher, second_matcher);
|
||||
|
||||
verify_that!(
|
||||
matcher.explain_match("A string"),
|
||||
displays_as(eq("which does not start with \"Another\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatch_description_is_simple_when_only_one_consistuent() -> Result<()> {
|
||||
let first_matcher = starts_with("Another");
|
||||
let matcher = all!(first_matcher);
|
||||
|
||||
verify_that!(
|
||||
matcher.explain_match("A string"),
|
||||
displays_as(eq("which does not start with \"Another\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_with_auto_eq() -> Result<()> {
|
||||
verify_that!(42, all![eq(42), 42, lt(100)])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// 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.
|
||||
|
||||
// There are no visible documentation elements in this module; the declarative
|
||||
// macro is documented in the matchers module.
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Matches a value which at least one of the given matchers match.
|
||||
///
|
||||
/// Each argument is a [`Matcher`][crate::matcher::Matcher] which matches
|
||||
/// against the actual value.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!("A string", any!(starts_with("A"), ends_with("string")))?; // Passes
|
||||
/// verify_that!("A string", any!(starts_with("A"), starts_with("string")))?; // Passes
|
||||
/// verify_that!("A string", any!(ends_with("A"), ends_with("string")))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!("A string", any!(starts_with("An"), ends_with("not a string")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// Using this macro is equivalent to using the
|
||||
/// [`or`][crate::matcher::MatcherBase::or] method:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(10, gt(9).or(lt(8)))?; // Also passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Assertion failure messages are not guaranteed to be identical, however.
|
||||
///
|
||||
/// If an inner matcher is `eq(...)`, it can be omitted:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
///
|
||||
/// verify_that!(123, any![lt(1), 123, gt(1000)])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __any {
|
||||
($(,)?) => {{
|
||||
$crate::matchers::not($crate::matchers::anything())
|
||||
}} ;
|
||||
($matcher:expr $(,)?) => {{
|
||||
use $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq;
|
||||
auto_eq!($matcher)
|
||||
}};
|
||||
($head:expr, $head2:expr $(,)?) => {{
|
||||
use $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq;
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatcher::new(auto_eq!($head), auto_eq!($head2))
|
||||
}};
|
||||
($head:expr, $head2:expr, $($tail:expr),+ $(,)?) => {{
|
||||
use $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq;
|
||||
$crate::__any![
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatcher::new(auto_eq!($head), auto_eq!($head2)),
|
||||
$($tail),+
|
||||
]
|
||||
}}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn description_shows_more_than_one_matcher() -> Result<()> {
|
||||
let first_matcher = starts_with("A");
|
||||
let second_matcher = ends_with("string");
|
||||
let matcher = any!(first_matcher, second_matcher);
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&String>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq(indoc!(
|
||||
"
|
||||
has at least one of the following properties:
|
||||
* starts with prefix \"A\"
|
||||
* ends with suffix \"string\""
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn description_shows_one_matcher_directly() -> Result<()> {
|
||||
let first_matcher = starts_with("A");
|
||||
let matcher = any!(first_matcher);
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("starts with prefix \"A\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()>
|
||||
{
|
||||
let first_matcher = starts_with("Another");
|
||||
let second_matcher = ends_with("string");
|
||||
let matcher = any!(first_matcher, second_matcher);
|
||||
|
||||
verify_that!(
|
||||
matcher.explain_match("A string"),
|
||||
displays_as(eq("which does not start with \"Another\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatch_description_is_simple_when_only_one_constituent() -> Result<()> {
|
||||
let first_matcher = starts_with("Another");
|
||||
let matcher = any!(first_matcher);
|
||||
|
||||
verify_that!(
|
||||
matcher.explain_match("A string"),
|
||||
displays_as(eq("which does not start with \"Another\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_any_matcher_never_matches() -> Result<()> {
|
||||
verify_that!(123, not(any![]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_with_auto_eq() -> Result<()> {
|
||||
verify_that!(42, any![1, 2, 42, gt(123)])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches anything. This matcher always succeeds.
|
||||
///
|
||||
/// This is useful to check if `actual` matches the specific structure (like
|
||||
/// `Some(...)`) but without caring about the internal value.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let option = Some("Some value");
|
||||
/// verify_that!(option, some(anything()))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
pub fn anything() -> Anything {
|
||||
Anything
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct Anything;
|
||||
|
||||
impl<T: Debug + Copy> Matcher<T> for Anything {
|
||||
fn matches(&self, _: T) -> MatcherResult {
|
||||
MatcherResult::Match
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => "is anything".into(),
|
||||
MatcherResult::NoMatch => "never matches".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn anything_matches_i32() -> Result<()> {
|
||||
let value = 32;
|
||||
verify_that!(value, anything())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anything_matches_str() -> Result<()> {
|
||||
let value = "32";
|
||||
verify_that!(value, anything())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anything_matches_option() -> Result<()> {
|
||||
let value = Some(32);
|
||||
verify_that!(value, some(anything()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2024 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
|
||||
/// Matches boolean value `true`.
|
||||
pub fn is_true() -> BoolMatcher {
|
||||
BoolMatcher { expected: true }
|
||||
}
|
||||
|
||||
/// Matches boolean value `false`.
|
||||
pub fn is_false() -> BoolMatcher {
|
||||
BoolMatcher { expected: false }
|
||||
}
|
||||
|
||||
/// Matches a bool value or bool reference.
|
||||
#[derive(MatcherBase)]
|
||||
pub struct BoolMatcher {
|
||||
expected: bool,
|
||||
}
|
||||
|
||||
impl BoolMatcher {
|
||||
fn matches(&self, actual: bool) -> MatcherResult {
|
||||
(actual == self.expected).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match (matcher_result, self.expected) {
|
||||
(MatcherResult::Match, true) | (MatcherResult::NoMatch, false) => "is true".into(),
|
||||
(MatcherResult::Match, false) | (MatcherResult::NoMatch, true) => "is false".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher<bool> for BoolMatcher {
|
||||
fn matches(&self, actual: bool) -> MatcherResult {
|
||||
self.matches(actual)
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
self.describe(matcher_result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Matcher<&'a bool> for BoolMatcher {
|
||||
fn matches(&self, actual: &'a bool) -> MatcherResult {
|
||||
self.matches(*actual)
|
||||
}
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
self.describe(matcher_result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn match_value() -> Result<()> {
|
||||
verify_that!(true, is_true())?;
|
||||
verify_that!(true, not(is_false()))?;
|
||||
verify_that!(false, is_false())?;
|
||||
verify_that!(false, not(is_true()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_ref() -> Result<()> {
|
||||
let t = true;
|
||||
let f = false;
|
||||
|
||||
verify_that!(&t, is_true())?;
|
||||
verify_that!(&t, not(is_false()))?;
|
||||
verify_that!(&f, is_false())?;
|
||||
verify_that!(&f, not(is_true()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn describe() {
|
||||
assert_eq!(is_true().describe(MatcherResult::Match).to_string(), "is true");
|
||||
assert_eq!(is_true().describe(MatcherResult::NoMatch).to_string(), "is false");
|
||||
assert_eq!(is_false().describe(MatcherResult::Match).to_string(), "is false");
|
||||
assert_eq!(is_false().describe(MatcherResult::NoMatch).to_string(), "is true");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// 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<E: Matcher<usize>>(expected: E) -> CharLenMatcher<E> {
|
||||
CharLenMatcher { expected }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct CharLenMatcher<E> {
|
||||
expected: E,
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy + AsRef<str>, E: Matcher<usize>> Matcher<T> for CharLenMatcher<E> {
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
self.expected.matches(actual.as_ref().chars().count())
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!(
|
||||
"has character count, which {}",
|
||||
self.expected.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
MatcherResult::NoMatch => format!(
|
||||
"has character count, which {}",
|
||||
self.expected.describe(MatcherResult::NoMatch)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: T) -> Description {
|
||||
let actual_size = actual.as_ref().chars().count();
|
||||
format!(
|
||||
"which has character count {}, {}",
|
||||
actual_size,
|
||||
self.expected.explain_match(actual_size)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::description::Description;
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[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<()> {
|
||||
#[derive(MatcherBase)]
|
||||
struct TestMatcher;
|
||||
|
||||
impl<T: Debug + Copy> Matcher<T> for TestMatcher {
|
||||
fn matches(&self, _: T) -> MatcherResult {
|
||||
false.into()
|
||||
}
|
||||
|
||||
fn describe(&self, _: MatcherResult) -> Description {
|
||||
"called described".into()
|
||||
}
|
||||
|
||||
fn explain_match(&self, _: T) -> Description {
|
||||
"called explain_match".into()
|
||||
}
|
||||
}
|
||||
verify_that!(
|
||||
char_count(TestMatcher).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"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// There are no visible documentation elements in this module.
|
||||
#![doc(hidden)]
|
||||
|
||||
use crate::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matcher created by [`Matcher::and`] and [`all!`].
|
||||
///
|
||||
/// Both [`Matcher::and`] and [`all!`] nest on m1. In other words,
|
||||
/// both `x.and(y).and(z)` and `all![x, y, z]` produce:
|
||||
/// ```ignore
|
||||
/// ConjunctionMatcher {
|
||||
/// m1: ConjunctionMatcher {
|
||||
/// m1: x,
|
||||
/// m2: y
|
||||
/// },
|
||||
/// m2: z
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This behavior must be respected
|
||||
/// to ensure that [`Matcher::explain_match`] and [`Matcher::describe`] produce
|
||||
/// useful descriptions.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
#[derive(MatcherBase)]
|
||||
pub struct ConjunctionMatcher<M1, M2> {
|
||||
m1: M1,
|
||||
m2: M2,
|
||||
}
|
||||
|
||||
impl<M1, M2> ConjunctionMatcher<M1, M2> {
|
||||
pub fn new(m1: M1, m2: M2) -> Self {
|
||||
Self { m1, m2 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, M1: Matcher<T>, M2: Matcher<T>> Matcher<T> for ConjunctionMatcher<M1, M2> {
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
match (self.m1.matches(actual), self.m2.matches(actual)) {
|
||||
(MatcherResult::Match, MatcherResult::Match) => MatcherResult::Match,
|
||||
_ => MatcherResult::NoMatch,
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: T) -> Description {
|
||||
match (self.m1.matches(actual), self.m2.matches(actual)) {
|
||||
(MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual),
|
||||
(MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual),
|
||||
(_, _) => {
|
||||
let m1_description = self.m1.explain_match(actual);
|
||||
if m1_description.is_conjunction_description() {
|
||||
m1_description.nested(self.m2.explain_match(actual))
|
||||
} else {
|
||||
Description::new()
|
||||
.bullet_list()
|
||||
.collect([m1_description, self.m2.explain_match(actual)])
|
||||
.conjunction_description()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
let m1_description = self.m1.describe(matcher_result);
|
||||
if m1_description.is_conjunction_description() {
|
||||
m1_description.push_in_last_nested(self.m2.describe(matcher_result))
|
||||
} else {
|
||||
let header = if matcher_result.into() {
|
||||
"has all the following properties:"
|
||||
} else {
|
||||
"has at least one of the following properties:"
|
||||
};
|
||||
Description::new()
|
||||
.text(header)
|
||||
.nested(
|
||||
Description::new()
|
||||
.bullet_list()
|
||||
.collect([m1_description, self.m2.describe(matcher_result)]),
|
||||
)
|
||||
.conjunction_description()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn and_true_true_matches() -> Result<()> {
|
||||
verify_that!(1, anything().and(anything()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and_true_false_does_not_match() -> Result<()> {
|
||||
let result = verify_that!(1, anything().and(not(anything())));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 1
|
||||
Expected: has all the following properties:
|
||||
* is anything
|
||||
* never matches
|
||||
Actual: 1,
|
||||
which is anything
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and_false_true_does_not_match() -> Result<()> {
|
||||
let result = verify_that!(1, not(anything()).and(anything()));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 1
|
||||
Expected: has all the following properties:
|
||||
* never matches
|
||||
* is anything
|
||||
Actual: 1,
|
||||
which is anything
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and_false_false_does_not_match() -> Result<()> {
|
||||
let result = verify_that!(1, not(anything()).and(not(anything())));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 1
|
||||
Expected: has all the following properties:
|
||||
* never matches
|
||||
* never matches
|
||||
Actual: 1,
|
||||
* which is anything
|
||||
* which is anything
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and_long_chain_of_matchers() -> Result<()> {
|
||||
let result = verify_that!(
|
||||
1,
|
||||
anything().and(not(anything())).and(anything()).and(not(anything())).and(anything())
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 1
|
||||
Expected: has all the following properties:
|
||||
* is anything
|
||||
* never matches
|
||||
* is anything
|
||||
* never matches
|
||||
* is anything
|
||||
Actual: 1,
|
||||
* which is anything
|
||||
* which is anything
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chained_and_matches() -> Result<()> {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Struct {
|
||||
a: i32,
|
||||
b: i32,
|
||||
c: i32,
|
||||
}
|
||||
verify_that!(
|
||||
Struct { a: 1, b: 2, c: 3 },
|
||||
field!(Struct.a, eq(1)).and(field!(Struct.b, eq(2))).and(field!(Struct.c, eq(3)))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_str_slices() -> Result<()> {
|
||||
verify_that!("A string", starts_with("A").and(ends_with("string")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_owned_strings() -> Result<()> {
|
||||
verify_that!("A string".to_string(), starts_with("A").and(ends_with("string")))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use crate::matcher_support::match_matrix::internal::{MatchMatrix, Requirements};
|
||||
use crate::matchers::eq_matcher::eq;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a container equal (in the sense of `==`) to `expected`.
|
||||
///
|
||||
/// This is similar to [`crate::matchers::eq`] except that an assertion failure
|
||||
/// message generated from this matcher will include the missing and unexpected
|
||||
/// items in the actual value, e.g.:
|
||||
///
|
||||
/// ```text
|
||||
/// Expected container to equal [1, 2, 3]
|
||||
/// but was: [1, 2, 4]
|
||||
/// Missing: [3]
|
||||
/// Unexpected: [4]
|
||||
/// ```
|
||||
///
|
||||
/// The actual value must be a container such as a `&Vec`, an array, or a
|
||||
/// dereferenced slice. More precisely, the actual value must
|
||||
/// implement [`IntoIterator`] whose `Item` type implements
|
||||
/// [`PartialEq<ExpectedT>`], where `ExpectedT` is the element type of the
|
||||
/// expected value.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let vec = vec![1, 2, 3];
|
||||
/// verify_that!(vec, container_eq([1, 2, 3]))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// *Performance note*: In the event of a mismatch leading to an assertion
|
||||
/// failure, the construction of the lists of missing and unexpected values
|
||||
/// uses a naive algorithm requiring time proportional to the product of the
|
||||
/// sizes of the expected and actual values. This should therefore only be used
|
||||
/// when the containers are small enough that this is not a problem.
|
||||
pub fn container_eq<ExpectedContainerT>(
|
||||
expected: ExpectedContainerT,
|
||||
) -> ContainerEqMatcher<ExpectedContainerT>
|
||||
where
|
||||
ExpectedContainerT: Debug,
|
||||
{
|
||||
ContainerEqMatcher { expected }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct ContainerEqMatcher<ExpectedContainerT> {
|
||||
expected: ExpectedContainerT,
|
||||
}
|
||||
|
||||
impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
|
||||
Matcher<ActualContainerT> for ContainerEqMatcher<ExpectedContainerT>
|
||||
where
|
||||
ActualElementT: for<'a> PartialEq<&'a ExpectedElementT> + Debug + Copy,
|
||||
ActualContainerT: for<'a> PartialEq<&'a ExpectedContainerT> + Debug + Copy,
|
||||
ExpectedElementT: Debug,
|
||||
ExpectedContainerT: Debug,
|
||||
ActualContainerT: IntoIterator<Item = ActualElementT>,
|
||||
for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
|
||||
{
|
||||
fn matches(&self, actual: ActualContainerT) -> MatcherResult {
|
||||
(actual == &self.expected).into()
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ActualContainerT) -> Description {
|
||||
build_explanation(self.get_missing_items(actual), self.get_unexpected_items(actual)).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!("is equal to {:?}", self.expected).into(),
|
||||
MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ExpectedElementT, ExpectedContainerT> ContainerEqMatcher<ExpectedContainerT>
|
||||
where
|
||||
for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
|
||||
{
|
||||
fn get_missing_items<ActualElementT, ActualContainerT>(
|
||||
&self,
|
||||
actual: ActualContainerT,
|
||||
) -> Vec<&'_ ExpectedElementT>
|
||||
where
|
||||
ActualElementT: for<'a> PartialEq<&'a ExpectedElementT> + Copy,
|
||||
ActualContainerT: for<'a> PartialEq<&'a ExpectedContainerT> + Copy,
|
||||
ActualContainerT: IntoIterator<Item = ActualElementT>,
|
||||
{
|
||||
self.expected.into_iter().filter(|i| !actual.into_iter().any(|j| j == *i)).collect()
|
||||
}
|
||||
|
||||
fn get_unexpected_items<ActualElementT, ActualContainerT>(
|
||||
&self,
|
||||
actual: ActualContainerT,
|
||||
) -> Vec<ActualElementT>
|
||||
where
|
||||
ActualElementT: for<'a> PartialEq<&'a ExpectedElementT> + Copy,
|
||||
ActualContainerT: for<'a> PartialEq<&'a ExpectedContainerT> + Copy,
|
||||
ActualContainerT: IntoIterator<Item = ActualElementT>,
|
||||
{
|
||||
actual.into_iter().filter(|i| !self.expected.into_iter().any(|j| i == &j)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn build_explanation<T: Debug, U: Debug>(missing: Vec<T>, unexpected: Vec<U>) -> String {
|
||||
match (missing.len(), unexpected.len()) {
|
||||
// TODO(b/261175849) add more data here (out of order elements, duplicated elements, etc...)
|
||||
(0, 0) => "which contains all the elements".to_string(),
|
||||
(0, 1) => format!("which contains the unexpected element {:?}", unexpected[0]),
|
||||
(0, _) => format!("which contains the unexpected elements {unexpected:?}",),
|
||||
(1, 0) => format!("which is missing the element {:?}", missing[0]),
|
||||
(1, 1) => {
|
||||
format!(
|
||||
"which is missing the element {:?} and contains the unexpected element {:?}",
|
||||
missing[0], unexpected[0]
|
||||
)
|
||||
}
|
||||
(1, _) => {
|
||||
format!(
|
||||
"which is missing the element {:?} and contains the unexpected elements {unexpected:?}",
|
||||
missing[0]
|
||||
)
|
||||
}
|
||||
(_, 0) => format!("which is missing the elements {missing:?}"),
|
||||
(_, 1) => {
|
||||
format!(
|
||||
"which is missing the elements {missing:?} and contains the unexpected element {:?}",
|
||||
unexpected[0]
|
||||
)
|
||||
}
|
||||
(_, _) => {
|
||||
format!(
|
||||
"which is missing the elements {missing:?} and contains the unexpected elements {unexpected:?}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ExpectedContainerT> ContainerEqMatcher<ExpectedContainerT> {
|
||||
/// Match container equality, but ignoring element order.
|
||||
pub fn ignore_order(self) -> IgnoringOrder<ExpectedContainerT> {
|
||||
IgnoringOrder { expected: self.expected }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct IgnoringOrder<ExpectedContainerT> {
|
||||
expected: ExpectedContainerT,
|
||||
}
|
||||
|
||||
/// Implements a matcher that ignores the relative order of the elements.
|
||||
impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
|
||||
Matcher<ActualContainerT> for IgnoringOrder<ExpectedContainerT>
|
||||
where
|
||||
ActualElementT: Debug + Copy + for<'a> PartialEq<&'a ExpectedElementT>,
|
||||
ActualContainerT: Debug + Copy + IntoIterator<Item = ActualElementT>,
|
||||
ExpectedElementT: Debug,
|
||||
for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
|
||||
{
|
||||
fn matches(&self, actual: ActualContainerT) -> MatcherResult {
|
||||
let expected: Vec<Box<dyn Matcher<ActualElementT>>> = self
|
||||
.expected
|
||||
.into_iter()
|
||||
.map(|x| Box::new(eq(x)) as Box<dyn Matcher<ActualElementT>>)
|
||||
.collect();
|
||||
let match_matrix = MatchMatrix::generate(actual, &expected);
|
||||
// TODO: investigate why Requirements::PerfectMatch and
|
||||
// match_matrix.is_full_match is not doing what we expect
|
||||
// here.
|
||||
(match_matrix.is_match_for(Requirements::Subset)
|
||||
&& match_matrix.is_match_for(Requirements::Superset))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ActualContainerT) -> Description {
|
||||
// We need to materialize the collections in order to have reliable iteration
|
||||
// order when generating our reports.
|
||||
let expected_items: Vec<&ExpectedElementT> = self.expected.into_iter().collect();
|
||||
let actual_items: Vec<ActualElementT> = actual.into_iter().collect();
|
||||
|
||||
let expected_matchers: Vec<_> = expected_items
|
||||
.iter()
|
||||
.map(|&x| Box::new(eq(x)) as Box<dyn Matcher<ActualElementT>>)
|
||||
.collect();
|
||||
let match_matrix = MatchMatrix::generate(actual_items.iter().copied(), &expected_matchers);
|
||||
|
||||
let best_match = match_matrix.find_best_match();
|
||||
|
||||
// Since we are doing equality checks, we can generate a slightly less verbose
|
||||
// message than BestMatch::get_explanation.
|
||||
let matches = best_match.get_matches().map(|(actual_idx, expected_idx)|{
|
||||
Description::new().text(
|
||||
format!(
|
||||
"Actual element {:?} at index {actual_idx} is equal to expected element at index {expected_idx}.",
|
||||
actual_items[actual_idx],
|
||||
))});
|
||||
|
||||
let unmatched_actual = best_match.get_unmatched_actual().map(|actual_idx| {
|
||||
Description::new().text(
|
||||
format!(
|
||||
"Actual element {:?} at index {actual_idx} did not match any remaining expected element.",
|
||||
actual_items[actual_idx],
|
||||
))
|
||||
});
|
||||
|
||||
let unmatched_expected =
|
||||
best_match.get_unmatched_expected().into_iter().map(|expected_idx| {
|
||||
Description::new().text(format!(
|
||||
"Expected element {:?} at index {expected_idx} did not match any remaining actual element.",
|
||||
expected_items[expected_idx]
|
||||
))
|
||||
});
|
||||
|
||||
Description::new()
|
||||
.text("which does not have a perfect match. The best match found was:")
|
||||
.collect(matches.chain(unmatched_actual).chain(unmatched_expected))
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
Description::new()
|
||||
.text(format!(
|
||||
"{} all elements matching in any order:",
|
||||
if matcher_result.into() { "contains" } else { "doesn't contain" },
|
||||
))
|
||||
.nested(
|
||||
self.expected
|
||||
.into_iter()
|
||||
.map(|element| format!("{element:?}"))
|
||||
.collect::<Description>()
|
||||
.bullet_list(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn container_eq_returns_match_when_containers_match() -> Result<()> {
|
||||
verify_that!(vec![1, 2, 3], container_eq(vec![1, 2, 3]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_matches_array_with_slice() -> Result<()> {
|
||||
let value = &[1, 2, 3];
|
||||
verify_that!(value, container_eq([1, 2, 3]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_matches_hash_set() -> Result<()> {
|
||||
let value: HashSet<i32> = [1, 2, 3].into();
|
||||
verify_that!(value, container_eq([1, 2, 3].into()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_full_error_message() -> Result<()> {
|
||||
let result = verify_that!(vec![1, 3, 2], container_eq(vec![1, 2, 3]));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![1, 3, 2]
|
||||
Expected: is equal to [1, 2, 3]
|
||||
Actual: [1, 3, 2],
|
||||
which contains all the elements
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_returns_mismatch_when_elements_out_of_order() -> Result<()> {
|
||||
verify_that!(
|
||||
container_eq(vec![1, 2, 3]).explain_match(&vec![1, 3, 2]),
|
||||
displays_as(eq("which contains all the elements"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_mismatch_shows_missing_elements_in_container() -> Result<()> {
|
||||
verify_that!(
|
||||
container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2]),
|
||||
displays_as(eq("which is missing the element 3"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_mismatch_shows_surplus_elements_in_container() -> Result<()> {
|
||||
verify_that!(
|
||||
container_eq(vec![1, 2]).explain_match(&vec![1, 2, 3]),
|
||||
displays_as(eq("which contains the unexpected element 3"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_mismatch_shows_missing_and_surplus_elements_in_container() -> Result<()> {
|
||||
verify_that!(
|
||||
container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 4]),
|
||||
displays_as(eq("which is missing the element 3 and contains the unexpected element 4"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_mismatch_does_not_show_duplicated_element() -> Result<()> {
|
||||
verify_that!(
|
||||
container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 3, 3]),
|
||||
displays_as(eq("which contains all the elements"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_matches_owned_vec_with_array() -> Result<()> {
|
||||
let vector = vec![123, 234];
|
||||
verify_that!(vector, container_eq([123, 234]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_matches_owned_vec_of_owned_strings_with_slice_of_string_references(
|
||||
) -> Result<()> {
|
||||
let vector = vec!["A string".to_string(), "Another string".to_string()];
|
||||
verify_that!(vector, container_eq(["A string", "Another string"]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_matches_owned_vec_of_owned_strings_with_shorter_slice_of_string_references(
|
||||
) -> Result<()> {
|
||||
let actual = vec!["A string".to_string(), "Another string".to_string()];
|
||||
let matcher = container_eq(["A string"]);
|
||||
|
||||
let result = matcher.matches(&actual);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_mismatch_with_slice_shows_missing_elements_in_container() -> Result<()> {
|
||||
verify_that!(
|
||||
container_eq([1, 2, 3]).explain_match(&vec![1, 2]),
|
||||
displays_as(eq("which is missing the element 3"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_mismatch_with_str_slice_shows_missing_elements_in_container() -> Result<()> {
|
||||
verify_that!(
|
||||
container_eq(["A", "B", "C"]).explain_match(&vec!["A".to_string(), "B".to_string()]),
|
||||
displays_as(eq("which is missing the element \"C\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_eq_mismatch_with_str_slice_shows_surplus_elements_in_container() -> Result<()> {
|
||||
verify_that!(
|
||||
container_eq(["A", "B"]).explain_match(&vec![
|
||||
"A".to_string(),
|
||||
"B".to_string(),
|
||||
"C".to_string()
|
||||
]),
|
||||
displays_as(eq("which contains the unexpected element \"C\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_match() -> Result<()> {
|
||||
verify_that!(vec!["a", "b"], container_eq(["b", "a"]).ignore_order())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_mismatch() -> Result<()> {
|
||||
verify_that!(vec!["a", "b"], not(container_eq(["1", "2"]).ignore_order()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_mismatch_explain() -> Result<()> {
|
||||
let expected_err = verify_that!(vec!["a", "b"], container_eq(["1", "2"]).ignore_order());
|
||||
verify_that!(
|
||||
expected_err,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
r#"
|
||||
Value of: vec!["a", "b"]
|
||||
Expected: contains all elements matching in any order:
|
||||
* "1"
|
||||
* "2"
|
||||
Actual: ["a", "b"],
|
||||
which does not have a perfect match. The best match found was:
|
||||
Actual element "a" at index 0 did not match any remaining expected element.
|
||||
Actual element "b" at index 1 did not match any remaining expected element.
|
||||
Expected element "1" at index 0 did not match any remaining actual element.
|
||||
Expected element "2" at index 1 did not match any remaining actual element.
|
||||
"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_unaccounted_extra_expected() -> Result<()> {
|
||||
verify_that!(vec!["a", "b"], not(container_eq(["a", "b", "a"]).ignore_order()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_unaccounted_extra_expected_explain() -> Result<()> {
|
||||
let expected_err =
|
||||
verify_that!(vec!["a", "b"], container_eq(["a", "b", "a"]).ignore_order());
|
||||
verify_that!(
|
||||
expected_err,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
r#"
|
||||
Value of: vec!["a", "b"]
|
||||
Expected: contains all elements matching in any order:
|
||||
* "a"
|
||||
* "b"
|
||||
* "a"
|
||||
Actual: ["a", "b"],
|
||||
which does not have a perfect match. The best match found was:
|
||||
Actual element "a" at index 0 is equal to expected element at index 0.
|
||||
Actual element "b" at index 1 is equal to expected element at index 1.
|
||||
Expected element "a" at index 2 did not match any remaining actual element.
|
||||
"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_unaccounted_extra_actual() -> Result<()> {
|
||||
verify_that!(vec!["a", "b", "a"], not(container_eq(["b", "a"]).ignore_order()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_unaccounted_extra_actual_explain() -> Result<()> {
|
||||
let expected_err =
|
||||
verify_that!(vec!["a", "b", "a"], container_eq(["b", "a"]).ignore_order());
|
||||
|
||||
verify_that!(
|
||||
expected_err,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
r#"
|
||||
Value of: vec!["a", "b", "a"]
|
||||
Expected: contains all elements matching in any order:
|
||||
* "b"
|
||||
* "a"
|
||||
Actual: ["a", "b", "a"],
|
||||
which does not have a perfect match. The best match found was:
|
||||
Actual element "a" at index 0 is equal to expected element at index 1.
|
||||
Actual element "b" at index 1 is equal to expected element at index 0.
|
||||
Actual element "a" at index 2 did not match any remaining expected element.
|
||||
"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_on_sets() -> Result<()> {
|
||||
let mut actual = std::collections::HashSet::new();
|
||||
actual.insert("b");
|
||||
actual.insert("a");
|
||||
actual.insert("c");
|
||||
verify_that!(actual, container_eq(["c", "b", "a"]).ignore_order())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_on_sets_explain() -> Result<()> {
|
||||
let mut actual = std::collections::HashSet::new();
|
||||
actual.insert("b");
|
||||
actual.insert("a");
|
||||
actual.insert("c");
|
||||
let expected_err = verify_that!(actual, container_eq(["c", "a"]).ignore_order());
|
||||
verify_that!(
|
||||
expected_err,
|
||||
err(displays_as(contains_regex(indoc!(
|
||||
r#"
|
||||
Value of: actual
|
||||
Expected: contains all elements matching in any order:
|
||||
\* "c"
|
||||
\* "a"
|
||||
Actual: \{"\w", "\w", "\w"\},
|
||||
which does not have a perfect match. The best match found was:
|
||||
Actual element "\w" at index \d is equal to expected element at index \d\.
|
||||
Actual element "\w" at index \d is equal to expected element at index \d\.
|
||||
Actual element "\w" at index \d did not match any remaining expected element\.
|
||||
"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignoring_order_on_number_sets() -> Result<()> {
|
||||
let mut actual = std::collections::HashSet::new();
|
||||
actual.insert(1);
|
||||
actual.insert(2);
|
||||
actual.insert(3);
|
||||
verify_that!(actual, container_eq([3, 2, 1]).ignore_order())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches an [`IntoIterator`] type whose elements contain a value matched by
|
||||
/// `inner`.
|
||||
///
|
||||
/// By default, this matches a container with any number of elements matched
|
||||
/// by `inner`. Use the method [`ContainsMatcher::times`] to constrain the
|
||||
/// matched containers to a specific number of matching elements.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(["Some value"], contains(eq("Some value")))?; // Passes
|
||||
/// verify_that!(vec!["Some value"], contains(eq(&"Some value")))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!([] as [&String; 0], contains(eq("Some value")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!(["Some value"], contains(eq("Some other value")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// ```
|
||||
pub fn contains<InnerMatcherT>(inner: InnerMatcherT) -> ContainsMatcher<InnerMatcherT> {
|
||||
ContainsMatcher { inner, count: None }
|
||||
}
|
||||
|
||||
/// A matcher which matches a container containing one or more elements a given
|
||||
/// inner [`Matcher`] matches.
|
||||
#[derive(MatcherBase)]
|
||||
pub struct ContainsMatcher<InnerMatcherT> {
|
||||
inner: InnerMatcherT,
|
||||
count: Option<Box<dyn Matcher<usize>>>,
|
||||
}
|
||||
|
||||
impl<InnerMatcherT> ContainsMatcher<InnerMatcherT> {
|
||||
/// Configures this instance to match containers which contain a number of
|
||||
/// matching items matched by `count`.
|
||||
///
|
||||
/// For example, to assert that exactly three matching items must be
|
||||
/// present, use:
|
||||
///
|
||||
/// ```ignore
|
||||
/// contains(...).times(eq(3))
|
||||
/// ```
|
||||
///
|
||||
/// One can also use `times(eq(0))` to test for the *absence* of an item
|
||||
/// matching the expected value.
|
||||
pub fn times(mut self, count: impl Matcher<usize> + 'static) -> Self {
|
||||
self.count = Some(Box::new(count));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(hovinen): Revisit the trait bounds to see whether this can be made more
|
||||
// flexible. Namely, the following doesn't compile currently:
|
||||
//
|
||||
// let matcher = contains(eq(&42));
|
||||
// let val = 42;
|
||||
// let _ = matcher.matches(&vec![&val]);
|
||||
//
|
||||
// because val is dropped before matcher but the trait bound requires that
|
||||
// the argument to matches outlive the matcher. It works fine if one defines
|
||||
// val before matcher.
|
||||
impl<T: Debug + Copy, InnerMatcherT: Matcher<T>, ContainerT: Debug + Copy> Matcher<ContainerT>
|
||||
for ContainsMatcher<InnerMatcherT>
|
||||
where
|
||||
ContainerT: IntoIterator<Item = T>,
|
||||
{
|
||||
fn matches(&self, actual: ContainerT) -> MatcherResult {
|
||||
if let Some(count) = &self.count {
|
||||
count.matches(self.count_matches(actual))
|
||||
} else {
|
||||
for v in actual.into_iter() {
|
||||
if self.inner.matches(v).into() {
|
||||
return MatcherResult::Match;
|
||||
}
|
||||
}
|
||||
MatcherResult::NoMatch
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ContainerT) -> Description {
|
||||
let count = self.count_matches(actual);
|
||||
match (count, &self.count) {
|
||||
(_, Some(_)) => format!("which contains {count} matching elements").into(),
|
||||
(0, None) => "which does not contain a matching element".into(),
|
||||
(_, None) => "which contains a matching element".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match (matcher_result, &self.count) {
|
||||
(MatcherResult::Match, Some(count)) => format!(
|
||||
"contains n elements which {}\n where n {}",
|
||||
self.inner.describe(MatcherResult::Match),
|
||||
count.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
(MatcherResult::NoMatch, Some(count)) => format!(
|
||||
"doesn't contain n elements which {}\n where n {}",
|
||||
self.inner.describe(MatcherResult::Match),
|
||||
count.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
(MatcherResult::Match, None) => format!(
|
||||
"contains at least one element which {}",
|
||||
self.inner.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
(MatcherResult::NoMatch, None) => {
|
||||
format!("contains no element which {}", self.inner.describe(MatcherResult::Match))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<InnerMatcherT> ContainsMatcher<InnerMatcherT> {
|
||||
fn count_matches<T: Debug + Copy, ContainerT>(&self, actual: ContainerT) -> usize
|
||||
where
|
||||
ContainerT: IntoIterator<Item = T>,
|
||||
InnerMatcherT: Matcher<T>,
|
||||
{
|
||||
let mut count = 0;
|
||||
for v in actual.into_iter() {
|
||||
if self.inner.matches(v).into() {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn contains_matches_singleton_slice_with_value() -> Result<()> {
|
||||
let matcher = contains(eq(&1));
|
||||
|
||||
let result = matcher.matches(&vec![1]);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_matches_singleton_vec_with_value() -> Result<()> {
|
||||
let matcher = contains(eq(&1));
|
||||
|
||||
let result = matcher.matches(&vec![1]);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_matches_two_element_slice_with_value() -> Result<()> {
|
||||
let matcher = contains(eq(&1));
|
||||
|
||||
let result = matcher.matches(&[0, 1]);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_does_not_match_singleton_slice_with_wrong_value() -> Result<()> {
|
||||
let matcher = contains(eq(&1));
|
||||
|
||||
let result = matcher.matches(&[0]);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_does_not_match_empty_slice() -> Result<()> {
|
||||
let matcher = contains(eq(&1));
|
||||
|
||||
let result = matcher.matches(&[1; 0]);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_matches_slice_with_repeated_value() -> Result<()> {
|
||||
let matcher = contains(eq(&1)).times(eq(2));
|
||||
|
||||
let result = matcher.matches(&[1, 1]);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_does_not_match_slice_with_too_few_of_value() -> Result<()> {
|
||||
let matcher = contains(eq(&1)).times(eq(2));
|
||||
|
||||
let result = matcher.matches(&[0, 1]);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_does_not_match_slice_with_too_many_of_value() -> Result<()> {
|
||||
let matcher = contains(eq(&1)).times(eq(1));
|
||||
|
||||
let result = matcher.matches(&[1, 1]);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_formats_without_multiplicity_by_default() -> Result<()> {
|
||||
let matcher = contains(eq(&1));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&Vec<i32>>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("contains at least one element which is equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_formats_with_multiplicity_when_specified() -> Result<()> {
|
||||
let matcher = contains(eq(&1)).times(eq(2));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&Vec<i32>>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("contains n elements which is equal to 1\n where n is equal to 2"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_mismatch_shows_number_of_times_element_was_found() -> Result<()> {
|
||||
verify_that!(
|
||||
contains(eq(&3)).times(eq(1)).explain_match(&vec![1, 2, 3, 3]),
|
||||
displays_as(eq("which contains 2 matching elements"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_mismatch_shows_when_matches() -> Result<()> {
|
||||
verify_that!(
|
||||
contains(eq(&3)).explain_match(&vec![1, 2, 3, 3]),
|
||||
displays_as(eq("which contains a matching element"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_mismatch_shows_when_no_matches() -> Result<()> {
|
||||
verify_that!(
|
||||
contains(eq(&3)).explain_match(&vec![1, 2]),
|
||||
displays_as(eq("which does not contain a matching element"))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use regex::Regex;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Matches a string containing a substring which matches the given regular
|
||||
/// expression.
|
||||
///
|
||||
/// Both the actual value and the expected regular expression may be either a
|
||||
/// `String` or a string reference.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass_1() -> Result<()> {
|
||||
/// verify_that!("Some value", contains_regex("S.*e"))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!("Another value", contains_regex("Some"))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_pass_2() -> Result<()> {
|
||||
/// verify_that!("Some value".to_string(), contains_regex("v.*e"))?; // Passes
|
||||
/// verify_that!("Some value", contains_regex("v.*e".to_string()))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass_1().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// # should_pass_2().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Panics if the given `pattern` is not a syntactically valid regular
|
||||
/// expression.
|
||||
#[track_caller]
|
||||
pub fn contains_regex<PatternT: Deref<Target = str>>(pattern: PatternT) -> ContainsRegexMatcher {
|
||||
ContainsRegexMatcher { regex: Regex::new(pattern.deref()).unwrap() }
|
||||
}
|
||||
|
||||
/// A matcher matching a string-like type containing a substring matching a
|
||||
/// given regular expression.
|
||||
///
|
||||
/// Intended only to be used from the function [`contains_regex`] only.
|
||||
/// Should not be referenced by code outside this library.
|
||||
#[derive(MatcherBase)]
|
||||
pub struct ContainsRegexMatcher {
|
||||
regex: Regex,
|
||||
}
|
||||
|
||||
impl<ActualT: AsRef<str> + Debug + Copy> Matcher<ActualT> for ContainsRegexMatcher {
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
self.regex.is_match(actual.as_ref()).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("contains the regular expression {:#?}", self.regex.as_str()).into()
|
||||
}
|
||||
MatcherResult::NoMatch => {
|
||||
format!("doesn't contain the regular expression {:#?}", self.regex.as_str()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn contains_regex_matches_string_reference_with_pattern() -> Result<()> {
|
||||
let matcher = contains_regex("S.*val");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_regex_does_not_match_string_without_pattern() -> Result<()> {
|
||||
let matcher = contains_regex("Another");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_regex_matches_owned_string_with_pattern() -> Result<()> {
|
||||
let matcher = contains_regex("value");
|
||||
|
||||
let result = matcher.matches(&"Some value".to_string());
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_regex_matches_string_reference_with_owned_string() -> Result<()> {
|
||||
let matcher = contains_regex("value");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_that_works_with_owned_string() -> Result<()> {
|
||||
verify_that!("Some value".to_string(), contains_regex("value"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_regex_displays_quoted_debug_of_pattern() -> Result<()> {
|
||||
let matcher = contains_regex("\n");
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("contains the regular expression \"\\n\""))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2024 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::{fmt::Debug, ops::Deref};
|
||||
|
||||
/// Dereferences the `actual` value and verifies that the returned reference
|
||||
/// matches the `inner` matcher.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::{matchers::{derefs_to, eq}, verify_that};
|
||||
/// verify_that!(Box::new(123), derefs_to(eq(&123)))
|
||||
/// # .unwrap()
|
||||
/// ```
|
||||
pub fn derefs_to<Inner>(inner: Inner) -> DerefsTo<Inner> {
|
||||
DerefsTo { inner }
|
||||
}
|
||||
|
||||
/// A matcher which derefs a value and verifies that the result matches the
|
||||
/// `inner` matcher.
|
||||
///
|
||||
/// See [`derefs_to`].
|
||||
#[derive(MatcherBase)]
|
||||
pub struct DerefsTo<InnerT> {
|
||||
pub(crate) inner: InnerT,
|
||||
}
|
||||
|
||||
impl<'a, ActualT, ExpectedT, Inner> Matcher<&'a ActualT> for DerefsTo<Inner>
|
||||
where
|
||||
ActualT: Deref<Target = ExpectedT> + Debug,
|
||||
ExpectedT: Copy + Debug + 'a,
|
||||
Inner: Matcher<&'a ExpectedT>,
|
||||
{
|
||||
fn matches(&self, actual: &'a ActualT) -> MatcherResult {
|
||||
self.inner.matches(actual.deref())
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
self.inner.describe(matcher_result)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a ActualT) -> Description {
|
||||
self.inner.explain_match(actual.deref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn deref_to_matches_box_of_int_with_int() -> Result<()> {
|
||||
let actual = Box::new(123);
|
||||
verify_that!(actual, derefs_to(eq(&123)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deref_to_matches_rc_of_int_with_int() -> Result<()> {
|
||||
verify_that!(Rc::new(123), derefs_to(eq(&123)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deref_to_combines_with_points_to_for_copy() -> Result<()> {
|
||||
verify_that!(Rc::new(123), derefs_to(points_to(eq(123))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_explanation_references_actual_value() -> Result<()> {
|
||||
let actual = Box::new(1);
|
||||
let result = verify_that!(actual, derefs_to(eq(&0)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Actual: 1,
|
||||
which isn't equal to 0
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// There are no visible documentation elements in this module.
|
||||
#![doc(hidden)]
|
||||
|
||||
use crate::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matcher created by [`Matcher::or`] and [`any!`].
|
||||
///
|
||||
/// Both [`Matcher::or`] and [`any!`] nest on m1. In other words,
|
||||
/// both `x.or(y).or(z)` and `any![x, y, z]` produce:
|
||||
/// ```ignore
|
||||
/// DisjunctionMatcher {
|
||||
/// m1: DisjunctionMatcher {
|
||||
/// m1: x, m2: y
|
||||
/// },
|
||||
/// m2: z
|
||||
/// }
|
||||
/// ```
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
#[derive(MatcherBase)]
|
||||
pub struct DisjunctionMatcher<M1, M2> {
|
||||
m1: M1,
|
||||
m2: M2,
|
||||
}
|
||||
|
||||
impl<M1, M2> DisjunctionMatcher<M1, M2> {
|
||||
pub fn new(m1: M1, m2: M2) -> Self {
|
||||
Self { m1, m2 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, M1: Matcher<T>, M2: Matcher<T>> Matcher<T> for DisjunctionMatcher<M1, M2> {
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
match (self.m1.matches(actual), self.m2.matches(actual)) {
|
||||
(MatcherResult::NoMatch, MatcherResult::NoMatch) => MatcherResult::NoMatch,
|
||||
_ => MatcherResult::Match,
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: T) -> Description {
|
||||
match (self.m1.matches(actual), self.m2.matches(actual)) {
|
||||
(MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual),
|
||||
(MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual),
|
||||
(_, _) => {
|
||||
let m1_description = self.m1.explain_match(actual);
|
||||
if m1_description.is_disjunction_description() {
|
||||
m1_description.nested(self.m2.explain_match(actual))
|
||||
} else {
|
||||
Description::new()
|
||||
.bullet_list()
|
||||
.collect([m1_description, self.m2.explain_match(actual)])
|
||||
.disjunction_description()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
let m1_description = self.m1.describe(matcher_result);
|
||||
if m1_description.is_disjunction_description() {
|
||||
m1_description.push_in_last_nested(self.m2.describe(matcher_result))
|
||||
} else {
|
||||
let header = if matcher_result.into() {
|
||||
"has at least one of the following properties:"
|
||||
} else {
|
||||
"has all of the following properties:"
|
||||
};
|
||||
Description::new()
|
||||
.text(header)
|
||||
.nested(
|
||||
Description::new()
|
||||
.bullet_list()
|
||||
.collect([m1_description, self.m2.describe(matcher_result)]),
|
||||
)
|
||||
.disjunction_description()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn or_true_true_matches() -> Result<()> {
|
||||
verify_that!(1, anything().or(anything()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_true_false_matches() -> Result<()> {
|
||||
verify_that!(1, anything().or(not(anything())))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_false_true_matches() -> Result<()> {
|
||||
verify_that!(1, not(anything()).or(anything()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_false_false_does_not_match() -> Result<()> {
|
||||
let result = verify_that!(1, not(anything()).or(not(anything())));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 1
|
||||
Expected: has at least one of the following properties:
|
||||
* never matches
|
||||
* never matches
|
||||
Actual: 1,
|
||||
* which is anything
|
||||
* which is anything
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chained_or_matches() -> Result<()> {
|
||||
verify_that!(10, eq(1).or(eq(5)).or(ge(9)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_str_slices() -> Result<()> {
|
||||
verify_that!("A string", ends_with("A").or(ends_with("string")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_with_owned_strings() -> Result<()> {
|
||||
verify_that!("A string".to_string(), ends_with("A").or(ends_with("string")))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
/// Matches the string representation of types that implement `Display`.
|
||||
///
|
||||
/// ```ignore
|
||||
/// let result: impl Display = ...;
|
||||
/// verify_that!(result, displays_as(eq(format!("{}", result))))?;
|
||||
/// ```
|
||||
pub fn displays_as<InnerMatcher: for<'a> Matcher<&'a str>>(
|
||||
inner: InnerMatcher,
|
||||
) -> DisplayMatcher<InnerMatcher> {
|
||||
DisplayMatcher { inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct DisplayMatcher<InnerMatcher> {
|
||||
inner: InnerMatcher,
|
||||
}
|
||||
|
||||
impl<T: Debug + Display + Copy, InnerMatcher: for<'a> Matcher<&'a str>> Matcher<T>
|
||||
for DisplayMatcher<InnerMatcher>
|
||||
{
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
self.inner.matches(&format!("{actual}"))
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: T) -> Description {
|
||||
format!(
|
||||
"which displays as {:?} {}",
|
||||
actual.to_string(),
|
||||
self.inner.explain_match(&format!("{actual}"))
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("displays as a string which {}", self.inner.describe(MatcherResult::Match))
|
||||
.into()
|
||||
}
|
||||
MatcherResult::NoMatch => format!(
|
||||
"doesn't display as a string which {}",
|
||||
self.inner.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
|
||||
#[test]
|
||||
fn display_matches_i32() -> Result<()> {
|
||||
let value = 32;
|
||||
verify_that!(value, displays_as(eq("32")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_matches_str() -> Result<()> {
|
||||
let value = "32";
|
||||
verify_that!(value, displays_as(eq("32")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_matches_struct() -> Result<()> {
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
struct Struct {
|
||||
a: i32,
|
||||
b: i64,
|
||||
}
|
||||
impl Display for Struct {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
verify_that!(Struct { a: 123, b: 321 }, displays_as(eq("Struct { a: 123, b: 321 }")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_displays_error_message_with_explanation_from_inner_matcher() -> Result<()> {
|
||||
let result = verify_that!("123\n234", displays_as(eq("123\n345")));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Actual: \"123\\n234\",
|
||||
which displays as \"123\\n234\" which isn't equal to \"123\\n345\"
|
||||
|
||||
Difference(-actual / +expected):
|
||||
123
|
||||
-234
|
||||
+345
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a container all of whose elements are matched by the matcher
|
||||
/// `inner`.
|
||||
///
|
||||
/// `T` must implement [`IntoIterator`]. This
|
||||
/// includes `&Vec`, arrays, and slices.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use std::collections::HashSet;
|
||||
/// # fn should_pass_1() -> Result<()> {
|
||||
/// let value = vec![1, 2, 3];
|
||||
/// verify_that!(value, each(gt(&0)))?; // Passes
|
||||
/// let array_value = [1, 2, 3];
|
||||
/// verify_that!(array_value, each(gt(0)))?; // Passes
|
||||
/// let slice_value = &[1, 2, 3];
|
||||
/// verify_that!(slice_value, each(gt(&0)))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// # let value = vec![1, 2, 3];
|
||||
/// verify_that!(value, each(lt(&2)))?; // Fails: 2 and 3 are not less than 2
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
///
|
||||
/// # fn should_pass_2() -> Result<()> {
|
||||
/// let value: HashSet<i32> = [1, 2, 3].into();
|
||||
/// verify_that!(value, each(gt(&0)))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass_1().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// # should_pass_2().unwrap();
|
||||
/// ```
|
||||
pub fn each<MatcherT>(inner: MatcherT) -> EachMatcher<MatcherT> {
|
||||
EachMatcher { inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct EachMatcher<MatcherT> {
|
||||
inner: MatcherT,
|
||||
}
|
||||
|
||||
impl<ElementT: Debug + Copy, ActualT: Debug + Copy, MatcherT> Matcher<ActualT>
|
||||
for EachMatcher<MatcherT>
|
||||
where
|
||||
ActualT: IntoIterator<Item = ElementT>,
|
||||
MatcherT: Matcher<ElementT>,
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
for element in actual {
|
||||
if self.inner.matches(element).is_no_match() {
|
||||
return MatcherResult::NoMatch;
|
||||
}
|
||||
}
|
||||
MatcherResult::Match
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ActualT) -> Description {
|
||||
let mut non_matching_elements = Vec::new();
|
||||
for (index, element) in actual.into_iter().enumerate() {
|
||||
if self.inner.matches(element).is_no_match() {
|
||||
non_matching_elements.push((index, element, self.inner.explain_match(element)));
|
||||
}
|
||||
}
|
||||
if non_matching_elements.is_empty() {
|
||||
return format!("whose each element {}", self.inner.describe(MatcherResult::Match))
|
||||
.into();
|
||||
}
|
||||
if non_matching_elements.len() == 1 {
|
||||
let (idx, element, explanation) = non_matching_elements.remove(0);
|
||||
return format!("whose element #{idx} is {element:?}, {explanation}").into();
|
||||
}
|
||||
|
||||
let failed_indexes = non_matching_elements
|
||||
.iter()
|
||||
.map(|&(idx, _, _)| format!("#{idx}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let element_explanations = non_matching_elements
|
||||
.iter()
|
||||
.map(|&(_, element, ref explanation)| format!("{element:?}, {explanation}"))
|
||||
.collect::<Description>()
|
||||
.indent();
|
||||
format!("whose elements {failed_indexes} don't match\n{element_explanations}").into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("only contains elements that {}", self.inner.describe(MatcherResult::Match))
|
||||
.into()
|
||||
}
|
||||
MatcherResult::NoMatch => {
|
||||
format!("contains no element that {}", self.inner.describe(MatcherResult::Match))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn each_matches_empty_vec() -> Result<()> {
|
||||
let value: Vec<i32> = vec![];
|
||||
verify_that!(value, each(gt(&0)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_matches_vec_with_one_element() -> Result<()> {
|
||||
let value = vec![1];
|
||||
verify_that!(value, each(gt(&0)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_matches_vec_with_two_elements() -> Result<()> {
|
||||
let value = vec![1, 2];
|
||||
verify_that!(value, each(gt(&0)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_matches_slice_with_one_element() -> Result<()> {
|
||||
let value = &[1];
|
||||
verify_that!(*value, each(gt(0)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_matches_hash_set_with_one_element() -> Result<()> {
|
||||
let value: HashSet<i32> = [1].into();
|
||||
verify_that!(value, each(gt(&0)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_does_not_match_when_first_element_does_not_match() -> Result<()> {
|
||||
let value = vec![0];
|
||||
verify_that!(value, not(each(gt(&1))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_does_not_match_when_second_element_does_not_match() -> Result<()> {
|
||||
let value = vec![2, 0];
|
||||
verify_that!(value, not(each(gt(&1))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
|
||||
let result = verify_that!(vec![0, 2, 3], each(gt(&0)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![0, 2, 3]
|
||||
Expected: only contains elements that is greater than 0
|
||||
Actual: [0, 2, 3],
|
||||
whose element #0 is 0, which is less than or equal to 0"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
|
||||
let result = verify_that!(vec![1, 0, 3], each(gt(&0)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![1, 0, 3]
|
||||
Expected: only contains elements that is greater than 0
|
||||
Actual: [1, 0, 3],
|
||||
whose element #1 is 0, which is less than or equal to 0"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
|
||||
let result = verify_that!(vec![0, 1, 3], each(gt(&1)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![0, 1, 3]
|
||||
Expected: only contains elements that is greater than 1
|
||||
Actual: [0, 1, 3],
|
||||
whose elements #0, #1 don't match
|
||||
0, which is less than or equal to 1
|
||||
1, which is less than or equal to 1"
|
||||
))))
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn each_shows_inner_explanation() -> Result<()> {
|
||||
let result = verify_that!(vec![vec![1, 2], vec![1]], each(each(eq(&1))));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Expected: only contains elements that only contains elements that is equal to 1
|
||||
Actual: [[1, 2], [1]],
|
||||
whose element #0 is [1, 2], whose element #1 is 2, which isn't equal to 1"
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// There are no visible documentation elements in this module; the declarative
|
||||
// macro is documented in the matchers module.
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Matches a container's elements to each matcher in order.
|
||||
///
|
||||
/// This macro produces a matcher against a container. It takes as arguments a
|
||||
/// sequence of matchers each of which should respectively match the
|
||||
/// corresponding element of the actual value.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// verify_that!(vec![1, 2, 3], elements_are![eq(&1), anything(), gt(&0).and(lt(&123))])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The actual value must be a container such as a `&Vec`, an array, or a slice.
|
||||
/// More precisely, the actual value must implement [`IntoIterator`].
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// let vector = vec![1, 2, 3];
|
||||
/// let slice = vector.as_slice();
|
||||
/// verify_that!(slice, elements_are![eq(&1), anything(), gt(&0).and(lt(&123))])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// This can also be omitted in [`verify_that!`] macros and replaced with square
|
||||
/// brackets.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// verify_that!(vec![1, 2], [eq(&1), eq(&2)])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Note: This behavior is only possible in [`verify_that!`] macros. In any
|
||||
/// other cases, it is still necessary to use the
|
||||
/// [`elements_are!`][crate::matchers::elements_are] macro.
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// verify_that!(vec![vec![1,2], vec![3]], [[eq(&1), eq(&2)], [eq(&3)]])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Use this instead:
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// verify_that!(vec![vec![1,2], vec![3]], [elements_are![eq(&1), eq(&2)], elements_are![eq(&3)]])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// If an inner matcher is `eq(...)`, it can be omitted:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
///
|
||||
/// verify_that!(vec![1,2,3], elements_are![&1, lt(&1000), gt(&1)])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Do not use this with unordered containers, since that will lead to flaky
|
||||
/// tests. Use
|
||||
/// [`unordered_elements_are!`][crate::matchers::unordered_elements_are]
|
||||
/// instead.
|
||||
///
|
||||
/// [`IntoIterator`]: std::iter::IntoIterator
|
||||
/// [`Iterator`]: std::iter::Iterator
|
||||
/// [`Iterator::collect`]: std::iter::Iterator::collect
|
||||
/// [`Vec`]: std::vec::Vec
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __elements_are {
|
||||
($($matcher:expr),* $(,)?) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::ElementsAre::new(
|
||||
vec![$(Box::new(
|
||||
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!(
|
||||
$matcher
|
||||
)
|
||||
)),*])
|
||||
}}
|
||||
}
|
||||
|
||||
/// Module for use only by the procedural macros in this module.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub mod internal {
|
||||
use crate::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use crate::matcher_support::zipped_iterator::zip;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// This struct is meant to be used only by the macro `elements_are!`.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
#[derive(MatcherBase)]
|
||||
pub struct ElementsAre<'a, T: Debug + Copy> {
|
||||
elements: Vec<Box<dyn Matcher<T> + 'a>>,
|
||||
}
|
||||
|
||||
impl<'a, T: Debug + Copy> ElementsAre<'a, T> {
|
||||
/// Factory only intended for use in the macro `elements_are!`.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub fn new(elements: Vec<Box<dyn Matcher<T> + 'a>>) -> Self {
|
||||
Self { elements }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, ContainerT: Debug + Copy> Matcher<ContainerT> for ElementsAre<'_, T>
|
||||
where
|
||||
ContainerT: IntoIterator<Item = T>,
|
||||
{
|
||||
fn matches(&self, actual: ContainerT) -> MatcherResult {
|
||||
let mut zipped_iterator = zip(actual.into_iter(), self.elements.iter());
|
||||
for (a, e) in zipped_iterator.by_ref() {
|
||||
if e.matches(a).is_no_match() {
|
||||
return MatcherResult::NoMatch;
|
||||
}
|
||||
}
|
||||
if !zipped_iterator.has_size_mismatch() {
|
||||
MatcherResult::Match
|
||||
} else {
|
||||
MatcherResult::NoMatch
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ContainerT) -> Description {
|
||||
let actual_iterator = actual.into_iter();
|
||||
let mut zipped_iterator = zip(actual_iterator, self.elements.iter());
|
||||
let mut mismatches = Vec::new();
|
||||
for (idx, (a, e)) in zipped_iterator.by_ref().enumerate() {
|
||||
if e.matches(a).is_no_match() {
|
||||
mismatches.push(format!("element #{idx} is {a:?}, {}", e.explain_match(a)));
|
||||
}
|
||||
}
|
||||
if mismatches.is_empty() {
|
||||
if !zipped_iterator.has_size_mismatch() {
|
||||
"whose elements all match".into()
|
||||
} else {
|
||||
format!("whose size is {}", zipped_iterator.left_size()).into()
|
||||
}
|
||||
} else if mismatches.len() == 1 {
|
||||
let mismatches = mismatches.into_iter().collect::<Description>();
|
||||
format!("where {mismatches}").into()
|
||||
} else {
|
||||
let mismatches = mismatches.into_iter().collect::<Description>();
|
||||
format!("where:\n{}", mismatches.bullet_list().indent()).into()
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
format!(
|
||||
"{} elements:\n{}",
|
||||
if matcher_result.into() { "has" } else { "doesn't have" },
|
||||
&self
|
||||
.elements
|
||||
.iter()
|
||||
.map(|matcher| matcher.describe(MatcherResult::Match))
|
||||
.collect::<Description>()
|
||||
.enumerate()
|
||||
.indent()
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches an empty container.
|
||||
///
|
||||
/// `T` can be any container that implements `IntoIterator`. For instance, `T`
|
||||
/// can be the reference of a common container like `&Vec` and
|
||||
/// [`&HashSet`][std::collections::HashSet].
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use std::collections::HashSet;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let value: Vec<i32> = vec![];
|
||||
/// verify_that!(value, is_empty())?;
|
||||
/// let value: HashSet<i32> = HashSet::new();
|
||||
/// verify_that!(value, is_empty())?;
|
||||
/// let value: &[u32] = &[];
|
||||
/// verify_that!(value, is_empty())?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
pub fn is_empty() -> EmptyMatcher {
|
||||
EmptyMatcher
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct EmptyMatcher;
|
||||
|
||||
impl<T: Debug + Copy> Matcher<T> for EmptyMatcher
|
||||
where
|
||||
T: IntoIterator,
|
||||
{
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
actual.into_iter().next().is_none().into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
if matcher_result.into() { "is empty" } else { "isn't empty" }.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn empty_matcher_match_empty_vec() -> Result<()> {
|
||||
let value: Vec<i32> = vec![];
|
||||
verify_that!(value, is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_matcher_does_not_match_empty_vec() -> Result<()> {
|
||||
let value = vec![1, 2, 3];
|
||||
verify_that!(value, not(is_empty()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_matcher_matches_empty_slice() -> Result<()> {
|
||||
let value: &[i32] = &[];
|
||||
verify_that!(value, is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_matcher_matches_empty_hash_set() -> Result<()> {
|
||||
let value: HashSet<i32> = HashSet::new();
|
||||
verify_that!(value, is_empty())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use crate::matcher_support::edit_distance;
|
||||
use crate::matcher_support::summarize_diff::create_diff;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a value equal (in the sense of `==`) to `expected`.
|
||||
///
|
||||
/// The type of `expected` must implement the [`PartialEq`] trait so that the
|
||||
/// expected and actual values can be compared.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(123, eq(123))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(123, eq(234))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// `expected` to `actual` must be comparable with one another via the
|
||||
/// [`PartialEq`] trait. In most cases, this means that they must be of the same
|
||||
/// type. However, there are a few cases where different but closely related
|
||||
/// types are comparable, for example `String` with `&str`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(String::from("Some value"), eq("Some value"))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// In most cases however, one must convert one of the arguments explicitly.
|
||||
/// This can be surprising when comparing integer types or references.
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// verify_that!(123u32, eq(123u64))?; // Does not compile
|
||||
/// verify_that!(123u32 as u64, eq(123u64))?; // Passes
|
||||
/// ```
|
||||
///
|
||||
/// ```ignore
|
||||
/// let actual: &T = ...;
|
||||
/// let expected: T = T{...};
|
||||
/// verify_that(actual, eq(expected))?; // Does not compile
|
||||
/// verify_that(actual, eq(&expected))?; // Compiles
|
||||
/// ```
|
||||
///
|
||||
/// When matching with string types (`&str` and `String`), one can set more
|
||||
/// options on how equality is checked through the
|
||||
/// [`StrMatcherConfigurator`][crate::matchers::str_matcher::StrMatcherConfigurator]
|
||||
/// extension trait, which is implemented for this matcher.
|
||||
pub fn eq<T>(expected: T) -> EqMatcher<T> {
|
||||
EqMatcher { expected }
|
||||
}
|
||||
|
||||
/// A matcher which matches a value equal to `expected`.
|
||||
///
|
||||
/// See [`eq`].
|
||||
#[derive(MatcherBase)]
|
||||
pub struct EqMatcher<T> {
|
||||
pub(crate) expected: T,
|
||||
}
|
||||
|
||||
impl<T: Debug, A: Debug + Copy + PartialEq<T>> Matcher<A> for EqMatcher<T> {
|
||||
fn matches(&self, actual: A) -> MatcherResult {
|
||||
(actual == self.expected).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!("is equal to {:?}", self.expected).into(),
|
||||
MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: A) -> Description {
|
||||
let expected_debug = format!("{:#?}", self.expected);
|
||||
let actual_debug = format!("{actual:#?}");
|
||||
let description = Matcher::<A>::describe(self, self.matches(actual));
|
||||
|
||||
let diff = if is_multiline_string_debug(&actual_debug)
|
||||
&& is_multiline_string_debug(&expected_debug)
|
||||
{
|
||||
create_diff(
|
||||
// The two calls below return None if and only if the strings expected_debug
|
||||
// respectively actual_debug are not enclosed in ". The calls to
|
||||
// is_multiline_string_debug above ensure that they are. So the calls cannot
|
||||
// actually return None and unwrap() should not panic.
|
||||
&to_display_output(&actual_debug).unwrap(),
|
||||
&to_display_output(&expected_debug).unwrap(),
|
||||
edit_distance::Mode::Exact,
|
||||
)
|
||||
} else {
|
||||
create_diff(&actual_debug, &expected_debug, edit_distance::Mode::Exact)
|
||||
};
|
||||
|
||||
if diff.is_empty() {
|
||||
format!("which {description}").into()
|
||||
} else {
|
||||
format!("which {description}\n\n{diff}").into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_multiline_string_debug(string: &str) -> bool {
|
||||
string.starts_with('"')
|
||||
&& string.ends_with('"')
|
||||
&& !string.contains('\n')
|
||||
&& string.contains("\\n")
|
||||
}
|
||||
|
||||
fn to_display_output(string: &str) -> Option<String> {
|
||||
Some(string.strip_prefix('"')?.strip_suffix('"')?.split("\\n").collect::<Vec<_>>().join("\n"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn eq_matches_string_reference_with_string_reference() -> Result<()> {
|
||||
verify_that!("A string", eq("A string"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_matches_owned_string_with_string_reference() -> Result<()> {
|
||||
let value = "A string".to_string();
|
||||
verify_that!(value, eq("A string"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_matches_owned_string_reference_with_string_reference() -> Result<()> {
|
||||
let value = "A string".to_string();
|
||||
verify_that!(&value, eq("A string"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_matches_i32_with_i32() -> Result<()> {
|
||||
verify_that!(123, eq(123))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_struct_debug_diff() -> Result<()> {
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Strukt {
|
||||
int: i32,
|
||||
string: String,
|
||||
}
|
||||
|
||||
let result = verify_that!(
|
||||
Strukt { int: 123, string: "something".into() },
|
||||
eq(&Strukt { int: 321, string: "someone".into() })
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc! {
|
||||
"
|
||||
Actual: Strukt { int: 123, string: \"something\" },
|
||||
which isn't equal to Strukt { int: 321, string: \"someone\" }
|
||||
|
||||
Difference(-actual / +expected):
|
||||
Strukt {
|
||||
- int: 123,
|
||||
+ int: 321,
|
||||
- string: \"something\",
|
||||
+ string: \"someone\",
|
||||
}
|
||||
"})))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_vec_debug_diff() -> Result<()> {
|
||||
let result = verify_that!(vec![1, 2, 3], eq(&vec![1, 3, 4]));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc! {
|
||||
"
|
||||
Value of: vec![1, 2, 3]
|
||||
Expected: is equal to [1, 3, 4]
|
||||
Actual: [1, 2, 3],
|
||||
which isn't equal to [1, 3, 4]
|
||||
|
||||
Difference(-actual / +expected):
|
||||
[
|
||||
1,
|
||||
- 2,
|
||||
3,
|
||||
+ 4,
|
||||
]
|
||||
"})))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_vec_debug_diff_length_mismatch() -> Result<()> {
|
||||
let result = verify_that!(vec![1, 2, 3, 4, 5], eq(&vec![1, 3, 5]));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc! {
|
||||
"
|
||||
Value of: vec![1, 2, 3, 4, 5]
|
||||
Expected: is equal to [1, 3, 5]
|
||||
Actual: [1, 2, 3, 4, 5],
|
||||
which isn't equal to [1, 3, 5]
|
||||
|
||||
Difference(-actual / +expected):
|
||||
[
|
||||
1,
|
||||
- 2,
|
||||
3,
|
||||
- 4,
|
||||
5,
|
||||
]
|
||||
"})))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_debug_diff_common_lines_omitted() -> Result<()> {
|
||||
let result = verify_that!((1..50).collect::<Vec<_>>(), eq(&(3..52).collect::<Vec<_>>()));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc! {
|
||||
"
|
||||
],
|
||||
which isn't equal to [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
|
||||
|
||||
Difference(-actual / +expected):
|
||||
[
|
||||
- 1,
|
||||
- 2,
|
||||
3,
|
||||
4,
|
||||
<---- 43 common lines omitted ---->
|
||||
48,
|
||||
49,
|
||||
+ 50,
|
||||
+ 51,
|
||||
]"})))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_debug_diff_5_common_lines_not_omitted() -> Result<()> {
|
||||
let result = verify_that!((1..8).collect::<Vec<_>>(), eq(&(3..10).collect::<Vec<_>>()));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc! {
|
||||
"
|
||||
Actual: [1, 2, 3, 4, 5, 6, 7],
|
||||
which isn't equal to [3, 4, 5, 6, 7, 8, 9]
|
||||
|
||||
Difference(-actual / +expected):
|
||||
[
|
||||
- 1,
|
||||
- 2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
+ 8,
|
||||
+ 9,
|
||||
]"})))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_debug_diff_start_common_lines_omitted() -> Result<()> {
|
||||
let result = verify_that!((1..50).collect::<Vec<_>>(), eq(&(1..52).collect::<Vec<_>>()));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc! {
|
||||
"
|
||||
],
|
||||
which isn't equal to [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
|
||||
|
||||
Difference(-actual / +expected):
|
||||
[
|
||||
1,
|
||||
<---- 46 common lines omitted ---->
|
||||
48,
|
||||
49,
|
||||
+ 50,
|
||||
+ 51,
|
||||
]"})))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_debug_diff_end_common_lines_omitted() -> Result<()> {
|
||||
let result = verify_that!((1..52).collect::<Vec<_>>(), eq(&(3..52).collect::<Vec<_>>()));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc! {
|
||||
"
|
||||
],
|
||||
which isn't equal to [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
|
||||
|
||||
Difference(-actual / +expected):
|
||||
[
|
||||
- 1,
|
||||
- 2,
|
||||
3,
|
||||
4,
|
||||
<---- 46 common lines omitted ---->
|
||||
51,
|
||||
]"})))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_multi_line_string_debug_diff() -> Result<()> {
|
||||
let result = verify_that!("One\nTwo\nThree", eq("One\nSix\nThree"));
|
||||
// TODO: b/257454450 - Make this more useful, by potentially unescaping the
|
||||
// line return.
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc! {
|
||||
r#"
|
||||
Value of: "One\nTwo\nThree"
|
||||
Expected: is equal to "One\nSix\nThree"
|
||||
Actual: "One\nTwo\nThree",
|
||||
which isn't equal to "One\nSix\nThree"
|
||||
"#})))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()> {
|
||||
let result = verify_that!(
|
||||
indoc!(
|
||||
"
|
||||
First line
|
||||
Second line
|
||||
Third line
|
||||
"
|
||||
),
|
||||
eq(indoc!(
|
||||
"
|
||||
First line
|
||||
Second lines
|
||||
Third line
|
||||
"
|
||||
))
|
||||
);
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(
|
||||
"\
|
||||
First line
|
||||
-Second line
|
||||
+Second lines
|
||||
Third line"
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()> {
|
||||
let result = verify_that!(
|
||||
"First line",
|
||||
eq(indoc!(
|
||||
"
|
||||
First line
|
||||
Second line
|
||||
Third line
|
||||
"
|
||||
))
|
||||
);
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()> {
|
||||
let result = verify_that!(
|
||||
indoc!(
|
||||
"
|
||||
First line
|
||||
Second line
|
||||
Third line
|
||||
"
|
||||
),
|
||||
eq("First line")
|
||||
);
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a `Result` containing `Err` with a value matched by `inner`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> googletest::Result<()> {
|
||||
/// verify_that!(Err::<(), _>("Some error"), err(eq("Some error")))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> googletest::Result<()> {
|
||||
/// verify_that!(Ok::<_, &str>("A value"), err(eq("A value")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> googletest::Result<()> {
|
||||
/// verify_that!(Err::<(), _>("Some error"), err(eq("Some other error")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// ```
|
||||
pub fn err<Inner>(inner: Inner) -> ErrMatcher<Inner> {
|
||||
ErrMatcher { inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct ErrMatcher<InnerMatcherT> {
|
||||
inner: InnerMatcherT,
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, E: Debug + Copy, InnerMatcherT: Matcher<E>> Matcher<std::result::Result<T, E>>
|
||||
for ErrMatcher<InnerMatcherT>
|
||||
{
|
||||
fn matches(&self, actual: std::result::Result<T, E>) -> MatcherResult {
|
||||
actual.err().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: std::result::Result<T, E>) -> Description {
|
||||
match actual {
|
||||
Err(e) => {
|
||||
Description::new().text("which is an error").nested(self.inner.explain_match(e))
|
||||
}
|
||||
Ok(_) => "which is a success".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("is an error which {}", self.inner.describe(MatcherResult::Match)).into()
|
||||
}
|
||||
MatcherResult::NoMatch => format!(
|
||||
"is a success or is an error containing a value which {}",
|
||||
self.inner.describe(MatcherResult::NoMatch)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Debug, E: Debug, InnerMatcherT: Matcher<&'a E>> Matcher<&'a std::result::Result<T, E>>
|
||||
for ErrMatcher<InnerMatcherT>
|
||||
{
|
||||
fn matches(&self, actual: &'a std::result::Result<T, E>) -> MatcherResult {
|
||||
actual.as_ref().err().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a std::result::Result<T, E>) -> Description {
|
||||
match actual {
|
||||
Err(e) => {
|
||||
Description::new().text("which is an error").nested(self.inner.explain_match(e))
|
||||
}
|
||||
Ok(_) => "which is a success".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("is an error which {}", self.inner.describe(MatcherResult::Match)).into()
|
||||
}
|
||||
MatcherResult::NoMatch => format!(
|
||||
"is a success or is an error containing a value which {}",
|
||||
self.inner.describe(MatcherResult::NoMatch)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn err_matches_result_with_err_value() -> Result<()> {
|
||||
let matcher = err(eq(1));
|
||||
let value: std::result::Result<i32, i32> = Err(1);
|
||||
|
||||
let result = matcher.matches(value);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_does_not_match_result_with_wrong_err_value() -> Result<()> {
|
||||
let matcher = err(eq(1));
|
||||
let value: std::result::Result<i32, i32> = Err(0);
|
||||
|
||||
let result = matcher.matches(value);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_does_not_match_result_with_ok() -> Result<()> {
|
||||
let matcher = err(eq(1));
|
||||
let value: std::result::Result<i32, i32> = Ok(1);
|
||||
|
||||
let result = matcher.matches(value);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_matches_result_with_err_value_by_ref() -> Result<()> {
|
||||
let value: std::result::Result<String, String> = Err("123".to_string());
|
||||
verify_that!(value, err(eq("123")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_does_not_match_result_with_wrong_err_value_by_ref() -> Result<()> {
|
||||
let value: std::result::Result<String, String> = Err("321".to_string());
|
||||
verify_that!(value, not(err(eq("123"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_does_not_match_result_with_ok_by_ref() -> Result<()> {
|
||||
let value: std::result::Result<String, String> = Ok("123".to_string());
|
||||
verify_that!(value, not(err(eq("123"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_full_error_message() -> Result<()> {
|
||||
let result = verify_that!(Err::<i32, i32>(1), err(eq(2)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: Err::<i32, i32>(1)
|
||||
Expected: is an error which is equal to 2
|
||||
Actual: Err(1),
|
||||
which is an error
|
||||
which isn't equal to 2
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_describe_match() -> Result<()> {
|
||||
let matcher = err(eq(1));
|
||||
verify_that!(
|
||||
Matcher::<std::result::Result<i32, i32>>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("is an error which is equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_describe_no_match() -> Result<()> {
|
||||
let matcher = err(eq(1));
|
||||
verify_that!(
|
||||
Matcher::<std::result::Result<i32, i32>>::describe(&matcher, MatcherResult::NoMatch),
|
||||
displays_as(eq(
|
||||
"is a success or is an error containing a value which isn't equal to 1"
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_describe_match_by_ref() -> Result<()> {
|
||||
let matcher = err(eq("123"));
|
||||
verify_that!(
|
||||
Matcher::<&std::result::Result<String, String>>::describe(
|
||||
&matcher,
|
||||
MatcherResult::Match
|
||||
),
|
||||
displays_as(eq("is an error which is equal to \"123\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_describe_no_match_by_ref() -> Result<()> {
|
||||
let matcher = err(eq("123"));
|
||||
verify_that!(
|
||||
Matcher::<&std::result::Result<String, String>>::describe(
|
||||
&matcher,
|
||||
MatcherResult::NoMatch
|
||||
),
|
||||
displays_as(eq(
|
||||
"is a success or is an error containing a value which isn't equal to \"123\""
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_explain_match_success() -> Result<()> {
|
||||
let actual: std::result::Result<i32, i32> = Err(1);
|
||||
let matcher = err(eq(1));
|
||||
verify_that!(
|
||||
matcher.explain_match(actual),
|
||||
displays_as(eq("which is an error\n which is equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_explain_match_fail() -> Result<()> {
|
||||
let actual: std::result::Result<i32, i32> = Err(2);
|
||||
let matcher = err(eq(1));
|
||||
verify_that!(
|
||||
matcher.explain_match(actual),
|
||||
displays_as(eq("which is an error\n which isn't equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_explain_match_ok() -> Result<()> {
|
||||
let actual: std::result::Result<i32, i32> = Ok(1);
|
||||
let matcher = err(eq(1));
|
||||
verify_that!(matcher.explain_match(actual), displays_as(eq("which is a success")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_explain_match_success_by_ref() -> Result<()> {
|
||||
let actual: std::result::Result<String, String> = Err("123".to_string());
|
||||
let matcher = err(eq("123"));
|
||||
verify_that!(
|
||||
matcher.explain_match(&actual),
|
||||
displays_as(eq("which is an error\n which is equal to \"123\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_explain_match_fail_by_ref() -> Result<()> {
|
||||
let actual: std::result::Result<String, String> = Err("321".to_string());
|
||||
let matcher = err(eq("123"));
|
||||
verify_that!(
|
||||
matcher.explain_match(&actual),
|
||||
displays_as(eq("which is an error\n which isn't equal to \"123\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_explain_match_ok_by_ref() -> Result<()> {
|
||||
let actual: std::result::Result<String, String> = Ok("123".to_string());
|
||||
let matcher = err(eq("123"));
|
||||
verify_that!(matcher.explain_match(&actual), displays_as(eq("which is a success")))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// There are no visible documentation elements in this module; the declarative
|
||||
// macro is documented in the matchers module.
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Matches a structure or enum with a given field which is matched by a given
|
||||
/// matcher.
|
||||
///
|
||||
/// This takes two arguments:
|
||||
///
|
||||
/// * a specification of the field against which to match, and
|
||||
/// * an inner [`Matcher`][crate::matcher::Matcher] to apply to that field.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct IntField {
|
||||
/// int: i32
|
||||
/// }
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(IntField{int: 32}, field!(&IntField.int, eq(32)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Tuple structs are also supported via the index syntax:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct IntField(i32);
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(IntField(32), field!(&IntField.0, eq(32)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Enums are also supported, in which case only the specified variant is
|
||||
/// matched:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// enum MyEnum {
|
||||
/// A(i32),
|
||||
/// B,
|
||||
/// }
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(MyEnum::A(32), field!(&MyEnum::A.0, eq(32)))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(MyEnum::B, field!(&MyEnum::A.0, eq(32)))?; // Fails: wrong enum variant
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// The structure or enum may also be referenced from a separate module:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// mod a_module {
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct AStruct(pub i32);
|
||||
/// }
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(a_module::AStruct(32), field!(&a_module::AStruct.0, eq(32)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// If the inner matcher is `eq(...)`, it can be omitted:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct IntField {
|
||||
/// int: i32
|
||||
/// }
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(IntField{int: 32}, field!(&IntField.int, 32))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Nested structures are *not supported*, however:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct InnerStruct(i32);
|
||||
/// #[derive(Debug)]
|
||||
/// struct OuterStruct {
|
||||
/// inner: InnerStruct,
|
||||
/// }
|
||||
/// # fn should_not_compile() -> Result<()> {
|
||||
/// verify_that!(value, field!(OuterStruct.inner.0, eq(32)))?; // Does not compile
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Specification of the field pattern
|
||||
///
|
||||
/// The specification of the field follow the syntax: `(ref)? (&)?
|
||||
/// $TYPE.$FIELD`.
|
||||
/// The `&` allows to specify whether this matcher matches against an actual of
|
||||
/// type `$TYPE` (`$TYPE`` must implement `Copy`) or a `&$TYPE`.
|
||||
///
|
||||
/// For instance:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct AStruct{a_field: i32};
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(AStruct{a_field: 32}, field!(&AStruct.a_field, eq(32)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug, Clone, Copy)]
|
||||
/// pub struct AStruct{a_field: i32};
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(AStruct{a_field: 32}, field!(AStruct.a_field, eq(32)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The `ref` allows to bind the field value by reference, which is required if
|
||||
/// the field type does not implement `Copy`.
|
||||
///
|
||||
/// For instance:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct AStruct{a_field: i32};
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(AStruct{a_field: 32}, field!(&AStruct.a_field, eq(32)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// If `field!` is qualified by both `&` and `ref`, they can both be omitted.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct AStruct{a_field: String};
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(AStruct{a_field: "32".into()}, field!(&AStruct.a_field, ref eq("32")))?;
|
||||
/// verify_that!(AStruct{a_field: "32".into()}, field!(AStruct.a_field, eq("32")))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// See also the macro [`property`][crate::matchers::property] for an analogous
|
||||
/// mechanism to extract a datum by invoking a method.
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __field {
|
||||
($($t:tt)*) => { $crate::field_internal!($($t)*) }
|
||||
}
|
||||
|
||||
// Internal-only macro created so that the macro definition does not appear in
|
||||
// generated documentation.
|
||||
// We cannot use `path` or `ty` to capture the type as we are terminating the
|
||||
// type with a . (dot).
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! field_internal {
|
||||
// `ref` variant.
|
||||
(
|
||||
// The 3 cases of `$(& $($_amp:literal)?)?` -> `$(& $($_amp)*)*`
|
||||
// 1. outer group captures nothing => expansion produces nothing
|
||||
// 2. outer group captures just `&` => expansion produces `&`
|
||||
// 3. outer group captures `& <literal>` => disallowed by `@assert_empty` subrule invocation
|
||||
//
|
||||
// `$_amp:literal` works only because the following `$t:ident` or `::` can't be captured by
|
||||
// it.
|
||||
$(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`.
|
||||
$(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`.
|
||||
$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt,
|
||||
ref $m:expr) => {{
|
||||
$crate::field_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*);
|
||||
$crate::field_internal!(@internal
|
||||
struct_type: [&_]
|
||||
field_prefix: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::* $(::<$($t_ty_args),*>)*]
|
||||
[$field] [ref] [$m])
|
||||
}};
|
||||
|
||||
// Non-`ref` variant.
|
||||
(
|
||||
// See comment on previous variant above.
|
||||
$(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`.
|
||||
$(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`.
|
||||
$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, $m:expr) => {{
|
||||
$crate::field_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*);
|
||||
$crate::field_internal!(@internal
|
||||
struct_type: [$(& $($_amp)*)* &_]
|
||||
field_prefix: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::* $(::<$($t_ty_args),*>)*]
|
||||
[$field] [] [$m])
|
||||
}};
|
||||
|
||||
(@assert_empty) => {};
|
||||
(@assert_empty $($l:literal)+) => {
|
||||
compile_error!("property! argument must start with an optional `&` followed by a path")
|
||||
};
|
||||
|
||||
(@internal struct_type: [$struct_ty:ty]
|
||||
field_prefix: [$($field_prefix:tt)*]
|
||||
[$field:tt] [$($ref:tt)?] [$m:expr]) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::field_matcher(
|
||||
|o: $struct_ty| {
|
||||
match o {
|
||||
$($field_prefix)* {$field: $($ref)* value, .. } => Some(value),
|
||||
// The pattern below is unreachable if the type is a struct (as opposed to an
|
||||
// enum). Since the macro can't know which it is, we always include it and just
|
||||
// tell the compiler not to complain.
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
&stringify!($field),
|
||||
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m))
|
||||
}}
|
||||
}
|
||||
|
||||
/// Functions for use only by the declarative macros in this module.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub mod internal {
|
||||
use crate::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Creates a matcher to verify a specific field of the actual struct using
|
||||
/// the provided inner matcher.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub fn field_matcher<OuterT, InnerT, InnerMatcher>(
|
||||
field_accessor: fn(&OuterT) -> Option<&InnerT>,
|
||||
field_path: &'static str,
|
||||
inner: InnerMatcher,
|
||||
) -> FieldMatcher<OuterT, InnerT, InnerMatcher> {
|
||||
FieldMatcher { field_accessor, field_path, inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct FieldMatcher<OuterT, InnerT, InnerMatcher> {
|
||||
field_accessor: fn(&OuterT) -> Option<&InnerT>,
|
||||
field_path: &'static str,
|
||||
inner: InnerMatcher,
|
||||
}
|
||||
|
||||
impl<'a, OuterT: Debug + 'a, InnerT: Debug + 'a, InnerMatcher: Matcher<&'a InnerT>>
|
||||
Matcher<&'a OuterT> for FieldMatcher<OuterT, InnerT, InnerMatcher>
|
||||
{
|
||||
fn matches(&self, actual: &'a OuterT) -> MatcherResult {
|
||||
if let Some(value) = (self.field_accessor)(actual) {
|
||||
self.inner.matches(value)
|
||||
} else {
|
||||
MatcherResult::NoMatch
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a OuterT) -> Description {
|
||||
if let Some(actual) = (self.field_accessor)(actual) {
|
||||
format!(
|
||||
"which has field `{}`, {}",
|
||||
self.field_path,
|
||||
self.inner.explain_match(actual)
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
let formatted_actual_value = format!("{actual:?}");
|
||||
let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
|
||||
let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
|
||||
format!("which has the wrong enum variant `{without_fields}`").into()
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
format!(
|
||||
"has field `{}`, which {}",
|
||||
self.field_path,
|
||||
self.inner.describe(matcher_result)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<OuterT: Debug + Copy, InnerT: Debug + Copy, InnerMatcher: Matcher<InnerT>> Matcher<OuterT>
|
||||
for FieldMatcher<OuterT, InnerT, InnerMatcher>
|
||||
{
|
||||
fn matches(&self, actual: OuterT) -> MatcherResult {
|
||||
if let Some(value) = (self.field_accessor)(&actual) {
|
||||
self.inner.matches(*value)
|
||||
} else {
|
||||
MatcherResult::NoMatch
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: OuterT) -> Description {
|
||||
if let Some(actual) = (self.field_accessor)(&actual) {
|
||||
format!(
|
||||
"which has field `{}`, {}",
|
||||
self.field_path,
|
||||
self.inner.explain_match(*actual)
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
let formatted_actual_value = format!("{actual:?}");
|
||||
let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
|
||||
let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
|
||||
format!("which has the wrong enum variant `{without_fields}`").into()
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
format!(
|
||||
"has field `{}`, which {}",
|
||||
self.field_path,
|
||||
self.inner.describe(matcher_result)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a value greater than or equal to (in the sense of `>=`) `expected`.
|
||||
///
|
||||
/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
|
||||
/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
|
||||
/// `PartialOrd<ExpectedT>`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(1, ge(0))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(0, ge(1))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// In most cases the params neeed to be the same type or they need to be cast
|
||||
/// explicitly. This can be surprising when comparing integer types or
|
||||
/// references:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_not_compile() -> Result<()> {
|
||||
/// verify_that!(123u32, ge(0u64))?; // Does not compile
|
||||
/// verify_that!(123u32 as u64, ge(0u64))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_not_compile() -> Result<()> {
|
||||
/// let actual: &u32 = &2;
|
||||
/// let expected: u32 = 0;
|
||||
/// verify_that!(actual, ge(expected))?; // Does not compile
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let actual: &u32 = &2;
|
||||
/// let expected: u32 = 0;
|
||||
/// verify_that!(actual, ge(&expected))?; // Compiles and passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// You can find the standard library `PartialOrd` implementation in
|
||||
/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
|
||||
pub fn ge<ExpectedT>(expected: ExpectedT) -> GeMatcher<ExpectedT> {
|
||||
GeMatcher { expected }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct GeMatcher<ExpectedT> {
|
||||
expected: ExpectedT,
|
||||
}
|
||||
|
||||
impl<ActualT: Debug + PartialOrd<ExpectedT> + Copy, ExpectedT: Debug> Matcher<ActualT>
|
||||
for GeMatcher<ExpectedT>
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
(actual >= self.expected).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("is greater than or equal to {:?}", self.expected).into()
|
||||
}
|
||||
MatcherResult::NoMatch => format!("is less than {:?}", self.expected).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[test]
|
||||
fn ge_matches_i32_with_i32() -> Result<()> {
|
||||
let actual: i32 = 0;
|
||||
let expected: i32 = 0;
|
||||
verify_that!(actual, ge(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ge_does_not_match_smaller_i32() -> Result<()> {
|
||||
let matcher = ge(10);
|
||||
let result = matcher.matches(9);
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ge_matches_bigger_str() -> Result<()> {
|
||||
verify_that!("B", ge("A"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ge_does_not_match_lesser_str() -> Result<()> {
|
||||
let matcher = ge("z");
|
||||
let result = matcher.matches("a");
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ge_mismatch_contains_actual_and_expected() -> Result<()> {
|
||||
let result = verify_that!(591, ge(927));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 591
|
||||
Expected: is greater than or equal to 927
|
||||
Actual: 591,
|
||||
which is less than 927
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
// Test `ge` matcher where actual is `&OsString` and expected is `&str`.
|
||||
// Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
|
||||
// `OsString` and `str`, but only in one direction: it's only possible to
|
||||
// compare `OsString` with `str` if `OsString` is on the left side of the
|
||||
// ">=" operator (`impl PartialOrd<str> for OsString`).
|
||||
//
|
||||
// The comparison in the other direction is not defined.
|
||||
//
|
||||
// This means that the test case bellow effectively ensures that
|
||||
// `verify_that(actual, ge(expected))` works if `actual >= expected` works
|
||||
// (regardless whether the `expected >= actual` works`).
|
||||
#[test]
|
||||
fn ge_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
|
||||
let expected = "A";
|
||||
let actual: OsString = "B".to_string().into();
|
||||
verify_that!(&actual, ge(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ge_matches_ipv6addr_with_ipaddr() -> Result<()> {
|
||||
use std::net::IpAddr;
|
||||
use std::net::Ipv6Addr;
|
||||
let actual: Ipv6Addr = "2001:4860:4860::8844".parse().unwrap();
|
||||
let expected: IpAddr = "127.0.0.1".parse().unwrap();
|
||||
verify_that!(actual, ge(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ge_matches_with_custom_partial_ord() -> Result<()> {
|
||||
/// A custom "number" that is lower than all other numbers. The only
|
||||
/// things we define about this "special" number is `PartialOrd` and
|
||||
/// `PartialEq` against `u32`.
|
||||
#[derive(Debug)]
|
||||
struct VeryLowNumber {}
|
||||
|
||||
impl std::cmp::PartialEq<u32> for VeryLowNumber {
|
||||
fn eq(&self, _other: &u32) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// PartialOrd (required for >) requires PartialEq.
|
||||
impl std::cmp::PartialOrd<u32> for VeryLowNumber {
|
||||
fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
|
||||
Some(std::cmp::Ordering::Less)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq<VeryLowNumber> for u32 {
|
||||
fn eq(&self, _other: &VeryLowNumber) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
|
||||
fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
|
||||
Some(std::cmp::Ordering::Greater)
|
||||
}
|
||||
}
|
||||
|
||||
let actual: u32 = 42;
|
||||
let expected = VeryLowNumber {};
|
||||
|
||||
verify_that!(actual, ge(expected))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a value greater (in the sense of `>`) than `expected`.
|
||||
///
|
||||
/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
|
||||
/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
|
||||
/// `PartialOrd<ExpectedT>`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(38, gt(1))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(234, gt(234))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// In most cases the params neeed to be the same type or they need to be cast
|
||||
/// explicitly. This can be surprising when comparing integer types or
|
||||
/// references:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_not_compile() -> Result<()> {
|
||||
/// verify_that!(123u32, gt(0u64))?; // Does not compile
|
||||
/// verify_that!(123u32 as u64, gt(0u64))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_not_compile() -> Result<()> {
|
||||
/// let actual: &u32 = &2;
|
||||
/// let expected: u32 = 1;
|
||||
/// verify_that!(actual, gt(expected))?; // Does not compile
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let actual: &u32 = &2;
|
||||
/// let expected: u32 = 1;
|
||||
/// verify_that!(actual, gt(&expected))?; // Compiles and passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// You can find the standard library `PartialOrd` implementation in
|
||||
/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
|
||||
pub fn gt<ExpectedT: Debug>(expected: ExpectedT) -> GtMatcher<ExpectedT> {
|
||||
GtMatcher { expected }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct GtMatcher<ExpectedT> {
|
||||
expected: ExpectedT,
|
||||
}
|
||||
|
||||
impl<ActualT: Debug + PartialOrd<ExpectedT> + Copy, ExpectedT: Debug> Matcher<ActualT>
|
||||
for GtMatcher<ExpectedT>
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
(actual > self.expected).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!("is greater than {:?}", self.expected).into(),
|
||||
MatcherResult::NoMatch => {
|
||||
format!("is less than or equal to {:?}", self.expected).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[test]
|
||||
fn gt_matches_i32_with_i32() -> Result<()> {
|
||||
let actual: i32 = 321;
|
||||
let expected: i32 = 123;
|
||||
verify_that!(actual, gt(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_does_not_match_equal_i32() -> Result<()> {
|
||||
let matcher = gt(10);
|
||||
let result = matcher.matches(10);
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_does_not_match_lower_i32() -> Result<()> {
|
||||
let matcher = gt(-50);
|
||||
let result = matcher.matches(-51);
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_matches_greater_str() -> Result<()> {
|
||||
verify_that!("B", gt("A"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_does_not_match_lesser_str() -> Result<()> {
|
||||
let matcher = gt("B");
|
||||
let result = matcher.matches("A");
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_mismatch_contains_actual_and_expected() -> Result<()> {
|
||||
let result = verify_that!(481, gt(632));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 481
|
||||
Expected: is greater than 632
|
||||
Actual: 481,
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_mismatch_combined_with_each() -> Result<()> {
|
||||
let result = verify_that!(vec![19, 23, 11], each(gt(&15)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![19, 23, 11]
|
||||
Expected: only contains elements that is greater than 15
|
||||
Actual: [19, 23, 11],
|
||||
whose element #2 is 11, which is less than or equal to 15
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_describe_matches() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<i32>::describe(>(232), MatcherResult::Match),
|
||||
displays_as(eq("is greater than 232"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_describe_does_not_match() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<i32>::describe(>(232), MatcherResult::NoMatch),
|
||||
displays_as(eq("is less than or equal to 232"))
|
||||
)
|
||||
}
|
||||
|
||||
// Test `gt` matcher where actual is `&OsString` and expected is `&str`.
|
||||
// Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
|
||||
// `OsString` and `str`, but only in one direction: it's only possible to
|
||||
// compare `OsString` with `str` if `OsString` is on the left side of the
|
||||
// ">" operator (`impl PartialOrd<str> for OsString`).
|
||||
//
|
||||
// The comparison in the other direction is not defined.
|
||||
//
|
||||
// This means that the test case bellow effectively ensures that
|
||||
// `verify_that(actual, gt(expected))` works if `actual > expected` works
|
||||
// (regardless whether the `expected > actual` works`).
|
||||
#[test]
|
||||
fn gt_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
|
||||
let expected = "A";
|
||||
let actual: OsString = "B".to_string().into();
|
||||
verify_that!(&actual, gt(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_matches_ipv6addr_with_ipaddr() -> Result<()> {
|
||||
use std::net::IpAddr;
|
||||
use std::net::Ipv6Addr;
|
||||
let actual: Ipv6Addr = "2001:4860:4860::8888".parse().unwrap();
|
||||
let expected: IpAddr = "127.0.0.1".parse().unwrap();
|
||||
verify_that!(actual, gt(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gt_matches_with_custom_partial_ord() -> Result<()> {
|
||||
/// A custom "number" that is smaller than all other numbers. The only
|
||||
/// things we define about this "special" number is `PartialOrd` and
|
||||
/// `PartialEq` against `u32`.
|
||||
#[derive(Debug)]
|
||||
struct VeryLowNumber {}
|
||||
|
||||
impl std::cmp::PartialEq<u32> for VeryLowNumber {
|
||||
fn eq(&self, _other: &u32) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// PartialOrd (required for >) requires PartialEq.
|
||||
impl std::cmp::PartialOrd<u32> for VeryLowNumber {
|
||||
fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
|
||||
Some(std::cmp::Ordering::Less)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq<VeryLowNumber> for u32 {
|
||||
fn eq(&self, _other: &VeryLowNumber) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
|
||||
fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
|
||||
Some(std::cmp::Ordering::Greater)
|
||||
}
|
||||
}
|
||||
|
||||
let actual: u32 = 42;
|
||||
let expected = VeryLowNumber {};
|
||||
|
||||
verify_that!(actual, gt(expected))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Matches a `&HashMap` containing the given `key` whose value is matched by
|
||||
/// the matcher `inner`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use std::collections::HashMap;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let value = HashMap::from([(0, 1), (1, -1)]);
|
||||
/// verify_that!(value, has_entry(0, eq(&1)))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// # let value = HashMap::from([(0, 1), (1, -1)]);
|
||||
/// verify_that!(value, has_entry(1, gt(&0)))?; // Fails: value not matched
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// # let value = HashMap::from([(0, 1), (1, -1)]);
|
||||
/// verify_that!(value, has_entry(2, eq(&0)))?; // Fails: key not present
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// Note: One could obtain the same effect by using `contains` and a
|
||||
/// `Matcher<(&Key, &Value)>`:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use std::collections::HashMap;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let value = HashMap::from([(0, 1), (1, -1)]);
|
||||
/// verify_that!(value, contains(eq((&0, &1))))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
pub fn has_entry<KeyT, MatcherT>(key: KeyT, inner: MatcherT) -> HasEntryMatcher<KeyT, MatcherT> {
|
||||
HasEntryMatcher { key, inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct HasEntryMatcher<KeyT, MatcherT> {
|
||||
key: KeyT,
|
||||
inner: MatcherT,
|
||||
}
|
||||
|
||||
impl<'a, KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<&'a ValueT>>
|
||||
Matcher<&'a HashMap<KeyT, ValueT>> for HasEntryMatcher<KeyT, MatcherT>
|
||||
{
|
||||
fn matches(&self, actual: &'a HashMap<KeyT, ValueT>) -> MatcherResult {
|
||||
if let Some(value) = actual.get(&self.key) {
|
||||
self.inner.matches(value)
|
||||
} else {
|
||||
MatcherResult::NoMatch
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a HashMap<KeyT, ValueT>) -> Description {
|
||||
if let Some(value) = actual.get(&self.key) {
|
||||
format!(
|
||||
"which contains key {:?}, but is mapped to value {:#?}, {}",
|
||||
self.key,
|
||||
value,
|
||||
self.inner.explain_match(value)
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
format!("which doesn't contain key {:?}", self.key).into()
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!(
|
||||
"contains key {:?}, which value {}",
|
||||
self.key,
|
||||
self.inner.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
MatcherResult::NoMatch => format!(
|
||||
"doesn't contain key {:?} or contains key {:?}, which value {}",
|
||||
self.key,
|
||||
self.key,
|
||||
self.inner.describe(MatcherResult::NoMatch)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn has_entry_does_not_match_empty_hash_map() -> Result<()> {
|
||||
let value: HashMap<i32, i32> = HashMap::new();
|
||||
verify_that!(&value, not(has_entry(0, eq(&0))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_entry_matches_hash_map_with_value() -> Result<()> {
|
||||
let value: HashMap<i32, i32> = HashMap::from([(0, 0)]);
|
||||
verify_that!(&value, has_entry(0, eq(&0)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_entry_does_not_match_hash_map_with_wrong_value() -> Result<()> {
|
||||
let value: HashMap<i32, i32> = HashMap::from([(0, 1)]);
|
||||
verify_that!(&value, not(has_entry(0, eq(&0))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_entry_does_not_match_hash_map_with_wrong_key() -> Result<()> {
|
||||
let value: HashMap<i32, i32> = HashMap::from([(1, 0)]);
|
||||
verify_that!(&value, not(has_entry(0, eq(&0))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_entry_shows_correct_message_when_key_is_not_present() -> Result<()> {
|
||||
let result = verify_that!(HashMap::from([(0, 0)]), has_entry(1, eq(&0)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: HashMap::from([(0, 0)])
|
||||
Expected: contains key 1, which value is equal to 0
|
||||
Actual: {0: 0},
|
||||
which doesn't contain key 1
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_entry_shows_correct_message_when_key_has_non_matching_value() -> Result<()> {
|
||||
let result = verify_that!(HashMap::from([(0, 0)]), has_entry(0, eq(&1)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: HashMap::from([(0, 0)])
|
||||
Expected: contains key 0, which value is equal to 1
|
||||
Actual: {0: 0},
|
||||
which contains key 0, but is mapped to value 0, which isn't equal to 1
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
// 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a byte sequence which is a UTF-8 encoded string matched by `inner`.
|
||||
///
|
||||
/// The matcher reports no match if either the string is not UTF-8 encoded or if
|
||||
/// `inner` does not match on the decoded string.
|
||||
///
|
||||
/// The input may be a slice `&[u8]` or a `Vec` of bytes.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let bytes: &[u8] = "A string".as_bytes();
|
||||
/// verify_that!(bytes, is_utf8_string(eq("A string")))?; // Passes
|
||||
/// let bytes: Vec<u8> = "A string".as_bytes().to_vec();
|
||||
/// verify_that!(bytes, is_utf8_string(eq("A string")))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// # let bytes: &[u8] = "A string".as_bytes();
|
||||
/// verify_that!(bytes, is_utf8_string(eq("Another string")))?; // Fails (inner matcher does not match)
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// let bytes: Vec<u8> = vec![255, 64, 128, 32];
|
||||
/// verify_that!(bytes, is_utf8_string(anything()))?; // Fails (not UTF-8 encoded)
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// ```
|
||||
pub fn is_utf8_string<InnerMatcherT>(inner: InnerMatcherT) -> IsEncodedStringMatcher<InnerMatcherT>
|
||||
where
|
||||
InnerMatcherT: for<'a> Matcher<&'a str>,
|
||||
{
|
||||
IsEncodedStringMatcher { inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct IsEncodedStringMatcher<InnerMatcherT> {
|
||||
inner: InnerMatcherT,
|
||||
}
|
||||
|
||||
impl<ActualT: AsRef<[u8]> + Debug + Copy, InnerMatcherT> Matcher<ActualT>
|
||||
for IsEncodedStringMatcher<InnerMatcherT>
|
||||
where
|
||||
InnerMatcherT: for<'a> Matcher<&'a str>,
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
std::str::from_utf8(actual.as_ref())
|
||||
.map(|s| self.inner.matches(s))
|
||||
.unwrap_or(MatcherResult::NoMatch)
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!(
|
||||
"is a UTF-8 encoded string which {}",
|
||||
self.inner.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
MatcherResult::NoMatch => format!(
|
||||
"is not a UTF-8 encoded string which {}",
|
||||
self.inner.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ActualT) -> Description {
|
||||
match std::str::from_utf8(actual.as_ref()) {
|
||||
Ok(s) => {
|
||||
format!("which is a UTF-8 encoded string {}", self.inner.explain_match(s)).into()
|
||||
}
|
||||
Err(e) => format!("which is not a UTF-8 encoded string: {e}").into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn matches_string_as_byte_slice() -> Result<()> {
|
||||
verify_that!("A string".as_bytes(), is_utf8_string(eq("A string")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_string_as_byte_vec() -> Result<()> {
|
||||
verify_that!("A string".as_bytes().to_vec(), is_utf8_string(eq("A string")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_string_with_utf_8_encoded_sequences() -> Result<()> {
|
||||
verify_that!("äöüÄÖÜ".as_bytes().to_vec(), is_utf8_string(eq("äöüÄÖÜ")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_non_equal_string() -> Result<()> {
|
||||
verify_that!("äöüÄÖÜ".as_bytes().to_vec(), not(is_utf8_string(eq("A string"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_non_utf_8_encoded_byte_sequence() -> Result<()> {
|
||||
verify_that!(&[192, 64, 255, 32], not(is_utf8_string(eq("A string"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_correct_description_in_matched_case() -> Result<()> {
|
||||
let matcher = is_utf8_string(eq("A string"));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&[u8]>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("is a UTF-8 encoded string which is equal to \"A string\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_correct_description_in_not_matched_case() -> Result<()> {
|
||||
let matcher = is_utf8_string(eq("A string"));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&[u8]>::describe(&matcher, MatcherResult::NoMatch),
|
||||
displays_as(eq("is not a UTF-8 encoded string which is equal to \"A string\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_correct_explanation_in_matched_case() -> Result<()> {
|
||||
let explanation = is_utf8_string(eq("A string")).explain_match("A string".as_bytes());
|
||||
|
||||
verify_that!(
|
||||
explanation,
|
||||
displays_as(eq("which is a UTF-8 encoded string which is equal to \"A string\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_correct_explanation_when_byte_array_is_not_utf8_encoded() -> Result<()> {
|
||||
let explanation = is_utf8_string(eq("A string")).explain_match([192, 128, 0, 64]);
|
||||
|
||||
verify_that!(explanation, displays_as(starts_with("which is not a UTF-8 encoded string: ")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_correct_explanation_when_inner_matcher_does_not_match() -> Result<()> {
|
||||
let explanation = is_utf8_string(eq("A string")).explain_match("Another string".as_bytes());
|
||||
|
||||
verify_that!(
|
||||
explanation,
|
||||
displays_as(eq("which is a UTF-8 encoded string which isn't equal to \"A string\""))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use num_traits::float::Float;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a floating point value which is Finite.
|
||||
pub fn is_finite() -> IsFiniteMatcher {
|
||||
IsFiniteMatcher
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct IsFiniteMatcher;
|
||||
|
||||
impl<T: Float + Debug + Copy> Matcher<T> for IsFiniteMatcher {
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
actual.is_finite().into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
if matcher_result.into() { "is Finite" } else { "isn't Finite" }.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn matches_f32_number() -> Result<()> {
|
||||
verify_that!(0.0f32, is_finite())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f32_pos_infinity() -> Result<()> {
|
||||
verify_that!(f32::INFINITY, not(is_finite()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f32_neg_infinity() -> Result<()> {
|
||||
verify_that!(f32::NEG_INFINITY, not(is_finite()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f32_nan() -> Result<()> {
|
||||
verify_that!(f32::NAN, not(is_finite()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_f64_number() -> Result<()> {
|
||||
verify_that!(0.0f64, is_finite())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f64_pos_infinity() -> Result<()> {
|
||||
verify_that!(f64::INFINITY, not(is_finite()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f64_neg_infinity() -> Result<()> {
|
||||
verify_that!(f64::NEG_INFINITY, not(is_finite()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f64_nan() -> Result<()> {
|
||||
verify_that!(f64::NAN, not(is_finite()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use num_traits::float::Float;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a floating point value which is Infinite.
|
||||
pub fn is_infinite() -> IsInfiniteMatcher {
|
||||
IsInfiniteMatcher
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct IsInfiniteMatcher;
|
||||
|
||||
impl<T: Float + Debug + Copy> Matcher<T> for IsInfiniteMatcher {
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
actual.is_infinite().into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
if matcher_result.into() { "is Infinite" } else { "isn't Infinite" }.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn matches_f32_pos_infinity() -> Result<()> {
|
||||
verify_that!(f32::INFINITY, is_infinite())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_f32_neg_infinity() -> Result<()> {
|
||||
verify_that!(f32::NEG_INFINITY, is_infinite())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f32_number() -> Result<()> {
|
||||
verify_that!(0.0f32, not(is_infinite()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f32_nan() -> Result<()> {
|
||||
verify_that!(f32::NAN, not(is_finite()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_f64_pos_infinity() -> Result<()> {
|
||||
verify_that!(f64::INFINITY, is_infinite())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_f64_neg_infinity() -> Result<()> {
|
||||
verify_that!(f64::NEG_INFINITY, is_infinite())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f64_number() -> Result<()> {
|
||||
verify_that!(0.0f64, not(is_infinite()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f64_nan() -> Result<()> {
|
||||
verify_that!(f64::NAN, not(is_finite()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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.
|
||||
|
||||
#![doc(hidden)]
|
||||
|
||||
use crate::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches precisely values matched by `inner`.
|
||||
///
|
||||
/// The returned matcher produces a description prefixed by the string
|
||||
/// `description`. This is useful in contexts where the test assertion failure
|
||||
/// output must include the additional description.
|
||||
pub fn is<InnerMatcherT>(description: &str, inner: InnerMatcherT) -> IsMatcher<'_, InnerMatcherT> {
|
||||
IsMatcher { description, inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct IsMatcher<'a, InnerMatcherT> {
|
||||
description: &'a str,
|
||||
inner: InnerMatcherT,
|
||||
}
|
||||
|
||||
impl<ActualT: Debug + Copy, InnerMatcherT: Matcher<ActualT>> Matcher<ActualT>
|
||||
for IsMatcher<'_, InnerMatcherT>
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
self.inner.matches(actual)
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!(
|
||||
"is {} which {}",
|
||||
self.description,
|
||||
self.inner.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
MatcherResult::NoMatch => format!(
|
||||
"is not {} which {}",
|
||||
self.description,
|
||||
self.inner.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ActualT) -> Description {
|
||||
self.inner.explain_match(actual)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use num_traits::float::Float;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a floating point value which is NaN.
|
||||
pub fn is_nan() -> IsNanMatcher {
|
||||
IsNanMatcher
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct IsNanMatcher;
|
||||
|
||||
impl<T: Float + Debug + Copy> Matcher<T> for IsNanMatcher {
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
actual.is_nan().into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
if matcher_result.into() { "is NaN" } else { "isn't NaN" }.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn matches_f32_nan() -> Result<()> {
|
||||
verify_that!(f32::NAN, is_nan())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f32_number() -> Result<()> {
|
||||
verify_that!(0.0f32, not(is_nan()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_f64_nan() -> Result<()> {
|
||||
verify_that!(f64::NAN, is_nan())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_f64_number() -> Result<()> {
|
||||
verify_that!(0.0f64, not(is_nan()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a value less than or equal to (in the sense of `<=`) `expected`.
|
||||
///
|
||||
/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
|
||||
/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
|
||||
/// `PartialOrd<ExpectedT>`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(0, le(0))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(1, le(0))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// In most cases the params neeed to be the same type or they need to be cast
|
||||
/// explicitly. This can be surprising when comparing integer types or
|
||||
/// references:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_not_compile() -> Result<()> {
|
||||
/// verify_that!(1u32, le(2u64))?; // Does not compile
|
||||
/// verify_that!(1u32 as u64, le(2u64))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_not_compile() -> Result<()> {
|
||||
/// let actual: &u32 = &1;
|
||||
/// let expected: u32 = 2;
|
||||
/// verify_that!(actual, le(expected))?; // Does not compile
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let actual: &u32 = &1;
|
||||
/// let expected: u32 = 2;
|
||||
/// verify_that!(actual, le(&expected))?; // Compiles and passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// You can find the standard library `PartialOrd` implementation in
|
||||
/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
|
||||
pub fn le<ExpectedT>(expected: ExpectedT) -> LeMatcher<ExpectedT> {
|
||||
LeMatcher { expected }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct LeMatcher<ExpectedT> {
|
||||
expected: ExpectedT,
|
||||
}
|
||||
|
||||
impl<ActualT: Debug + PartialOrd<ExpectedT> + Copy, ExpectedT: Debug> Matcher<ActualT>
|
||||
for LeMatcher<ExpectedT>
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
(actual <= self.expected).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!("is less than or equal to {:?}", self.expected).into(),
|
||||
MatcherResult::NoMatch => format!("is greater than {:?}", self.expected).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[test]
|
||||
fn le_matches_i32_with_i32() -> Result<()> {
|
||||
let actual: i32 = 0;
|
||||
let expected: i32 = 0;
|
||||
verify_that!(actual, le(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn le_does_not_match_bigger_i32() -> Result<()> {
|
||||
let matcher = le(0);
|
||||
let result = matcher.matches(1);
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn le_matches_smaller_str() -> Result<()> {
|
||||
verify_that!("A", le("B"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn le_does_not_match_bigger_str() -> Result<()> {
|
||||
let matcher = le("a");
|
||||
let result = matcher.matches("z");
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn le_mismatch_contains_actual_and_expected() -> Result<()> {
|
||||
let result = verify_that!(489, le(294));
|
||||
let formatted_message = format!("{}", result.unwrap_err());
|
||||
|
||||
verify_that!(
|
||||
formatted_message.as_str(),
|
||||
contains_substring(indoc!(
|
||||
"
|
||||
Value of: 489
|
||||
Expected: is less than or equal to 294
|
||||
Actual: 489,
|
||||
which is greater than 294
|
||||
"
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
// Test `le` matcher where actual is `&OsString` and expected is `&str`.
|
||||
// Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
|
||||
// `OsString` and `str`, but only in one direction: it's only possible to
|
||||
// compare `OsString` with `str` if `OsString` is on the left side of the
|
||||
// "<=" operator (`impl PartialOrd<str> for OsString`).
|
||||
//
|
||||
// The comparison in the other direction is not defined.
|
||||
//
|
||||
// This means that the test case bellow effectively ensures that
|
||||
// `verify_that(actual, le(expected))` works if `actual <= expected` works
|
||||
// (regardless whether the `expected <= actual` works`).
|
||||
#[test]
|
||||
fn le_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
|
||||
let expected = "B";
|
||||
let actual: OsString = "A".into();
|
||||
verify_that!(&actual, le(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn le_matches_ipv6addr_with_ipaddr() -> Result<()> {
|
||||
use std::net::IpAddr;
|
||||
use std::net::Ipv6Addr;
|
||||
let actual: IpAddr = "127.0.0.1".parse().unwrap();
|
||||
let expected: Ipv6Addr = "2001:4860:4860::8844".parse().unwrap();
|
||||
verify_that!(actual, le(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn le_matches_with_custom_partial_ord() -> Result<()> {
|
||||
/// A custom "number" that is lower than all other numbers. The only
|
||||
/// things we define about this "special" number is `PartialOrd` and
|
||||
/// `PartialEq` against `u32`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct VeryLowNumber {}
|
||||
|
||||
impl std::cmp::PartialEq<u32> for VeryLowNumber {
|
||||
fn eq(&self, _other: &u32) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// PartialOrd (required for >) requires PartialEq.
|
||||
impl std::cmp::PartialOrd<u32> for VeryLowNumber {
|
||||
fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
|
||||
Some(std::cmp::Ordering::Less)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq<VeryLowNumber> for u32 {
|
||||
fn eq(&self, _other: &VeryLowNumber) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
|
||||
fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
|
||||
Some(std::cmp::Ordering::Greater)
|
||||
}
|
||||
}
|
||||
|
||||
let actual = VeryLowNumber {};
|
||||
let expected: u32 = 42;
|
||||
|
||||
verify_that!(actual, le(expected))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use crate::matcher_support::count_elements::count_elements;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a container whose number of elements matches `expected`.
|
||||
///
|
||||
/// This matches against a container over which one can iterate. This includes
|
||||
/// the standard Rust containers, arrays, and slices. More
|
||||
/// precisely, the actual type must implement [`IntoIterator`].
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let array = [1,2,3];
|
||||
/// verify_that!(array, len(eq(3)))?;
|
||||
/// let vec = vec![1,2,3];
|
||||
/// verify_that!(vec, len(eq(3)))?;
|
||||
/// let slice = vec.as_slice();
|
||||
/// verify_that!(*slice, len(eq(3)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The parameter `expected` can be any integer numeric matcher.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let vec = vec![1,2,3];
|
||||
/// verify_that!(vec, len(gt(1)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
pub fn len<E>(expected: E) -> LenMatcher<E> {
|
||||
LenMatcher { expected }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct LenMatcher<E> {
|
||||
expected: E,
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, E: Matcher<usize>> Matcher<T> for LenMatcher<E>
|
||||
where
|
||||
T: IntoIterator,
|
||||
{
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
self.expected.matches(count_elements(actual))
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("has length, which {}", self.expected.describe(MatcherResult::Match)).into()
|
||||
}
|
||||
MatcherResult::NoMatch => {
|
||||
format!("has length, which {}", self.expected.describe(MatcherResult::NoMatch))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: T) -> Description {
|
||||
let actual_size = count_elements(actual);
|
||||
format!("which has length {}, {}", actual_size, self.expected.explain_match(actual_size))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::description::Description;
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::collections::{
|
||||
BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_vec() -> Result<()> {
|
||||
let value = vec![1, 2, 3];
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_array_reference() -> Result<()> {
|
||||
let value = &[1, 2, 3];
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_slice_of_array() -> Result<()> {
|
||||
let value = &[1, 2, 3];
|
||||
verify_that!(value[0..1], len(eq(1)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_slice_of_vec() -> Result<()> {
|
||||
let value = vec![1, 2, 3];
|
||||
let slice = value.as_slice();
|
||||
verify_that!(slice, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_sized_slice() -> Result<()> {
|
||||
let value = [1, 2, 3];
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_btreemap() -> Result<()> {
|
||||
let value = BTreeMap::from([(1, 2), (2, 3), (3, 4)]);
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_btreeset() -> Result<()> {
|
||||
let value = BTreeSet::from([1, 2, 3]);
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_binaryheap() -> Result<()> {
|
||||
let value = BinaryHeap::from([1, 2, 3]);
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_hashmap() -> Result<()> {
|
||||
let value = HashMap::from([(1, 2), (2, 3), (3, 4)]);
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_hashset() -> Result<()> {
|
||||
let value = HashSet::from([1, 2, 3]);
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_linkedlist() -> Result<()> {
|
||||
let value = LinkedList::from([1, 2, 3]);
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_match_vecdeque() -> Result<()> {
|
||||
let value = VecDeque::from([1, 2, 3]);
|
||||
verify_that!(value, len(eq(3)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_explain_match() -> Result<()> {
|
||||
#[derive(MatcherBase)]
|
||||
struct TestMatcher;
|
||||
impl<T: Debug + Copy> Matcher<T> for TestMatcher {
|
||||
fn matches(&self, _: T) -> MatcherResult {
|
||||
false.into()
|
||||
}
|
||||
|
||||
fn describe(&self, _: MatcherResult) -> Description {
|
||||
"called described".into()
|
||||
}
|
||||
|
||||
fn explain_match(&self, _: T) -> Description {
|
||||
"called explain_match".into()
|
||||
}
|
||||
}
|
||||
verify_that!(
|
||||
len(TestMatcher).explain_match([1, 2, 3]),
|
||||
displays_as(eq("which has length 3, called explain_match"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_matcher_error_message() -> Result<()> {
|
||||
let result = verify_that!(vec![1, 2, 3, 4], len(eq(3)));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![1, 2, 3, 4]
|
||||
Expected: has length, which is equal to 3
|
||||
Actual: [1, 2, 3, 4],
|
||||
which has length 4, which isn't equal to 3"
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a value less (in the sense of `<`) than `expected`.
|
||||
///
|
||||
/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
|
||||
/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
|
||||
/// `PartialOrd<ExpectedT>`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(1, lt(2))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(2, lt(2))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// In most cases the params neeed to be the same type or they need to be cast
|
||||
/// explicitly. This can be surprising when comparing integer types or
|
||||
/// references:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_not_compile() -> Result<()> {
|
||||
/// verify_that!(123u32, lt(0u64))?; // Does not compile
|
||||
/// verify_that!(123u32 as u64, lt(100000000u64))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_not_compile() -> Result<()> {
|
||||
/// let actual: &u32 = &2;
|
||||
/// let expected: u32 = 70;
|
||||
/// verify_that!(actual, lt(expected))?; // Does not compile
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let actual: &u32 = &2;
|
||||
/// let expected: u32 = 70;
|
||||
/// verify_that!(actual, lt(&expected))?; // Compiles and passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// You can find the standard library `PartialOrd` implementation in
|
||||
/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
|
||||
pub fn lt<ExpectedT>(expected: ExpectedT) -> LtMatcher<ExpectedT> {
|
||||
LtMatcher { expected }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct LtMatcher<ExpectedT> {
|
||||
expected: ExpectedT,
|
||||
}
|
||||
|
||||
impl<ActualT: Debug + PartialOrd<ExpectedT> + Copy, ExpectedT: Debug> Matcher<ActualT>
|
||||
for LtMatcher<ExpectedT>
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
(actual < self.expected).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!("is less than {:?}", self.expected).into(),
|
||||
MatcherResult::NoMatch => {
|
||||
format!("is greater than or equal to {:?}", self.expected).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[test]
|
||||
fn lt_matches_i32_with_i32() -> Result<()> {
|
||||
let actual: i32 = 10000;
|
||||
let expected: i32 = 20000;
|
||||
verify_that!(actual, lt(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lt_does_not_match_equal_i32() -> Result<()> {
|
||||
let matcher = lt(10);
|
||||
let result = matcher.matches(10);
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lt_does_not_match_lower_i32() -> Result<()> {
|
||||
let matcher = lt(-50);
|
||||
let result = matcher.matches(50);
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lt_matches_lesser_str() -> Result<()> {
|
||||
verify_that!("A", lt("B"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lt_does_not_match_bigger_str() -> Result<()> {
|
||||
let matcher = lt("ab");
|
||||
let result = matcher.matches("az");
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lt_mismatch_contains_actual_and_expected() -> Result<()> {
|
||||
let result = verify_that!(481, lt(45));
|
||||
let formatted_message = format!("{}", result.unwrap_err());
|
||||
|
||||
verify_that!(
|
||||
formatted_message.as_str(),
|
||||
contains_substring(indoc!(
|
||||
"
|
||||
Value of: 481
|
||||
Expected: is less than 45
|
||||
Actual: 481,
|
||||
which is greater than or equal to 45
|
||||
"
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
// Test `lt` matcher where actual is `&OsString` and expected is `&str`.
|
||||
// Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
|
||||
// `OsString` and `str`, but only in one direction: it's only possible to
|
||||
// compare `OsString` with `str` if `OsString` is on the left side of the
|
||||
// "<" operator (`impl PartialOrd<str> for OsString`).
|
||||
//
|
||||
// The comparison in the other direction is not defined.
|
||||
//
|
||||
// This means that the test case bellow effectively ensures that
|
||||
// `verify_that(actual, lt(expected))` works if `actual < expected` works
|
||||
// (regardless whether the `expected < actual` works`).
|
||||
#[test]
|
||||
fn lt_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
|
||||
let expected = "C";
|
||||
let actual: OsString = "B".to_string().into();
|
||||
verify_that!(&actual, lt(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lt_matches_ipv6addr_with_ipaddr() -> Result<()> {
|
||||
use std::net::IpAddr;
|
||||
use std::net::Ipv6Addr;
|
||||
let actual: IpAddr = "127.0.0.1".parse().unwrap();
|
||||
let expected: Ipv6Addr = "2001:4860:4860::8844".parse().unwrap();
|
||||
verify_that!(actual, lt(expected))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lt_matches_with_custom_partial_ord() -> Result<()> {
|
||||
/// A custom "number" that is smaller than all other numbers. The only
|
||||
/// things we define about this "special" number is `PartialOrd` and
|
||||
/// `PartialEq` against `u32`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct VeryLowNumber {}
|
||||
|
||||
impl std::cmp::PartialEq<u32> for VeryLowNumber {
|
||||
fn eq(&self, _other: &u32) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// PartialOrd (required for >) requires PartialEq.
|
||||
impl std::cmp::PartialOrd<u32> for VeryLowNumber {
|
||||
fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
|
||||
Some(std::cmp::Ordering::Less)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq<VeryLowNumber> for u32 {
|
||||
fn eq(&self, _other: &VeryLowNumber) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
|
||||
fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
|
||||
Some(std::cmp::Ordering::Greater)
|
||||
}
|
||||
}
|
||||
|
||||
let actual = VeryLowNumber {};
|
||||
let expected: u32 = 42;
|
||||
|
||||
verify_that!(actual, lt(expected))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// There are no visible documentation elements in this module; the declarative
|
||||
// macro is documented in the matchers module.
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Matches a value according to a pattern of matchers.
|
||||
///
|
||||
/// This takes as an argument a specification similar to a struct or enum
|
||||
/// initialiser, where each value is a [`Matcher`][crate::matcher::Matcher]
|
||||
/// which is applied to the corresponding field.
|
||||
///
|
||||
/// This can be used to match arbitrary combinations of fields on structures
|
||||
/// using arbitrary matchers:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct MyStruct {
|
||||
/// a_field: String,
|
||||
/// another_field: String,
|
||||
/// }
|
||||
///
|
||||
/// let my_struct = MyStruct {
|
||||
/// a_field: "Something to believe in".into(),
|
||||
/// another_field: "Something else".into()
|
||||
/// };
|
||||
/// verify_that!(my_struct, matches_pattern!(MyStruct {
|
||||
/// a_field: starts_with("Something"),
|
||||
/// another_field: ends_with("else"),
|
||||
/// }))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// If any fields are provided in the pattern, then all fields must be
|
||||
/// specified, or the pattern must end with `..`, just like regular match
|
||||
/// patterns. Omitted fields have no effect on the output of the matcher.
|
||||
/// The `..` is unnecessary when no fields are provided and only method
|
||||
/// values are checked.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct MyStruct {
|
||||
/// # a_field: String,
|
||||
/// # another_field: String,
|
||||
/// # }
|
||||
/// #
|
||||
/// # let my_struct = MyStruct {
|
||||
/// # a_field: "Something to believe in".into(),
|
||||
/// # another_field: "Something else".into()
|
||||
/// # };
|
||||
/// verify_that!(my_struct, matches_pattern!(MyStruct {
|
||||
/// a_field: starts_with("Something"),
|
||||
/// .. // another_field is missing, so it may be anything.
|
||||
/// }))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// One can use it recursively to match nested structures:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct MyStruct {
|
||||
/// a_nested_struct: MyInnerStruct,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct MyInnerStruct {
|
||||
/// a_field: String,
|
||||
/// }
|
||||
///
|
||||
/// let my_struct = MyStruct {
|
||||
/// a_nested_struct: MyInnerStruct { a_field: "Something to believe in".into() },
|
||||
/// };
|
||||
/// verify_that!(my_struct, matches_pattern!(MyStruct {
|
||||
/// a_nested_struct: matches_pattern!(MyInnerStruct {
|
||||
/// a_field: starts_with("Something"),
|
||||
/// }),
|
||||
/// }))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// One can use the alias [`pat`][crate::matchers::pat] to make this less
|
||||
/// verbose:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct MyStruct {
|
||||
/// # a_nested_struct: MyInnerStruct,
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct MyInnerStruct {
|
||||
/// # a_field: String,
|
||||
/// # }
|
||||
/// #
|
||||
/// # let my_struct = MyStruct {
|
||||
/// # a_nested_struct: MyInnerStruct { a_field: "Something to believe in".into() },
|
||||
/// # };
|
||||
/// verify_that!(my_struct, matches_pattern!(MyStruct {
|
||||
/// a_nested_struct: pat!(MyInnerStruct {
|
||||
/// a_field: starts_with("Something"),
|
||||
/// }),
|
||||
/// }))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// In addition to fields, one can match on the outputs of methods
|
||||
/// ("properties"):
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct MyStruct {
|
||||
/// a_field: String,
|
||||
/// }
|
||||
///
|
||||
/// impl MyStruct {
|
||||
/// fn get_a_field(&self) -> String { self.a_field.clone() }
|
||||
/// }
|
||||
///
|
||||
/// let my_struct = MyStruct { a_field: "Something to believe in".into() };
|
||||
/// verify_that!(my_struct, matches_pattern!(MyStruct {
|
||||
/// get_a_field(): starts_with("Something"),
|
||||
/// }))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// If an inner matcher is `eq(...)`, it can be omitted:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct MyStruct {
|
||||
/// a_field: String,
|
||||
/// another_field: String,
|
||||
/// }
|
||||
///
|
||||
/// let my_struct = MyStruct {
|
||||
/// a_field: "this".into(),
|
||||
/// another_field: "that".into()
|
||||
/// };
|
||||
/// verify_that!(my_struct, matches_pattern!(MyStruct {
|
||||
/// a_field: "this",
|
||||
/// another_field: "that",
|
||||
/// }))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// **Important**: The method should be pure function with a deterministic
|
||||
/// output and no side effects. In particular, in the event of an assertion
|
||||
/// failure, it will be invoked a second time, with the assertion failure output
|
||||
/// reflecting the *second* invocation.
|
||||
///
|
||||
/// These may also include extra litteral parameters you pass in:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct MyStruct {
|
||||
/// # a_field: String,
|
||||
/// # }
|
||||
/// #
|
||||
/// impl MyStruct {
|
||||
/// fn append_to_a_field(&self, suffix: &str) -> String { self.a_field.clone() + suffix }
|
||||
/// }
|
||||
///
|
||||
/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
|
||||
/// verify_that!(my_struct, matches_pattern!(&MyStruct {
|
||||
/// append_to_a_field("a suffix"): ref ends_with("a suffix"),
|
||||
/// }))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// You can precede both field and property matchers with a `ref` to match the
|
||||
/// result by reference:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct MyStruct {
|
||||
/// # a_field: String,
|
||||
/// # }
|
||||
/// #
|
||||
/// impl MyStruct {
|
||||
/// fn get_a_field_ref(&self) -> String { self.a_field.clone() }
|
||||
/// }
|
||||
///
|
||||
/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
|
||||
/// verify_that!(my_struct, matches_pattern!(&MyStruct {
|
||||
/// get_a_field_ref(): ref starts_with("Something"),
|
||||
/// }))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Note that if the `actual` is of type `&ActualT` and the pattern type is
|
||||
/// `ActualT`, this is automatically performed. This behavior is similar to the
|
||||
/// reference binding mode in pattern matching.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct MyStruct {
|
||||
/// # a_field: String,
|
||||
/// # }
|
||||
/// #
|
||||
/// impl MyStruct {
|
||||
/// fn get_a_field_ref(&self) -> String { self.a_field.clone() }
|
||||
/// }
|
||||
///
|
||||
/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
|
||||
/// verify_that!(my_struct, matches_pattern!(MyStruct {
|
||||
/// get_a_field_ref(): starts_with("Something"),
|
||||
/// }))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// One can also match tuple structs with up to 10 fields. In this case, all
|
||||
/// fields must have matchers:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct MyTupleStruct(String, String);
|
||||
///
|
||||
/// let my_struct = MyTupleStruct("Something".into(), "Some other thing".into());
|
||||
/// verify_that!(
|
||||
/// my_struct,
|
||||
/// matches_pattern!(&MyTupleStruct(ref eq("Something"), ref eq("Some other thing")))
|
||||
/// )
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The macro also allows matching on specific enum values and supports wildcard
|
||||
/// patterns like `MyEnum::Case(_)`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// enum MyEnum {
|
||||
/// A(u32),
|
||||
/// B,
|
||||
/// }
|
||||
///
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(MyEnum::A(123), matches_pattern!(&MyEnum::A(eq(123))))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_pass_with_wildcard() -> Result<()> {
|
||||
/// verify_that!(MyEnum::A(123), matches_pattern!(MyEnum::A(_)))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(MyEnum::B, matches_pattern!(&MyEnum::A(eq(123))))?; // Fails - wrong enum variant
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_pass_with_wildcard().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// This macro does not support plain (non-struct) tuples. But it should not be
|
||||
/// necessary as tuple of matchers are matchers of tuple. In other words, if
|
||||
/// `MatcherU: Matcher<U>` and `MatcherT: Matcher<T>`, then `(MatcherU,
|
||||
/// MatcherT): Matcher<(U, T)>`.
|
||||
///
|
||||
/// Trailing commas are allowed (but not required) in both ordinary and tuple
|
||||
/// structs.
|
||||
///
|
||||
/// Note that the default format (rustfmt) can format macros if the macro
|
||||
/// argument is parseable Rust code. This is mostly true for this macro with two
|
||||
/// exceptions:
|
||||
/// * property matching
|
||||
/// * `ref` keyword with named fields
|
||||
///
|
||||
/// An option for formatting large is to avoid these exceptions (by removing the
|
||||
/// parenthesis of properties and the `ref` keywords), run `rustfmt` and add
|
||||
/// them back.
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __matches_pattern {
|
||||
($($t:tt)*) => { $crate::matches_pattern_internal!($($t)*) }
|
||||
}
|
||||
|
||||
// Internal-only macro created so that the macro definition does not appear in
|
||||
// generated documentation.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! matches_pattern_internal {
|
||||
($($tt:tt)*) => {
|
||||
{
|
||||
use $crate::{self as googletest};
|
||||
#[allow(unused)]
|
||||
use $crate::matchers::{all, field, property};
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::__googletest_macro_matches_pattern!($($tt)*)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// An alias for [`matches_pattern`][crate::matchers::matches_pattern!].
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __pat {
|
||||
($($t:tt)*) => { $crate::matches_pattern_internal!($($t)*) }
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod internal {
|
||||
use crate::matcher::{Matcher, MatcherBase};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub use ::googletest_macro::__googletest_macro_matches_pattern;
|
||||
|
||||
// Specialized implementation of the `predicate` matcher to support ref binding
|
||||
// mode for `matches_pattern`.
|
||||
pub fn pattern_only<T>(
|
||||
matcher_function: fn(&T) -> bool,
|
||||
match_description: &'static str,
|
||||
no_match_description: &'static str,
|
||||
) -> PatternOnlyMatcher<T> {
|
||||
PatternOnlyMatcher { matcher_function, match_description, no_match_description }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
#[doc(hidden)]
|
||||
pub struct PatternOnlyMatcher<T> {
|
||||
matcher_function: fn(&T) -> bool,
|
||||
match_description: &'static str,
|
||||
no_match_description: &'static str,
|
||||
}
|
||||
|
||||
impl<'a, T: Debug> Matcher<&'a T> for PatternOnlyMatcher<T> {
|
||||
fn matches(&self, actual: &'a T) -> crate::matcher::MatcherResult {
|
||||
(self.matcher_function)(actual).into()
|
||||
}
|
||||
|
||||
fn describe(
|
||||
&self,
|
||||
matcher_result: crate::matcher::MatcherResult,
|
||||
) -> crate::description::Description {
|
||||
match matcher_result {
|
||||
crate::matcher::MatcherResult::Match => self.match_description.into(),
|
||||
crate::matcher::MatcherResult::NoMatch => self.no_match_description.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy> Matcher<T> for PatternOnlyMatcher<T> {
|
||||
fn matches(&self, actual: T) -> crate::matcher::MatcherResult {
|
||||
(self.matcher_function)(&actual).into()
|
||||
}
|
||||
|
||||
fn describe(
|
||||
&self,
|
||||
matcher_result: crate::matcher::MatcherResult,
|
||||
) -> crate::description::Description {
|
||||
match matcher_result {
|
||||
crate::matcher::MatcherResult::Match => self.match_description.into(),
|
||||
crate::matcher::MatcherResult::NoMatch => self.no_match_description.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A matcher that ensures that the passed-in function compiles with the
|
||||
/// benefit of inference from the value being tested. The passed-in
|
||||
/// matcher is what is actually invoked for matching.
|
||||
///
|
||||
/// It forwards all description responsibilities to the passed-in matcher.
|
||||
pub fn compile_assert_and_match<T, M>(
|
||||
must_compile_function: fn(&T),
|
||||
matcher: M,
|
||||
) -> CompileAssertAndMatch<T, M> {
|
||||
CompileAssertAndMatch { must_compile_function, matcher }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
#[doc(hidden)]
|
||||
pub struct CompileAssertAndMatch<T, M> {
|
||||
#[allow(dead_code)]
|
||||
must_compile_function: fn(&T),
|
||||
matcher: M,
|
||||
}
|
||||
|
||||
impl<'a, T: Debug, M> Matcher<&'a T> for CompileAssertAndMatch<T, M>
|
||||
where
|
||||
M: Matcher<&'a T>,
|
||||
{
|
||||
fn matches(&self, actual: &'a T) -> crate::matcher::MatcherResult {
|
||||
self.matcher.matches(actual)
|
||||
}
|
||||
|
||||
fn describe(
|
||||
&self,
|
||||
matcher_result: crate::matcher::MatcherResult,
|
||||
) -> crate::description::Description {
|
||||
self.matcher.describe(matcher_result)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a T) -> crate::description::Description {
|
||||
self.matcher.explain_match(actual)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, M> Matcher<T> for CompileAssertAndMatch<T, M>
|
||||
where
|
||||
M: Matcher<T>,
|
||||
{
|
||||
fn matches(&self, actual: T) -> crate::matcher::MatcherResult {
|
||||
self.matcher.matches(actual)
|
||||
}
|
||||
|
||||
fn describe(
|
||||
&self,
|
||||
matcher_result: crate::matcher::MatcherResult,
|
||||
) -> crate::description::Description {
|
||||
self.matcher.describe(matcher_result)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: T) -> crate::description::Description {
|
||||
self.matcher.explain_match(actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod compile_fail_tests {
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct Foo { a: u32 }
|
||||
/// impl Foo {
|
||||
/// fn b() {}
|
||||
/// }
|
||||
/// let actual = Foo { a: 1 };
|
||||
/// verify_that!(actual, matches_pattern!(Foo { a: eq(&1), b(): _ }));
|
||||
/// ```
|
||||
fn _underscore_unsupported_for_methods() {}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct Foo { a: u32, b: u32 }
|
||||
/// let actual = Foo { a: 1, b: 2 };
|
||||
/// verify_that!(actual, matches_pattern!(Foo { a: eq(&1), .., }));
|
||||
/// ```
|
||||
fn _dot_dot_supported_only_at_end_of_struct_pattern() {}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct Foo(u32, u32);
|
||||
/// let actual = Foo(1, 2);
|
||||
/// verify_that!(actual, matches_pattern!(Foo(eq(&1), .., )));
|
||||
/// ```
|
||||
fn _dot_dot_supported_only_at_end_of_tuple_struct_pattern() {}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct Foo { a: u32, b: u32 }
|
||||
/// let actual = Foo { a: 1, b: 2 };
|
||||
/// verify_that!(actual, matches_pattern!(Foo { a: eq(&1) }));
|
||||
/// ```
|
||||
fn _unexhaustive_struct_field_check_requires_dot_dot() {}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// enum Foo {
|
||||
/// Bar { a: u32, b: u32 }
|
||||
/// }
|
||||
/// let actual = Foo::Bar { a: 1, b: 2 };
|
||||
/// verify_that!(actual, matches_pattern!(Foo::Bar { a: eq(&1) }));
|
||||
/// ```
|
||||
fn _unexhaustive_enum_struct_field_check_requires_dot_dot() {}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// struct Foo(u32, u32, u32);
|
||||
/// let actual = Foo(1, 2, 3);
|
||||
/// verify_that!(actual, matches_pattern!(Foo(eq(&1), eq(&2) )));
|
||||
/// ```
|
||||
fn _unexhaustive_tuple_struct_field_check_requires_dot_dot() {}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// verify_that!(1, matches_pattern!(UndefinedSymbol { }));
|
||||
/// ```
|
||||
fn _should_fail_to_compile_unknown_struct_with_no_fields() {}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// verify_that!(1, matches_pattern!(UndefinedSymbol { a: 1 }));
|
||||
/// ```
|
||||
fn _should_fail_to_compile_unknown_struct_with_field() {}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// verify_that!(1, matches_pattern!(UndefinedSymbol { .. }));
|
||||
/// ```
|
||||
fn _should_fail_to_compile_unknown_struct_with_dot_dot() {}
|
||||
|
||||
/// ```compile_fail
|
||||
/// use ::googletest::prelude::*;
|
||||
/// verify_that!(1, matches_pattern!(UndefinedSymbol( .. )));
|
||||
/// ```
|
||||
fn _should_fail_to_compile_unknown_tuple_struct_with_dot_dot() {}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use regex::Regex;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Matches a string the entirety of which which matches the given regular
|
||||
/// expression.
|
||||
///
|
||||
/// This is similar to [`contains_regex`][crate::matchers::contains_regex],
|
||||
/// except that the match must cover the whole string and not a substring.
|
||||
///
|
||||
/// Both the actual value and the expected regular expression may be either a
|
||||
/// `String` or a string reference.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass_1() -> Result<()> {
|
||||
/// verify_that!("Some value", matches_regex("S.*e"))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!("Another value", matches_regex("Some"))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!("Some value", matches_regex("Some"))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_pass_2() -> Result<()> {
|
||||
/// verify_that!("Some value".to_string(), matches_regex(".*v.*e"))?; // Passes
|
||||
/// verify_that!("Some value", matches_regex(".*v.*e".to_string()))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass_1().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// # should_pass_2().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Panics if the given `pattern` is not a syntactically valid regular
|
||||
/// expression.
|
||||
// N.B. This returns the concrete type rather than an impl Matcher so that it
|
||||
// can act simultaneously as a Matcher<str> and a Matcher<String>. Otherwise the
|
||||
// compiler treats it as a Matcher<str> only and the code
|
||||
// verify_that!("Some value".to_string(), matches_regex(".*value"))?;
|
||||
// doesn't compile.
|
||||
pub fn matches_regex<PatternT: Deref<Target = str>>(
|
||||
pattern: PatternT,
|
||||
) -> MatchesRegexMatcher<PatternT> {
|
||||
let adjusted_pattern = format!("^{}$", pattern.deref());
|
||||
let regex = Regex::new(adjusted_pattern.as_str()).unwrap();
|
||||
MatchesRegexMatcher { regex, pattern, _adjusted_pattern: adjusted_pattern }
|
||||
}
|
||||
|
||||
/// A matcher matching a string-like type matching a given regular expression.
|
||||
///
|
||||
/// Intended only to be used from the function [`matches_regex`] only.
|
||||
/// Should not be referenced by code outside this library.
|
||||
#[derive(MatcherBase)]
|
||||
pub struct MatchesRegexMatcher<PatternT: Deref<Target = str>> {
|
||||
regex: Regex,
|
||||
pattern: PatternT,
|
||||
_adjusted_pattern: String,
|
||||
}
|
||||
|
||||
impl<PatternT, ActualT> Matcher<ActualT> for MatchesRegexMatcher<PatternT>
|
||||
where
|
||||
PatternT: Deref<Target = str>,
|
||||
ActualT: AsRef<str> + Debug + Copy,
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
self.regex.is_match(actual.as_ref()).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("matches the regular expression {:#?}", self.pattern.deref()).into()
|
||||
}
|
||||
MatcherResult::NoMatch => {
|
||||
format!("doesn't match the regular expression {:#?}", self.pattern.deref()).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn matches_regex_matches_string_reference_with_pattern() -> Result<()> {
|
||||
let matcher = matches_regex("S.*e");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_regex_does_not_match_string_without_pattern() -> Result<()> {
|
||||
let matcher = matches_regex("Another");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_regex_does_not_match_string_only_beginning_of_which_matches() -> Result<()> {
|
||||
let matcher = matches_regex("Some");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_regex_does_not_match_string_only_end_of_which_matches() -> Result<()> {
|
||||
let matcher = matches_regex("value");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_regex_matches_owned_string_with_pattern() -> Result<()> {
|
||||
let matcher = matches_regex(".*value");
|
||||
|
||||
let result = matcher.matches(&"Some value".to_string());
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_regex_matches_string_when_regex_has_beginning_of_string_marker() -> Result<()> {
|
||||
let matcher = matches_regex("^Some value");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_regex_matches_string_when_regex_has_end_of_string_marker() -> Result<()> {
|
||||
let matcher = matches_regex("Some value$");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_regex_matches_string_when_regex_has_both_end_markers() -> Result<()> {
|
||||
let matcher = matches_regex("^Some value$");
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_regex_matches_string_reference_with_owned_string() -> Result<()> {
|
||||
let matcher = matches_regex(".*value".to_string());
|
||||
|
||||
let result = matcher.matches("Some value");
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_that_works_with_owned_string() -> Result<()> {
|
||||
verify_that!("Some value".to_string(), matches_regex(".*value"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_regex_displays_quoted_debug_of_pattern() -> Result<()> {
|
||||
let matcher = matches_regex("\n");
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&str>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("matches the regular expression \"\\n\""))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
//! All built-in matchers of this crate are in submodules of this module.
|
||||
|
||||
mod all_matcher;
|
||||
mod any_matcher;
|
||||
mod anything_matcher;
|
||||
mod bool_matcher;
|
||||
mod char_count_matcher;
|
||||
mod conjunction_matcher;
|
||||
mod container_eq_matcher;
|
||||
mod contains_matcher;
|
||||
mod contains_regex_matcher;
|
||||
mod derefs_to_matcher;
|
||||
mod disjunction_matcher;
|
||||
mod display_matcher;
|
||||
mod each_matcher;
|
||||
mod elements_are_matcher;
|
||||
mod empty_matcher;
|
||||
mod eq_matcher;
|
||||
mod err_matcher;
|
||||
mod field_matcher;
|
||||
mod ge_matcher;
|
||||
mod gt_matcher;
|
||||
mod has_entry_matcher;
|
||||
mod is_encoded_string_matcher;
|
||||
mod is_finite_matcher;
|
||||
mod is_infinite_matcher;
|
||||
mod is_matcher;
|
||||
mod is_nan_matcher;
|
||||
mod le_matcher;
|
||||
mod len_matcher;
|
||||
mod lt_matcher;
|
||||
mod matches_pattern;
|
||||
mod matches_regex_matcher;
|
||||
mod ne_matcher;
|
||||
mod near_matcher;
|
||||
mod none_matcher;
|
||||
mod not_matcher;
|
||||
mod ok_matcher;
|
||||
mod points_to_matcher;
|
||||
mod pointwise_matcher;
|
||||
mod predicate_matcher;
|
||||
mod property_matcher;
|
||||
mod result_of_matcher;
|
||||
mod some_matcher;
|
||||
mod str_matcher;
|
||||
mod subset_of_matcher;
|
||||
mod superset_of_matcher;
|
||||
mod tuple_matcher;
|
||||
mod unordered_elements_are_matcher;
|
||||
|
||||
pub use anything_matcher::anything;
|
||||
pub use bool_matcher::{is_false, is_true};
|
||||
pub use char_count_matcher::char_count;
|
||||
pub use container_eq_matcher::container_eq;
|
||||
pub use contains_matcher::{contains, ContainsMatcher};
|
||||
pub use contains_regex_matcher::contains_regex;
|
||||
pub use derefs_to_matcher::derefs_to;
|
||||
pub use display_matcher::displays_as;
|
||||
pub use each_matcher::each;
|
||||
pub use empty_matcher::is_empty;
|
||||
pub use eq_matcher::{eq, EqMatcher};
|
||||
pub use err_matcher::err;
|
||||
pub use ge_matcher::ge;
|
||||
pub use gt_matcher::gt;
|
||||
pub use has_entry_matcher::has_entry;
|
||||
pub use is_encoded_string_matcher::is_utf8_string;
|
||||
pub use is_finite_matcher::is_finite;
|
||||
pub use is_infinite_matcher::is_infinite;
|
||||
pub use is_nan_matcher::is_nan;
|
||||
pub use le_matcher::le;
|
||||
pub use len_matcher::len;
|
||||
pub use lt_matcher::lt;
|
||||
pub use matches_regex_matcher::matches_regex;
|
||||
pub use ne_matcher::ne;
|
||||
pub use near_matcher::{approx_eq, near, NearMatcher};
|
||||
pub use none_matcher::none;
|
||||
pub use not_matcher::not;
|
||||
pub use ok_matcher::ok;
|
||||
pub use points_to_matcher::points_to;
|
||||
pub use predicate_matcher::{predicate, PredicateMatcher};
|
||||
pub use some_matcher::some;
|
||||
pub use str_matcher::{
|
||||
contains_substring, ends_with, starts_with, StrMatcher, StrMatcherConfigurator,
|
||||
};
|
||||
pub use subset_of_matcher::subset_of;
|
||||
pub use superset_of_matcher::superset_of;
|
||||
|
||||
// Reexport and unmangle the macros.
|
||||
#[doc(inline)]
|
||||
pub use crate::{
|
||||
__all as all, __any as any, __contains_each as contains_each, __elements_are as elements_are,
|
||||
__field as field, __is_contained_in as is_contained_in, __matches_pattern as matches_pattern,
|
||||
__pat as pat, __pointwise as pointwise, __property as property, __result_of as result_of,
|
||||
__result_of_ref as result_of_ref, __unordered_elements_are as unordered_elements_are,
|
||||
};
|
||||
|
||||
// Types and functions used by macros matchers.
|
||||
// Do not use directly.
|
||||
// We may perform incompatible changes without major release. These elements
|
||||
// should only be used through their respective macros.
|
||||
#[doc(hidden)]
|
||||
pub mod __internal_unstable_do_not_depend_on_these {
|
||||
pub use super::conjunction_matcher::ConjunctionMatcher;
|
||||
pub use super::disjunction_matcher::DisjunctionMatcher;
|
||||
pub use super::elements_are_matcher::internal::ElementsAre;
|
||||
pub use super::field_matcher::internal::field_matcher;
|
||||
pub use super::is_matcher::is;
|
||||
pub use super::matches_pattern::internal::{
|
||||
__googletest_macro_matches_pattern, compile_assert_and_match, pattern_only,
|
||||
};
|
||||
pub use super::pointwise_matcher::internal::PointwiseMatcher;
|
||||
pub use super::property_matcher::internal::{property_matcher, property_ref_matcher};
|
||||
pub use super::result_of_matcher::internal::{result_of, result_of_ref};
|
||||
pub use super::unordered_elements_are_matcher::internal::UnorderedElementsAreMatcher;
|
||||
pub use crate::matcher_support::match_matrix::internal::Requirements;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a value is not equal (in the sense of `!=`) to `expected`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(0, ne(1))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(0, ne(0))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
pub fn ne<T>(expected: T) -> NEMatcher<T> {
|
||||
NEMatcher { expected }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct NEMatcher<T> {
|
||||
expected: T,
|
||||
}
|
||||
|
||||
impl<T: Debug, A: Debug + Copy + PartialEq<T>> Matcher<A> for NEMatcher<T> {
|
||||
fn matches(&self, actual: A) -> MatcherResult {
|
||||
(actual != self.expected).into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!("is not equal to {:?}", self.expected).into(),
|
||||
MatcherResult::NoMatch => format!("is equal to {:?}", self.expected).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn ne_matches_i32_with_i32() -> Result<()> {
|
||||
verify_that!(123, ne(234))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ne_matches_string_reference_with_string_reference() -> Result<()> {
|
||||
verify_that!("A string", ne("B string"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ne_matches_owned_string_with_string_reference() -> Result<()> {
|
||||
let value = "A string".to_string();
|
||||
verify_that!(value, ne("B string"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ne_matches_owned_string_reference_with_string_reference() -> Result<()> {
|
||||
let value = "A string".to_string();
|
||||
verify_that!(&value, ne("B string"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ne_struct_debug_diff() -> Result<()> {
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Strukt {
|
||||
int: i32,
|
||||
string: String,
|
||||
}
|
||||
|
||||
let result = verify_that!(
|
||||
Strukt { int: 123, string: "something".into() },
|
||||
ne(&Strukt { int: 123, string: "something".into() })
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc! {
|
||||
"
|
||||
Expected: is not equal to Strukt { int: 123, string: \"something\" }
|
||||
Actual: Strukt { int: 123, string: \"something\" },
|
||||
which is equal to Strukt { int: 123, string: \"something\" }
|
||||
"})))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use num_traits::{Float, FloatConst};
|
||||
use std::{borrow::Borrow, fmt::Debug};
|
||||
|
||||
/// Matches a value equal within `max_abs_error` of `expected`.
|
||||
///
|
||||
/// The type `T` of the actual, `expected`, and `max_abs_error` values must
|
||||
/// implement [`Float`].
|
||||
///
|
||||
/// The values `expected` and `max_abs_error` may not be NaN. The value
|
||||
/// `max_abs_error` must be non-negative. The matcher panics on construction
|
||||
/// otherwise.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass_1() -> Result<()> {
|
||||
/// verify_that!(1.0, near(1.0, 0.1))?; // Passes
|
||||
/// verify_that!(1.01, near(1.0, 0.1))?; // Passes
|
||||
/// verify_that!(1.25, near(1.0, 0.25))?; // Passes
|
||||
/// verify_that!(0.75, near(1.0, 0.25))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!(1.101, near(1.0, 0.1))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!(0.899, near(1.0, 0.1))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_pass_2() -> Result<()> {
|
||||
/// verify_that!(100.25, near(100.0, 0.25))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass_1().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// # should_pass_2().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The default behaviour for special values is consistent with the IEEE
|
||||
/// floating point standard. Thus infinity is infinitely far away from any
|
||||
/// floating point value:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!(f64::INFINITY, near(0.0, f64::MAX))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!(0.0, near(f64::INFINITY, f64::MAX))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_3() -> Result<()> {
|
||||
/// verify_that!(f64::INFINITY, near(f64::INFINITY, f64::MAX))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// # should_fail_3().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// Similarly, by default, `NaN` is infinitely far away from any value:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!(f64::NAN, near(0.0, f64::MAX))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!(0.0, near(f64::NAN, f64::MAX))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_3() -> Result<()> {
|
||||
/// verify_that!(f64::NAN, near(f64::NAN, f64::MAX))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// # should_fail_3().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// To treat two `NaN` values as equal, use the method
|
||||
/// [`NearMatcher::nans_are_equal`].
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(f64::NAN, near(f64::NAN, f64::MAX).nans_are_equal())?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn near<T: Debug + Float + Copy>(expected: T, max_abs_error: T) -> NearMatcher<T> {
|
||||
if max_abs_error.is_nan() {
|
||||
panic!("max_abs_error must not be NaN");
|
||||
}
|
||||
if max_abs_error < T::zero() {
|
||||
panic!("max_abs_error must be non-negative");
|
||||
}
|
||||
NearMatcher { expected, max_abs_error, nans_are_equal: false }
|
||||
}
|
||||
|
||||
/// Matches a value approximately equal to `expected`.
|
||||
///
|
||||
/// This automatically computes a tolerance from the magnitude of `expected` and
|
||||
/// matches any actual value within this tolerance of the expected value. The
|
||||
/// tolerance is chosen to account for the inaccuracies in most ordinary
|
||||
/// floating point calculations.
|
||||
///
|
||||
/// Otherwise this works analogously to [`near`]; see its documentation for
|
||||
/// further notes.
|
||||
pub fn approx_eq<T: Debug + Float + FloatConst + Copy>(expected: T) -> NearMatcher<T> {
|
||||
// The FloatConst trait doesn't offer 2 as a constant but does offer 1.
|
||||
let five_bits_of_mantissa = (T::one() + T::one()).powi(5);
|
||||
let abs_tolerance = five_bits_of_mantissa * T::epsilon();
|
||||
let max_abs_error = T::max(expected.abs() * abs_tolerance, abs_tolerance);
|
||||
NearMatcher { expected, max_abs_error, nans_are_equal: false }
|
||||
}
|
||||
|
||||
/// A matcher which matches floating-point numbers approximately equal to its
|
||||
/// expected value.
|
||||
#[derive(MatcherBase)]
|
||||
pub struct NearMatcher<T: Debug> {
|
||||
expected: T,
|
||||
max_abs_error: T,
|
||||
nans_are_equal: bool,
|
||||
}
|
||||
|
||||
impl<T: Debug> NearMatcher<T> {
|
||||
/// Configures this instance to treat two NaNs as equal.
|
||||
///
|
||||
/// This behaviour differs from the IEEE standad for floating point which
|
||||
/// treats two NaNs as infinitely far apart.
|
||||
pub fn nans_are_equal(mut self) -> Self {
|
||||
self.nans_are_equal = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures this instance to treat two NaNs as not equal.
|
||||
///
|
||||
/// This behaviour complies with the IEEE standad for floating point. It is
|
||||
/// the default behaviour for this matcher, so invoking this method is
|
||||
/// usually redunant.
|
||||
pub fn nans_are_not_equal(mut self) -> Self {
|
||||
self.nans_are_equal = false;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Borrow<F> + Debug + Copy, F: Debug + Float> Matcher<T> for NearMatcher<F> {
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
if self.nans_are_equal && self.expected.is_nan() && actual.borrow().is_nan() {
|
||||
return MatcherResult::Match;
|
||||
}
|
||||
|
||||
let delta = *actual.borrow() - self.expected;
|
||||
if delta >= -self.max_abs_error && delta <= self.max_abs_error {
|
||||
MatcherResult::Match
|
||||
} else {
|
||||
MatcherResult::NoMatch
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("is within {:?} of {:?}", self.max_abs_error, self.expected).into()
|
||||
}
|
||||
MatcherResult::NoMatch => {
|
||||
format!("isn't within {:?} of {:?}", self.max_abs_error, self.expected).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn matches_value_inside_range() -> Result<()> {
|
||||
let matcher = near(1.0f64, 0.1f64);
|
||||
|
||||
let result = matcher.matches(1.0f64);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_value_at_low_end_of_range() -> Result<()> {
|
||||
let matcher = near(1.0f64, 0.1f64);
|
||||
|
||||
let result = matcher.matches(0.9f64);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_value_at_high_end_of_range() -> Result<()> {
|
||||
let matcher = near(1.0f64, 0.25f64);
|
||||
|
||||
let result = matcher.matches(1.25f64);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_value_below_low_end_of_range() -> Result<()> {
|
||||
let matcher = near(1.0f64, 0.1f64);
|
||||
|
||||
let result = matcher.matches(0.899999f64);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_value_above_high_end_of_range() -> Result<()> {
|
||||
let matcher = near(1.0f64, 0.1f64);
|
||||
|
||||
let result = matcher.matches(1.100001f64);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nan_is_not_near_a_number() -> Result<()> {
|
||||
let matcher = near(0.0f64, f64::MAX);
|
||||
|
||||
let result = matcher.matches(f64::NAN);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nan_is_not_near_nan_by_default() -> Result<()> {
|
||||
verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nan_is_not_near_nan_when_explicitly_configured() -> Result<()> {
|
||||
verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX).nans_are_not_equal()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nan_is_near_nan_if_nans_are_equal() -> Result<()> {
|
||||
verify_that!(f64::NAN, near(f64::NAN, f64::MAX).nans_are_equal())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nan_is_not_near_number_when_nans_are_equal() -> Result<()> {
|
||||
verify_that!(f64::NAN, not(near(0.0, f64::MAX).nans_are_equal()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_is_not_near_nan_when_nans_are_equal() -> Result<()> {
|
||||
verify_that!(0.0, not(near(f64::NAN, f64::MAX).nans_are_equal()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inf_is_not_near_inf() -> Result<()> {
|
||||
let matcher = near(f64::INFINITY, f64::MAX);
|
||||
|
||||
let result = matcher.matches(f64::INFINITY);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inf_is_not_near_a_number() -> Result<()> {
|
||||
let matcher = near(f64::INFINITY, f64::MAX);
|
||||
|
||||
let result = matcher.matches(f64::MIN);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn any_two_numbers_are_within_inf_of_each_other() -> Result<()> {
|
||||
let matcher = near(f64::MIN, f64::INFINITY);
|
||||
|
||||
let result = matcher.matches(f64::MAX);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[::core::prelude::v1::test]
|
||||
#[should_panic]
|
||||
fn panics_if_max_abs_error_is_nan() {
|
||||
near(0.0, f64::NAN);
|
||||
}
|
||||
|
||||
#[::core::prelude::v1::test]
|
||||
#[should_panic]
|
||||
fn panics_if_tolerance_is_negative() {
|
||||
near(0.0, -1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_eq_matches_equal_number() -> Result<()> {
|
||||
verify_that!(1.0f64, approx_eq(1.0f64))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_eq_matches_really_close_f64_number() -> Result<()> {
|
||||
verify_that!(1.0f64, approx_eq(1.0 + 16.0 * f64::EPSILON))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_eq_matches_really_close_f64_number_to_large_number() -> Result<()> {
|
||||
verify_that!(1000f64, approx_eq(1000.0 + 16000.0 * f64::EPSILON))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_eq_matches_really_close_f64_number_to_zero() -> Result<()> {
|
||||
verify_that!(16.0 * f64::EPSILON, approx_eq(0.0))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_eq_matches_really_close_f32_number() -> Result<()> {
|
||||
verify_that!(1.0f32, approx_eq(1.0 + 16.0 * f32::EPSILON))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_eq_does_not_match_distant_number() -> Result<()> {
|
||||
verify_that!(0.0f64, not(approx_eq(1.0f64)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_eq_supports_ref() -> Result<()> {
|
||||
verify_that!(&0.0f64, approx_eq(0.0f64))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_eq_supports_container_matchers() -> Result<()> {
|
||||
verify_that!(
|
||||
vec![1., 2., 3.],
|
||||
unordered_elements_are![approx_eq(2.), approx_eq(3.), approx_eq(1.)]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches an `Option` containing `None`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(None::<()>, none())?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(Some("Some value"), none())?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
pub fn none() -> NoneMatcher {
|
||||
NoneMatcher
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct NoneMatcher;
|
||||
|
||||
impl<T: Debug + Copy> Matcher<Option<T>> for NoneMatcher {
|
||||
fn matches(&self, actual: Option<T>) -> MatcherResult {
|
||||
actual.is_none().into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => "is none".into(),
|
||||
MatcherResult::NoMatch => "is some(_)".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Debug> Matcher<&'a Option<T>> for NoneMatcher {
|
||||
fn matches(&self, actual: &'a Option<T>) -> MatcherResult {
|
||||
actual.is_none().into()
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => "is none".into(),
|
||||
MatcherResult::NoMatch => "is some(_)".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn none_matches_option_with_none() -> Result<()> {
|
||||
let matcher = none();
|
||||
|
||||
let result = matcher.matches(None::<i32>);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_does_not_match_option_with_value() -> Result<()> {
|
||||
let matcher = none();
|
||||
|
||||
let result = matcher.matches(Some(0));
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_matches_option_by_ref() -> Result<()> {
|
||||
verify_that!(None::<String>, none())
|
||||
}
|
||||
#[test]
|
||||
fn none_does_not_match_option_with_value_by_ref() -> Result<()> {
|
||||
verify_that!(Some("123".to_string()), not(none()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_describe_match_option_by_ref() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<&Option<String>>::describe(&none(), MatcherResult::Match),
|
||||
displays_as(eq("is none"))
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn none_describe_no_match_option_by_ref() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<&Option<String>>::describe(&none(), MatcherResult::NoMatch),
|
||||
displays_as(eq("is some(_)"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_describe_match_option() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<Option<i32>>::describe(&none(), MatcherResult::Match),
|
||||
displays_as(eq("is none"))
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn none_describe_no_match_option() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<Option<i32>>::describe(&none(), MatcherResult::NoMatch),
|
||||
displays_as(eq("is some(_)"))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches the actual value exactly when the inner matcher does _not_ match.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(0, not(eq(1)))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(0, not(eq(0)))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
pub fn not<InnerMatcherT>(inner: InnerMatcherT) -> NotMatcher<InnerMatcherT> {
|
||||
NotMatcher { inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct NotMatcher<InnerMatcherT> {
|
||||
inner: InnerMatcherT,
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, InnerMatcherT: Matcher<T>> Matcher<T> for NotMatcher<InnerMatcherT> {
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
match self.inner.matches(actual) {
|
||||
MatcherResult::Match => MatcherResult::NoMatch,
|
||||
MatcherResult::NoMatch => MatcherResult::Match,
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: T) -> Description {
|
||||
self.inner.explain_match(actual)
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
self.inner.describe(if matcher_result.into() {
|
||||
MatcherResult::NoMatch
|
||||
} else {
|
||||
MatcherResult::Match
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn matches_when_inner_matcher_does_not_match() -> Result<()> {
|
||||
let matcher = not(eq(1));
|
||||
|
||||
let result = matcher.matches(0);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_when_inner_matcher_matches() -> Result<()> {
|
||||
let matcher = not(eq(1));
|
||||
|
||||
let result = matcher.matches(1);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_explanation_references_actual_value() -> Result<()> {
|
||||
let result = verify_that!(&[1], not(container_eq([1])));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Actual: [1],
|
||||
which contains all the elements
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a `Result` containing `Ok` with a value matched by `inner`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> googletest::Result<()> {
|
||||
/// verify_that!(Ok::<_, ()>("Some value"), ok(eq("Some value")))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> googletest::Result<()> {
|
||||
/// verify_that!(Err::<&str, _>("An error"), ok(eq("An error")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> googletest::Result<()> {
|
||||
/// verify_that!(Ok::<_, ()>("Some value"), ok(eq("Some other value")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// ```
|
||||
pub fn ok<InnerMatcherT>(inner: InnerMatcherT) -> OkMatcher<InnerMatcherT> {
|
||||
OkMatcher { inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct OkMatcher<InnerMatcherT> {
|
||||
inner: InnerMatcherT,
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, E: Debug + Copy, InnerMatcherT: Matcher<T>> Matcher<std::result::Result<T, E>>
|
||||
for OkMatcher<InnerMatcherT>
|
||||
{
|
||||
fn matches(&self, actual: std::result::Result<T, E>) -> MatcherResult {
|
||||
actual.map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: std::result::Result<T, E>) -> Description {
|
||||
match actual {
|
||||
Ok(o) => {
|
||||
Description::new().text("which is a success").nested(self.inner.explain_match(o))
|
||||
}
|
||||
Err(_) => "which is an error".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!(
|
||||
"is a success containing a value, which {}",
|
||||
self.inner.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
MatcherResult::NoMatch => format!(
|
||||
"is an error or a success containing a value, which {}",
|
||||
self.inner.describe(MatcherResult::NoMatch)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Debug, E: Debug, InnerMatcherT: Matcher<&'a T>> Matcher<&'a std::result::Result<T, E>>
|
||||
for OkMatcher<InnerMatcherT>
|
||||
{
|
||||
fn matches(&self, actual: &'a std::result::Result<T, E>) -> MatcherResult {
|
||||
actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a std::result::Result<T, E>) -> Description {
|
||||
match actual {
|
||||
Ok(o) => {
|
||||
Description::new().text("which is a success").nested(self.inner.explain_match(o))
|
||||
}
|
||||
Err(_) => "which is an error".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!(
|
||||
"is a success containing a value, which {}",
|
||||
self.inner.describe(MatcherResult::Match)
|
||||
)
|
||||
.into(),
|
||||
MatcherResult::NoMatch => format!(
|
||||
"is an error or a success containing a value, which {}",
|
||||
self.inner.describe(MatcherResult::NoMatch)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn ok_matches_result_with_value() -> Result<()> {
|
||||
let matcher = ok(eq(1));
|
||||
let value: std::result::Result<i32, i32> = Ok(1);
|
||||
|
||||
let result = matcher.matches(value);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_does_not_match_result_with_wrong_value() -> Result<()> {
|
||||
let matcher = ok(eq(1));
|
||||
let value: std::result::Result<i32, i32> = Ok(0);
|
||||
|
||||
let result = matcher.matches(value);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_does_not_match_result_with_err() -> Result<()> {
|
||||
let matcher = ok(eq(1));
|
||||
let value: std::result::Result<i32, i32> = Err(1);
|
||||
|
||||
let result = matcher.matches(value);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_matches_result_with_value_by_ref() -> Result<()> {
|
||||
let result: std::result::Result<String, String> = Ok("123".into());
|
||||
verify_that!(result, ok(eq("123")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_does_not_match_result_with_wrong_value_by_ref() -> Result<()> {
|
||||
let result: std::result::Result<String, String> = Ok("321".into());
|
||||
verify_that!(result, not(ok(eq("123"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_does_not_match_result_with_err_by_ref() -> Result<()> {
|
||||
let result: std::result::Result<String, String> = Err("123".into());
|
||||
verify_that!(result, not(ok(eq("123"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_full_error_message() -> Result<()> {
|
||||
let result = verify_that!(Ok::<i32, i32>(1), ok(eq(2)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: Ok::<i32, i32>(1)
|
||||
Expected: is a success containing a value, which is equal to 2
|
||||
Actual: Ok(1),
|
||||
which is a success
|
||||
which isn't equal to 2
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_describe_match() -> Result<()> {
|
||||
let matcher = ok(eq(1));
|
||||
verify_that!(
|
||||
Matcher::<std::result::Result<i32, i32>>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("is a success containing a value, which is equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_describe_no_match() -> Result<()> {
|
||||
let matcher = ok(eq(1));
|
||||
verify_that!(
|
||||
Matcher::<std::result::Result<i32, i32>>::describe(&matcher, MatcherResult::NoMatch),
|
||||
displays_as(eq("is an error or a success containing a value, which isn't equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_describe_match_by_ref() -> Result<()> {
|
||||
let matcher = ok(eq(&1));
|
||||
verify_that!(
|
||||
Matcher::<&std::result::Result<i32, String>>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq("is a success containing a value, which is equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_describe_no_match_by_ref() -> Result<()> {
|
||||
let matcher = ok(eq(&1));
|
||||
verify_that!(
|
||||
Matcher::<&std::result::Result<i32, String>>::describe(
|
||||
&matcher,
|
||||
MatcherResult::NoMatch
|
||||
),
|
||||
displays_as(eq("is an error or a success containing a value, which isn't equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_explain_match_ok_success() -> Result<()> {
|
||||
let actual = Ok(1);
|
||||
let matcher = ok(eq(1));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<std::result::Result<i32, i32>>::explain_match(&matcher, actual),
|
||||
displays_as(eq("which is a success\n which is equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_explain_match_ok_fail() -> Result<()> {
|
||||
let actual = Ok(1);
|
||||
let matcher = ok(eq(2));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<std::result::Result<i32, i32>>::explain_match(&matcher, actual),
|
||||
displays_as(eq("which is a success\n which isn't equal to 2"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_explain_match_ok_err() -> Result<()> {
|
||||
let actual = Err(1);
|
||||
let matcher = ok(eq(2));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<std::result::Result<i32, i32>>::explain_match(&matcher, actual),
|
||||
displays_as(eq("which is an error"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_explain_match_ok_success_by_ref() -> Result<()> {
|
||||
let actual = Ok("123".to_string());
|
||||
let matcher = ok(eq("123"));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&std::result::Result<String, String>>::explain_match(&matcher, &actual),
|
||||
displays_as(eq("which is a success\n which is equal to \"123\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_explain_match_ok_fail_by_ref() -> Result<()> {
|
||||
let actual = Ok("321".to_string());
|
||||
let matcher = ok(eq("123"));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&std::result::Result<String, String>>::explain_match(&matcher, &actual),
|
||||
displays_as(eq("which is a success\n which isn't equal to \"123\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_explain_match_ok_err_by_ref() -> Result<()> {
|
||||
let actual = Err("123".to_string());
|
||||
let matcher = ok(eq("123"));
|
||||
|
||||
verify_that!(
|
||||
Matcher::<&std::result::Result<String, String>>::explain_match(&matcher, &actual),
|
||||
displays_as(eq("which is an error"))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2022 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::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a reference pointing to a value matched by the [`Matcher`]
|
||||
/// `expected`.
|
||||
///
|
||||
/// This is useful for combining matchers, especially when working with
|
||||
/// iterators.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(&123, points_to(eq(123)))?;
|
||||
/// verify_that!(vec![1,2,3], each(points_to(gt(0))))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
pub fn points_to<MatcherT>(expected: MatcherT) -> PointsToMatcher<MatcherT> {
|
||||
PointsToMatcher { expected }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct PointsToMatcher<MatcherT> {
|
||||
expected: MatcherT,
|
||||
}
|
||||
|
||||
impl<'a, ExpectedT, MatcherT> Matcher<&'a ExpectedT> for PointsToMatcher<MatcherT>
|
||||
where
|
||||
ExpectedT: Debug + Copy,
|
||||
MatcherT: Matcher<ExpectedT>,
|
||||
{
|
||||
fn matches(&self, actual: &'a ExpectedT) -> MatcherResult {
|
||||
self.expected.matches(*actual)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a ExpectedT) -> Description {
|
||||
self.expected.explain_match(*actual)
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
self.expected.describe(matcher_result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn points_to_matches_ref() -> Result<()> {
|
||||
verify_that!(&123, points_to(eq(123)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_explanation_references_actual_value() -> Result<()> {
|
||||
let result = verify_that!(&1, points_to(eq(0)));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Actual: 1,
|
||||
which isn't equal to 0
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// There are no visible documentation elements in this module; the declarative
|
||||
// macro is documented in the matcher module.
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Generates a matcher which matches a container each of whose elements match
|
||||
/// the given matcher name applied respectively to each element of the given
|
||||
/// container.
|
||||
///
|
||||
/// For example, the following matches a container of integers each of which
|
||||
/// does not exceed the given upper bounds:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let value = vec![1, 2, 3];
|
||||
/// verify_that!(value, pointwise!(le, [&1, &2, &3]))?; // Passes
|
||||
/// verify_that!(value, pointwise!(|e| points_to(le(e)), [1, 2, 3]))?; // Passes
|
||||
/// verify_that!(value, pointwise!(|e| points_to(le(e)), vec![1, 3, 3]))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// # let value = vec![1, 2, 3];
|
||||
/// verify_that!(value, pointwise!(|e| points_to(le(e)), [1, 1, 3]))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// One can also use a closure which returns a matcher:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let value = vec![1.00001, 2.000001, 3.00001];
|
||||
/// verify_that!(value, pointwise!(|v| points_to(near(v, 0.001)), [1.0, 2.0, 3.0]))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// One can pass up to three containers to supply arguments to the function
|
||||
/// creating the matcher:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let value = vec![1.00001, 2.000001, 3.00001];
|
||||
/// verify_that!(value, pointwise!(|v, t| points_to(near(v, t)), [1.0, 2.0, 3.0], [0.001, 0.0001, 0.01]))?;
|
||||
/// verify_that!(
|
||||
/// value,
|
||||
/// pointwise!(
|
||||
/// |v, t, u| points_to(near(v, t * u)),
|
||||
/// [1.0, 2.0, 3.0],
|
||||
/// [0.001, 0.0001, 0.01],
|
||||
/// [0.5, 0.5, 1.0]
|
||||
/// )
|
||||
/// )?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// When using `pointwise!` with multiple containers, the caller must ensure
|
||||
/// that all of the containers have the same size. This matcher does not check
|
||||
/// whether the sizes match.
|
||||
///
|
||||
/// The actual value must be a container such as a `&Vec`, an array, or a
|
||||
/// slice. More precisely, the actual value must implement [`IntoIterator`].
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let value = vec![1, 2, 3];
|
||||
/// verify_that!(value, pointwise!(|i| points_to(le(i)), [1, 3, 3]))?; // Passes
|
||||
/// verify_that!([1, 2, 3], pointwise!(le, [1, 3, 3]))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The second argument can be any value implementing `IntoIterator`, such as a
|
||||
/// `Vec` or an array. The container does not have to have the same type as the
|
||||
/// actual value, but the value type must be the same.
|
||||
///
|
||||
/// **Note for users of the [`Pointwise`] matcher in C++ GoogleTest:**
|
||||
///
|
||||
/// This macro differs from `Pointwise` in that the first parameter is not a
|
||||
/// matcher which matches a pair but rather the name of a function of one
|
||||
/// argument whose output is a matcher. This means that one can use standard
|
||||
/// matchers like `eq`, `le`, and so on with `pointwise!` but certain C++ tests
|
||||
/// using `Pointwise` will require some extra work to port.
|
||||
///
|
||||
/// [`IntoIterator`]: std::iter::IntoIterator
|
||||
/// [`Iterator`]: std::iter::Iterator
|
||||
/// [`Iterator::collect`]: std::iter::Iterator::collect
|
||||
/// [`Pointwise`]: https://google.github.io/googletest/reference/matchers.html#container-matchers
|
||||
/// [`Vec`]: std::vec::Vec
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __pointwise {
|
||||
($matcher:expr, $container:expr) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::PointwiseMatcher::new(
|
||||
$container.into_iter().map($matcher).collect(),
|
||||
)
|
||||
}};
|
||||
|
||||
($matcher:expr, $left_container:expr, $right_container:expr) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::PointwiseMatcher::new(
|
||||
$left_container
|
||||
.into_iter()
|
||||
.zip($right_container.into_iter())
|
||||
.map(|(l, r)| $matcher(l, r))
|
||||
.collect(),
|
||||
)
|
||||
}};
|
||||
|
||||
($matcher:expr, $left_container:expr, $middle_container:expr, $right_container:expr) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::PointwiseMatcher::new(
|
||||
$left_container
|
||||
.into_iter()
|
||||
.zip($right_container.into_iter().zip($middle_container.into_iter()))
|
||||
.map(|(l, (m, r))| $matcher(l, m, r))
|
||||
.collect(),
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Module for use only by the procedural macros in this module.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub mod internal {
|
||||
use crate::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use crate::matcher_support::zipped_iterator::zip;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// This struct is meant to be used only through the `pointwise` macro.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
#[derive(MatcherBase)]
|
||||
pub struct PointwiseMatcher<MatcherT> {
|
||||
matchers: Vec<MatcherT>,
|
||||
}
|
||||
|
||||
impl<MatcherT> PointwiseMatcher<MatcherT> {
|
||||
pub fn new(matchers: Vec<MatcherT>) -> Self {
|
||||
Self { matchers }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, MatcherT: Matcher<T>, ContainerT: Copy + Debug> Matcher<ContainerT>
|
||||
for PointwiseMatcher<MatcherT>
|
||||
where
|
||||
ContainerT: IntoIterator<Item = T>,
|
||||
{
|
||||
fn matches(&self, actual: ContainerT) -> MatcherResult {
|
||||
let mut zipped_iterator = zip(actual.into_iter(), self.matchers.iter());
|
||||
for (element, matcher) in zipped_iterator.by_ref() {
|
||||
if matcher.matches(element).is_no_match() {
|
||||
return MatcherResult::NoMatch;
|
||||
}
|
||||
}
|
||||
if zipped_iterator.has_size_mismatch() {
|
||||
MatcherResult::NoMatch
|
||||
} else {
|
||||
MatcherResult::Match
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ContainerT) -> Description {
|
||||
// TODO(b/260819741) This code duplicates elements_are_matcher.rs. Consider
|
||||
// extract as a separate library. (or implement pointwise! with
|
||||
// elements_are)
|
||||
let actual_iterator = actual.into_iter();
|
||||
let mut zipped_iterator = zip(actual_iterator, self.matchers.iter());
|
||||
let mut mismatches = Vec::new();
|
||||
for (idx, (a, e)) in zipped_iterator.by_ref().enumerate() {
|
||||
if e.matches(a).is_no_match() {
|
||||
mismatches.push(format!("element #{idx} is {a:?}, {}", e.explain_match(a)));
|
||||
}
|
||||
}
|
||||
if mismatches.is_empty() {
|
||||
if !zipped_iterator.has_size_mismatch() {
|
||||
"which matches all elements".into()
|
||||
} else {
|
||||
format!(
|
||||
"which has size {} (expected {})",
|
||||
zipped_iterator.left_size(),
|
||||
self.matchers.len()
|
||||
)
|
||||
.into()
|
||||
}
|
||||
} else if mismatches.len() == 1 {
|
||||
format!("where {}", mismatches[0]).into()
|
||||
} else {
|
||||
let mismatches = mismatches.into_iter().collect::<Description>();
|
||||
format!("where:\n{}", mismatches.bullet_list().indent()).into()
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
format!(
|
||||
"{} elements satisfying respectively:\n{}",
|
||||
if matcher_result.into() { "has" } else { "doesn't have" },
|
||||
self.matchers
|
||||
.iter()
|
||||
.map(|m| m.describe(MatcherResult::Match))
|
||||
.collect::<Description>()
|
||||
.enumerate()
|
||||
.indent()
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Creates a matcher based on the predicate provided.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(3, predicate(|x: i32| x % 2 == 1))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The predicate should take the subject type and return a
|
||||
/// boolean.
|
||||
///
|
||||
/// Note: even if the Rust compiler should be able to infer the type of
|
||||
/// the closure argument, it is likely that it won't.
|
||||
/// See <https://github.com/rust-lang/rust/issues/12679> for update on this issue.
|
||||
/// This is easily fixed by explicitly declaring the type of the argument
|
||||
pub fn predicate<P>(predicate: P) -> PredicateMatcher<P, NoDescription, NoDescription> {
|
||||
PredicateMatcher {
|
||||
predicate,
|
||||
positive_description: NoDescription,
|
||||
negative_description: NoDescription,
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> PredicateMatcher<P, NoDescription, NoDescription> {
|
||||
/// Configures this instance to provide a more meaningful description.
|
||||
///
|
||||
/// For example, to make sure the error message is more useful
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::matchers::{predicate, PredicateMatcher};
|
||||
/// # let _ =
|
||||
/// predicate(|x: i32| x % 2 == 1)
|
||||
/// .with_description("is odd", "is even")
|
||||
/// # ;
|
||||
/// ```
|
||||
///
|
||||
/// This is optional as it only provides value when the test fails.
|
||||
///
|
||||
/// Description can be passed by `&str`, `String` or `Fn() -> Into<String>`.
|
||||
pub fn with_description<D1: PredicateDescription, D2: PredicateDescription>(
|
||||
self,
|
||||
positive_description: D1,
|
||||
negative_description: D2,
|
||||
) -> PredicateMatcher<P, D1, D2> {
|
||||
PredicateMatcher { predicate: self.predicate, positive_description, negative_description }
|
||||
}
|
||||
}
|
||||
|
||||
/// A matcher which applies `predicate` on the value.
|
||||
///
|
||||
/// See [`predicate`].
|
||||
#[derive(MatcherBase)]
|
||||
pub struct PredicateMatcher<P, D1, D2> {
|
||||
predicate: P,
|
||||
positive_description: D1,
|
||||
negative_description: D2,
|
||||
}
|
||||
|
||||
/// A trait to allow [`PredicateMatcher::with_description`] to accept multiple
|
||||
/// types.
|
||||
///
|
||||
/// See [`PredicateMatcher::with_description`]
|
||||
pub trait PredicateDescription {
|
||||
fn to_description(&self) -> Description;
|
||||
}
|
||||
|
||||
impl PredicateDescription for &str {
|
||||
fn to_description(&self) -> Description {
|
||||
self.to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl PredicateDescription for String {
|
||||
fn to_description(&self) -> Description {
|
||||
self.to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> PredicateDescription for T
|
||||
where
|
||||
T: Fn() -> S,
|
||||
S: Into<String>,
|
||||
{
|
||||
fn to_description(&self) -> Description {
|
||||
self().into().into()
|
||||
}
|
||||
}
|
||||
|
||||
// Sentinel type to tag a MatcherBuilder as without a description.
|
||||
#[doc(hidden)]
|
||||
pub struct NoDescription;
|
||||
|
||||
impl<T: Debug + Copy, P> Matcher<T> for PredicateMatcher<P, NoDescription, NoDescription>
|
||||
where
|
||||
P: Fn(T) -> bool,
|
||||
{
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
(self.predicate)(actual).into()
|
||||
}
|
||||
|
||||
fn describe(&self, result: MatcherResult) -> Description {
|
||||
match result {
|
||||
MatcherResult::Match => "matches".into(),
|
||||
MatcherResult::NoMatch => "does not match".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, P, D1: PredicateDescription, D2: PredicateDescription> Matcher<T>
|
||||
for PredicateMatcher<P, D1, D2>
|
||||
where
|
||||
P: Fn(T) -> bool,
|
||||
{
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
(self.predicate)(actual).into()
|
||||
}
|
||||
|
||||
fn describe(&self, result: MatcherResult) -> Description {
|
||||
match result {
|
||||
MatcherResult::Match => self.positive_description.to_description(),
|
||||
MatcherResult::NoMatch => self.negative_description.to_description(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
// Simple matcher with a description
|
||||
fn is_odd() -> impl Matcher<i32> {
|
||||
predicate(|x| x % 2 == 1).with_description("is odd", "is even")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicate_matcher_odd() -> Result<()> {
|
||||
verify_that!(1, is_odd())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicate_matcher_odd_explain_match_matches() -> Result<()> {
|
||||
verify_that!(is_odd().explain_match(1), displays_as(eq("which is odd")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicate_matcher_odd_explain_match_does_not_match() -> Result<()> {
|
||||
verify_that!(is_odd().explain_match(2), displays_as(eq("which is even")))
|
||||
}
|
||||
|
||||
// Simple Matcher without description
|
||||
fn is_even() -> impl Matcher<i32> {
|
||||
predicate(|x| x % 2 == 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicate_matcher_even() -> Result<()> {
|
||||
verify_that!(2, is_even())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicate_matcher_even_explain_match_matches() -> Result<()> {
|
||||
verify_that!(is_even().explain_match(2), displays_as(eq("which matches")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicate_matcher_even_explain_match_does_not_match() -> Result<()> {
|
||||
verify_that!(is_even().explain_match(1), displays_as(eq("which does not match")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicate_matcher_generator_lambda() -> Result<()> {
|
||||
let is_divisible_by = |quotient| {
|
||||
predicate(move |x: i32| x % quotient == 0).with_description(
|
||||
move || format!("is divisible by {quotient}"),
|
||||
move || format!("is not divisible by {quotient}"),
|
||||
)
|
||||
};
|
||||
verify_that!(49, is_divisible_by(7))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicate_matcher_inline() -> Result<()> {
|
||||
verify_that!(2048, predicate(|x: i32| x.count_ones() == 1))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predicate_matcher_function_pointer() -> Result<()> {
|
||||
use std::time::Duration;
|
||||
verify_that!(&Duration::new(0, 0), predicate(Duration::is_zero))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
// 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.
|
||||
|
||||
// There are no visible documentation elements in this module; the declarative
|
||||
// macro is documented in the matcher module.
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Matches an object which, upon calling the given method on it with the given
|
||||
/// arguments, produces a value matched by the given inner matcher.
|
||||
///
|
||||
/// This is particularly useful as a nested matcher when the desired
|
||||
/// property cannot be accessed through a field and must instead be
|
||||
/// extracted through a method call. For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct MyStruct {
|
||||
/// a_field: u32,
|
||||
/// }
|
||||
///
|
||||
/// impl MyStruct {
|
||||
/// pub fn get_a_field(&self) -> u32 { self.a_field }
|
||||
/// }
|
||||
///
|
||||
/// let value = vec![MyStruct { a_field: 100 }];
|
||||
/// verify_that!(value, contains(property!(&MyStruct.get_a_field(), eq(100))))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// If the inner matcher is `eq(...)`, it can be omitted:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct MyStruct {
|
||||
/// a_field: u32,
|
||||
/// }
|
||||
///
|
||||
/// impl MyStruct {
|
||||
/// pub fn get_a_field(&self) -> u32 { self.a_field }
|
||||
/// }
|
||||
///
|
||||
/// let value = vec![MyStruct { a_field: 100 }];
|
||||
/// verify_that!(value, contains(property!(&MyStruct.get_a_field(), 100)))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// **Important**: The method should be pure function with a deterministic
|
||||
/// output and no side effects. In particular, in the event of an assertion
|
||||
/// failure, it will be invoked a second time, with the assertion failure output
|
||||
/// reflecting the *second* invocation.
|
||||
///
|
||||
/// The method may also take additional litteral arguments:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # #[derive(Debug)]
|
||||
/// # pub struct MyStruct {
|
||||
/// # a_field: u32,
|
||||
/// # }
|
||||
/// impl MyStruct {
|
||||
/// pub fn add_to_a_field(&self, a: u32) -> u32 { self.a_field + a }
|
||||
/// }
|
||||
///
|
||||
/// # let value = vec![MyStruct { a_field: 100 }];
|
||||
/// verify_that!(value, contains(property!(&MyStruct.add_to_a_field(50), eq(150))))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The arguments must be litteral as `property!` is not able to capture them.
|
||||
///
|
||||
/// # Specification of the property pattern
|
||||
///
|
||||
/// The specification of the field follow the syntax: `(ref)? (&)?
|
||||
/// $TYPE.$PROPERTY\($ARGUMENT\)`.
|
||||
///
|
||||
/// The `&` allows to specify whether this matcher matches against an actual of
|
||||
/// type `$TYPE` (`$TYPE` must implement `Copy`) or a `&$TYPE`.
|
||||
///
|
||||
/// For instance:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct AStruct;
|
||||
///
|
||||
/// impl AStruct {
|
||||
/// fn a_property(&self) -> i32 {32}
|
||||
/// }
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(AStruct, property!(&AStruct.a_property(), eq(32)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug, Clone, Copy)]
|
||||
/// pub struct AStruct;
|
||||
///
|
||||
/// impl AStruct {
|
||||
/// fn a_property(self) -> i32 {32}
|
||||
/// }
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(AStruct, property!(AStruct.a_property(), eq(32)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The `ref` allows to bind the property returned value by reference, which is
|
||||
/// required if the field type does not implement `Copy`.
|
||||
///
|
||||
/// For instance:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct AStruct;
|
||||
///
|
||||
/// impl AStruct {
|
||||
/// fn a_property(&self) -> i32 {32}
|
||||
/// }
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(AStruct, property!(&AStruct.a_property(), eq(32)))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// If `property!` is qualified by both `&` and `ref`, they can both be omitted.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct AStruct;
|
||||
///
|
||||
/// impl AStruct {
|
||||
/// fn a_property(&self) -> String {"32".into()}
|
||||
/// }
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(AStruct, property!(&AStruct.a_property(), ref eq("32")))?;
|
||||
/// verify_that!(AStruct, property!(AStruct.a_property(), eq("32")))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// This macro is analogous to [`field`][crate::matchers::field], except that it
|
||||
/// extracts the datum to be matched from the given object by invoking a method
|
||||
/// rather than accessing a field.
|
||||
///
|
||||
/// The list of arguments may optionally have a trailing comma.
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __property {
|
||||
($($t:tt)*) => { $crate::property_internal!($($t)*) }
|
||||
}
|
||||
|
||||
// Internal-only macro created so that the macro definition does not appear in
|
||||
// generated documentation.
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! property_internal {
|
||||
// `ref` variant.
|
||||
(
|
||||
// The 3 cases of `$(& $($_amp:literal)?)?` -> `$(& $($_amp)*)*`
|
||||
// 1. outer group captures nothing => expansion produces nothing
|
||||
// 2. outer group captures just `&` => expansion produces `&`
|
||||
// 3. outer group captures `& <literal>` => disallowed by `@assert_empty` subrule invocation
|
||||
//
|
||||
// `$_amp:literal` works only because the following `$t:ident` or `::` can't be captured by
|
||||
// it.
|
||||
$(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`.
|
||||
$(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`.
|
||||
$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)?
|
||||
.$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?),
|
||||
ref $m:expr
|
||||
) => {{
|
||||
$crate::property_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*);
|
||||
$crate::property_internal!(
|
||||
@self_arg
|
||||
struct_type: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::+ $( <$($t_ty_args),*>)*]
|
||||
method_prefix: [ $(:: $($_cs)*)* $($t)::+ $(::<$($t_ty_args),*>)*]
|
||||
[$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m])
|
||||
}};
|
||||
|
||||
// Non-`ref` variant.
|
||||
(
|
||||
// See comment on previous variant above.
|
||||
$(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`.
|
||||
$(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`.
|
||||
$($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)?
|
||||
.$method:tt $(::<$($m_ty_args:ty),* $(,)?>)? ($($argument:expr),* $(,)?),
|
||||
$m:expr) => {{
|
||||
$crate::property_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*);
|
||||
$crate::property_internal!(
|
||||
@self_dot
|
||||
struct_type: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::+ $(<$($t_ty_args),*>)*]
|
||||
[$method $(::<$($m_ty_args),*>)*] [$($argument),*] [$m])
|
||||
}};
|
||||
|
||||
(@assert_empty) => {};
|
||||
(@assert_empty $($l:literal)+) => {
|
||||
compile_error!("property! argument must start with an optional `&` followed by a path")
|
||||
};
|
||||
|
||||
(@self_arg struct_type: [$struct_ty:ty]
|
||||
method_prefix: [$($method_prefix:tt)+]
|
||||
[$($method:tt)*] [$($argument:expr),*] [$m:expr]) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::property_ref_matcher(
|
||||
|o: $struct_ty| $($method_prefix)*::$($method)* (o, $($argument),*),
|
||||
&stringify!($($method)* ($($argument),*)),
|
||||
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m))
|
||||
}};
|
||||
|
||||
(@self_dot struct_type: [$struct_ty:ty]
|
||||
[$($method:tt)*] [$($argument:expr),*] [$m:expr]) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::property_matcher(
|
||||
|o: &$struct_ty| o.$($method)* ($($argument),*),
|
||||
&stringify!($($method)* ($($argument),*)),
|
||||
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m))
|
||||
}};
|
||||
}
|
||||
|
||||
/// Items for use only by the declarative macros in this module.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub mod internal {
|
||||
use crate::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub fn property_matcher<OuterT: Debug, InnerT: Debug, MatcherT>(
|
||||
extractor: fn(&OuterT) -> InnerT,
|
||||
property_desc: &'static str,
|
||||
inner: MatcherT,
|
||||
) -> PropertyMatcher<OuterT, InnerT, MatcherT> {
|
||||
PropertyMatcher { extractor, property_desc, inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct PropertyMatcher<OuterT, InnerT, MatcherT> {
|
||||
extractor: fn(&OuterT) -> InnerT,
|
||||
property_desc: &'static str,
|
||||
inner: MatcherT,
|
||||
}
|
||||
|
||||
impl<InnerT, OuterT, MatcherT> Matcher<OuterT> for PropertyMatcher<OuterT, InnerT, MatcherT>
|
||||
where
|
||||
InnerT: Debug + Copy,
|
||||
OuterT: Debug + Copy,
|
||||
MatcherT: Matcher<InnerT>,
|
||||
{
|
||||
fn matches(&self, actual: OuterT) -> MatcherResult {
|
||||
self.inner.matches((self.extractor)(&actual))
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
format!(
|
||||
"has property `{}`, which {}",
|
||||
self.property_desc,
|
||||
self.inner.describe(matcher_result)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: OuterT) -> Description {
|
||||
let actual_inner = (self.extractor)(&actual);
|
||||
format!(
|
||||
"whose property `{}` is `{:#?}`, {}",
|
||||
self.property_desc,
|
||||
actual_inner,
|
||||
self.inner.explain_match(actual_inner)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, InnerT, OuterT, MatcherT> Matcher<&'a OuterT> for PropertyMatcher<OuterT, InnerT, MatcherT>
|
||||
where
|
||||
InnerT: Debug,
|
||||
OuterT: Debug,
|
||||
MatcherT: for<'b> Matcher<&'b InnerT>,
|
||||
{
|
||||
fn matches(&self, actual: &'a OuterT) -> MatcherResult {
|
||||
self.inner.matches(&(self.extractor)(actual))
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
format!(
|
||||
"has property `{}`, which {}",
|
||||
self.property_desc,
|
||||
self.inner.describe(matcher_result)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a OuterT) -> Description {
|
||||
let actual_inner = (self.extractor)(actual);
|
||||
format!(
|
||||
"whose property `{}` is `{:#?}`, {}",
|
||||
self.property_desc,
|
||||
actual_inner,
|
||||
self.inner.explain_match(&actual_inner)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub fn property_ref_matcher<OuterT, InnerT, ExtractorT, MatcherT>(
|
||||
extractor: ExtractorT,
|
||||
property_desc: &'static str,
|
||||
inner: MatcherT,
|
||||
) -> PropertyRefMatcher<ExtractorT, MatcherT>
|
||||
where
|
||||
OuterT: Debug,
|
||||
InnerT: Debug,
|
||||
MatcherT: for<'a> Matcher<&'a InnerT>,
|
||||
ExtractorT: Fn(OuterT) -> InnerT,
|
||||
{
|
||||
PropertyRefMatcher { extractor, property_desc, inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct PropertyRefMatcher<ExtractorT, MatcherT> {
|
||||
extractor: ExtractorT,
|
||||
property_desc: &'static str,
|
||||
inner: MatcherT,
|
||||
}
|
||||
|
||||
impl<
|
||||
InnerT: Debug,
|
||||
OuterT: Debug + Copy,
|
||||
MatcherT: for<'a> Matcher<&'a InnerT>,
|
||||
ExtractorT: Fn(OuterT) -> InnerT,
|
||||
> Matcher<OuterT> for PropertyRefMatcher<ExtractorT, MatcherT>
|
||||
{
|
||||
fn matches(&self, actual: OuterT) -> MatcherResult {
|
||||
self.inner.matches(&(self.extractor)(actual))
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
format!(
|
||||
"has property `{}`, which {}",
|
||||
self.property_desc,
|
||||
self.inner.describe(matcher_result)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: OuterT) -> Description {
|
||||
let actual_inner = (self.extractor)(actual);
|
||||
format!(
|
||||
"whose property `{}` is `{:#?}`, {}",
|
||||
self.property_desc,
|
||||
actual_inner,
|
||||
self.inner.explain_match(&actual_inner)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
/// Matches a value where the result of `callable` applied to the value matches
|
||||
/// the inner matcher.
|
||||
///
|
||||
/// The `callable` will be called twice, so make sure it is pure.
|
||||
/// ```
|
||||
/// use googletest::prelude::*;
|
||||
/// fn should_pass() -> googletest::Result<()> {
|
||||
/// verify_that!(100, result_of!(|value| value + 1, eq(101)))?; // Passes
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn should_fail() -> googletest::Result<()> {
|
||||
/// verify_that!(100, result_of!(|value| value * 2, eq(100)))?; // Fails
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// should_pass().unwrap();
|
||||
/// should_fail().unwrap_err();
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! __result_of {
|
||||
($function: expr, $matcher: expr) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::result_of(
|
||||
$function,
|
||||
$matcher,
|
||||
stringify!($function),
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Matches a value where the reference to the result of `callable` applied to
|
||||
/// the value matches the inner matcher.
|
||||
///
|
||||
/// The `callable` will be called twice, so make sure it is pure.
|
||||
/// ```
|
||||
/// use googletest::prelude::*;
|
||||
/// fn should_pass_1() -> googletest::Result<()> {
|
||||
/// verify_that!("hello", result_of_ref!(|s: &str| s.to_uppercase(), eq("HELLO")))?; // Passes
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn should_pass_2() -> googletest::Result<()> {
|
||||
/// verify_that!(100, result_of_ref!(|value| value + 1, eq(&101)))?; // Passes
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn should_fail() -> googletest::Result<()> {
|
||||
/// verify_that!("world", result_of_ref!(|s: &str| s.to_uppercase(), eq("HELLO")))?; // Passes
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// should_pass_1().unwrap();
|
||||
/// should_pass_2().unwrap();
|
||||
/// should_fail().unwrap_err();
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! __result_of_ref {
|
||||
($function: expr, $matcher: expr) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::result_of_ref(
|
||||
$function,
|
||||
$matcher,
|
||||
stringify!($function),
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Items for use only by the declarative macros in this module.
|
||||
///
|
||||
/// **For internal use only. API stability is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub mod internal {
|
||||
use crate::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub fn result_of<Callable, InnerMatcher>(
|
||||
callable: Callable,
|
||||
inner_matcher: InnerMatcher,
|
||||
callable_description: &'static str,
|
||||
) -> ResultOfMatcher<Callable, InnerMatcher> {
|
||||
ResultOfMatcher { callable, inner_matcher, callable_description }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct ResultOfMatcher<Callable, InnerMatcher> {
|
||||
callable: Callable,
|
||||
inner_matcher: InnerMatcher,
|
||||
callable_description: &'static str,
|
||||
}
|
||||
|
||||
impl<I: Copy + Debug, T: Debug + Copy, CallableT: Fn(I) -> T, InnerMatcherT: Matcher<T>>
|
||||
Matcher<I> for ResultOfMatcher<CallableT, InnerMatcherT>
|
||||
{
|
||||
fn matches(&self, actual: I) -> MatcherResult {
|
||||
self.inner_matcher.matches((self.callable)(actual))
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
Description::new()
|
||||
.text(format!("by applying {},", self.callable_description))
|
||||
.nested(self.inner_matcher.describe(matcher_result))
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: I) -> Description {
|
||||
let actual_result = (self.callable)(actual);
|
||||
Description::new()
|
||||
.text(format!("which, results into {actual_result:?}",))
|
||||
.nested(self.describe(self.matches(actual)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn result_of_ref<Callable, InnerMatcher>(
|
||||
callable: Callable,
|
||||
inner_matcher: InnerMatcher,
|
||||
callable_description: &'static str,
|
||||
) -> ResultOfRefMatcher<Callable, InnerMatcher> {
|
||||
ResultOfRefMatcher { callable, inner_matcher, callable_description }
|
||||
}
|
||||
#[derive(MatcherBase)]
|
||||
pub struct ResultOfRefMatcher<Callable, InnerMatcher> {
|
||||
callable: Callable,
|
||||
inner_matcher: InnerMatcher,
|
||||
callable_description: &'static str,
|
||||
}
|
||||
|
||||
impl<
|
||||
I: Copy + Debug,
|
||||
T: Debug,
|
||||
Callable: Fn(I) -> T,
|
||||
InnerMatcherT: for<'a> Matcher<&'a T>,
|
||||
> Matcher<I> for ResultOfRefMatcher<Callable, InnerMatcherT>
|
||||
{
|
||||
fn matches(&self, actual: I) -> MatcherResult {
|
||||
self.inner_matcher.matches(&(self.callable)(actual))
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
Description::new()
|
||||
.text(format!("by applying {},", self.callable_description))
|
||||
.nested(self.inner_matcher.describe(matcher_result))
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: I) -> Description {
|
||||
let actual_result = (self.callable)(actual);
|
||||
Description::new()
|
||||
.text(format!("which, results into {actual_result:?}",))
|
||||
.nested(self.describe(self.matches(actual)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn result_of_match_with_value() -> Result<()> {
|
||||
verify_that!(1, result_of!(|value| value + 1, eq(2)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_match_with_value_function() -> Result<()> {
|
||||
fn inc_by_one(value: i32) -> i32 {
|
||||
value + 1
|
||||
}
|
||||
verify_that!(1, result_of!(inc_by_one, eq(2)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_match_with_different_value() -> Result<()> {
|
||||
let result = verify_that!(0, result_of!(|value| value - 1, eq(2)));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 0
|
||||
Expected: by applying |value| value - 1,
|
||||
is equal to 2
|
||||
Actual: 0,
|
||||
which, results into -1
|
||||
by applying |value| value - 1,
|
||||
isn't equal to 2
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_match_with_different_value_block_closure() -> Result<()> {
|
||||
let result = verify_that!(0, result_of!(|value| { value - 1 }, eq(2)));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 0
|
||||
Expected: by applying |value| { value - 1 },
|
||||
is equal to 2
|
||||
Actual: 0,
|
||||
which, results into -1
|
||||
by applying |value| { value - 1 },
|
||||
isn't equal to 2
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_match_with_different_value_multiline_closure() -> Result<()> {
|
||||
let result = verify_that!(
|
||||
0,
|
||||
result_of!(
|
||||
|value| {
|
||||
let dec = value - 1;
|
||||
let inc = dec + 1;
|
||||
inc - 2
|
||||
},
|
||||
eq(2)
|
||||
)
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 0
|
||||
Expected: by applying |value| { let dec = value - 1; let inc = dec + 1; inc - 2 },
|
||||
is equal to 2
|
||||
Actual: 0,
|
||||
which, results into -2
|
||||
by applying |value| { let dec = value - 1; let inc = dec + 1; inc - 2 },
|
||||
isn't equal to 2
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_match_with_different_value_function() -> Result<()> {
|
||||
fn dec_by_one(value: i32) -> i32 {
|
||||
value - 1
|
||||
}
|
||||
let result = verify_that!(0, result_of!(dec_by_one, eq(2)));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: 0
|
||||
Expected: by applying dec_by_one,
|
||||
is equal to 2
|
||||
Actual: 0,
|
||||
which, results into -1
|
||||
by applying dec_by_one,
|
||||
isn't equal to 2
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_ref_match_with_string_reference() -> Result<()> {
|
||||
verify_that!("hello", result_of_ref!(|s: &str| s.to_uppercase(), eq("HELLO")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_ref_match_with_string_reference_function() -> Result<()> {
|
||||
fn to_upper_case<S: AsRef<str>>(s: S) -> String {
|
||||
s.as_ref().to_uppercase()
|
||||
}
|
||||
verify_that!("hello", result_of_ref!(to_upper_case, eq("HELLO")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_ref_match_with_copy_types() -> Result<()> {
|
||||
verify_that!(100, result_of_ref!(|value| value + 1, eq(&101)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_ref_match_with_different_value() -> Result<()> {
|
||||
let result = verify_that!("world", result_of_ref!(|s: &str| s.to_uppercase(), eq("HELLO")));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
r#"
|
||||
Value of: "world"
|
||||
Expected: by applying |s: &str| s.to_uppercase(),
|
||||
is equal to "HELLO"
|
||||
Actual: "world",
|
||||
which, results into "WORLD"
|
||||
by applying |s: &str| s.to_uppercase(),
|
||||
isn't equal to "HELLO""#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_ref_match_with_different_value_block_closure() -> Result<()> {
|
||||
let result =
|
||||
verify_that!("world", result_of_ref!(|s: &str| { s.to_uppercase() }, eq("HELLO")));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
r#"
|
||||
Value of: "world"
|
||||
Expected: by applying |s: &str| { s.to_uppercase() },
|
||||
is equal to "HELLO"
|
||||
Actual: "world",
|
||||
which, results into "WORLD"
|
||||
by applying |s: &str| { s.to_uppercase() },
|
||||
isn't equal to "HELLO"
|
||||
"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_ref_match_with_different_value_function() -> Result<()> {
|
||||
fn to_upper_case<S: AsRef<str>>(s: S) -> String {
|
||||
s.as_ref().to_uppercase()
|
||||
}
|
||||
let result = verify_that!("world", result_of_ref!(to_upper_case, eq("HELLO")));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
r#"
|
||||
Value of: "world"
|
||||
Expected: by applying to_upper_case,
|
||||
is equal to "HELLO"
|
||||
Actual: "world",
|
||||
which, results into "WORLD"
|
||||
by applying to_upper_case,
|
||||
isn't equal to "HELLO"
|
||||
"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_ref_match_different_with_closure_variable() -> Result<()> {
|
||||
let to_upper_case = |s: &str| s.to_uppercase();
|
||||
let result = verify_that!("world", result_of_ref!(to_upper_case, eq("HELLO")));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
r#"
|
||||
Value of: "world"
|
||||
Expected: by applying to_upper_case,
|
||||
is equal to "HELLO"
|
||||
Actual: "world",
|
||||
which, results into "WORLD"
|
||||
by applying to_upper_case,
|
||||
isn't equal to "HELLO"
|
||||
"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_ref_match_different_with_method_literal() -> Result<()> {
|
||||
let result = verify_that!("world", result_of_ref!(str::to_uppercase, eq("HELLO")));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
r#"
|
||||
Value of: "world"
|
||||
Expected: by applying str::to_uppercase,
|
||||
is equal to "HELLO"
|
||||
Actual: "world",
|
||||
which, results into "WORLD"
|
||||
by applying str::to_uppercase,
|
||||
isn't equal to "HELLO"
|
||||
"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_of_ref_match_different_with_function_return_closure() -> Result<()> {
|
||||
fn upper_case() -> impl Fn(&str) -> String {
|
||||
|s: &str| s.to_uppercase()
|
||||
}
|
||||
let result = verify_that!("world", result_of_ref!(upper_case(), eq("HELLO")));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
r#"
|
||||
Value of: "world"
|
||||
Expected: by applying upper_case(),
|
||||
is equal to "HELLO"
|
||||
Actual: "world",
|
||||
which, results into "WORLD"
|
||||
by applying upper_case(),
|
||||
isn't equal to "HELLO"
|
||||
"#
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_describe_simple() -> Result<()> {
|
||||
let matcher = result_of!(|x| x + 1, eq(2));
|
||||
let description = matcher.describe(matcher.matches(0));
|
||||
verify_that!(
|
||||
description,
|
||||
displays_as(eq(indoc!(
|
||||
r#"
|
||||
by applying |x| x + 1,
|
||||
isn't equal to 2"#
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_describe_complicated() -> Result<()> {
|
||||
let matcher = result_of_ref!(
|
||||
|s: &str| s.chars().collect::<Vec<_>>(),
|
||||
each(predicate(char::is_ascii_alphabetic))
|
||||
);
|
||||
let description = matcher.describe(matcher.matches("A quick brown fox"));
|
||||
verify_that!(
|
||||
description,
|
||||
displays_as(eq(indoc!(
|
||||
r#"
|
||||
by applying |s: &str| s.chars().collect::<Vec<_>>(),
|
||||
contains no element that matches"#
|
||||
)))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches an `Option` containing a value matched by `inner`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(Some("Some value"), some(eq("Some value")))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!(None::<&str>, some(eq("Some value")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!(Some("Some value"), some(eq("Some other value")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// ```
|
||||
pub fn some<Inner>(inner: Inner) -> SomeMatcher<Inner> {
|
||||
SomeMatcher { inner }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct SomeMatcher<InnerMatcherT> {
|
||||
inner: InnerMatcherT,
|
||||
}
|
||||
|
||||
impl<T: Debug + Copy, InnerMatcherT: Matcher<T>> Matcher<Option<T>> for SomeMatcher<InnerMatcherT> {
|
||||
fn matches(&self, actual: Option<T>) -> MatcherResult {
|
||||
actual.map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: Option<T>) -> Description {
|
||||
match (self.matches(actual), actual) {
|
||||
(_, Some(t)) => {
|
||||
Description::new().text("which has a value").nested(self.inner.explain_match(t))
|
||||
}
|
||||
(_, None) => "which is None".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("has a value which {}", self.inner.describe(MatcherResult::Match)).into()
|
||||
}
|
||||
MatcherResult::NoMatch => format!(
|
||||
"is None or has a value which {}",
|
||||
self.inner.describe(MatcherResult::NoMatch)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Debug, InnerMatcherT: Matcher<&'a T>> Matcher<&'a Option<T>>
|
||||
for SomeMatcher<InnerMatcherT>
|
||||
{
|
||||
fn matches(&self, actual: &'a Option<T>) -> MatcherResult {
|
||||
actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a Option<T>) -> Description {
|
||||
match (self.matches(actual), actual) {
|
||||
(_, Some(t)) => {
|
||||
Description::new().text("which has a value").nested(self.inner.explain_match(t))
|
||||
}
|
||||
(_, None) => "which is None".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
format!("has a value which {}", self.inner.describe(MatcherResult::Match)).into()
|
||||
}
|
||||
MatcherResult::NoMatch => format!(
|
||||
"is None or has a value which {}",
|
||||
self.inner.describe(MatcherResult::NoMatch)
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn some_matches_option_with_value() -> Result<()> {
|
||||
let matcher = some(eq(1));
|
||||
|
||||
let result = matcher.matches(Some(1));
|
||||
|
||||
verify_that!(result, eq(MatcherResult::Match))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_does_not_match_option_with_wrong_value() -> Result<()> {
|
||||
let matcher = some(eq(1));
|
||||
|
||||
let result = matcher.matches(Some(0));
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_does_not_match_option_with_none() -> Result<()> {
|
||||
let matcher = some(eq(1));
|
||||
|
||||
let result = matcher.matches(None::<i32>);
|
||||
|
||||
verify_that!(result, eq(MatcherResult::NoMatch))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_matches_option_with_by_ref_value() -> Result<()> {
|
||||
verify_that!(Some("123".to_string()), some(eq("123")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_does_not_match_option_with_wrong_by_ref_value() -> Result<()> {
|
||||
verify_that!(Some("321".to_string()), not(some(eq("123"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_does_not_match_option_with_by_ref_none() -> Result<()> {
|
||||
verify_that!(None::<String>, not(some(eq("123"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_full_error_message() -> Result<()> {
|
||||
let result = verify_that!(Some(2), some(eq(1)));
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: Some(2)
|
||||
Expected: has a value which is equal to 1
|
||||
Actual: Some(2),
|
||||
which has a value
|
||||
which isn't equal to 1
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_describe_matches() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<Option<i32>>::describe(&some(eq(1)), MatcherResult::Match),
|
||||
displays_as(eq("has a value which is equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_describe_does_not_match() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<Option<i32>>::describe(&some(eq(1)), MatcherResult::NoMatch),
|
||||
displays_as(eq("is None or has a value which isn't equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_describe_matches_of_by_ref() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<Option<&String>>::describe(&some(eq("123")), MatcherResult::Match),
|
||||
displays_as(eq("has a value which is equal to \"123\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_describe_does_not_match_of_by_ref() -> Result<()> {
|
||||
verify_that!(
|
||||
Matcher::<Option<&String>>::describe(&some(eq("123")), MatcherResult::NoMatch),
|
||||
displays_as(eq("is None or has a value which isn't equal to \"123\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_explain_match_with_none() -> Result<()> {
|
||||
verify_that!(some(eq(1)).explain_match(None::<i32>), displays_as(eq("which is None")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_explain_match_with_some_success() -> Result<()> {
|
||||
verify_that!(
|
||||
some(eq(1)).explain_match(Some(1)),
|
||||
displays_as(eq("which has a value\n which is equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_explain_match_with_some_fail() -> Result<()> {
|
||||
verify_that!(
|
||||
some(eq(1)).explain_match(Some(2)),
|
||||
displays_as(eq("which has a value\n which isn't equal to 1"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_explain_match_with_none_by_ref() -> Result<()> {
|
||||
verify_that!(
|
||||
some(eq("123")).explain_match(&None::<String>),
|
||||
displays_as(eq("which is None"))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_explain_match_with_some_success_by_ref() -> Result<()> {
|
||||
verify_that!(
|
||||
some(eq("123")).explain_match(&Some("123".to_string())),
|
||||
displays_as(eq("which has a value\n which is equal to \"123\""))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_explain_match_with_some_fail_by_ref() -> Result<()> {
|
||||
verify_that!(
|
||||
some(eq("123")).explain_match(&Some("321".to_string())),
|
||||
displays_as(eq("which has a value\n which isn't equal to \"123\""))
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,253 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a container all of whose items are in the given container
|
||||
/// `superset`.
|
||||
///
|
||||
/// The element type `ElementT` must implement `PartialEq` to allow element
|
||||
/// comparison.
|
||||
///
|
||||
/// `ActualT` and `ExpectedT` can each be any container which
|
||||
/// implements `IntoIterator`. For instance, `T` can be a common container like
|
||||
/// `&Vec` or arrays. They need not be the same container type.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use std::collections::HashSet;
|
||||
/// # fn should_pass_1() -> Result<()> {
|
||||
/// let value = vec![1, 2, 3];
|
||||
/// verify_that!(value, subset_of([&1, &2, &3, &4]))?; // Passes
|
||||
/// let array_value = [1, 2, 3];
|
||||
/// verify_that!(array_value, subset_of([1, 2, 3, 4]))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// # let value = vec![1, 2, 3];
|
||||
/// verify_that!(value, subset_of([&1, &2]))?; // Fails: 3 is not in the superset
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass_1().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
///
|
||||
/// # fn should_pass_2() -> Result<()> {
|
||||
/// let value: HashSet<i32> = [1, 2, 3].into();
|
||||
/// verify_that!(value, subset_of([&1, &2, &3]))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass_2().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Item multiplicity in both the actual and expected containers is ignored:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let value: Vec<i32> = vec![0, 0, 1];
|
||||
/// verify_that!(value, subset_of([&0, &1]))?; // Passes
|
||||
/// verify_that!(value, subset_of([&0, &1, &1]))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// A note on performance: This matcher uses a naive algorithm with a worst-case
|
||||
/// runtime proportional to the *product* of the sizes of the actual and
|
||||
/// expected containers as well as the time to check equality of each pair of
|
||||
/// items. It should not be used on especially large containers.
|
||||
pub fn subset_of<ExpectedT>(superset: ExpectedT) -> SubsetOfMatcher<ExpectedT> {
|
||||
SubsetOfMatcher { superset }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct SubsetOfMatcher<ExpectedT> {
|
||||
superset: ExpectedT,
|
||||
}
|
||||
|
||||
impl<ElementT: Debug + PartialEq + Copy, ActualT: Debug + Copy, ExpectedT: Debug> Matcher<ActualT>
|
||||
for SubsetOfMatcher<ExpectedT>
|
||||
where
|
||||
ActualT: IntoIterator<Item = ElementT>,
|
||||
for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
for actual_item in actual {
|
||||
if self.expected_is_missing(&actual_item) {
|
||||
return MatcherResult::NoMatch;
|
||||
}
|
||||
}
|
||||
MatcherResult::Match
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ActualT) -> Description {
|
||||
let unexpected_elements = actual
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|item| self.expected_is_missing(&item.1))
|
||||
.map(|(idx, actual_item)| format!("{actual_item:#?} at #{idx}"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match unexpected_elements.len() {
|
||||
0 => "which no element is unexpected".into(),
|
||||
1 => format!("whose element {} is unexpected", &unexpected_elements[0]).into(),
|
||||
_ => format!("whose elements {} are unexpected", unexpected_elements.join(", ")).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!("is a subset of {:#?}", self.superset).into(),
|
||||
MatcherResult::NoMatch => format!("isn't a subset of {:#?}", self.superset).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ElementT: PartialEq, ExpectedT> SubsetOfMatcher<ExpectedT>
|
||||
where
|
||||
for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
|
||||
{
|
||||
fn expected_is_missing(&self, needle: &ElementT) -> bool {
|
||||
!self.superset.into_iter().any(|item| item == needle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn subset_of_matches_empty_vec() -> Result<()> {
|
||||
let value: Vec<i32> = vec![];
|
||||
verify_that!(value, subset_of([]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_matches_vec_with_one_element() -> Result<()> {
|
||||
let value = vec![1];
|
||||
verify_that!(value, subset_of([&1]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_matches_vec_with_two_elements() -> Result<()> {
|
||||
let value = vec![1, 2];
|
||||
verify_that!(value, subset_of([&1, &2]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_matches_vec_when_expected_has_excess_element() -> Result<()> {
|
||||
let value = vec![1, 2];
|
||||
verify_that!(value, subset_of([&1, &2, &3]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_matches_vec_when_expected_has_excess_element_first() -> Result<()> {
|
||||
let value = vec![1, 2];
|
||||
verify_that!(value, subset_of([&3, &1, &2]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_matches_slice_with_one_element() -> Result<()> {
|
||||
let value = &[1];
|
||||
verify_that!(value, subset_of([&1]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_matches_hash_set_with_one_element() -> Result<()> {
|
||||
let value: HashSet<i32> = [1].into();
|
||||
verify_that!(value, subset_of([&1]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_does_not_match_when_first_element_does_not_match() -> Result<()> {
|
||||
let value = vec![0];
|
||||
verify_that!(value, not(subset_of([&1])))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_does_not_match_when_second_element_does_not_match() -> Result<()> {
|
||||
let value = vec![2, 0];
|
||||
verify_that!(value, not(subset_of([&2])))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
|
||||
let result = verify_that!(vec![0, 2, 3], subset_of([&1, &2, &3]));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![0, 2, 3]
|
||||
Expected: is a subset of [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
Actual: [0, 2, 3],
|
||||
whose element 0 at #0 is unexpected
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
|
||||
let result = verify_that!(vec![1, 0, 3], subset_of([&1, &2, &3]));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![1, 0, 3]
|
||||
Expected: is a subset of [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
Actual: [1, 0, 3],
|
||||
whose element 0 at #1 is unexpected
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_of_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
|
||||
let result = verify_that!(vec![0, 0, 3], subset_of([&1, &2, &3]));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![0, 0, 3]
|
||||
Expected: is a subset of [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
Actual: [0, 0, 3],
|
||||
whose elements 0 at #0, 0 at #1 are unexpected
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright 2022 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::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Matches a container containing all of the items in the given container
|
||||
/// `subset`.
|
||||
///
|
||||
/// The element type `ElementT` must implement `PartialEq` to allow element
|
||||
/// comparison.
|
||||
///
|
||||
/// `ActualT` and `ExpectedT` can each be any container which implements
|
||||
/// `IntoIterator`. For instance, `ActualT` and `ExpectedT` can be a
|
||||
/// common container like `&Vec`, arrays or slices. They need not be the same
|
||||
/// container type.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use std::collections::HashSet;
|
||||
/// # fn should_pass_1() -> Result<()> {
|
||||
/// let value = vec![1, 2, 3];
|
||||
/// verify_that!(value, superset_of([&1, &2]))?; // Passes
|
||||
/// let array_value = [1, 2, 3];
|
||||
/// verify_that!(array_value, superset_of([1, 2]))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// # let value = vec![1, 2, 3];
|
||||
/// verify_that!(value, superset_of([&1, &2, &4]))?; // Fails: 4 is not in the subset
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass_1().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
///
|
||||
/// # fn should_pass_2() -> Result<()> {
|
||||
/// let value: HashSet<i32> = [1, 2, 3].into();
|
||||
/// verify_that!(value, superset_of([&1, &2, &3]))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass_2().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Item multiplicity in both the actual and expected containers is ignored:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// let value: Vec<i32> = vec![0, 0, 1];
|
||||
/// verify_that!(value, superset_of([&0, &1]))?; // Passes
|
||||
/// verify_that!(value, superset_of([&0, &1, &1]))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// A note on performance: This matcher uses a naive algorithm with a worst-case
|
||||
/// runtime proportional to the *product* of the sizes of the actual and
|
||||
/// expected containers as well as the time to check equality of each pair of
|
||||
/// items. It should not be used on especially large containers.
|
||||
pub fn superset_of<ExpectedT>(subset: ExpectedT) -> SupersetOfMatcher<ExpectedT> {
|
||||
SupersetOfMatcher { subset }
|
||||
}
|
||||
|
||||
#[derive(MatcherBase)]
|
||||
pub struct SupersetOfMatcher<ExpectedT> {
|
||||
subset: ExpectedT,
|
||||
}
|
||||
|
||||
impl<ElementT: Debug + Copy + PartialEq, ActualT: Debug + Copy, ExpectedT: Debug> Matcher<ActualT>
|
||||
for SupersetOfMatcher<ExpectedT>
|
||||
where
|
||||
ActualT: IntoIterator<Item = ElementT>,
|
||||
for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
|
||||
{
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult {
|
||||
for expected_item in &self.subset {
|
||||
if actual_is_missing(actual, expected_item) {
|
||||
return MatcherResult::NoMatch;
|
||||
}
|
||||
}
|
||||
MatcherResult::Match
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ActualT) -> Description {
|
||||
let missing_items: Vec<_> = self
|
||||
.subset
|
||||
.into_iter()
|
||||
.filter(|expected_item| actual_is_missing(actual, *expected_item))
|
||||
.map(|expected_item| format!("{expected_item:#?}"))
|
||||
.collect();
|
||||
match missing_items.len() {
|
||||
0 => "whose no element is missing".into(),
|
||||
1 => format!("whose element {} is missing", &missing_items[0]).into(),
|
||||
_ => format!("whose elements {} are missing", missing_items.join(", ")).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => format!("is a superset of {:#?}", self.subset).into(),
|
||||
MatcherResult::NoMatch => format!("isn't a superset of {:#?}", self.subset).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actual_is_missing<ElementT: PartialEq, ActualT>(actual: ActualT, needle: &ElementT) -> bool
|
||||
where
|
||||
ActualT: IntoIterator<Item = ElementT>,
|
||||
{
|
||||
!actual.into_iter().any(|item| &item == needle)
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate as googletest;
|
||||
use crate::prelude::*;
|
||||
use indoc::indoc;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn superset_of_matches_empty_vec() -> googletest::Result<()> {
|
||||
let value: Vec<i32> = vec![];
|
||||
verify_that!(value, superset_of([]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_matches_vec_with_one_element() -> googletest::Result<()> {
|
||||
let value = vec![1];
|
||||
verify_that!(value, superset_of([&1]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_matches_vec_with_two_items() -> googletest::Result<()> {
|
||||
let value = vec![1, 2];
|
||||
verify_that!(value, superset_of([&1, &2]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_matches_vec_when_actual_has_excess_element() -> googletest::Result<()> {
|
||||
let value = vec![1, 2, 3];
|
||||
verify_that!(value, superset_of([&1, &2]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_matches_vec_when_actual_has_excess_element_first() -> googletest::Result<()> {
|
||||
let value = vec![3, 1, 2];
|
||||
verify_that!(value, superset_of([&1, &2]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_matches_slice_with_one_element() -> googletest::Result<()> {
|
||||
let value = &[1];
|
||||
verify_that!(value, superset_of([&1]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_matches_hash_set_with_one_element() -> googletest::Result<()> {
|
||||
let value: HashSet<i32> = [1].into();
|
||||
verify_that!(value, superset_of([&1]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_does_not_match_when_first_element_does_not_match() -> googletest::Result<()> {
|
||||
let value = vec![0];
|
||||
verify_that!(value, not(superset_of([&1])))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_does_not_match_when_second_element_does_not_match() -> googletest::Result<()> {
|
||||
let value = vec![2];
|
||||
verify_that!(value, not(superset_of([&2, &0])))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_shows_correct_message_when_first_item_does_not_match() -> googletest::Result<()>
|
||||
{
|
||||
let result = verify_that!(vec![0, 2, 3], superset_of([&1, &2, &3]));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![0, 2, 3]
|
||||
Expected: is a superset of [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
Actual: [0, 2, 3],
|
||||
whose element 1 is missing
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_shows_correct_message_when_second_item_does_not_match() -> googletest::Result<()>
|
||||
{
|
||||
let result = verify_that!(vec![1, 0, 3], superset_of([&1, &2, &3]));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![1, 0, 3]
|
||||
Expected: is a superset of [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
Actual: [1, 0, 3],
|
||||
whose element 2 is missing
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superset_of_shows_correct_message_when_first_two_items_do_not_match(
|
||||
) -> googletest::Result<()> {
|
||||
let result = verify_that!(vec![0, 0, 3], superset_of([&1, &2, &3]));
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
err(displays_as(contains_substring(indoc!(
|
||||
"
|
||||
Value of: vec![0, 0, 3]
|
||||
Expected: is a superset of [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
Actual: [0, 0, 3],
|
||||
whose elements 1, 2 are missing
|
||||
"
|
||||
))))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// There are no visible documentation elements in this module; the declarative
|
||||
// macro is documented at the top level.
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Functions for use only by the declarative macros in this module.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub mod internal {
|
||||
use crate::{
|
||||
description::Description,
|
||||
matcher::{Matcher, MatcherBase, MatcherResult},
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
impl MatcherBase for () {}
|
||||
|
||||
// This implementation is provided for completeness, but is completely trivial.
|
||||
// The only actual value which can be supplied is (), which must match.
|
||||
impl Matcher<()> for () {
|
||||
fn matches(&self, _: ()) -> MatcherResult {
|
||||
MatcherResult::Match
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => "is the empty tuple".into(),
|
||||
MatcherResult::NoMatch => "is not the empty tuple".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher<&()> for () {
|
||||
fn matches(&self, _: &()) -> MatcherResult {
|
||||
MatcherResult::Match
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
<Self as Matcher<()>>::describe(self, matcher_result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a tuple matcher for tuples of a specific length.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
macro_rules! tuple_matcher_n {
|
||||
($([$field_number:tt, $matcher_type:ident, $field_type:ident]),*) => {
|
||||
impl<$($matcher_type: MatcherBase),*> MatcherBase for ($($matcher_type,)*){}
|
||||
|
||||
impl<$($field_type: Debug + Copy, $matcher_type: Matcher<$field_type>),*>
|
||||
Matcher<($($field_type,)*)> for ($($matcher_type,)*)
|
||||
{
|
||||
fn matches(&self, actual: ($($field_type,)*)) -> MatcherResult {
|
||||
$(match self.$field_number.matches(actual.$field_number) {
|
||||
MatcherResult::Match => {},
|
||||
MatcherResult::NoMatch => {
|
||||
return MatcherResult::NoMatch;
|
||||
}
|
||||
})*
|
||||
MatcherResult::Match
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ($($field_type,)*)) -> Description {
|
||||
let mut explanation = Description::new().text("which").nested(
|
||||
self.describe(self.matches(actual)));
|
||||
$(match self.$field_number.matches(actual.$field_number) {
|
||||
MatcherResult::Match => {},
|
||||
MatcherResult::NoMatch => {
|
||||
explanation = explanation
|
||||
.text(format!(concat!("Element #", $field_number, " is {:?},"),
|
||||
actual.$field_number))
|
||||
.nested(self.$field_number.explain_match(actual.$field_number));
|
||||
}
|
||||
})*
|
||||
explanation
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
let mut description = Description::new().text(
|
||||
"is a tuple whose values respectively match:");
|
||||
$(description = description.nested(
|
||||
self.$field_number.describe(matcher_result));)*
|
||||
description
|
||||
}
|
||||
MatcherResult::NoMatch => {
|
||||
let mut description = Description::new().text(
|
||||
"is a tuple whose values do not respectively match:");
|
||||
$(description = description.nested(
|
||||
self.$field_number.describe(MatcherResult::Match));)*
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a, $($field_type: Debug, $matcher_type: Matcher<&'a $field_type>),*>
|
||||
Matcher<&'a ($($field_type,)*)> for ($($matcher_type,)*)
|
||||
{
|
||||
fn matches(&self, actual: &'a ($($field_type,)*)) -> MatcherResult {
|
||||
$(match self.$field_number.matches(&actual.$field_number) {
|
||||
MatcherResult::Match => {},
|
||||
MatcherResult::NoMatch => {
|
||||
return MatcherResult::NoMatch;
|
||||
}
|
||||
})*
|
||||
MatcherResult::Match
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a ($($field_type,)*)) -> Description {
|
||||
let mut explanation = Description::new()
|
||||
.text("which")
|
||||
.nested(
|
||||
Matcher::<&'a ($($field_type,)*)>::describe(
|
||||
self, self.matches(actual)));
|
||||
$(match self.$field_number.matches(&actual.$field_number) {
|
||||
MatcherResult::Match => {},
|
||||
MatcherResult::NoMatch => {
|
||||
explanation = explanation
|
||||
.text(format!(
|
||||
concat!(
|
||||
"Element #",
|
||||
$field_number,
|
||||
" is {:?},"),
|
||||
actual.$field_number))
|
||||
.nested(self.$field_number.explain_match(&actual.$field_number));
|
||||
}
|
||||
})*
|
||||
explanation
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
match matcher_result {
|
||||
MatcherResult::Match => {
|
||||
let mut description = Description::new().text(
|
||||
"is a tuple whose values respectively match:");
|
||||
$(description = description.nested(
|
||||
self.$field_number.describe(matcher_result));)*
|
||||
description
|
||||
}
|
||||
MatcherResult::NoMatch => {
|
||||
let mut description = Description::new().text(
|
||||
"is a tuple whose values do not respectively match:");
|
||||
$(description = description.nested(
|
||||
self.$field_number.describe(MatcherResult::Match));)*
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
tuple_matcher_n!([0, I0, T0]);
|
||||
|
||||
tuple_matcher_n!([0, I0, T0], [1, I1, T1]);
|
||||
|
||||
tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2]);
|
||||
|
||||
tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2], [3, I3, T3]);
|
||||
|
||||
tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2], [3, I3, T3], [4, I4, T4]);
|
||||
|
||||
tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2], [3, I3, T3], [4, I4, T4], [5, I5, T5]);
|
||||
|
||||
tuple_matcher_n!(
|
||||
[0, I0, T0],
|
||||
[1, I1, T1],
|
||||
[2, I2, T2],
|
||||
[3, I3, T3],
|
||||
[4, I4, T4],
|
||||
[5, I5, T5],
|
||||
[6, I6, T6]
|
||||
);
|
||||
|
||||
tuple_matcher_n!(
|
||||
[0, I0, T0],
|
||||
[1, I1, T1],
|
||||
[2, I2, T2],
|
||||
[3, I3, T3],
|
||||
[4, I4, T4],
|
||||
[5, I5, T5],
|
||||
[6, I6, T6],
|
||||
[7, I7, T7]
|
||||
);
|
||||
|
||||
tuple_matcher_n!(
|
||||
[0, I0, T0],
|
||||
[1, I1, T1],
|
||||
[2, I2, T2],
|
||||
[3, I3, T3],
|
||||
[4, I4, T4],
|
||||
[5, I5, T5],
|
||||
[6, I6, T6],
|
||||
[7, I7, T7],
|
||||
[8, I8, T8]
|
||||
);
|
||||
|
||||
tuple_matcher_n!(
|
||||
[0, I0, T0],
|
||||
[1, I1, T1],
|
||||
[2, I2, T2],
|
||||
[3, I3, T3],
|
||||
[4, I4, T4],
|
||||
[5, I5, T5],
|
||||
[6, I6, T6],
|
||||
[7, I7, T7],
|
||||
[8, I8, T8],
|
||||
[9, I9, T9]
|
||||
);
|
||||
|
||||
tuple_matcher_n!(
|
||||
[0, I0, T0],
|
||||
[1, I1, T1],
|
||||
[2, I2, T2],
|
||||
[3, I3, T3],
|
||||
[4, I4, T4],
|
||||
[5, I5, T5],
|
||||
[6, I6, T6],
|
||||
[7, I7, T7],
|
||||
[8, I8, T8],
|
||||
[9, I9, T9],
|
||||
[10, I10, T10]
|
||||
);
|
||||
|
||||
tuple_matcher_n!(
|
||||
[0, I0, T0],
|
||||
[1, I1, T1],
|
||||
[2, I2, T2],
|
||||
[3, I3, T3],
|
||||
[4, I4, T4],
|
||||
[5, I5, T5],
|
||||
[6, I6, T6],
|
||||
[7, I7, T7],
|
||||
[8, I8, T8],
|
||||
[9, I9, T9],
|
||||
[10, I10, T10],
|
||||
[11, I11, T11]
|
||||
);
|
||||
}
|
||||
+609
@@ -0,0 +1,609 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
// There are no visible documentation elements in this module; the declarative
|
||||
// macro is documented in the matchers module.
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Matches a container whose elements in any order have a 1:1 correspondence
|
||||
/// with the provided element matchers.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(vec![3, 2, 1], unordered_elements_are![eq(&1), ge(&2), anything()])?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!(vec![1], unordered_elements_are![eq(&1), ge(&2)])?; // Fails: container has wrong size
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!(vec![3, 2, 1], unordered_elements_are![eq(&1), ge(&4), eq(&2)])?; // Fails: second matcher not matched
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_3() -> Result<()> {
|
||||
/// verify_that!(vec![3, 2, 1], unordered_elements_are![ge(&3), ge(&3), ge(&3)])?; // Fails: no 1:1 correspondence
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// # should_fail_3().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// The actual value must be a container such as a `&Vec`, an array, or a
|
||||
/// slice. More precisely, the actual value must implement [`IntoIterator`].
|
||||
///
|
||||
/// This can also be omitted in [`verify_that!`] macros and replaced with curly
|
||||
/// brackets.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// verify_that!(vec![1, 2], {eq(&2), eq(&1)})
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Note: This behavior is only possible in [`verify_that!`] macros. In any
|
||||
/// other cases, it is still necessary to use the
|
||||
/// [`unordered_elements_are!`][crate::matchers::unordered_elements_are] macro.
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use googletest::prelude::*;
|
||||
/// verify_that!(vec![vec![1,2], vec![3]], {{eq(2), eq(1)}, {eq(3)}})
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// Use this instead:
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// verify_that!(vec![vec![1,2], vec![3]],
|
||||
/// {unordered_elements_are![eq(&2), eq(&1)], unordered_elements_are![eq(&3)]})
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// If an inner matcher is `eq(...)`, it can be omitted:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
///
|
||||
/// verify_that!(vec![1,2,3], unordered_elements_are![lt(&2), gt(&1), &3])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The matcher proceeds in three stages:
|
||||
///
|
||||
/// 1. It first checks whether the actual value is of the right size to possibly
|
||||
/// be matched by each of the given matchers. If not, then it immediately
|
||||
/// fails explaining that the size is incorrect.
|
||||
///
|
||||
/// 2. It then checks whether each matcher matches at least one corresponding
|
||||
/// element in the actual container and each element in the actual container
|
||||
/// is matched by at least one matcher. If not, it fails with a message
|
||||
/// indicating which matcher respectively container elements had no
|
||||
/// counterparts.
|
||||
///
|
||||
/// 3. Finally, it checks whether the mapping of matchers to corresponding
|
||||
/// actual elements is a 1-1 correspondence and fails if that is not the
|
||||
/// case. The failure message then shows the best matching it could find,
|
||||
/// including which matchers did not have corresponding unique elements in
|
||||
/// the container and which container elements had no corresponding matchers.
|
||||
///
|
||||
/// [`IntoIterator`]: std::iter::IntoIterator
|
||||
/// [`Iterator`]: std::iter::Iterator
|
||||
/// [`Iterator::collect`]: std::iter::Iterator::collect
|
||||
/// [`Vec`]: std::vec::Vec
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __unordered_elements_are {
|
||||
($(,)?) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::
|
||||
UnorderedElementsAreMatcher::new(
|
||||
(),
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::
|
||||
Requirements::PerfectMatch)
|
||||
}};
|
||||
|
||||
($($matcher:expr),* $(,)?) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::
|
||||
UnorderedElementsAreMatcher::new(
|
||||
($(
|
||||
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!(
|
||||
$matcher
|
||||
)
|
||||
,)*),
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::
|
||||
Requirements::PerfectMatch)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Matches a container containing elements matched by the given matchers.
|
||||
///
|
||||
/// To match, each given matcher must have a corresponding element in the
|
||||
/// container which it matches. There must be a mapping uniquely matching each
|
||||
/// matcher to a container element. The container can, however, contain
|
||||
/// additional elements that don't correspond to any matcher.
|
||||
///
|
||||
/// Put another way, `contains_each!` matches if there is a subset of the actual
|
||||
/// container which
|
||||
/// [`unordered_elements_are`][crate::matchers::unordered_elements_are] would
|
||||
/// match.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(vec![3, 2, 1], contains_each![eq(&2), ge(&3)])?; // Passes
|
||||
/// verify_that!(vec![3, 2, 1], contains_each![ge(&2), ge(&2)])?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!(vec![1], contains_each![eq(&1), ge(&2)])?; // Fails: container too small
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!(vec![3, 2, 1], contains_each![eq(&1), ge(&4)])?; // Fails: second matcher unmatched
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_3() -> Result<()> {
|
||||
/// verify_that!(vec![3, 2, 1], contains_each![ge(&3), ge(&3), ge(&3)])?; // Fails: no matching
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// # should_fail_3().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// The actual value must be a container such as a `&Vec`, an array, or a
|
||||
/// slice. More precisely, the actual value must implement [`IntoIterator`].
|
||||
///
|
||||
/// If an inner matcher is `eq(...)`, it can be omitted:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
///
|
||||
/// verify_that!(vec![1,2,3], contains_each![lt(&2), &3])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The matcher proceeds in three stages:
|
||||
///
|
||||
/// 1. It first checks whether the actual value is large enough to possibly be
|
||||
/// matched by each of the given matchers. If not, then it immediately fails
|
||||
/// explaining that the size is too small.
|
||||
///
|
||||
/// 2. It then checks whether each matcher matches at least one corresponding
|
||||
/// element in the actual container and fails if that is not the case. The
|
||||
/// failure message indicates which matcher had no corresponding element.
|
||||
///
|
||||
/// 3. Finally, it checks whether the mapping of matchers to corresponding
|
||||
/// actual elements is 1-1 and fails if that is not the case. The failure
|
||||
/// message then shows the best matching it could find, including which
|
||||
/// matchers did not have corresponding unique elements in the container.
|
||||
///
|
||||
/// [`IntoIterator`]: std::iter::IntoIterator
|
||||
/// [`Iterator`]: std::iter::Iterator
|
||||
/// [`Iterator::collect`]: std::iter::Iterator::collect
|
||||
/// [`Vec`]: std::vec::Vec
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __contains_each {
|
||||
($(,)?) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::
|
||||
UnorderedElementsAreMatcher::new(
|
||||
(),
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::Superset)
|
||||
}};
|
||||
|
||||
($($matcher:expr),* $(,)?) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::
|
||||
UnorderedElementsAreMatcher::new(
|
||||
($(
|
||||
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!(
|
||||
$matcher
|
||||
)
|
||||
,)*),
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::Superset)
|
||||
}}
|
||||
}
|
||||
|
||||
/// Matches a container all of whose elements are matched by the given matchers.
|
||||
///
|
||||
/// To match, each element in the container must have a corresponding matcher
|
||||
/// which matches it. There must be a 1-1 mapping from container elements to
|
||||
/// matchers, so that no matcher has more than one corresponding element.
|
||||
///
|
||||
/// There may, however, be matchers not corresponding to any elements in the
|
||||
/// container.
|
||||
///
|
||||
/// Put another way, `is_contained_in!` matches if there is a subset of the
|
||||
/// matchers which would match with
|
||||
/// [`unordered_elements_are`][crate::matchers::unordered_elements_are].
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(vec![2, 1], is_contained_in![eq(&1), ge(&2)])?; // Passes
|
||||
/// verify_that!(vec![2, 1], is_contained_in![ge(&1), ge(&1)])?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!(vec![1, 2, 3], is_contained_in![eq(&1), ge(&2)])?; // Fails: container too large
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!(vec![2, 1], is_contained_in![eq(&1), ge(&4)])?; // Fails: second matcher unmatched
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_3() -> Result<()> {
|
||||
/// verify_that!(vec![3, 1], is_contained_in![ge(&3), ge(&3), ge(&3)])?; // Fails: no matching
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// # should_fail_3().unwrap_err();
|
||||
/// ```
|
||||
///
|
||||
/// The actual value must be a container such as a `&Vec`, an array, or a slice.
|
||||
/// More precisely, the actual value must implement [`IntoIterator`].
|
||||
///
|
||||
/// If an inner matcher is `eq(...)`, it can be omitted:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
///
|
||||
/// verify_that!(vec![1,2,3], is_contained_in![lt(&2), &3, &4, gt(&0)])
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The matcher proceeds in three stages:
|
||||
///
|
||||
/// 1. It first checks whether the actual value is too large to possibly be
|
||||
/// matched by each of the given matchers. If so, it immediately fails
|
||||
/// explaining that the size is too large.
|
||||
///
|
||||
/// 2. It then checks whether each actual container element is matched by at
|
||||
/// least one matcher and fails if that is not the case. The failure message
|
||||
/// indicates which element had no corresponding matcher.
|
||||
///
|
||||
/// 3. Finally, it checks whether the mapping of elements to corresponding
|
||||
/// matchers is 1-1 and fails if that is not the case. The failure message
|
||||
/// then shows the best matching it could find, including which container
|
||||
/// elements did not have corresponding matchers.
|
||||
///
|
||||
/// [`IntoIterator`]: std::iter::IntoIterator
|
||||
/// [`Iterator`]: std::iter::Iterator
|
||||
/// [`Iterator::collect`]: std::iter::Iterator::collect
|
||||
/// [`Vec`]: std::vec::Vec
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __is_contained_in {
|
||||
($(,)?) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::
|
||||
UnorderedElementsAreMatcher::new(
|
||||
(), $crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::Subset)
|
||||
}};
|
||||
|
||||
($($matcher:expr),* $(,)?) => {{
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::
|
||||
UnorderedElementsAreMatcher::new(
|
||||
($(
|
||||
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!(
|
||||
$matcher
|
||||
)
|
||||
,)*),
|
||||
$crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::Subset)
|
||||
}}
|
||||
}
|
||||
|
||||
/// Module for use only by the macros in this module.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub mod internal {
|
||||
use crate::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherBase, MatcherResult};
|
||||
use crate::matcher_support::match_matrix::internal::{MatchMatrix, Requirements};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// A trait for a tuple of matchers.
|
||||
pub trait MatcherTuple<T: Debug + Copy> {
|
||||
/// Turn the tuple into a vector of matchers.
|
||||
fn to_matchers(&self) -> Vec<Box<dyn Matcher<T> + '_>>;
|
||||
}
|
||||
|
||||
// Base case, no matchers.
|
||||
impl<T: Debug + Copy> MatcherTuple<T> for () {
|
||||
fn to_matchers(&self) -> Vec<Box<dyn Matcher<T> + '_>> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
// A macro for generating a MatcherTuple implementation for a given number
|
||||
// of matchers.
|
||||
macro_rules! matcher_tuple_n {
|
||||
// Given list of (field_number, matcher) pairs, generate a MatcherTuple
|
||||
// implementation.
|
||||
($([$field_number:tt, $matcher_type:ident]),*) => {
|
||||
impl<T: Debug + Copy, $($matcher_type: Matcher<T>),*> MatcherTuple<T>
|
||||
for ($($matcher_type,)*) {
|
||||
// Our implementation of to_matchers just converts our tuple into a vector of
|
||||
// matchers by expanding self.0, self.1, etc.
|
||||
fn to_matchers(&self) -> Vec<Box<dyn Matcher<T> + '_>> {
|
||||
vec![$(Box::new(&self.$field_number)),*]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
matcher_tuple_n!([0, M0]);
|
||||
matcher_tuple_n!([0, M0], [1, M1]);
|
||||
matcher_tuple_n!([0, M0], [1, M1], [2, M2]);
|
||||
matcher_tuple_n!([0, M0], [1, M1], [2, M2], [3, M3]);
|
||||
matcher_tuple_n!([0, M0], [1, M1], [2, M2], [3, M3], [4, M4]);
|
||||
matcher_tuple_n!([0, M0], [1, M1], [2, M2], [3, M3], [4, M4], [5, M5]);
|
||||
matcher_tuple_n!([0, M0], [1, M1], [2, M2], [3, M3], [4, M4], [5, M5], [6, M6]);
|
||||
matcher_tuple_n!([0, M0], [1, M1], [2, M2], [3, M3], [4, M4], [5, M5], [6, M6], [7, M7]);
|
||||
matcher_tuple_n!(
|
||||
[0, M0],
|
||||
[1, M1],
|
||||
[2, M2],
|
||||
[3, M3],
|
||||
[4, M4],
|
||||
[5, M5],
|
||||
[6, M6],
|
||||
[7, M7],
|
||||
[8, M8]
|
||||
);
|
||||
matcher_tuple_n!(
|
||||
[0, M0],
|
||||
[1, M1],
|
||||
[2, M2],
|
||||
[3, M3],
|
||||
[4, M4],
|
||||
[5, M5],
|
||||
[6, M6],
|
||||
[7, M7],
|
||||
[8, M8],
|
||||
[9, M9]
|
||||
);
|
||||
matcher_tuple_n!(
|
||||
[0, M0],
|
||||
[1, M1],
|
||||
[2, M2],
|
||||
[3, M3],
|
||||
[4, M4],
|
||||
[5, M5],
|
||||
[6, M6],
|
||||
[7, M7],
|
||||
[8, M8],
|
||||
[9, M9],
|
||||
[10, M10]
|
||||
);
|
||||
matcher_tuple_n!(
|
||||
[0, M0],
|
||||
[1, M1],
|
||||
[2, M2],
|
||||
[3, M3],
|
||||
[4, M4],
|
||||
[5, M5],
|
||||
[6, M6],
|
||||
[7, M7],
|
||||
[8, M8],
|
||||
[9, M9],
|
||||
[10, M10],
|
||||
[11, M11]
|
||||
);
|
||||
|
||||
/// This struct is meant to be used only through the
|
||||
/// `unordered_elements_are![...]` macro.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
#[derive(MatcherBase)]
|
||||
pub struct UnorderedElementsAreMatcher<M> {
|
||||
matchers: M,
|
||||
requirements: Requirements,
|
||||
}
|
||||
|
||||
impl<M> UnorderedElementsAreMatcher<M> {
|
||||
pub fn new(matchers: M, requirements: Requirements) -> Self {
|
||||
Self { matchers, requirements }
|
||||
}
|
||||
}
|
||||
|
||||
// This matcher performs the checks in three different steps in both `matches`
|
||||
// and `explain_match`. This is useful for performance but also to produce
|
||||
// an actionable error message.
|
||||
// 1. `UnorderedElementsAreMatcher` verifies that both collections have the same
|
||||
// size
|
||||
// 2. `UnorderedElementsAreMatcher` verifies that each actual element matches at
|
||||
// least one expected element and vice versa.
|
||||
// 3. `UnorderedElementsAreMatcher` verifies that a perfect matching exists
|
||||
// using Ford-Fulkerson.
|
||||
impl<T: Debug + Copy, ContainerT: Debug + Copy, M> Matcher<ContainerT>
|
||||
for UnorderedElementsAreMatcher<M>
|
||||
where
|
||||
ContainerT: IntoIterator<Item = T>,
|
||||
M: MatcherTuple<T>,
|
||||
{
|
||||
fn matches(&self, actual: ContainerT) -> MatcherResult {
|
||||
let matchers = self.matchers.to_matchers();
|
||||
let match_matrix = MatchMatrix::generate(actual, &matchers);
|
||||
match_matrix.is_match_for(self.requirements).into()
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: ContainerT) -> Description {
|
||||
let matchers = self.matchers.to_matchers();
|
||||
if let Some(size_mismatch_explanation) =
|
||||
self.requirements.explain_size_mismatch(actual, matchers.len())
|
||||
{
|
||||
return size_mismatch_explanation;
|
||||
}
|
||||
|
||||
let match_matrix = MatchMatrix::generate(actual, &matchers);
|
||||
if let Some(unmatchable_explanation) =
|
||||
match_matrix.explain_unmatchable(self.requirements)
|
||||
{
|
||||
return unmatchable_explanation;
|
||||
}
|
||||
|
||||
let best_match = match_matrix.find_best_match();
|
||||
best_match
|
||||
.get_explanation(actual, &matchers, self.requirements)
|
||||
.unwrap_or("whose elements all match".into())
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
let matchers = self.matchers.to_matchers();
|
||||
format!(
|
||||
"{} elements matching in any order:\n{}",
|
||||
if matcher_result.into() { "contains" } else { "doesn't contain" },
|
||||
matchers
|
||||
.iter()
|
||||
.map(|matcher| matcher.describe(MatcherResult::Match))
|
||||
.collect::<Description>()
|
||||
.enumerate()
|
||||
.indent()
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate as googletest;
|
||||
use crate::description::Description;
|
||||
use crate::matcher::MatcherResult;
|
||||
use crate::prelude::*;
|
||||
use indoc::indoc;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[test]
|
||||
fn has_correct_description_for_map() -> googletest::Result<()> {
|
||||
// UnorderedElementsAreMatcher maintains references to the matchers, so the
|
||||
// constituent matchers must live longer. Inside a verify_that! macro, the
|
||||
// compiler takes care of that, but when the matcher is created separately,
|
||||
// we must create the constitute matchers separately so that they
|
||||
// aren't dropped too early.
|
||||
let matchers = ((eq(&2), eq(&"Two")), (eq(&1), eq(&"One")), (eq(&3), eq(&"Three")));
|
||||
let matcher = unordered_elements_are![
|
||||
(matchers.0 .0, matchers.0 .1),
|
||||
(matchers.1 .0, matchers.1 .1),
|
||||
(matchers.2 .0, matchers.2 .1)
|
||||
];
|
||||
verify_that!(
|
||||
Matcher::<&HashMap<i32, String>>::describe(&matcher, MatcherResult::Match),
|
||||
displays_as(eq(indoc!(
|
||||
"
|
||||
contains elements matching in any order:
|
||||
0. is a tuple whose values respectively match:
|
||||
is equal to 2
|
||||
is equal to \"Two\"
|
||||
1. is a tuple whose values respectively match:
|
||||
is equal to 1
|
||||
is equal to \"One\"
|
||||
2. is a tuple whose values respectively match:
|
||||
is equal to 3
|
||||
is equal to \"Three\""
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unordered_elements_are_description_no_full_match_with_map() -> googletest::Result<()> {
|
||||
// UnorderedElementsAreMatcher maintains references to the matchers, so the
|
||||
// constituent matchers must live longer. Inside a verify_that! macro, the
|
||||
// compiler takes care of that, but when the matcher is created separately,
|
||||
// we must create the constitute matchers separately so that they
|
||||
// aren't dropped too early.
|
||||
let value: HashMap<u32, u32> = HashMap::from_iter([(0, 1), (1, 1), (2, 2)]);
|
||||
let matchers = ((anything(), eq(&1)), (anything(), eq(&2)), (anything(), eq(&2)));
|
||||
let matcher = unordered_elements_are![
|
||||
(matchers.0 .0, matchers.0 .1),
|
||||
(matchers.1 .0, matchers.1 .1),
|
||||
(matchers.2 .0, matchers.2 .1),
|
||||
];
|
||||
verify_that!(
|
||||
matcher.explain_match(&value),
|
||||
all![
|
||||
displays_as(contains_regex(
|
||||
"Actual element \\(2, 2\\) at index [0-2] matched expected element `is a tuple whose values respectively match:\n is anything\n is equal to 2` at index [0-2]."
|
||||
)),
|
||||
displays_as(contains_regex(
|
||||
"Actual element \\(\n [0-1],\n [0-1],\n \\) at index [0-2] did not match any remaining expected element."
|
||||
)),
|
||||
displays_as(contains_substring(
|
||||
"Expected element `is a tuple whose values respectively match:\n is anything\n is equal to 2` at index 2 did not match any remaining actual element."
|
||||
))
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
// This test verifies that unordered_elements_are can be used as the inner
|
||||
// matcher of another matcher, where the inner matcher is given a reference
|
||||
// to a local variable. If unordered_elements_are stores its matchers in a
|
||||
// Vec with the same type and lifetimes then this fails to compile.
|
||||
#[test]
|
||||
fn works_with_inner_matcher_that_modifies_the_input() -> googletest::Result<()> {
|
||||
// A custom matcher that applies an operation to the input hashmap (filtering
|
||||
// out any keys present in `to_filter`) and then applies the inner
|
||||
// matcher to the modified input.
|
||||
fn matches_filtered<'a, M>(
|
||||
to_filter: &'a HashSet<i32>,
|
||||
inner: M,
|
||||
) -> impl Matcher<&'a HashSet<i32>>
|
||||
where
|
||||
M: for<'b> Matcher<&'b HashSet<i32>>,
|
||||
{
|
||||
#[derive(MatcherBase)]
|
||||
struct FilteredMatcher<'a, M> {
|
||||
to_filter: &'a HashSet<i32>,
|
||||
inner: M,
|
||||
}
|
||||
|
||||
impl<'a, M> Matcher<&'a HashSet<i32>> for FilteredMatcher<'a, M>
|
||||
where
|
||||
M: for<'b> Matcher<&'b HashSet<i32>>,
|
||||
{
|
||||
fn matches(&self, actual: &'a HashSet<i32>) -> MatcherResult {
|
||||
let filtered: HashSet<i32> =
|
||||
actual.iter().filter(|k| !self.to_filter.contains(k)).cloned().collect();
|
||||
self.inner.matches(&filtered)
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
self.inner.describe(matcher_result)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: &'a HashSet<i32>) -> Description {
|
||||
let filtered: HashSet<i32> =
|
||||
actual.iter().filter(|k| !self.to_filter.contains(k)).cloned().collect();
|
||||
self.inner.explain_match(&filtered)
|
||||
}
|
||||
}
|
||||
|
||||
FilteredMatcher { to_filter, inner }
|
||||
}
|
||||
|
||||
let before: HashSet<i32> = HashSet::from([1]);
|
||||
let after: HashSet<i32> = HashSet::from([1, 2]);
|
||||
|
||||
verify_that!(&after, matches_filtered(&before, unordered_elements_are![eq(&2)]))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user