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
+1918
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,406 @@
|
||||
// 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 std::{
|
||||
borrow::Cow,
|
||||
fmt::{Display, Formatter, Result},
|
||||
};
|
||||
|
||||
use crate::internal::description_renderer::{List, INDENTATION_SIZE};
|
||||
|
||||
/// A structured description, either of a (composed) matcher or of an
|
||||
/// assertion failure.
|
||||
///
|
||||
/// One can compose blocks of text into a `Description`. Each one appears on a
|
||||
/// new line. For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use googletest::description::Description;
|
||||
/// let description = Description::new()
|
||||
/// .text("A block")
|
||||
/// .text("Another block");
|
||||
/// verify_that!(description, displays_as(eq("A block\nAnother block")))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// One can embed nested descriptions into a `Description`. The resulting
|
||||
/// nested description is then rendered with an additional level of
|
||||
/// indentation. For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use googletest::description::Description;
|
||||
/// let inner_description = Description::new()
|
||||
/// .text("A block")
|
||||
/// .text("Another block");
|
||||
/// let outer_description = Description::new()
|
||||
/// .text("Header")
|
||||
/// .nested(inner_description);
|
||||
/// verify_that!(outer_description, displays_as(eq("\
|
||||
/// Header
|
||||
/// A block
|
||||
/// Another block")))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// One can also enumerate or bullet list the elements of a `Description`:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use googletest::description::Description;
|
||||
/// let description = Description::new()
|
||||
/// .text("First item")
|
||||
/// .text("Second item")
|
||||
/// .bullet_list();
|
||||
/// verify_that!(description, displays_as(eq("\
|
||||
/// * First item
|
||||
/// * Second item")))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// One can construct a `Description` from a [`String`] or a string slice, an
|
||||
/// iterator thereof, or from an iterator over other `Description`s:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::description::Description;
|
||||
/// let single_element_description: Description =
|
||||
/// "A single block description".into();
|
||||
/// let two_element_description: Description =
|
||||
/// ["First item", "Second item"].into_iter().collect();
|
||||
/// let two_element_description_from_strings: Description =
|
||||
/// ["First item".to_string(), "Second item".to_string()].into_iter().collect();
|
||||
/// ```
|
||||
///
|
||||
/// No newline is added after the last element during rendering. This makes it
|
||||
/// easier to support single-line matcher descriptions and match explanations.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Description {
|
||||
elements: List,
|
||||
initial_indentation: usize,
|
||||
is_conjunction: bool,
|
||||
is_disjunction: bool,
|
||||
}
|
||||
|
||||
impl Description {
|
||||
/// Returns a new empty [`Description`].
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Appends a block of text to this instance.
|
||||
///
|
||||
/// The block is indented uniformly when this instance is rendered.
|
||||
pub fn text(mut self, text: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.elements.push_literal(text.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Appends a nested [`Description`] to this instance.
|
||||
///
|
||||
/// The nested [`Description`] `inner` is indented uniformly at the next
|
||||
/// level of indentation when this instance is rendered.
|
||||
pub fn nested(mut self, inner: Description) -> Self {
|
||||
self.elements.push_nested(inner.elements);
|
||||
self
|
||||
}
|
||||
|
||||
/// Appends all [`Description`] in the given sequence `inner` to this
|
||||
/// instance.
|
||||
///
|
||||
/// Each element is treated as a nested [`Description`] in the sense of
|
||||
/// [`Self::nested`].
|
||||
pub fn collect(self, inner: impl IntoIterator<Item = Description>) -> Self {
|
||||
inner.into_iter().fold(self, |outer, inner| outer.nested(inner))
|
||||
}
|
||||
|
||||
/// Indents the lines in elements of this description.
|
||||
///
|
||||
/// This operation will be performed lazily when [`self`] is displayed.
|
||||
///
|
||||
/// This will indent every line inside each element.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use googletest::description::Description;
|
||||
/// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
|
||||
/// verify_that!(description.indent(), displays_as(eq(" A B C\n D E F")))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
pub fn indent(self) -> Self {
|
||||
Self { initial_indentation: INDENTATION_SIZE, ..self }
|
||||
}
|
||||
|
||||
/// Instructs this instance to render its elements as a bullet list.
|
||||
///
|
||||
/// Each element (from either [`Description::text`] or
|
||||
/// [`Description::nested`]) is rendered as a bullet point. If an element
|
||||
/// contains multiple lines, the following lines are aligned with the first
|
||||
/// one in the block.
|
||||
///
|
||||
/// For instance:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use googletest::description::Description;
|
||||
/// let description = Description::new()
|
||||
/// .text("First line\nsecond line")
|
||||
/// .bullet_list();
|
||||
/// verify_that!(description, displays_as(eq("\
|
||||
/// * First line
|
||||
/// second line")))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
pub fn bullet_list(self) -> Self {
|
||||
Self { elements: self.elements.bullet_list(), ..self }
|
||||
}
|
||||
|
||||
/// Instructs this instance to render its elements as an enumerated list.
|
||||
///
|
||||
/// Each element (from either [`Description::text`] or
|
||||
/// [`Description::nested`]) is rendered with its zero-based index. If an
|
||||
/// element contains multiple lines, the following lines are aligned with
|
||||
/// the first one in the block.
|
||||
///
|
||||
/// For instance:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use googletest::description::Description;
|
||||
/// let description = Description::new()
|
||||
/// .text("First line\nsecond line")
|
||||
/// .enumerate();
|
||||
/// verify_that!(description, displays_as(eq("\
|
||||
/// 0. First line
|
||||
/// second line")))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
pub fn enumerate(self) -> Self {
|
||||
Self { elements: self.elements.enumerate(), ..self }
|
||||
}
|
||||
|
||||
/// Returns the length of elements.
|
||||
pub fn len(&self) -> usize {
|
||||
self.elements.len()
|
||||
}
|
||||
|
||||
/// Returns whether the set of elements is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.elements.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn push_in_last_nested(mut self, inner: Description) -> Self {
|
||||
self.elements.push_at_end(inner.elements);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn conjunction_description(self) -> Self {
|
||||
Self { is_conjunction: true, ..self }
|
||||
}
|
||||
|
||||
pub(crate) fn is_conjunction_description(&self) -> bool {
|
||||
self.is_conjunction
|
||||
}
|
||||
|
||||
pub(crate) fn disjunction_description(self) -> Self {
|
||||
Self { is_disjunction: true, ..self }
|
||||
}
|
||||
|
||||
pub(crate) fn is_disjunction_description(&self) -> bool {
|
||||
self.is_disjunction
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Description {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
self.elements.render(f, self.initial_indentation)
|
||||
}
|
||||
}
|
||||
|
||||
impl<ElementT: Into<Cow<'static, str>>> FromIterator<ElementT> for Description {
|
||||
fn from_iter<T>(iter: T) -> Self
|
||||
where
|
||||
T: IntoIterator<Item = ElementT>,
|
||||
{
|
||||
Self { elements: iter.into_iter().map(ElementT::into).collect(), ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Description> for Description {
|
||||
fn from_iter<T>(iter: T) -> Self
|
||||
where
|
||||
T: IntoIterator<Item = Description>,
|
||||
{
|
||||
Self { elements: iter.into_iter().map(|s| s.elements).collect(), ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<Cow<'static, str>>> From<T> for Description {
|
||||
fn from(value: T) -> Self {
|
||||
let mut elements = List::default();
|
||||
elements.push_literal(value.into());
|
||||
Self { elements, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Description;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn renders_single_fragment() -> Result<()> {
|
||||
let description: Description = "A B C".into();
|
||||
verify_that!(description, displays_as(eq("A B C")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_two_fragments() -> Result<()> {
|
||||
let description =
|
||||
["A B C".to_string(), "D E F".to_string()].into_iter().collect::<Description>();
|
||||
verify_that!(description, displays_as(eq("A B C\nD E F")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_description_is_indented() -> Result<()> {
|
||||
let description = Description::new()
|
||||
.text("Header")
|
||||
.nested(["A B C".to_string()].into_iter().collect::<Description>());
|
||||
verify_that!(description, displays_as(eq("Header\n A B C")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_description_indents_two_elements() -> Result<()> {
|
||||
let description = Description::new().text("Header").nested(
|
||||
["A B C".to_string(), "D E F".to_string()].into_iter().collect::<Description>(),
|
||||
);
|
||||
verify_that!(description, displays_as(eq("Header\n A B C\n D E F")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_description_indents_one_element_on_two_lines() -> Result<()> {
|
||||
let description = Description::new().text("Header").nested("A B C\nD E F".into());
|
||||
verify_that!(description, displays_as(eq("Header\n A B C\n D E F")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_fragment_renders_with_bullet_when_bullet_list_enabled() -> Result<()> {
|
||||
let description = Description::new().text("A B C").bullet_list();
|
||||
verify_that!(description, displays_as(eq("* A B C")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_nested_fragment_renders_with_bullet_when_bullet_list_enabled() -> Result<()> {
|
||||
let description = Description::new().nested("A B C".into()).bullet_list();
|
||||
verify_that!(description, displays_as(eq("* A B C")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_fragments_render_with_bullet_when_bullet_list_enabled() -> Result<()> {
|
||||
let description = Description::new().text("A B C").text("D E F").bullet_list();
|
||||
verify_that!(description, displays_as(eq("* A B C\n* D E F")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_nested_fragments_render_with_bullet_when_bullet_list_enabled() -> Result<()> {
|
||||
let description =
|
||||
Description::new().nested("A B C".into()).nested("D E F".into()).bullet_list();
|
||||
verify_that!(description, displays_as(eq("* A B C\n* D E F")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_fragment_with_more_than_one_line_renders_with_one_bullet() -> Result<()> {
|
||||
let description = Description::new().text("A B C\nD E F").bullet_list();
|
||||
verify_that!(description, displays_as(eq("* A B C\n D E F")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_fragment_renders_with_enumeration_when_enumerate_enabled() -> Result<()> {
|
||||
let description = Description::new().text("A B C").enumerate();
|
||||
verify_that!(description, displays_as(eq("0. A B C")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_fragments_render_with_enumeration_when_enumerate_enabled() -> Result<()> {
|
||||
let description = Description::new().text("A B C").text("D E F").enumerate();
|
||||
verify_that!(description, displays_as(eq("0. A B C\n1. D E F")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_fragment_with_two_lines_renders_with_one_enumeration_label() -> Result<()> {
|
||||
let description = Description::new().text("A B C\nD E F").enumerate();
|
||||
verify_that!(description, displays_as(eq("0. A B C\n D E F")))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_digit_enumeration_renders_with_correct_offset() -> Result<()> {
|
||||
let description = ["A B C\nD E F"; 11]
|
||||
.into_iter()
|
||||
.map(str::to_string)
|
||||
.collect::<Description>()
|
||||
.enumerate();
|
||||
verify_that!(
|
||||
description,
|
||||
displays_as(eq(indoc!(
|
||||
"
|
||||
0. A B C
|
||||
D E F
|
||||
1. A B C
|
||||
D E F
|
||||
2. A B C
|
||||
D E F
|
||||
3. A B C
|
||||
D E F
|
||||
4. A B C
|
||||
D E F
|
||||
5. A B C
|
||||
D E F
|
||||
6. A B C
|
||||
D E F
|
||||
7. A B C
|
||||
D E F
|
||||
8. A B C
|
||||
D E F
|
||||
9. A B C
|
||||
D E F
|
||||
10. A B C
|
||||
D E F"
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_is_empty() -> Result<()> {
|
||||
verify_that!(Description::new(), predicate(Description::is_empty))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_is_not_empty() -> Result<()> {
|
||||
verify_that!(Description::new().text("something"), not(predicate(Description::is_empty)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_zero_length() -> Result<()> {
|
||||
verify_that!(Description::new().len(), eq(0))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_one_length() -> Result<()> {
|
||||
verify_that!(Description::new().text("something").len(), eq(1))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
// 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 std::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{Mutex, OnceLock},
|
||||
};
|
||||
|
||||
/// Interface for structure to be set up and torn down as part of a test.
|
||||
/// Types implementing `Fixture` can be passed as a reference argument to a
|
||||
/// test function.
|
||||
///
|
||||
/// ```ignore
|
||||
/// struct MyFixture { ... }
|
||||
///
|
||||
/// impl Fixture for MyFixture { ... }
|
||||
///
|
||||
/// #[gtest]
|
||||
/// fn test_with_fixture(my_fixture: &MyFixture) {...}
|
||||
/// ```
|
||||
pub trait Fixture: Sized {
|
||||
/// Factory method of the `Fixture`.
|
||||
///
|
||||
/// This method is called by the test harness before the test case
|
||||
/// that uses this fixture. If this method returns an `Err(...)`,
|
||||
/// then the test case is not evaluated, automatically fails, and
|
||||
/// only the fixtures previously set up are torn down.
|
||||
fn set_up() -> crate::Result<Self>;
|
||||
|
||||
/// Clean up method for the fixture.
|
||||
///
|
||||
/// This method is called by the test harness after the test case
|
||||
/// that uses this fixture. If the `Fixture` has been set up, the
|
||||
/// test harness will call this method, even if the test case failed
|
||||
/// or panicked.
|
||||
fn tear_down(self) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
/// Interface for structure to be set up before the test case.
|
||||
/// Types implementing `ConsumableFixture` can be passed by value to
|
||||
/// a test function.
|
||||
///
|
||||
/// ```ignore
|
||||
/// struct MyFixture { ... }
|
||||
///
|
||||
/// impl ConsumableFixture for MyFixture { ... }
|
||||
///
|
||||
/// #[gtest]
|
||||
/// fn test_with_fixture(my_fixture: MyFixture) {...}
|
||||
/// ```
|
||||
pub trait ConsumableFixture: Sized {
|
||||
/// Factory method of the `ConsumableFixture`.
|
||||
///
|
||||
/// This method is called by the test harness before the test case
|
||||
/// that uses this fixture. If this method returns an `Err(...)`,
|
||||
/// then the test case is not evaluated, automatically fails, and
|
||||
/// only the fixtures previously set up are torn down.
|
||||
fn set_up() -> crate::Result<Self>;
|
||||
}
|
||||
|
||||
/// Generic adapter to implement `ConsumableFixture` on any type implementing
|
||||
/// `Default`.
|
||||
pub struct FixtureOf<T>(T);
|
||||
|
||||
impl<T: Default> ConsumableFixture for FixtureOf<T> {
|
||||
fn set_up() -> crate::Result<Self> {
|
||||
Ok(Self(T::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for FixtureOf<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for FixtureOf<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface for structure to be set up only once before all tests.
|
||||
/// Types implementing `StaticFixture` can be passed as a double referenced
|
||||
/// argument to a test function.
|
||||
///
|
||||
/// ```ignore
|
||||
/// struct MyFixture{ ... }
|
||||
///
|
||||
/// impl StaticFixture for MyFixture { ... }
|
||||
///
|
||||
/// #[gtest]
|
||||
/// fn test_with_fixture(my_fixture: &&MyFixture){...}
|
||||
/// ```
|
||||
pub trait StaticFixture: Sized + Sync + Send {
|
||||
/// Factory method of the `StaticFixture`.
|
||||
///
|
||||
/// This method is called by the test harness before the first test case
|
||||
/// using this fixture. If this method returns an `Err(...)`, then every
|
||||
/// test case using this fixture is not evaluated and automatically fails.
|
||||
fn set_up_once() -> crate::Result<Self>;
|
||||
}
|
||||
|
||||
impl<F: StaticFixture + 'static> Fixture for &'static F {
|
||||
fn set_up() -> crate::Result<Self> {
|
||||
static ONCE_FIXTURE_REPO: OnceLock<
|
||||
Mutex<HashMap<TypeId, &'static (dyn Any + Sync + Send)>>,
|
||||
> = OnceLock::new();
|
||||
let mut map = ONCE_FIXTURE_REPO.get_or_init(|| Mutex::new(HashMap::new())).lock()?;
|
||||
let any =
|
||||
map.entry(TypeId::of::<F>()).or_insert_with(|| Box::leak(Box::new(F::set_up_once())));
|
||||
match any.downcast_ref::<crate::Result<F>>() {
|
||||
Some(Ok(fixture)) => Ok(fixture),
|
||||
Some(Err(e)) => Err(e.clone()),
|
||||
None => panic!("Downcast failed. This is a bug in GoogleTest Rust"),
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this is `&F` being torn down, not `F`.
|
||||
fn tear_down(self) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
use super::FixtureOf;
|
||||
use super::StaticFixture;
|
||||
use crate as googletest;
|
||||
use crate::prelude::*;
|
||||
use crate::test;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn fixture_no_fixture() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct AlwaysSucceed;
|
||||
|
||||
impl Fixture for AlwaysSucceed {
|
||||
fn set_up() -> crate::Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn tear_down(self) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_one_fixture(_: &AlwaysSucceed) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_three_fixtures(
|
||||
_: &AlwaysSucceed,
|
||||
_: &AlwaysSucceed,
|
||||
_: &AlwaysSucceed,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct NotAFixture {
|
||||
a_field: i32,
|
||||
}
|
||||
|
||||
impl Default for NotAFixture {
|
||||
fn default() -> Self {
|
||||
Self { a_field: 33 }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_of_non_fixture(not_a_fixture: FixtureOf<NotAFixture>) -> Result<()> {
|
||||
verify_that!(not_a_fixture.a_field, eq(33))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_of_non_fixture_mut(mut not_a_fixture: FixtureOf<NotAFixture>) -> Result<()> {
|
||||
not_a_fixture.a_field += 10;
|
||||
verify_that!(not_a_fixture.a_field, eq(43))
|
||||
}
|
||||
struct PanickyFixture;
|
||||
|
||||
impl Fixture for PanickyFixture {
|
||||
fn set_up() -> crate::Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn tear_down(self) -> crate::Result<()> {
|
||||
panic!("Whoooops");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Whoooops")]
|
||||
fn fixture_teardown_called_even_if_test_fail(_: &PanickyFixture) -> Result<()> {
|
||||
Err(googletest::TestAssertionFailure::create("It must fail!".into()))
|
||||
}
|
||||
|
||||
struct AbortIfNotTornDownFixture {
|
||||
has_been_torn_down: bool,
|
||||
}
|
||||
|
||||
impl Fixture for AbortIfNotTornDownFixture {
|
||||
fn set_up() -> crate::Result<Self> {
|
||||
Ok(Self { has_been_torn_down: false })
|
||||
}
|
||||
fn tear_down(mut self) -> crate::Result<()> {
|
||||
self.has_been_torn_down = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AbortIfNotTornDownFixture {
|
||||
fn drop(&mut self) {
|
||||
if !self.has_been_torn_down {
|
||||
eprintln!("AbortIfNotTornDownFixture was not torn down");
|
||||
std::process::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "simple_fail")]
|
||||
fn fixture_torn_down_after_test_panics(f: &AbortIfNotTornDownFixture) {
|
||||
expect_that!(f.has_been_torn_down, eq(false));
|
||||
panic!("simple_fail");
|
||||
}
|
||||
|
||||
struct OnlyOnce;
|
||||
|
||||
impl StaticFixture for OnlyOnce {
|
||||
fn set_up_once() -> crate::Result<Self> {
|
||||
static ONCE: Once = Once::new();
|
||||
assert!(!ONCE.is_completed());
|
||||
ONCE.call_once(|| {});
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_fixture_works(_: &&OnlyOnce) {}
|
||||
|
||||
#[test]
|
||||
fn static_fixture_same_static_fixture_twice(once: &&OnlyOnce, twice: &&OnlyOnce) {
|
||||
// checks it points to the same memory address.
|
||||
let once: *const OnlyOnce = *once;
|
||||
let twice: *const OnlyOnce = *twice;
|
||||
expect_eq!(once, twice);
|
||||
}
|
||||
|
||||
struct AnotherStaticFixture;
|
||||
|
||||
impl StaticFixture for AnotherStaticFixture {
|
||||
fn set_up_once() -> crate::Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_fixture_two_different_static_fixtures(_: &&OnlyOnce, _: &&AnotherStaticFixture) {}
|
||||
|
||||
struct FailingSetUp;
|
||||
|
||||
impl Fixture for FailingSetUp {
|
||||
fn set_up() -> crate::Result<Self> {
|
||||
Err(googletest::TestAssertionFailure::create("sad fixture".into()))
|
||||
}
|
||||
|
||||
fn tear_down(self) -> crate::Result<()> {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "See failure output above")]
|
||||
fn failing_fixture_causes_test_failure(_: &FailingSetUp) {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
struct FailingTearDown;
|
||||
|
||||
impl Fixture for FailingTearDown {
|
||||
fn set_up() -> crate::Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn tear_down(self) -> crate::Result<()> {
|
||||
Err(googletest::TestAssertionFailure::create("It must fail!".into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "See failure output above")]
|
||||
fn failing_teardown_causes_test_failure(_: &FailingTearDown) {}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// 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.
|
||||
|
||||
/// Functions 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 std::fmt::{Debug, Write};
|
||||
|
||||
/// Wrapper to allow for inherent-method specialization based on whether a
|
||||
/// type implements `Debug`.
|
||||
pub struct FormatWrapper<'a, T: ?Sized>(pub &'a T);
|
||||
|
||||
/// Default implementation to render values that implement `Debug`.
|
||||
///
|
||||
/// Used for autoref specialization to conditionally
|
||||
/// render only values that implement `Debug`. See also
|
||||
/// [`FormatNonDebugFallback`].
|
||||
impl<T: Debug + ?Sized> FormatWrapper<'_, T> {
|
||||
#[track_caller]
|
||||
pub fn __googletest_write_expr_value(&self, output: &mut dyn Write, expr_label: &str) {
|
||||
write!(output, "\n {} = {:?},", expr_label, self.0)
|
||||
.expect("Formatting to String should never fail");
|
||||
}
|
||||
}
|
||||
|
||||
/// Fallback implementation for rendering values for non-`Debug` types..
|
||||
///
|
||||
/// Used for inherent-method specialization to conditionally render only
|
||||
/// values that implement `Debug`. See also the specialized inherent impl on
|
||||
/// [`FormatWrapper`] above.
|
||||
pub trait FormatNonDebugFallback {
|
||||
fn __googletest_write_expr_value(&self, output: &mut dyn Write, expr_label: &str);
|
||||
}
|
||||
|
||||
impl<T: ?Sized> FormatNonDebugFallback for FormatWrapper<'_, T> {
|
||||
#[track_caller]
|
||||
fn __googletest_write_expr_value(&self, output: &mut dyn Write, expr_label: &str) {
|
||||
write!(output, "\n {expr_label} does not implement Debug,")
|
||||
.expect("Formatting to String should never fail");
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! __googletest__write_expr_value(
|
||||
($output:expr, $expr_str:expr, $value:expr $(,)?) => {
|
||||
{
|
||||
use $crate::fmt::internal::FormatNonDebugFallback as _;
|
||||
$crate::fmt::internal::FormatWrapper(&$value)
|
||||
.__googletest_write_expr_value(&mut $output, $expr_str)
|
||||
}
|
||||
}
|
||||
);
|
||||
pub use __googletest__write_expr_value;
|
||||
}
|
||||
@@ -0,0 +1,626 @@
|
||||
// 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 std::{
|
||||
borrow::Cow,
|
||||
fmt::{Result, Write},
|
||||
};
|
||||
|
||||
/// Number of space used to indent lines when no alignement is required.
|
||||
pub(crate) const INDENTATION_SIZE: usize = 2;
|
||||
|
||||
/// A list of [`Block`] possibly rendered with a [`Decoration`].
|
||||
///
|
||||
/// This is the top-level renderable component, corresponding to the description
|
||||
/// or match explanation of a single matcher.
|
||||
///
|
||||
/// The constituent [`Block`] of a `List` can be decorated with either bullets
|
||||
/// (`* `) or enumeration (`0. `, `1. `, ...). This is controlled via the
|
||||
/// methods [`List::bullet_list`] and [`List::enumerate`]. By default, there is
|
||||
/// no decoration.
|
||||
///
|
||||
/// A `List` can be constructed as follows:
|
||||
///
|
||||
/// * [`Default::default()`] constructs an empty `List`.
|
||||
/// * [`Iterator::collect()`] on an [`Iterator`] of [`Block`].
|
||||
/// * [`Iterator::collect()`] on an [`Iterator`] of `String`, which produces a
|
||||
/// [`Block::Literal`] for each `String`.
|
||||
/// * [`Iterator::collect()`] on an [`Iterator`] of `List`, which produces a
|
||||
/// [`Block::Nested`] for each `List`.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct List(Vec<Block>, Decoration);
|
||||
|
||||
impl List {
|
||||
/// Render this instance using the formatter `f`.
|
||||
///
|
||||
/// Indent each line of output by `indentation` spaces.
|
||||
pub(crate) fn render(&self, f: &mut dyn Write, indentation: usize) -> Result {
|
||||
self.render_with_prefix(f, indentation, "".into())
|
||||
}
|
||||
|
||||
/// Append a new [`Block`] containing `literal`.
|
||||
///
|
||||
/// The input `literal` is split into lines so that each line will be
|
||||
/// indented correctly.
|
||||
pub(crate) fn push_literal(&mut self, literal: Cow<'static, str>) {
|
||||
self.0.push(literal.into());
|
||||
}
|
||||
|
||||
/// Append a new [`Block`] containing `inner` as a nested [`List`].
|
||||
pub(crate) fn push_nested(&mut self, inner: List) {
|
||||
self.0.push(Block::Nested(inner));
|
||||
}
|
||||
|
||||
/// Render each [`Block`] of this instance preceded with a bullet "* ".
|
||||
pub(crate) fn bullet_list(self) -> Self {
|
||||
Self(self.0, Decoration::Bullet)
|
||||
}
|
||||
|
||||
/// Render each [`Block`] of this instance preceded with its 0-based index.
|
||||
pub(crate) fn enumerate(self) -> Self {
|
||||
Self(self.0, Decoration::Enumerate)
|
||||
}
|
||||
|
||||
/// Return the number of [`Block`] in this instance.
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Return `true` if there are no [`Block`] in this instance, `false`
|
||||
/// otherwise.
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Append a new [`List`] in the last element which must be a
|
||||
/// [`Block::Nested`]. Panic if `self` is empty or the last element is
|
||||
/// not [`Block::Nested`].
|
||||
pub(crate) fn push_at_end(&mut self, list: List) {
|
||||
if let Some(Block::Nested(self_list)) = self.0.last_mut() {
|
||||
self_list.push_nested(list);
|
||||
} else {
|
||||
panic!("pushing elements at the end of {self:#?} which last element is not Nested")
|
||||
}
|
||||
}
|
||||
|
||||
fn render_with_prefix(
|
||||
&self,
|
||||
f: &mut dyn Write,
|
||||
indentation: usize,
|
||||
prefix: Cow<'static, str>,
|
||||
) -> Result {
|
||||
if self.0.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let enumeration_padding = self.enumeration_padding();
|
||||
|
||||
self.0[0].render(
|
||||
f,
|
||||
indentation,
|
||||
self.full_prefix(0, enumeration_padding, &prefix).into(),
|
||||
)?;
|
||||
for (index, block) in self.0[1..].iter().enumerate() {
|
||||
writeln!(f)?;
|
||||
block.render(
|
||||
f,
|
||||
indentation + prefix.len(),
|
||||
self.prefix(index + 1, enumeration_padding),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn full_prefix(&self, index: usize, enumeration_padding: usize, prior_prefix: &str) -> String {
|
||||
format!("{prior_prefix}{}", self.prefix(index, enumeration_padding))
|
||||
}
|
||||
|
||||
fn prefix(&self, index: usize, enumeration_padding: usize) -> Cow<'static, str> {
|
||||
match self.1 {
|
||||
Decoration::None => "".into(),
|
||||
Decoration::Bullet => "* ".into(),
|
||||
Decoration::Enumerate => format!("{index:>enumeration_padding$}. ").into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn enumeration_padding(&self) -> usize {
|
||||
match self.1 {
|
||||
Decoration::None => 0,
|
||||
Decoration::Bullet => 0,
|
||||
Decoration::Enumerate => {
|
||||
if self.0.len() > 1 {
|
||||
((self.0.len() - 1) as f64).log10().floor() as usize + 1
|
||||
} else {
|
||||
// Avoid negative logarithm when there is only 0 or 1 element.
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Block> for List {
|
||||
fn from_iter<T>(iter: T) -> Self
|
||||
where
|
||||
T: IntoIterator<Item = Block>,
|
||||
{
|
||||
Self(iter.into_iter().collect(), Decoration::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<ElementT: Into<Cow<'static, str>>> FromIterator<ElementT> for List {
|
||||
fn from_iter<T>(iter: T) -> Self
|
||||
where
|
||||
T: IntoIterator<Item = ElementT>,
|
||||
{
|
||||
Self(iter.into_iter().map(|b| b.into().into()).collect(), Decoration::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<List> for List {
|
||||
fn from_iter<T>(iter: T) -> Self
|
||||
where
|
||||
T: IntoIterator<Item = List>,
|
||||
{
|
||||
Self(iter.into_iter().map(Block::nested).collect(), Decoration::None)
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequence of [`Fragment`] or a nested [`List`].
|
||||
///
|
||||
/// This may be rendered with a prefix specified by the [`Decoration`] of the
|
||||
/// containing [`List`]. In this case, all lines are indented to align with the
|
||||
/// first character of the first line of the block.
|
||||
#[derive(Debug)]
|
||||
enum Block {
|
||||
/// A block of text.
|
||||
///
|
||||
/// Each constituent [`Fragment`] contains one line of text. The lines are
|
||||
/// indented uniformly to the current indentation of this block when
|
||||
/// rendered.
|
||||
Literal(Vec<Fragment>),
|
||||
|
||||
/// A nested [`List`].
|
||||
///
|
||||
/// The [`List`] is rendered recursively at the next level of indentation.
|
||||
Nested(List),
|
||||
}
|
||||
|
||||
impl Block {
|
||||
fn nested(inner: List) -> Self {
|
||||
Self::Nested(inner)
|
||||
}
|
||||
|
||||
fn render(&self, f: &mut dyn Write, indentation: usize, prefix: Cow<'static, str>) -> Result {
|
||||
match self {
|
||||
Self::Literal(fragments) => {
|
||||
if fragments.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
write!(f, "{:indentation$}{prefix}", "")?;
|
||||
fragments[0].render(f)?;
|
||||
let block_indentation = indentation + prefix.as_ref().len();
|
||||
for fragment in &fragments[1..] {
|
||||
writeln!(f)?;
|
||||
write!(f, "{:block_indentation$}", "")?;
|
||||
fragment.render(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::Nested(inner) => inner.render_with_prefix(
|
||||
f,
|
||||
indentation + INDENTATION_SIZE.saturating_sub(prefix.len()),
|
||||
prefix,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Block {
|
||||
fn from(value: String) -> Self {
|
||||
Block::Literal(value.lines().map(|v| Fragment(v.to_string().into())).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Block {
|
||||
fn from(value: &'static str) -> Self {
|
||||
Block::Literal(value.lines().map(|v| Fragment(v.into())).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for Block {
|
||||
fn from(value: Cow<'static, str>) -> Self {
|
||||
match value {
|
||||
Cow::Borrowed(value) => value.into(),
|
||||
Cow::Owned(value) => value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A string representing one line of a description or match explanation.
|
||||
#[derive(Debug)]
|
||||
struct Fragment(Cow<'static, str>);
|
||||
|
||||
impl Fragment {
|
||||
fn render(&self, f: &mut dyn Write) -> Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The decoration which appears on [`Block`] of a [`List`] when rendered.
|
||||
#[derive(Debug, Default)]
|
||||
enum Decoration {
|
||||
/// No decoration on each [`Block`]. The default.
|
||||
#[default]
|
||||
None,
|
||||
|
||||
/// Each [`Block`] is preceded by a bullet (`* `).
|
||||
Bullet,
|
||||
|
||||
/// Each [`Block`] is preceded by its index in the [`List`] (`0. `, `1. `,
|
||||
/// ...).
|
||||
Enumerate,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Block, Fragment, List};
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn renders_fragment() -> Result<()> {
|
||||
let fragment = Fragment("A fragment".into());
|
||||
let mut result = String::new();
|
||||
|
||||
fragment.render(&mut result)?;
|
||||
|
||||
verify_that!(result, eq("A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_empty_block() -> Result<()> {
|
||||
let block = Block::Literal(vec![]);
|
||||
let mut result = String::new();
|
||||
|
||||
block.render(&mut result, 0, "".into())?;
|
||||
|
||||
verify_that!(result, eq(""))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_block_with_one_fragment() -> Result<()> {
|
||||
let block: Block = "A fragment".into();
|
||||
let mut result = String::new();
|
||||
|
||||
block.render(&mut result, 0, "".into())?;
|
||||
|
||||
verify_that!(result, eq("A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_block_with_two_fragments() -> Result<()> {
|
||||
let block: Block = "A fragment\nAnother fragment".into();
|
||||
let mut result = String::new();
|
||||
|
||||
block.render(&mut result, 0, "".into())?;
|
||||
|
||||
verify_that!(result, eq("A fragment\nAnother fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_indented_block() -> Result<()> {
|
||||
let block: Block = "A fragment\nAnother fragment".into();
|
||||
let mut result = String::new();
|
||||
|
||||
block.render(&mut result, 2, "".into())?;
|
||||
|
||||
verify_that!(result, eq(" A fragment\n Another fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_block_with_prefix() -> Result<()> {
|
||||
let block: Block = "A fragment\nAnother fragment".into();
|
||||
let mut result = String::new();
|
||||
|
||||
block.render(&mut result, 0, "* ".into())?;
|
||||
|
||||
verify_that!(result, eq("* A fragment\n Another fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_indented_block_with_prefix() -> Result<()> {
|
||||
let block: Block = "A fragment\nAnother fragment".into();
|
||||
let mut result = String::new();
|
||||
|
||||
block.render(&mut result, 2, "* ".into())?;
|
||||
|
||||
verify_that!(result, eq(" * A fragment\n Another fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_empty_list() -> Result<()> {
|
||||
let list = list(vec![]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq(""))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_plain_list_with_one_block() -> Result<()> {
|
||||
let list = list(vec!["A fragment".into()]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_plain_list_with_two_blocks() -> Result<()> {
|
||||
let list = list(vec!["A fragment".into(), "A fragment in a second block".into()]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("A fragment\nA fragment in a second block"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_plain_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||
let list = list(vec!["A fragment\nA second fragment".into()]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("A fragment\nA second fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_nested_plain_list_with_one_block() -> Result<()> {
|
||||
let list = list(vec![Block::nested(list(vec!["A fragment".into()]))]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq(" A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_nested_plain_list_with_two_blocks() -> Result<()> {
|
||||
let list = list(vec![Block::nested(list(vec![
|
||||
"A fragment".into(),
|
||||
"A fragment in a second block".into(),
|
||||
]))]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq(" A fragment\n A fragment in a second block"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_nested_plain_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||
let list = list(vec![Block::nested(list(vec!["A fragment\nA second fragment".into()]))]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq(" A fragment\n A second fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_bulleted_list_with_one_block() -> Result<()> {
|
||||
let list = list(vec!["A fragment".into()]).bullet_list();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("* A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_bulleted_list_with_two_blocks() -> Result<()> {
|
||||
let list =
|
||||
list(vec!["A fragment".into(), "A fragment in a second block".into()]).bullet_list();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("* A fragment\n* A fragment in a second block"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_bulleted_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||
let list = list(vec!["A fragment\nA second fragment".into()]).bullet_list();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("* A fragment\n A second fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_nested_bulleted_list_with_one_block() -> Result<()> {
|
||||
let list = list(vec![Block::nested(list(vec!["A fragment".into()]).bullet_list())]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq(" * A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_nested_bulleted_list_with_two_blocks() -> Result<()> {
|
||||
let list = list(vec![Block::nested(
|
||||
list(vec!["A fragment".into(), "A fragment in a second block".into()]).bullet_list(),
|
||||
)]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq(" * A fragment\n * A fragment in a second block"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_nested_bulleted_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||
let list = list(vec![Block::nested(
|
||||
list(vec!["A fragment\nA second fragment".into()]).bullet_list(),
|
||||
)]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq(" * A fragment\n A second fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_enumerated_list_with_one_block() -> Result<()> {
|
||||
let list = list(vec!["A fragment".into()]).enumerate();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("0. A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_enumerated_list_with_two_blocks() -> Result<()> {
|
||||
let list =
|
||||
list(vec!["A fragment".into(), "A fragment in a second block".into()]).enumerate();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("0. A fragment\n1. A fragment in a second block"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_enumerated_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||
let list = list(vec!["A fragment\nA second fragment".into()]).enumerate();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("0. A fragment\n A second fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aligns_renders_enumerated_list_with_more_than_ten_blocks() -> Result<()> {
|
||||
let list =
|
||||
(0..11).map(|i| Block::from(format!("Fragment {i}"))).collect::<List>().enumerate();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(
|
||||
result,
|
||||
eq(indoc! {"
|
||||
0. Fragment 0
|
||||
1. Fragment 1
|
||||
2. Fragment 2
|
||||
3. Fragment 3
|
||||
4. Fragment 4
|
||||
5. Fragment 5
|
||||
6. Fragment 6
|
||||
7. Fragment 7
|
||||
8. Fragment 8
|
||||
9. Fragment 9
|
||||
10. Fragment 10"})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_fragment_plus_nested_plain_list_with_one_block() -> Result<()> {
|
||||
let list =
|
||||
list(vec!["A fragment".into(), Block::nested(list(vec!["Another fragment".into()]))]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("A fragment\n Another fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_double_nested_plain_list_with_one_block() -> Result<()> {
|
||||
let list =
|
||||
list(vec![Block::nested(list(vec![Block::nested(list(vec!["A fragment".into()]))]))]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq(" A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_headers_plus_double_nested_plain_list() -> Result<()> {
|
||||
let list = list(vec![
|
||||
"First header".into(),
|
||||
Block::nested(list(vec![
|
||||
"Second header".into(),
|
||||
Block::nested(list(vec!["A fragment".into()])),
|
||||
])),
|
||||
]);
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("First header\n Second header\n A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_double_nested_bulleted_list() -> Result<()> {
|
||||
let list =
|
||||
list(vec![Block::nested(list(vec!["A fragment".into()]).bullet_list())]).bullet_list();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("* * A fragment"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_nested_enumeration_with_two_blocks_inside_bulleted_list() -> Result<()> {
|
||||
let list =
|
||||
list(vec![Block::nested(list(vec!["Block 1".into(), "Block 2".into()]).enumerate())])
|
||||
.bullet_list();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("* 0. Block 1\n 1. Block 2"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_nested_enumeration_with_block_with_two_fragments_inside_bulleted_list() -> Result<()>
|
||||
{
|
||||
let list = list(vec![Block::nested(
|
||||
list(vec!["A fragment\nAnother fragment".into()]).enumerate(),
|
||||
)])
|
||||
.bullet_list();
|
||||
let mut result = String::new();
|
||||
|
||||
list.render(&mut result, 0)?;
|
||||
|
||||
verify_that!(result, eq("* 0. A fragment\n Another fragment"))
|
||||
}
|
||||
|
||||
fn list(blocks: Vec<Block>) -> List {
|
||||
List(blocks, super::Decoration::None)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
//! Pattern matches using glob wildcards `*` and `?` with the
|
||||
//! linear-time algorithm <https://research.swtch.com/glob>.
|
||||
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
|
||||
pub struct Pattern(String);
|
||||
|
||||
impl Pattern {
|
||||
/// Creates a new pattern matcher. Each pattern consists of
|
||||
/// regular characters, single-character wildcards `'?'`, and
|
||||
/// multi-character wildcards `'*'`.
|
||||
pub fn new(pattern: String) -> Self {
|
||||
Self(pattern)
|
||||
}
|
||||
|
||||
/// Returns true if and only if the wildcard pattern matches the
|
||||
/// string.
|
||||
pub fn matches(&self, string: &str) -> bool {
|
||||
let processor = Processor {
|
||||
pattern: self.0.chars().peekable(),
|
||||
string: string.chars().peekable(),
|
||||
restart: None,
|
||||
};
|
||||
|
||||
processor.process()
|
||||
}
|
||||
}
|
||||
|
||||
type PeekableChars<'a> = Peekable<Chars<'a>>;
|
||||
|
||||
/// Represents the state we need to restart search from a star wildcard (`*`).
|
||||
struct Restart<'a> {
|
||||
pattern_next: PeekableChars<'a>,
|
||||
string_next: PeekableChars<'a>,
|
||||
}
|
||||
|
||||
/// The runtime state for glob matching.
|
||||
struct Processor<'a> {
|
||||
pattern: PeekableChars<'a>,
|
||||
string: PeekableChars<'a>,
|
||||
restart: Option<Restart<'a>>,
|
||||
}
|
||||
|
||||
/// Represents what to do after any step through the processor.
|
||||
enum StepOutcome {
|
||||
Proceed,
|
||||
TryRestart,
|
||||
}
|
||||
|
||||
impl Processor<'_> {
|
||||
/// Runs the pattern matching until we find an unrecoverable
|
||||
/// mismatch, or the input is consumed.
|
||||
fn process(mut self) -> bool {
|
||||
while self.is_unfinished() {
|
||||
let outcome = self.step();
|
||||
if let StepOutcome::TryRestart = outcome {
|
||||
let restarted = self.try_restart();
|
||||
if !restarted {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn is_unfinished(&mut self) -> bool {
|
||||
self.pattern.peek().is_some() || self.string.peek().is_some()
|
||||
}
|
||||
|
||||
/// Takes a single step forward, and returns whether to proceed or try to
|
||||
/// restart.
|
||||
fn step(&mut self) -> StepOutcome {
|
||||
match self.pattern.peek() {
|
||||
Some('?') => self.step_question_wildcard(),
|
||||
Some('*') => self.step_star_wildcard(),
|
||||
Some(pattern_ch) => {
|
||||
let pattern_ch = *pattern_ch;
|
||||
self.step_ordinary_character(pattern_ch)
|
||||
}
|
||||
None => StepOutcome::TryRestart,
|
||||
}
|
||||
}
|
||||
|
||||
/// Match any single character.
|
||||
fn step_question_wildcard(&mut self) -> StepOutcome {
|
||||
if self.string.peek().is_some() {
|
||||
_ = self.pattern.next();
|
||||
_ = self.string.next();
|
||||
StepOutcome::Proceed
|
||||
} else {
|
||||
StepOutcome::TryRestart
|
||||
}
|
||||
}
|
||||
|
||||
/// Match zero or more characters. Start by skipping over the
|
||||
/// wildcard and matching zero characters from string. If that
|
||||
/// fails, restart and match one more character than the last
|
||||
/// attempt.
|
||||
fn step_star_wildcard(&mut self) -> StepOutcome {
|
||||
self.restart = if self.string.peek().is_none() {
|
||||
// Subtle: if the string is already exhausted, we mark
|
||||
// that we can't restart.
|
||||
None
|
||||
} else {
|
||||
let pattern_next = self.pattern.clone();
|
||||
let mut string_next = self.string.clone();
|
||||
string_next.next();
|
||||
Some(Restart { pattern_next, string_next })
|
||||
};
|
||||
|
||||
_ = self.pattern.next();
|
||||
StepOutcome::Proceed
|
||||
}
|
||||
|
||||
/// Match an ordinary (non-wildcard) character.
|
||||
fn step_ordinary_character(&mut self, pattern_ch: char) -> StepOutcome {
|
||||
if self.string.peek() == Some(&pattern_ch) {
|
||||
_ = self.pattern.next();
|
||||
_ = self.string.next();
|
||||
StepOutcome::Proceed
|
||||
} else {
|
||||
StepOutcome::TryRestart
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to restart from failing to match a character. If true, the
|
||||
/// matching can restart.
|
||||
fn try_restart(&mut self) -> bool {
|
||||
if let Some(Restart { pattern_next, string_next }) = &self.restart {
|
||||
self.pattern = pattern_next.clone();
|
||||
self.string = string_next.clone();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if `s` contains glob wildcards.
|
||||
pub fn is_glob_pattern(s: &str) -> bool {
|
||||
s.contains(['?', '*'])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn simple_character_match() -> Result<()> {
|
||||
verify_that!(Pattern::new("a".into()).matches("a"), is_true())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_character_mismatch() -> Result<()> {
|
||||
verify_that!(Pattern::new("b".into()).matches("a"), is_false())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_word_mismatch() -> Result<()> {
|
||||
verify_that!(Pattern::new("judgement".into()).matches("judgment"), is_false())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn question_match() -> Result<()> {
|
||||
verify_that!(Pattern::new("?".into()).matches("a"), is_true())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_word_question_match() -> Result<()> {
|
||||
let pattern = Pattern::new("judg?ment".into());
|
||||
verify_that!(pattern.matches("judgment"), is_false())?;
|
||||
verify_that!(pattern.matches("judgement"), is_true())?;
|
||||
verify_that!(pattern.matches("judge ment"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn question_mismatch() -> Result<()> {
|
||||
let pattern = Pattern::new("?".into());
|
||||
verify_that!(pattern.matches(""), is_false())?;
|
||||
verify_that!(pattern.matches("aa"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glob_on_empty() -> Result<()> {
|
||||
verify_that!(Pattern::new("*".into()).matches(""), is_true())?;
|
||||
verify_that!(Pattern::new("**".into()).matches(""), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glob_prefix() -> Result<()> {
|
||||
let pattern = Pattern::new("*a".into());
|
||||
verify_that!(pattern.matches(""), is_false())?;
|
||||
verify_that!(pattern.matches("a"), is_true())?;
|
||||
verify_that!(pattern.matches("ba"), is_true())?;
|
||||
verify_that!(pattern.matches("bba"), is_true())?;
|
||||
verify_that!(pattern.matches("bbab"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glob_within() -> Result<()> {
|
||||
let pattern = Pattern::new("b*a".into());
|
||||
verify_that!(pattern.matches(""), is_false())?;
|
||||
verify_that!(pattern.matches("b"), is_false())?;
|
||||
verify_that!(pattern.matches("bb"), is_false())?;
|
||||
verify_that!(pattern.matches("ba"), is_true())?;
|
||||
verify_that!(pattern.matches("bbbba"), is_true())?;
|
||||
verify_that!(pattern.matches("baa"), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glob_suffix() -> Result<()> {
|
||||
let pattern = Pattern::new("ba*".into());
|
||||
verify_that!(pattern.matches(""), is_false())?;
|
||||
verify_that!(pattern.matches("b"), is_false())?;
|
||||
verify_that!(pattern.matches("bb"), is_false())?;
|
||||
verify_that!(pattern.matches("ba"), is_true())?;
|
||||
verify_that!(pattern.matches("baa"), is_true())?;
|
||||
verify_that!(pattern.matches("bab"), is_true())?;
|
||||
verify_that!(pattern.matches("bba"), is_false())?;
|
||||
verify_that!(pattern.matches("bbbba"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redundant_stars() -> Result<()> {
|
||||
let pattern = Pattern::new("**a".into());
|
||||
verify_that!(pattern.matches(""), is_false())?;
|
||||
verify_that!(pattern.matches("a"), is_true())?;
|
||||
verify_that!(pattern.matches("ba"), is_true())?;
|
||||
verify_that!(pattern.matches("bba"), is_true())?;
|
||||
verify_that!(pattern.matches("bbab"), is_false())?;
|
||||
verify_that!(pattern.matches("bbaba"), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn star_question_star_case() -> Result<()> {
|
||||
let pattern = Pattern::new("*?*".into());
|
||||
verify_that!(pattern.matches(""), is_false())?;
|
||||
verify_that!(pattern.matches("a"), is_true())?;
|
||||
verify_that!(pattern.matches("aa"), is_true())?;
|
||||
verify_that!(pattern.matches("aaa"), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn another_case_finding_two_separated_a() -> Result<()> {
|
||||
let pattern = Pattern::new("*a?a*".into());
|
||||
verify_that!(pattern.matches(""), is_false())?;
|
||||
verify_that!(pattern.matches("a"), is_false())?;
|
||||
verify_that!(pattern.matches("aa"), is_false())?;
|
||||
verify_that!(pattern.matches("aaa"), is_true())?;
|
||||
verify_that!(pattern.matches("aba"), is_true())?;
|
||||
verify_that!(pattern.matches("baba"), is_true())?;
|
||||
verify_that!(pattern.matches("abab"), is_true())?;
|
||||
verify_that!(pattern.matches("babab"), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn banana() -> Result<()> {
|
||||
let pattern = Pattern::new("b?n???".into());
|
||||
verify_that!(pattern.matches("banana"), is_true())?;
|
||||
verify_that!(pattern.matches("binary"), is_true())?;
|
||||
verify_that!(pattern.matches("bundle"), is_true())?;
|
||||
verify_that!(pattern.matches("bindir"), is_true())?;
|
||||
|
||||
verify_that!(pattern.matches("bananas"), is_false())?;
|
||||
verify_that!(pattern.matches("bucket"), is_false())?;
|
||||
verify_that!(pattern.matches("budget"), is_false())?;
|
||||
verify_that!(pattern.matches("bazzar"), is_false())?;
|
||||
verify_that!(pattern.matches("burger"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glob_word() -> Result<()> {
|
||||
let word = Pattern::new("*word*".into());
|
||||
verify_that!(word.matches("bird"), is_false())?;
|
||||
verify_that!(word.matches("This is a wordy sentence"), is_true())?;
|
||||
verify_that!(word.matches("word soup"), is_true())?;
|
||||
verify_that!(word.matches("bird is the word"), is_true())?;
|
||||
verify_that!(word.matches("word"), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_glob() -> Result<()> {
|
||||
verify_that!(
|
||||
Pattern::new("************************************************.*".into())
|
||||
.matches("this is a test.com"),
|
||||
is_true()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_glob_2_mismatch() -> Result<()> {
|
||||
// The first example from https://research.swtch.com/glob, with N=1000.
|
||||
const N: usize = 1000;
|
||||
let long_pattern = ["a*"; N].into_iter().collect::<String>() + "b";
|
||||
let long_string = ["a"; N].into_iter().collect::<String>();
|
||||
verify_that!(Pattern::new(long_pattern).matches(&long_string), is_false())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
#![doc(hidden)]
|
||||
|
||||
pub(crate) mod description_renderer;
|
||||
pub mod glob;
|
||||
pub mod scoped_trace;
|
||||
pub mod test_data;
|
||||
pub mod test_filter;
|
||||
pub mod test_outcome;
|
||||
pub mod test_sharding;
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2026 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 std::cell::RefCell;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
static NEXT_TRACE_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
/// Information about a scoped trace.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TraceInfo {
|
||||
pub id: usize,
|
||||
pub file: &'static str,
|
||||
pub line: u32,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static TRACE_STACK: RefCell<Vec<TraceInfo>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
/// RAII guard to manage the push and pop of trace information.
|
||||
///
|
||||
/// This struct is `!Send` and `!Sync` to prevent it from being held across
|
||||
/// `.await` points in async tests, which would cause incorrect trace tracking
|
||||
/// if the task moves between threads.
|
||||
#[doc(hidden)]
|
||||
pub struct ScopedTraceGuard {
|
||||
id: usize,
|
||||
_phantom: std::marker::PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl ScopedTraceGuard {
|
||||
#[doc(hidden)]
|
||||
#[track_caller]
|
||||
pub fn new(message: String) -> Self {
|
||||
let caller = std::panic::Location::caller();
|
||||
let id = NEXT_TRACE_ID.fetch_add(1, Ordering::Relaxed);
|
||||
TRACE_STACK.with(|stack| {
|
||||
// Use try_borrow_mut to avoid double panic if called during unwinding.
|
||||
if let Ok(mut s) = stack.try_borrow_mut() {
|
||||
s.push(TraceInfo { id, file: caller.file(), line: caller.line(), message });
|
||||
}
|
||||
});
|
||||
Self { id, _phantom: std::marker::PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ScopedTraceGuard {
|
||||
fn drop(&mut self) {
|
||||
TRACE_STACK.with(|stack| {
|
||||
// Use try_borrow_mut to avoid double panic if called during unwinding.
|
||||
if let Ok(mut s) = stack.try_borrow_mut() {
|
||||
if let Some(pos) = s.iter().rposition(|t| t.id == self.id) {
|
||||
s.remove(pos);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves a clone of the current thread's trace stack.
|
||||
pub fn get_scoped_traces() -> Vec<TraceInfo> {
|
||||
TRACE_STACK.with(|stack| stack.try_borrow().map(|s| s.clone()).unwrap_or_default())
|
||||
}
|
||||
|
||||
// Test-only state and helpers, hidden from production API.
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_helpers {
|
||||
use super::*;
|
||||
use std::cell::Cell;
|
||||
|
||||
thread_local! {
|
||||
pub static CAPTURED_TRACES_IN_HOOK: RefCell<Vec<TraceInfo>> = const { RefCell::new(Vec::new()) };
|
||||
pub static USE_CAPTURE_HOOK: Cell<bool> = const { Cell::new(false) };
|
||||
}
|
||||
|
||||
pub fn enable_capture_in_hook(enable: bool) {
|
||||
USE_CAPTURE_HOOK.with(|v| v.set(enable));
|
||||
}
|
||||
|
||||
pub fn get_captured_traces_in_hook() -> Vec<TraceInfo> {
|
||||
CAPTURED_TRACES_IN_HOOK.with(|v| v.borrow().clone())
|
||||
}
|
||||
|
||||
pub fn clear_captured_traces_in_hook() {
|
||||
CAPTURED_TRACES_IN_HOOK.with(|v| v.borrow_mut().clear());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::test_helpers::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_scoped_trace_fatal() {
|
||||
enable_capture_in_hook(true);
|
||||
clear_captured_traces_in_hook();
|
||||
|
||||
// Ensure hook is installed
|
||||
crate::internal::test_outcome::TestOutcome::init_current_test_outcome();
|
||||
|
||||
let _ = std::panic::catch_unwind(|| {
|
||||
let _guard = ScopedTraceGuard::new("Second trace".to_string());
|
||||
panic!("Intentional panic");
|
||||
});
|
||||
|
||||
let captured = get_captured_traces_in_hook();
|
||||
assert_eq!(captured.len(), 1);
|
||||
assert_eq!(captured[0].message, "Second trace");
|
||||
|
||||
enable_capture_in_hook(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//! This is for testing only to provide fully-qualified struct paths to macros.
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct TestStruct {
|
||||
#[doc(hidden)]
|
||||
pub value: u32,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
#[doc(hidden)]
|
||||
pub fn get_value(&self) -> u32 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct GenericTestStruct<T> {
|
||||
#[doc(hidden)]
|
||||
pub value: T,
|
||||
}
|
||||
|
||||
impl<T> GenericTestStruct<T> {
|
||||
#[doc(hidden)]
|
||||
pub fn get_value<U>(&self, a: U) -> U {
|
||||
a
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
//! Implements the googletest test filtering protocol.
|
||||
//!
|
||||
//! The Google test filtering protocol consists of the following
|
||||
//! environment variable:
|
||||
//!
|
||||
//! * TESTBRIDGE_TEST_ONLY: string passed from --test_filter
|
||||
//!
|
||||
//! The format of a filter is a ‘,‘-separated list of wildcard
|
||||
//! patterns (called the positive patterns) optionally followed by a
|
||||
//! ‘-’ and another ‘,‘-separated pattern list (called the negative
|
||||
//! patterns). A test matches the filter if and only if it matches any
|
||||
//! of the positive patterns but does not match any of the negative
|
||||
//! patterns. (Note that this is a deliberate devation from GTest
|
||||
//! C++, which uses colons to separate the patterns, as colons will
|
||||
//! unfortunately clash with Rust's "::" namespacing operator.)
|
||||
//!
|
||||
//! As an example: "*mount*-*doom*" will accept any string that contains the
|
||||
//! substring "mount", as long as it also doesn't contain "doom"
|
||||
use crate::internal::glob::{is_glob_pattern, Pattern};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static TEST_FILTER: OnceLock<Box<dyn TestFilter + Send + Sync>> = OnceLock::new();
|
||||
|
||||
pub fn test_should_run(test_name: &str) -> bool {
|
||||
let test_filter = TEST_FILTER.get_or_init(|| {
|
||||
if let Ok(testbridge_test_only) = std::env::var("TESTBRIDGE_TEST_ONLY") {
|
||||
Box::new(get_test_filter(&testbridge_test_only))
|
||||
} else {
|
||||
Box::new(AcceptAll)
|
||||
}
|
||||
});
|
||||
|
||||
test_filter.filter(test_name)
|
||||
}
|
||||
|
||||
trait TestFilter {
|
||||
/// Returns true if the test should run.
|
||||
fn filter(&self, test_name: &str) -> bool;
|
||||
}
|
||||
|
||||
struct AcceptAll;
|
||||
impl TestFilter for AcceptAll {
|
||||
fn filter(&self, _test_name: &str) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct Equals(String);
|
||||
impl TestFilter for Equals {
|
||||
fn filter(&self, test_name: &str) -> bool {
|
||||
test_name == self.0
|
||||
}
|
||||
}
|
||||
|
||||
struct Matches(Pattern);
|
||||
impl TestFilter for Matches {
|
||||
fn filter(&self, test_name: &str) -> bool {
|
||||
self.0.matches(test_name)
|
||||
}
|
||||
}
|
||||
|
||||
struct Collection {
|
||||
// The positive portion:
|
||||
positive_equals: Box<[Equals]>,
|
||||
positive_matches: Box<[Matches]>,
|
||||
|
||||
// The negative portion:
|
||||
negative_equals: Box<[Equals]>,
|
||||
negative_matches: Box<[Matches]>,
|
||||
}
|
||||
|
||||
impl TestFilter for Collection {
|
||||
fn filter(&self, test_name: &str) -> bool {
|
||||
(self.positive_equals.iter().any(|f| f.filter(test_name))
|
||||
|| self.positive_matches.iter().any(|f| f.filter(test_name)))
|
||||
&& (!self.negative_equals.iter().any(|f| f.filter(test_name)))
|
||||
&& (!self.negative_matches.iter().any(|f| f.filter(test_name)))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_test_filter(testbridge_test_only: &str) -> Collection {
|
||||
let positive_negative: Vec<&str> = testbridge_test_only.splitn(2, '-').collect();
|
||||
|
||||
let (positive_with_globs, positive_literals): (Vec<_>, Vec<_>) = {
|
||||
let positive = positive_negative[0];
|
||||
if positive.is_empty() {
|
||||
// Forces the empty positive filter to accept everything:
|
||||
(vec!["*"], vec![])
|
||||
} else {
|
||||
positive.split(',').partition(|s| is_glob_pattern(s))
|
||||
}
|
||||
};
|
||||
|
||||
let (negative_with_globs, negative_literals): (Vec<_>, Vec<_>) = match positive_negative.get(1)
|
||||
{
|
||||
Some(negative) if !negative.is_empty() => {
|
||||
negative.split(',').partition(|s| is_glob_pattern(s))
|
||||
}
|
||||
_ => (vec![], vec![]),
|
||||
};
|
||||
|
||||
Collection {
|
||||
positive_equals: positive_literals.into_iter().map(|s| Equals(s.to_string())).collect(),
|
||||
positive_matches: positive_with_globs
|
||||
.into_iter()
|
||||
.map(|s| Matches(Pattern::new(s.to_string())))
|
||||
.collect(),
|
||||
negative_equals: negative_literals.into_iter().map(|s| Equals(s.to_string())).collect(),
|
||||
negative_matches: negative_with_globs
|
||||
.into_iter()
|
||||
.map(|s| Matches(Pattern::new(s.to_string())))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn accept_all_accepts_all() -> Result<()> {
|
||||
let filter = AcceptAll;
|
||||
|
||||
verify_that!(filter.filter(""), is_true())?;
|
||||
verify_that!(filter.filter("abcdefg"), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_filter_accepts_all() -> Result<()> {
|
||||
let filter = get_test_filter("");
|
||||
|
||||
verify_that!(filter.filter(""), is_true())?;
|
||||
verify_that!(filter.filter("abcdefg"), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_negation_filter_accepts_all() -> Result<()> {
|
||||
let filter = get_test_filter("-");
|
||||
|
||||
verify_that!(filter.filter(""), is_true())?;
|
||||
verify_that!(filter.filter("abcdefg"), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_literal_filter() -> Result<()> {
|
||||
let filter = get_test_filter("*magic*");
|
||||
|
||||
verify_that!(filter.filter("this_is_magic"), is_true())?;
|
||||
verify_that!(filter.filter(""), is_false())?;
|
||||
verify_that!(filter.filter("magic"), is_true())?;
|
||||
verify_that!(filter.filter("science"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn star_globs() -> Result<()> {
|
||||
let filter = get_test_filter("a*b");
|
||||
|
||||
verify_that!(filter.filter(""), is_false())?;
|
||||
verify_that!(filter.filter("a"), is_false())?;
|
||||
verify_that!(filter.filter("ab"), is_true())?;
|
||||
verify_that!(filter.filter("a b"), is_true())?;
|
||||
verify_that!(filter.filter("b"), is_false())?;
|
||||
verify_that!(filter.filter("b a"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn question_globs() -> Result<()> {
|
||||
let filter = get_test_filter("a?b");
|
||||
|
||||
verify_that!(filter.filter(""), is_false())?;
|
||||
verify_that!(filter.filter("a"), is_false())?;
|
||||
verify_that!(filter.filter("ab"), is_false())?;
|
||||
verify_that!(filter.filter("aXb"), is_true())?;
|
||||
verify_that!(filter.filter("a b"), is_true())?;
|
||||
verify_that!(filter.filter("b"), is_false())?;
|
||||
verify_that!(filter.filter("b a"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collection() -> Result<()> {
|
||||
let filter = get_test_filter("a,b");
|
||||
|
||||
verify_that!(filter.filter(""), is_false())?;
|
||||
verify_that!(filter.filter("a"), is_true())?;
|
||||
verify_that!(filter.filter("ab"), is_false())?;
|
||||
verify_that!(filter.filter("a b"), is_false())?;
|
||||
verify_that!(filter.filter("b"), is_true())?;
|
||||
verify_that!(filter.filter("b a"), is_false())?;
|
||||
verify_that!(filter.filter("c"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collection_with_globs() -> Result<()> {
|
||||
let filter = get_test_filter("*test1*,*test2*");
|
||||
|
||||
verify_that!(filter.filter(""), is_false())?;
|
||||
verify_that!(filter.filter("this is test1"), is_true())?;
|
||||
verify_that!(filter.filter("and test2 is it"), is_true())?;
|
||||
verify_that!(filter.filter("but test3 is not"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collection_with_globs_negation() -> Result<()> {
|
||||
let filter = get_test_filter("*test*-*testbad");
|
||||
|
||||
verify_that!(filter.filter(""), is_false())?;
|
||||
verify_that!(filter.filter("module"), is_false())?;
|
||||
verify_that!(filter.filter("module::my_test1"), is_true())?;
|
||||
verify_that!(filter.filter("module::my_test2"), is_true())?;
|
||||
verify_that!(filter.filter("module::my_testbad"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mount_doom() -> Result<()> {
|
||||
let filter = get_test_filter("*mount*-*doom*");
|
||||
|
||||
verify_that!(filter.filter(""), is_false())?;
|
||||
verify_that!(filter.filter("mount rushmore"), is_true())?;
|
||||
verify_that!(filter.filter("doom mount"), is_false())?;
|
||||
verify_that!(filter.filter("dismount"), is_true())?;
|
||||
verify_that!(filter.filter("mountains of moria"), is_true())?;
|
||||
verify_that!(filter.filter("frodo and sam went to mount doom"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collection_with_only_negation() -> Result<()> {
|
||||
let filter = get_test_filter("-testbad1,testbad2");
|
||||
|
||||
verify_that!(filter.filter(""), is_true())?;
|
||||
verify_that!(filter.filter("test"), is_true())?;
|
||||
verify_that!(filter.filter("testbad1"), is_false())?;
|
||||
verify_that!(filter.filter("testbad2"), is_false())?;
|
||||
verify_that!(filter.filter("testbad3"), is_true())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn magic_words_a_and_e() -> Result<()> {
|
||||
let filter = get_test_filter("a*,e*-abracadabra,elbereth,avada kedavra");
|
||||
|
||||
verify_that!(filter.filter("alakazam"), is_true())?;
|
||||
verify_that!(filter.filter("abracadabra"), is_false())?;
|
||||
verify_that!(filter.filter("avada kedavra"), is_false())?;
|
||||
verify_that!(filter.filter("enchantment"), is_true())?;
|
||||
verify_that!(filter.filter("elbereth"), is_false())?;
|
||||
verify_that!(filter.filter("expecto patronum"), is_true())?;
|
||||
verify_that!(filter.filter("fuego"), is_false())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
// 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 std::cell::{RefCell, RefMut};
|
||||
use std::fmt::{Debug, Display, Error, Formatter};
|
||||
use std::sync::OnceLock;
|
||||
use std::thread_local;
|
||||
|
||||
/// The outcome hitherto of running a test.
|
||||
///
|
||||
/// This is kept as a running record as the test progresses. One can access it
|
||||
/// with `TestOutcome::with_current_test_outcome`.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub enum TestOutcome {
|
||||
/// The test ran or is currently running and no assertions have failed.
|
||||
Success,
|
||||
/// The test ran or is currently running and at least one assertion has
|
||||
/// failed.
|
||||
Failure,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_TEST_OUTCOME: RefCell<Option<TestOutcome>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
impl TestOutcome {
|
||||
/// Resets the current test's [`TestOutcome`].
|
||||
///
|
||||
/// This is intended only for use by the attribute macro `#[gtest]`.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub fn init_current_test_outcome() {
|
||||
static INSTALL_HOOK: OnceLock<()> = OnceLock::new();
|
||||
INSTALL_HOOK.get_or_init(|| {
|
||||
let prev_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let traces = crate::internal::scoped_trace::get_scoped_traces();
|
||||
if !traces.is_empty() {
|
||||
#[cfg(test)]
|
||||
{
|
||||
use crate::internal::scoped_trace::test_helpers::*;
|
||||
let use_capture = USE_CAPTURE_HOOK.with(|v| v.get());
|
||||
if use_capture {
|
||||
let _ = CAPTURED_TRACES_IN_HOOK
|
||||
.with(|v| v.try_borrow_mut().map(|mut b| *b = traces.clone()));
|
||||
prev_hook(info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("Google Test trace:");
|
||||
for trace in traces.iter().rev() {
|
||||
eprintln!(" {}:{}: {}", trace.file, trace.line, trace.message);
|
||||
}
|
||||
}
|
||||
prev_hook(info);
|
||||
}));
|
||||
});
|
||||
|
||||
Self::with_current_test_outcome(|mut current_test_outcome| {
|
||||
*current_test_outcome = Some(TestOutcome::Success);
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluates the current test's [`TestOutcome`], producing a suitable
|
||||
/// `Result`.
|
||||
///
|
||||
/// The parameter `result` is the value returned by the test function
|
||||
/// itself. This returns `Result::Err` with a `Display`-formatted string of
|
||||
/// the error if `result` is `Result::Err`.
|
||||
///
|
||||
/// Otherwise, this returns `Result::Err` precisely when a test failure has
|
||||
/// been recorded with
|
||||
/// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure).
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
pub fn close_current_test_outcome<E: Display>(
|
||||
inner_result: Result<(), E>,
|
||||
) -> Result<(), TestFailure> {
|
||||
TestOutcome::with_current_test_outcome(|mut outcome| {
|
||||
let outer_result = match &*outcome {
|
||||
Some(TestOutcome::Success) => match inner_result {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) => Err(TestFailure),
|
||||
},
|
||||
Some(TestOutcome::Failure) => Err(TestFailure),
|
||||
None => {
|
||||
panic!("No test context found. This indicates a bug in GoogleTest.")
|
||||
}
|
||||
};
|
||||
if let Err(fatal_assertion_failure) = inner_result {
|
||||
println!("{fatal_assertion_failure}");
|
||||
}
|
||||
*outcome = None;
|
||||
outer_result
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a `Result` corresponding to the outcome of the currently running
|
||||
/// test.
|
||||
#[track_caller]
|
||||
pub(crate) fn get_current_test_outcome() -> Result<(), TestAssertionFailure> {
|
||||
TestOutcome::with_current_test_outcome(|mut outcome| {
|
||||
let outcome = outcome
|
||||
.as_mut()
|
||||
.expect("No test context found. This indicates a bug in GoogleTest.");
|
||||
match outcome {
|
||||
TestOutcome::Success => Ok(()),
|
||||
TestOutcome::Failure => Err(TestAssertionFailure::create("Test failed".into())),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Records that the currently running test has failed.
|
||||
fn fail_current_test() {
|
||||
TestOutcome::with_current_test_outcome(|mut outcome| {
|
||||
let outcome = outcome
|
||||
.as_mut()
|
||||
.expect("No test context found. This indicates a bug in GoogleTest.");
|
||||
*outcome = TestOutcome::Failure;
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs `action` with the [`TestOutcome`] for the currently running test.
|
||||
///
|
||||
/// This is primarily intended for use by assertion macros like
|
||||
/// `expect_that!`.
|
||||
fn with_current_test_outcome<T>(action: impl FnOnce(RefMut<Option<TestOutcome>>) -> T) -> T {
|
||||
CURRENT_TEST_OUTCOME.with(|current_test_outcome| action(current_test_outcome.borrow_mut()))
|
||||
}
|
||||
|
||||
/// Ensure that there is a test context present and panic if there is not.
|
||||
pub(crate) fn ensure_test_context_present() {
|
||||
if FAILURE_REPORTER_HOOK.get().is_some() {
|
||||
// Bypassed because an external runner hook is registered.
|
||||
return;
|
||||
}
|
||||
TestOutcome::with_current_test_outcome(|outcome| {
|
||||
outcome.as_ref().expect(
|
||||
"
|
||||
No test context found.
|
||||
* Did you annotate the test with gtest?
|
||||
* Is the assertion running in the original test thread?
|
||||
",
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A marking struct indicating that a test has failed.
|
||||
///
|
||||
/// This exists to implement the [Error][std::error::Error] trait. It displays
|
||||
/// to a message indicating that the actual test assertion failure messages are
|
||||
/// in the text above.
|
||||
pub struct TestFailure;
|
||||
|
||||
impl std::error::Error for TestFailure {}
|
||||
|
||||
impl std::fmt::Debug for TestFailure {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
writeln!(f, "See failure output above")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TestFailure {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
writeln!(f, "See failure output above")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A report that a single test assertion failed.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
///
|
||||
/// See pub type Result in googletest/src/lib.rs for the public alias.
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
pub struct TestAssertionFailure {
|
||||
/// A human-readable formatted string describing the error.
|
||||
pub description: String,
|
||||
pub custom_message: Option<String>,
|
||||
pub traces: Vec<crate::internal::scoped_trace::TraceInfo>,
|
||||
location: Location,
|
||||
}
|
||||
|
||||
/// A code location.
|
||||
///
|
||||
/// `std::panic::Location` does not provide a constructor, hence we cannot
|
||||
/// construct a fake value.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone)]
|
||||
enum Location {
|
||||
Real(&'static std::panic::Location<'static>),
|
||||
Fake { file: &'static str, line: u32, column: u32 },
|
||||
}
|
||||
|
||||
impl Display for Location {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Location::Real(l) => write!(f, "{l}"),
|
||||
Location::Fake { file, line, column } => write!(f, "{file}:{line}:{column}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A hook to capture non fatal test failures. This is a global static, and it
|
||||
/// is initialized exactly once.
|
||||
#[doc(hidden)]
|
||||
static FAILURE_REPORTER_HOOK: OnceLock<fn(&TestAssertionFailure)> = OnceLock::new();
|
||||
|
||||
/// Initializes the failure reporter hook.
|
||||
///
|
||||
/// This is a global static, and it is initialized at most once and returns true
|
||||
/// if the initialization was successful.
|
||||
/// If the hook is already set, this will do nothing and return false.
|
||||
#[doc(hidden)]
|
||||
pub fn set_failure_reporter_hook_if_not_set(capture_fn: fn(&TestAssertionFailure)) -> bool {
|
||||
FAILURE_REPORTER_HOOK.set(capture_fn).is_ok()
|
||||
}
|
||||
|
||||
impl TestAssertionFailure {
|
||||
/// Creates a new instance with the given `description`.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[track_caller]
|
||||
pub fn create(description: String) -> Self {
|
||||
Self {
|
||||
description,
|
||||
custom_message: None,
|
||||
traces: crate::internal::scoped_trace::get_scoped_traces(),
|
||||
location: Location::Real(std::panic::Location::caller()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set `location`` to a fake value.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
pub fn with_fake_location(mut self, file: &'static str, line: u32, column: u32) -> Self {
|
||||
self.location = Location::Fake { file, line, column };
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn log(&self) {
|
||||
println!("{self}");
|
||||
if let Some(capture_fn) = FAILURE_REPORTER_HOOK.get() {
|
||||
capture_fn(self);
|
||||
return;
|
||||
}
|
||||
TestOutcome::fail_current_test();
|
||||
}
|
||||
|
||||
/// Returns the file name of the location.
|
||||
pub fn file(&self) -> &'static str {
|
||||
match self.location {
|
||||
Location::Real(l) => l.file(),
|
||||
Location::Fake { file, .. } => file,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the line number of the location.
|
||||
pub fn line(&self) -> u32 {
|
||||
match self.location {
|
||||
Location::Real(l) => l.line(),
|
||||
Location::Fake { line, .. } => line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TestAssertionFailure {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
writeln!(f, "{}", self.description)?;
|
||||
if let Some(custom_message) = &self.custom_message {
|
||||
writeln!(f, "{custom_message}")?;
|
||||
}
|
||||
if !self.traces.is_empty() {
|
||||
writeln!(f, "Google Test trace:")?;
|
||||
for trace in self.traces.iter().rev() {
|
||||
writeln!(f, " {}:{}: {}", trace.file, trace.line, trace.message)?;
|
||||
}
|
||||
}
|
||||
writeln!(f, " at {}", self.location)
|
||||
}
|
||||
}
|
||||
|
||||
// The standard Rust test harness outputs the TestAssertionFailure with the
|
||||
// Debug trait. We want the output to be formatted, so we use a custom Debug
|
||||
// implementation which defers to Display.
|
||||
impl Debug for TestAssertionFailure {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::error::Error> From<T> for TestAssertionFailure {
|
||||
#[track_caller]
|
||||
fn from(value: T) -> Self {
|
||||
TestAssertionFailure::create(format!("{value}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "proptest")]
|
||||
impl From<TestAssertionFailure> for proptest::test_runner::TestCaseError {
|
||||
fn from(value: TestAssertionFailure) -> Self {
|
||||
proptest::test_runner::TestCaseError::Fail(format!("{value}").into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/// This module implements the googletest test sharding protocol. The Google
|
||||
/// sharding protocol consists of the following environment variables:
|
||||
///
|
||||
/// * GTEST_TOTAL_SHARDS: total number of shards.
|
||||
/// * GTEST_SHARD_INDEX: number of this shard
|
||||
/// * GTEST_SHARD_STATUS_FILE: touch this file to indicate support for sharding.
|
||||
///
|
||||
/// See also <https://google.github.io/googletest/advanced.html>
|
||||
use std::cell::OnceCell;
|
||||
use std::env::{var, var_os};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs::{self, File};
|
||||
use std::num::NonZeroU64;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Environment variable specifying the total number of test shards.
|
||||
const TEST_TOTAL_SHARDS: &[&str] = &["GTEST_TOTAL_SHARDS", "TEST_TOTAL_SHARDS"];
|
||||
|
||||
/// Environment variable specifyign the index of this test shard.
|
||||
const TEST_SHARD_INDEX: &[&str] = &["GTEST_SHARD_INDEX", "TEST_SHARD_INDEX"];
|
||||
|
||||
/// Environment variable specifying the name of the file we create (or cause a
|
||||
/// timestamp change on) to indicate that we support the sharding protocol.
|
||||
const TEST_SHARD_STATUS_FILE: &[&str] = &["GTEST_SHARD_STATUS_FILE", "TEST_SHARD_STATUS_FILE"];
|
||||
|
||||
thread_local! {
|
||||
static SHARDING: OnceCell<Sharding> = const { OnceCell::new() };
|
||||
}
|
||||
|
||||
struct Sharding {
|
||||
this_shard: u64,
|
||||
total_shards: NonZeroU64,
|
||||
}
|
||||
|
||||
impl Default for Sharding {
|
||||
fn default() -> Self {
|
||||
Self { this_shard: 0, total_shards: NonZeroU64::MIN }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_should_run(test_case_hash: u64) -> bool {
|
||||
SHARDING.with(|sharding_cell| {
|
||||
sharding_cell.get_or_init(Sharding::from_environment).test_should_run(test_case_hash)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_var(keys: &[&str]) -> Option<String> {
|
||||
for key in keys {
|
||||
if let Ok(value) = var(OsStr::new(key)) {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn get_var_os(keys: &[&str]) -> Option<OsString> {
|
||||
for key in keys {
|
||||
if let Some(value) = var_os(OsStr::new(key)) {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
impl Sharding {
|
||||
fn test_should_run(&self, test_case_hash: u64) -> bool {
|
||||
(test_case_hash % self.total_shards.get()) == self.this_shard
|
||||
}
|
||||
|
||||
fn from_environment() -> Sharding {
|
||||
let this_shard: Option<u64> =
|
||||
{ get_var(TEST_SHARD_INDEX).and_then(|value| value.parse().ok()) };
|
||||
let total_shards: Option<NonZeroU64> = {
|
||||
get_var(TEST_TOTAL_SHARDS)
|
||||
.and_then(|value| value.parse().ok())
|
||||
.and_then(NonZeroU64::new)
|
||||
};
|
||||
|
||||
match (this_shard, total_shards) {
|
||||
(Some(this_shard), Some(total_shards)) if this_shard < total_shards.get() => {
|
||||
if let Some(name) = get_var_os(TEST_SHARD_STATUS_FILE) {
|
||||
let pathbuf = PathBuf::from(name);
|
||||
if let Err(e) = create_status_file(&pathbuf) {
|
||||
eprintln!(
|
||||
"failed to create $GTEST_SHARD_STATUS_FILE file {}: {}",
|
||||
pathbuf.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Sharding { this_shard, total_shards }
|
||||
}
|
||||
_ => Sharding::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_status_file(path: &Path) -> std::io::Result<()> {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
File::create(path).map(|_| ())
|
||||
}
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
// 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.
|
||||
|
||||
#![doc = include_str!("../crate_docs.md")]
|
||||
|
||||
extern crate googletest_macro;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate quickcheck;
|
||||
|
||||
#[macro_use]
|
||||
pub mod assertions;
|
||||
pub mod description;
|
||||
pub mod fixtures;
|
||||
#[macro_use]
|
||||
pub mod fmt;
|
||||
pub mod internal;
|
||||
pub mod matcher;
|
||||
pub mod matcher_support;
|
||||
pub mod matchers;
|
||||
|
||||
pub use googletest_macro::__abbreviated_stringify;
|
||||
|
||||
/// Re-exports of the symbols in this crate which are most likely to be used.
|
||||
///
|
||||
/// This includes:
|
||||
/// * All assertion macros,
|
||||
/// * Traits and type definitions normally used by tests, and
|
||||
/// * All built-in matchers.
|
||||
///
|
||||
/// Typically, one imports everything in the prelude in one's test module:
|
||||
///
|
||||
/// ```
|
||||
/// mod tests {
|
||||
/// use googletest::prelude::*;
|
||||
/// }
|
||||
/// ```
|
||||
pub mod prelude {
|
||||
pub use super::fixtures::{ConsumableFixture, Fixture, FixtureOf, StaticFixture};
|
||||
pub use super::gtest;
|
||||
pub use super::matcher::{Matcher, MatcherBase};
|
||||
pub use super::matchers::*;
|
||||
pub use super::scoped_trace;
|
||||
pub use super::verify_current_test_outcome;
|
||||
pub use super::GoogleTestSupport;
|
||||
pub use super::OrFail;
|
||||
pub use super::Result;
|
||||
// Assert macros
|
||||
pub use super::{
|
||||
add_failure, add_failure_at, assert_false, assert_pred, assert_that, assert_true,
|
||||
expect_eq, expect_false, expect_float_eq, expect_ge, expect_gt, expect_le, expect_lt,
|
||||
expect_ne, expect_near, expect_pred, expect_that, expect_true, fail, succeed, verify_eq,
|
||||
verify_false, verify_float_eq, verify_ge, verify_gt, verify_le, verify_lt, verify_ne,
|
||||
verify_near, verify_pred, verify_that, verify_true,
|
||||
};
|
||||
}
|
||||
|
||||
/// Causes a trace to be included in every test failure message generated
|
||||
/// by code in the current scope.
|
||||
#[macro_export]
|
||||
macro_rules! scoped_trace {
|
||||
($($arg:tt)*) => {
|
||||
#[allow(clippy::shadow_same, clippy::shadow_unrelated)]
|
||||
let _gtest_trace_guard = $crate::internal::scoped_trace::ScopedTraceGuard::new(
|
||||
format!($($arg)*),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
pub use googletest_macro::gtest;
|
||||
pub use googletest_macro::test;
|
||||
|
||||
use internal::test_outcome::{TestAssertionFailure, TestOutcome};
|
||||
|
||||
/// A `Result` whose `Err` variant indicates a test failure.
|
||||
///
|
||||
/// The assertions [`verify_that!`][crate::verify_that],
|
||||
/// [`verify_pred!`][crate::verify_pred], and [`fail!`][crate::fail] evaluate
|
||||
/// to `Result<()>`. A test function may return `Result<()>` in combination with
|
||||
/// those macros to abort immediately on assertion failure.
|
||||
///
|
||||
/// This can be used with subroutines which may cause the test to fatally fail
|
||||
/// and which return some value needed by the caller. For example:
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn load_file_content_as_string() -> Result<String> {
|
||||
/// let file_stream = load_file().err_to_test_failure()?;
|
||||
/// Ok(file_stream.to_string())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The `Err` variant contains a [`TestAssertionFailure`] which carries the data
|
||||
/// of the (fatal) assertion failure which generated this result. Non-fatal
|
||||
/// assertion failures, which log the failure and report the test as having
|
||||
/// failed but allow it to continue running, are not encoded in this type.
|
||||
pub type Result<T, E = TestAssertionFailure> = std::result::Result<T, E>;
|
||||
|
||||
/// Returns a [`Result`] corresponding to the outcome of the currently running
|
||||
/// test.
|
||||
///
|
||||
/// This returns `Result::Err` precisely if the current test has recorded at
|
||||
/// least one test assertion failure via [`expect_that!`][crate::expect_that],
|
||||
/// [`expect_pred!`][crate::expect_pred], or
|
||||
/// [`GoogleTestSupport::and_log_failure`]. It can be used in concert with the
|
||||
/// `?` operator to continue execution of the test conditionally on there not
|
||||
/// having been any failure yet.
|
||||
///
|
||||
/// This requires the use of the [`#[gtest]`][crate::gtest] attribute macro.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # /* Make sure this also compiles as a doctest.
|
||||
/// #[gtest]
|
||||
/// # */
|
||||
/// # fn foo() -> u32 { 1 }
|
||||
/// # fn bar() -> u32 { 2 }
|
||||
/// fn should_fail_and_not_execute_last_assertion() -> Result<()> {
|
||||
/// # googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
|
||||
/// expect_that!(foo(), eq(2)); // May fail, but will not abort the test.
|
||||
/// expect_that!(bar(), gt(1)); // May fail, but will not abort the test.
|
||||
/// verify_current_test_outcome()?; // Aborts the test if one of the previous assertions failed.
|
||||
/// verify_that!(foo(), gt(0)) // Does not execute if the line above aborts.
|
||||
/// }
|
||||
/// # verify_that!(should_fail_and_not_execute_last_assertion(), err(displays_as(contains_substring("Test failed")))).unwrap();
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn verify_current_test_outcome() -> Result<()> {
|
||||
TestOutcome::get_current_test_outcome()
|
||||
}
|
||||
|
||||
/// Adds to `Result` support for GoogleTest Rust functionality.
|
||||
pub trait GoogleTestSupport {
|
||||
/// If `self` is a `Result::Err`, writes to `stdout` a failure report
|
||||
/// and marks the test failed. Otherwise, does nothing.
|
||||
///
|
||||
/// This can be used for non-fatal test assertions, for example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # use googletest::internal::test_outcome::TestOutcome;
|
||||
/// # TestOutcome::init_current_test_outcome();
|
||||
/// let actual = 42;
|
||||
/// verify_that!(actual, eq(42)).and_log_failure();
|
||||
/// // Test still passing; nothing happens
|
||||
/// verify_that!(actual, eq(10)).and_log_failure();
|
||||
/// // Test now fails and failure output to stdout
|
||||
/// verify_that!(actual, eq(100)).and_log_failure();
|
||||
/// // Test still fails and new failure also output to stdout
|
||||
/// # TestOutcome::close_current_test_outcome::<&str>(Ok(())).unwrap_err();
|
||||
/// ```
|
||||
fn and_log_failure(self);
|
||||
|
||||
/// If `self` is a `Result::Err`, writes to `stdout` with a failure report
|
||||
/// and the message returned by `provider`.
|
||||
///
|
||||
/// This is equivalent to combining [`GoogleTestSupport::and_log_failure`]
|
||||
/// with a call to [`GoogleTestSupport::with_failure_message`].
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::GoogleTestSupport;
|
||||
/// # use googletest::assertions::verify_eq;
|
||||
/// # use googletest::internal::test_outcome::TestOutcome;
|
||||
/// # TestOutcome::init_current_test_outcome();
|
||||
/// let actual = 0;
|
||||
/// verify_eq!(actual, 42)
|
||||
/// .and_log_failure_with_message(|| format!("Actual {} was wrong!", actual));
|
||||
/// # TestOutcome::close_current_test_outcome::<&str>(Ok(())).unwrap_err();
|
||||
/// ```
|
||||
fn and_log_failure_with_message(self, provider: impl FnOnce() -> String)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.with_failure_message(provider).and_log_failure();
|
||||
}
|
||||
|
||||
/// Adds `message` to the logged failure message if `self` is a
|
||||
/// `Result::Err`. Otherwise, does nothing.
|
||||
///
|
||||
/// If this method is called more than once, only `message` from the last
|
||||
/// invocation is output.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// let actual = 0;
|
||||
/// verify_that!(actual, eq(42)).failure_message("Actual was wrong!")?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual was wrong"))))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// results in the following failure message:
|
||||
///
|
||||
/// ```text
|
||||
/// Expected: actual equal to 42
|
||||
/// but was: 0
|
||||
/// Actual was wrong!
|
||||
/// ```
|
||||
///
|
||||
/// One can pass a `String` too:
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// let actual = 0;
|
||||
/// verify_that!(actual, eq(42))
|
||||
/// .failure_message(format!("Actual {} was wrong!", actual))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// However, consider using [`GoogleTestSupport::with_failure_message`]
|
||||
/// instead in that case to avoid unnecessary memory allocation when the
|
||||
/// message is not needed.
|
||||
fn failure_message(self, message: impl Into<String>) -> Self;
|
||||
|
||||
/// Adds the output of the closure `provider` to the logged failure message
|
||||
/// if `self` is a `Result::Err`. Otherwise, does nothing.
|
||||
///
|
||||
/// This is analogous to [`GoogleTestSupport::failure_message`] but
|
||||
/// only executes the closure `provider` if it actually produces the
|
||||
/// message, thus saving possible memory allocation.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// let actual = 0;
|
||||
/// verify_that!(actual, eq(42))
|
||||
/// .with_failure_message(|| format!("Actual {} was wrong!", actual))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
|
||||
/// # .unwrap();
|
||||
/// ```
|
||||
fn with_failure_message(self, provider: impl FnOnce() -> String) -> Self;
|
||||
}
|
||||
|
||||
impl<T> GoogleTestSupport for std::result::Result<T, TestAssertionFailure> {
|
||||
fn and_log_failure(self) {
|
||||
TestOutcome::ensure_test_context_present();
|
||||
if let Err(failure) = self {
|
||||
failure.log();
|
||||
}
|
||||
}
|
||||
|
||||
fn failure_message(mut self, message: impl Into<String>) -> Self {
|
||||
if let Err(ref mut failure) = self {
|
||||
failure.custom_message = Some(message.into());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn with_failure_message(mut self, provider: impl FnOnce() -> String) -> Self {
|
||||
if let Err(ref mut failure) = self {
|
||||
failure.custom_message = Some(provider());
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides an extension method for converting an arbitrary type into
|
||||
/// `googletest`'s [`Result`] type.
|
||||
///
|
||||
/// A type can implement this trait to provide an easy way to return immediately
|
||||
/// from a test in conjunction with the `?` operator. This is useful for
|
||||
/// [`Option`] and [`Result`][std::result::Result] types whose `Result::Err`
|
||||
/// variant does not implement [`std::error::Error`].
|
||||
///
|
||||
/// If `Result::Err` implements [`std::error::Error`] you can just use the `?`
|
||||
/// operator directly.
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[test]
|
||||
/// fn should_work() -> googletest::Result<()> {
|
||||
/// let value = something_which_can_fail().or_fail()?;
|
||||
/// let value = something_which_can_fail_with_option().or_fail()?;
|
||||
/// ...
|
||||
/// }
|
||||
///
|
||||
/// fn something_which_can_fail() -> std::result::Result<T, String> { ... }
|
||||
/// fn something_which_can_fail_with_option() -> Option<T> { ... }
|
||||
/// ```
|
||||
pub trait OrFail {
|
||||
/// The success type of the test result.
|
||||
type Output;
|
||||
|
||||
/// Converts a value into a [`Result`] containing
|
||||
/// either the [`Self::Output`] type or a [`TestAssertionFailure`].
|
||||
///
|
||||
/// The most frequently used implementations convert
|
||||
/// `Result<T, E>` into `Result<T, TestAssertionFailure>` and
|
||||
/// `Option<T>` into `Result<T, TestAssertionFailure>`.
|
||||
fn or_fail(self) -> Result<Self::Output>;
|
||||
}
|
||||
|
||||
impl<T, E: std::fmt::Debug> OrFail for std::result::Result<T, E> {
|
||||
type Output = T;
|
||||
|
||||
#[track_caller]
|
||||
fn or_fail(self) -> std::result::Result<T, TestAssertionFailure> {
|
||||
match self {
|
||||
Ok(t) => Ok(t),
|
||||
Err(e) => Err(TestAssertionFailure::create(format!("{e:?}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OrFail for Option<T> {
|
||||
type Output = T;
|
||||
|
||||
#[track_caller]
|
||||
fn or_fail(self) -> std::result::Result<T, TestAssertionFailure> {
|
||||
match self {
|
||||
Some(t) => Ok(t),
|
||||
None => Err(TestAssertionFailure::create(format!(
|
||||
"called `Option::or_fail()` on a `Option::<{}>::None` value",
|
||||
std::any::type_name::<T>()
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod __internal_macro_support {
|
||||
use crate::fixtures::Fixture;
|
||||
|
||||
pub struct FixtureTearDownOnDrop<T: Fixture> {
|
||||
fixture: Option<T>,
|
||||
}
|
||||
|
||||
impl<T: Fixture> FixtureTearDownOnDrop<T> {
|
||||
pub fn new(fixture: T) -> Self {
|
||||
Self { fixture: Some(fixture) }
|
||||
}
|
||||
pub fn tear_down(mut self) -> crate::Result<()> {
|
||||
if let Some(fixture) = self.fixture.take() {
|
||||
fixture.tear_down()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Fixture> AsRef<T> for FixtureTearDownOnDrop<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
self.fixture.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Fixture> AsMut<T> for FixtureTearDownOnDrop<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
self.fixture.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Fixture> Drop for FixtureTearDownOnDrop<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(fixture) = self.fixture.take() {
|
||||
fixture.tear_down().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
// 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.
|
||||
|
||||
//! The components required to implement matchers.
|
||||
|
||||
use crate::description::Description;
|
||||
use crate::internal::test_outcome::TestAssertionFailure;
|
||||
use crate::matchers::__internal_unstable_do_not_depend_on_these::ConjunctionMatcher;
|
||||
use crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatcher;
|
||||
pub use googletest_macro::MatcherBase;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// An interface for checking an arbitrary condition on a datum.
|
||||
///
|
||||
/// This trait is automatically implemented for a reference of any type
|
||||
/// implementing `Matcher`. This simplifies reusing a matcher in different
|
||||
/// assertions.
|
||||
///
|
||||
/// It is also implemented for tuple of `Matcher`. If `MatcherT: Matcher<T>` and
|
||||
/// `MatcherU: Matcher<U>`, then `(MatcherT, MatcherU): Matcher<(T, U)>`, and so
|
||||
/// on, up to 12 elements. Tuples longer than that do not automatically inherit
|
||||
/// the `Debug` trait from their members, so are generally not well-supported;
|
||||
/// see [Rust by Example](https://doc.rust-lang.org/rust-by-example/primitives/tuples.html#tuples).
|
||||
// `ActualT` requires `Copy` so that `actual` could be passed to `matches` and
|
||||
// if it fails passed to `explain_match`. We can relax this constraint later by
|
||||
// requiring only `Clone`.
|
||||
pub trait Matcher<ActualT: Debug + Copy>: MatcherBase {
|
||||
/// Returns whether the condition matches the datum `actual`.
|
||||
///
|
||||
/// The trait implementation defines what it means to "match". Often the
|
||||
/// matching condition is based on data stored in the matcher. For example,
|
||||
/// `eq` matches when its stored expected value is equal (in the sense of
|
||||
/// the `==` operator) to the value `actual`.
|
||||
fn matches(&self, actual: ActualT) -> MatcherResult;
|
||||
|
||||
/// Returns a description of `self` according to the supplied matching
|
||||
/// sense: a positive description if `sense` is `Match`, and a negative
|
||||
/// description if it's `NoMatch`.
|
||||
///
|
||||
/// The function should print a verb phrase that describes the property a
|
||||
/// value matching, respectively not matching, this matcher should have.
|
||||
/// The subject of the verb phrase is the value being matched.
|
||||
///
|
||||
/// The output appears next to `Expected` in an assertion failure message.
|
||||
/// For example:
|
||||
///
|
||||
/// ```text
|
||||
/// Value of: ...
|
||||
/// Expected: is equal to 7
|
||||
/// ^^^^^^^^^^^^^
|
||||
/// Actual: ...
|
||||
/// ```
|
||||
///
|
||||
/// When the matcher contains one or more inner matchers, the implementation
|
||||
/// should invoke [`Self::describe`] on the inner matchers to complete the
|
||||
/// description. It should place the inner description at a point where a
|
||||
/// verb phrase would fit. For example, the matcher
|
||||
/// [`some`][crate::matchers::some] implements `describe` as follows:
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn describe(&self, sense: MatcherResult) -> Description {
|
||||
/// match sense {
|
||||
/// MatcherResult::Match => {
|
||||
/// Description::new()
|
||||
/// .text("has a value which")
|
||||
/// .nested(self.inner.describe(MatcherResult::Match))
|
||||
/// // Inner matcher: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
/// }
|
||||
/// MatcherResult::NoMatch => {
|
||||
/// Description::new()
|
||||
/// .text("is None or has a value which")
|
||||
/// .nested(self.inner.describe(MatcherResult::NoMatch))
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The output expectation differs from that of
|
||||
/// [`explain_match`][Self::explain_match] in that it is a verb phrase
|
||||
/// (beginning with a verb like "is") rather than a relative clause
|
||||
/// (beginning with "which" or "whose"). This difference is because the
|
||||
/// output of `explain_match` is always used adjectivally to describe the
|
||||
/// actual value, while `describe` is used in contexts where a relative
|
||||
/// clause would not make sense.
|
||||
fn describe(&self, sense: MatcherResult) -> Description;
|
||||
|
||||
/// Prepares a [`String`] describing how the expected value
|
||||
/// encoded in this instance matches or does not match the given value
|
||||
/// `actual`.
|
||||
///
|
||||
/// This should be in the form of a relative clause, i.e. something starting
|
||||
/// with a relative pronoun such as "which" or "whose". It will appear next
|
||||
/// to the actual value in an assertion failure. For example:
|
||||
///
|
||||
/// ```text
|
||||
/// Value of: ...
|
||||
/// Expected: ...
|
||||
/// Actual: ["Something"], which does not contain "Something else"
|
||||
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
/// ```
|
||||
///
|
||||
/// The default implementation relies on [`describe`][Self::describe]. Thus
|
||||
/// it does not make any use of the actual value itself, but rather only
|
||||
/// whether the value is matched.
|
||||
///
|
||||
/// Override the default implementation to provide additional context on why
|
||||
/// a particular value matched or did not match. For example, the
|
||||
/// [`container_eq`][crate::matchers::container_eq] matcher displays
|
||||
/// information on which elements of the actual value were not present in
|
||||
/// the expected value and vice versa.
|
||||
///
|
||||
/// This implementation should be overridden in any matcher which contains
|
||||
/// one or more inner matchers. The implementation should invoke
|
||||
/// `explain_match` on the inner matchers, so that the generated match
|
||||
/// explanation also reflects their implementation. Without this, the match
|
||||
/// explanation of the inner matchers will not be able to make use of the
|
||||
/// actual value at all.
|
||||
///
|
||||
/// For example, the `explain_match` implementation of the matcher
|
||||
/// [`points_to`][crate::matchers::points_to] defers immediately to the
|
||||
/// inner matcher and appears as follows:
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn explain_match(&self, actual: &ActualT) -> Description {
|
||||
/// self.expected.explain_match(*actual)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The matcher can also provide some additional context before deferring to
|
||||
/// an inner matcher. In that case it should invoke `explain_match` on the
|
||||
/// inner matcher at a point where a relative clause would fit. For example:
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn explain_match(&self, actual: ActualT) -> Description {
|
||||
/// Description::new()
|
||||
/// .text("which points to a value")
|
||||
/// .nested(self.expected.explain_match(*actual))
|
||||
/// }
|
||||
/// ```
|
||||
fn explain_match(&self, actual: ActualT) -> Description {
|
||||
format!("which {}", self.describe(self.matches(actual))).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Base trait for matchers. Any type implementing `Matcher` must implement
|
||||
/// `MatcherBase`, but that should be done through the `#[derive(MatcherBase)]`
|
||||
/// macro.
|
||||
// The `and` and `or` functions cannot be part of the `Matcher` trait since it
|
||||
// can be implemented multiple times for a given matcher type. Consider that
|
||||
// `and` and `or` are part of the `Matcher` trait and `MyMatcher` implements
|
||||
// both `Matcher<A>` and `Matcher<B>`. Then `MyMatcher{...}.and(...)` can be
|
||||
// either:
|
||||
// * `Matcher::<A>::and(MyMatcher{...}, ...)` or
|
||||
// * `Matcher::<B>::and(MyMatcher{...}, ...)`.
|
||||
// Moving the `and` and `or` functions in a non-generic trait removes this
|
||||
// confusion by making `and` and `or` unique for a given type.
|
||||
pub trait MatcherBase {
|
||||
/// Constructs a matcher that matches both `self` and `right`.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!("A string", starts_with("A").and(ends_with("string")))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_1() -> Result<()> {
|
||||
/// verify_that!("A string", starts_with("Another").and(ends_with("string")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail_2() -> Result<()> {
|
||||
/// verify_that!("A string", starts_with("A").and(ends_with("non-string")))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail_1().unwrap_err();
|
||||
/// # should_fail_2().unwrap_err();
|
||||
/// ```
|
||||
// TODO(b/264518763): Replace the return type with impl Matcher and reduce
|
||||
// visibility of ConjunctionMatcher once impl in return position in trait
|
||||
// methods is stable.
|
||||
fn and<Right>(self, right: Right) -> ConjunctionMatcher<Self, Right>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ConjunctionMatcher::new(self, right)
|
||||
}
|
||||
|
||||
/// Constructs a matcher that matches when at least one of `self` or `right`
|
||||
/// matches the input.
|
||||
///
|
||||
/// ```
|
||||
/// # use googletest::prelude::*;
|
||||
/// # fn should_pass() -> Result<()> {
|
||||
/// verify_that!(10, eq(2).or(ge(5)))?; // Passes
|
||||
/// verify_that!(10, eq(2).or(eq(5)).or(ge(9)))?; // Passes
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn should_fail() -> Result<()> {
|
||||
/// verify_that!(10, eq(2).or(ge(15)))?; // Fails
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # should_pass().unwrap();
|
||||
/// # should_fail().unwrap_err();
|
||||
/// ```
|
||||
// TODO(b/264518763): Replace the return type with impl Matcher and reduce
|
||||
// visibility of DisjunctionMatcher once impl in return position in trait
|
||||
// methods is stable.
|
||||
fn or<Right>(self, right: Right) -> DisjunctionMatcher<Self, Right>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
DisjunctionMatcher::new(self, right)
|
||||
}
|
||||
}
|
||||
|
||||
/// Any actual value whose debug length is greater than this value will be
|
||||
/// pretty-printed. Otherwise, it will have normal debug output formatting.
|
||||
const PRETTY_PRINT_LENGTH_THRESHOLD: usize = 60;
|
||||
|
||||
/// Constructs a [`TestAssertionFailure`] reporting that the given `matcher`
|
||||
/// does not match the value `actual`.
|
||||
///
|
||||
/// The parameter `actual_expr` contains the expression which was evaluated to
|
||||
/// obtain `actual`.
|
||||
#[track_caller]
|
||||
pub(crate) fn create_assertion_failure<T: Debug + Copy>(
|
||||
matcher: &impl Matcher<T>,
|
||||
actual: T,
|
||||
actual_expr: &'static str,
|
||||
) -> TestAssertionFailure {
|
||||
let actual_formatted = format!("{actual:?}");
|
||||
let actual_formatted = if actual_formatted.len() > PRETTY_PRINT_LENGTH_THRESHOLD {
|
||||
format!("{actual:#?}")
|
||||
} else {
|
||||
actual_formatted
|
||||
};
|
||||
TestAssertionFailure::create(format!(
|
||||
"\
|
||||
Value of: {actual_expr}
|
||||
Expected: {}
|
||||
Actual: {actual_formatted},
|
||||
{}",
|
||||
matcher.describe(MatcherResult::Match),
|
||||
matcher.explain_match(actual).indent(),
|
||||
))
|
||||
}
|
||||
|
||||
/// The result of applying a [`Matcher`] on an actual value.
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum MatcherResult {
|
||||
/// The actual value matches according to the [`Matcher`] definition.
|
||||
Match,
|
||||
/// The actual value does not match according to the [`Matcher`] definition.
|
||||
NoMatch,
|
||||
}
|
||||
|
||||
impl From<bool> for MatcherResult {
|
||||
fn from(b: bool) -> Self {
|
||||
if b {
|
||||
MatcherResult::Match
|
||||
} else {
|
||||
MatcherResult::NoMatch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MatcherResult> for bool {
|
||||
fn from(matcher_result: MatcherResult) -> Self {
|
||||
matcher_result.is_match()
|
||||
}
|
||||
}
|
||||
|
||||
impl MatcherResult {
|
||||
/// Returns `true` if `self` is [`MatcherResult::Match`], otherwise
|
||||
/// `false`.
|
||||
pub fn is_match(self) -> bool {
|
||||
matches!(self, MatcherResult::Match)
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is [`MatcherResult::NoMatch`], otherwise
|
||||
/// `false`.
|
||||
pub fn is_no_match(self) -> bool {
|
||||
matches!(self, MatcherResult::NoMatch)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: ?Sized + MatcherBase> MatcherBase for &M {}
|
||||
|
||||
impl<T: Debug + Copy, M: Matcher<T>> Matcher<T> for &M {
|
||||
fn matches(&self, actual: T) -> MatcherResult {
|
||||
(*self).matches(actual)
|
||||
}
|
||||
|
||||
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||
(*self).describe(matcher_result)
|
||||
}
|
||||
|
||||
fn explain_match(&self, actual: T) -> Description {
|
||||
(*self).explain_match(actual)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn ref_matchers_can_be_reused() -> Result<()> {
|
||||
let matcher = eq(1);
|
||||
|
||||
verify_that!(1, &matcher)?;
|
||||
verify_that!(1, &matcher)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_matchers_as_inner_matcher() -> Result<()> {
|
||||
let matcher = gt(1);
|
||||
|
||||
verify_that!([2, 3, 4, 5], [&matcher, &matcher, &matcher, &matcher])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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.
|
||||
|
||||
#![doc(hidden)]
|
||||
|
||||
/// Macro that wraps the expression with `eq(...)` if the expression is
|
||||
/// not a matcher.
|
||||
///
|
||||
/// This is useful to let users pass expected value to macro matchers like
|
||||
/// `field!` and `property!`.
|
||||
///`
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
/// If you are interested in using it in your matcher, please file an issue to
|
||||
/// stabilize this.
|
||||
#[macro_export]
|
||||
macro_rules! __auto_eq {
|
||||
($e:expr) => {{
|
||||
#[allow(unused_imports)]
|
||||
use $crate::matcher_support::__internal_unstable_do_not_depend_on_these::ExpectedKind as _;
|
||||
match $e {
|
||||
expected => {
|
||||
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::Wrapper(
|
||||
&expected,
|
||||
)
|
||||
.kind()
|
||||
.matcher(expected)
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
// This reimplements the pattern presented in
|
||||
// https://github.com/dtolnay/case-studies/issues/14
|
||||
pub mod internal {
|
||||
use crate::{
|
||||
matcher::MatcherBase,
|
||||
matchers::{eq, EqMatcher},
|
||||
};
|
||||
|
||||
pub struct Wrapper<T>(pub T);
|
||||
|
||||
impl<T: MatcherBase> Wrapper<&'_ T> {
|
||||
#[inline]
|
||||
pub fn kind(&self) -> MatcherTag {
|
||||
MatcherTag
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExpectedKind {
|
||||
#[inline]
|
||||
fn kind(&self) -> ExpectedTag {
|
||||
ExpectedTag
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ExpectedKind for Wrapper<T> {}
|
||||
|
||||
pub struct MatcherTag;
|
||||
|
||||
impl MatcherTag {
|
||||
#[inline]
|
||||
pub fn matcher<M>(self, matcher: M) -> M {
|
||||
matcher
|
||||
}
|
||||
}
|
||||
pub struct ExpectedTag;
|
||||
|
||||
impl ExpectedTag {
|
||||
#[inline]
|
||||
pub fn matcher<T>(self, expected: T) -> EqMatcher<T> {
|
||||
eq(expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn auto_ref_matcher() -> Result<()> {
|
||||
verify_that!(123, __auto_eq!(ge(9)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_ref_expected() -> Result<()> {
|
||||
verify_that!(123, __auto_eq!(123))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_ref_on_ref_matcher() -> Result<()> {
|
||||
let matcher = eq(123);
|
||||
verify_that!(123, __auto_eq!(&matcher))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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.
|
||||
|
||||
/// Counts the number of elements in `value`.
|
||||
///
|
||||
/// This uses [`Iterator::size_hint`] when that function returns an
|
||||
/// unambiguous answer, i.e., the upper bound exists and the lower and upper
|
||||
/// bounds agree. Otherwise it iterates through `value` and counts the
|
||||
/// elements.
|
||||
pub(crate) fn count_elements<ContainerT: IntoIterator>(value: ContainerT) -> usize {
|
||||
let iterator = value.into_iter();
|
||||
if let (lower, Some(higher)) = iterator.size_hint() {
|
||||
if lower == higher {
|
||||
return lower;
|
||||
}
|
||||
}
|
||||
iterator.count()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
|
||||
#[test]
|
||||
fn count_elements_vec() -> Result<()> {
|
||||
verify_that!(count_elements(vec![1, 2, 3]), eq(3))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_elements_with_imprecise_hint() -> Result<()> {
|
||||
struct FakeIterator;
|
||||
|
||||
impl Iterator for FakeIterator {
|
||||
type Item = ();
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
None
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(0, Some(123))
|
||||
}
|
||||
}
|
||||
|
||||
verify_that!(count_elements(FakeIterator), eq(0))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_elements_with_no_hint() -> Result<()> {
|
||||
struct FakeIterator;
|
||||
|
||||
impl Iterator for FakeIterator {
|
||||
type Item = ();
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
None
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
|
||||
verify_that!(count_elements(FakeIterator), eq(0))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,649 @@
|
||||
// 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 std::fmt::Debug;
|
||||
|
||||
/// Maximum number of edits which can exist before [`edit_list`] falls back to a
|
||||
/// complete rewrite to produce the edit list.
|
||||
///
|
||||
/// Increasing this limit increases the accuracy of [`edit_list`] while
|
||||
/// quadratically increasing its worst-case runtime.
|
||||
const MAX_DISTANCE: i32 = 50;
|
||||
|
||||
/// The difference between two inputs as produced by [`edit_list`].
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Difference<T> {
|
||||
/// No differences were detected at all.
|
||||
Equal,
|
||||
|
||||
/// At most [`MAX_DISTANCE`] edits are required to convert one input to the
|
||||
/// other.
|
||||
///
|
||||
/// Contains the list of [`Edit`] to perform the transformation.
|
||||
Editable(Vec<Edit<T>>),
|
||||
|
||||
/// More than [`MAX_DISTANCE`] edits are required to convert one input to
|
||||
/// the other.
|
||||
///
|
||||
/// The inputs are therefore considered unrelated and no edit list is
|
||||
/// provided.
|
||||
Unrelated,
|
||||
}
|
||||
|
||||
/// An edit operation on two sequences of `T`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Edit<T> {
|
||||
/// An extra `T` was added to the actual sequence.
|
||||
ExtraActual(T),
|
||||
|
||||
/// An extra `T` was added to the expected sequence.
|
||||
ExtraExpected(T),
|
||||
|
||||
/// An element was added to each sequence.
|
||||
Both(T),
|
||||
|
||||
/// Additional (unlisted) elements are present in the actual sequence.
|
||||
///
|
||||
/// This is only output in the mode [`Mode::Prefix`]. Its presence precludes
|
||||
/// reconstructing the actual sequence from the expected sequence.
|
||||
AdditionalActual,
|
||||
}
|
||||
|
||||
/// Controls the termination condition of [`edit_list`].
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum Mode {
|
||||
/// Indicates that the two arguments are intended to be equal.
|
||||
///
|
||||
/// The entire edit list to transform between `actual` and `expected` is
|
||||
/// returned.
|
||||
Exact,
|
||||
|
||||
/// Indicates that `expected` is inteded to be a prefix of `actual`.
|
||||
///
|
||||
/// Any additional parts of `actual` after the prefix `expected` are omitted
|
||||
/// from the output.
|
||||
Prefix,
|
||||
|
||||
/// Similar to [`Mode::Prefix`], except it is also assumed that `actual` has
|
||||
/// some number of initial lines which should not be in the output.
|
||||
///
|
||||
/// Any initial [`Edit::ExtraActual`] entries are replaced with
|
||||
/// [`Edit::AdditionalActual`] in the edit list. If the first entry which is
|
||||
/// not an [`Edit::ExtraActual`] is [`Edit::ExtraExpected`], then the last
|
||||
/// [`Edit::ExtraActual`] is actual in the output.
|
||||
Contains,
|
||||
}
|
||||
|
||||
/// Computes the edit list of `actual` and `expected`.
|
||||
///
|
||||
/// If `actual` and `expected` are equal, then this returns
|
||||
/// [`Difference::Equal`]. If they are different but have an
|
||||
/// [edit distance](https://en.wikipedia.org/wiki/Edit_distance)
|
||||
/// of at most [`MAX_DISTANCE`], this returns [`Difference::Editable`] with the
|
||||
/// sequence of [`Edit`] which can be applied to `actual` to obtain `expected`.
|
||||
/// Otherwise this returns [`Difference::Unrelated`].
|
||||
///
|
||||
/// This uses [Myers Algorithm](https://neil.fraser.name/writing/diff/myers.pdf)
|
||||
/// with a maximum edit distance of [`MAX_DISTANCE`]. Thus the worst-case
|
||||
/// runtime is linear in both the input length and [`MAX_DISTANCE`].
|
||||
pub(crate) fn edit_list<T: PartialEq + Copy>(
|
||||
actual: impl IntoIterator<Item = T>,
|
||||
expected: impl IntoIterator<Item = T>,
|
||||
mode: Mode,
|
||||
) -> Difference<T> {
|
||||
let actual: Vec<_> = actual.into_iter().collect();
|
||||
let expected: Vec<_> = expected.into_iter().collect();
|
||||
|
||||
let mut paths_last: Vec<Path<T>> = Vec::new();
|
||||
|
||||
for distance in 0..=MAX_DISTANCE {
|
||||
let mut paths_current = Vec::new();
|
||||
for k in (-distance..=distance).step_by(2) {
|
||||
// The following will be None when k is at the edges of the range,
|
||||
// since no paths have been created for k outside the range in the
|
||||
// previous iteration.
|
||||
let path_k_minus_1 = paths_last.get(index_of_k(k - 1, -distance + 1));
|
||||
let path_k_plus_1 = paths_last.get(index_of_k(k + 1, -distance + 1));
|
||||
|
||||
let (mut path, edit) = match (path_k_minus_1, path_k_plus_1) {
|
||||
// This the first (outer) iteration.
|
||||
(None, None) => (Path::default(), None),
|
||||
|
||||
// k = -distance. There is no previous parent path yet.
|
||||
(None, Some(path_k_plus_1)) => (
|
||||
path_k_plus_1.clone(),
|
||||
expected.get(path_k_plus_1.expected_endpoint).copied().map(Edit::ExtraExpected),
|
||||
),
|
||||
|
||||
// k = distance. There is no next parent path yet.
|
||||
(Some(path_k_minus_1), None) => (
|
||||
path_k_minus_1.extend_actual_endpoint(),
|
||||
actual.get(path_k_minus_1.actual_endpoint).copied().map(Edit::ExtraActual),
|
||||
),
|
||||
|
||||
// k is strictly between -distance and distance. Both parent paths were set in the
|
||||
// last iteration.
|
||||
(Some(path_k_minus_1), Some(path_k_plus_1)) => {
|
||||
// This decides whether the algorithm prefers to add an edit
|
||||
// from the actual or from the expected when the rows differ. We
|
||||
// alternate so that the elements of differing blocks
|
||||
// interleave rather than all elements of each respective
|
||||
// side being output in a single block.
|
||||
if (distance % 2 == 0
|
||||
&& path_k_plus_1.actual_endpoint > path_k_minus_1.actual_endpoint)
|
||||
|| (distance % 2 == 1
|
||||
&& path_k_plus_1.expected_endpoint > path_k_minus_1.expected_endpoint)
|
||||
{
|
||||
(
|
||||
path_k_plus_1.clone(),
|
||||
expected
|
||||
.get(path_k_plus_1.expected_endpoint)
|
||||
.copied()
|
||||
.map(Edit::ExtraExpected),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
path_k_minus_1.extend_actual_endpoint(),
|
||||
actual
|
||||
.get(path_k_minus_1.actual_endpoint)
|
||||
.copied()
|
||||
.map(Edit::ExtraActual),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
path.edits.extend(edit);
|
||||
|
||||
// Advance through any common elements starting at the current path.
|
||||
let (mut actual_endpoint, mut expected_endpoint) =
|
||||
(path.actual_endpoint, (path.actual_endpoint as i32 - k) as usize);
|
||||
while actual_endpoint < actual.len()
|
||||
&& expected_endpoint < expected.len()
|
||||
&& actual[actual_endpoint] == expected[expected_endpoint]
|
||||
{
|
||||
path.edits.push(Edit::Both(actual[actual_endpoint]));
|
||||
(actual_endpoint, expected_endpoint) = (actual_endpoint + 1, expected_endpoint + 1);
|
||||
}
|
||||
|
||||
// If we have exhausted both inputs, we are done.
|
||||
if actual_endpoint == actual.len() && expected_endpoint == expected.len() {
|
||||
return if path.edits.iter().any(|v| !matches!(v, Edit::Both(_))) {
|
||||
if matches!(mode, Mode::Contains) {
|
||||
compress_prefix_and_suffix(&mut path.edits);
|
||||
}
|
||||
Difference::Editable(path.edits)
|
||||
} else {
|
||||
Difference::Equal
|
||||
};
|
||||
}
|
||||
|
||||
path.actual_endpoint = actual_endpoint;
|
||||
path.expected_endpoint = expected_endpoint;
|
||||
paths_current.push(path);
|
||||
}
|
||||
|
||||
if matches!(mode, Mode::Prefix) {
|
||||
if let Some(path) = paths_current
|
||||
.iter_mut()
|
||||
.filter(|p| p.expected_endpoint == expected.len())
|
||||
.max_by(|p1, p2| p1.edits.len().cmp(&p2.edits.len()))
|
||||
{
|
||||
// We've reached the end of the expected side but there could still be a
|
||||
// corresponding line on the actual which we haven't picked up into the edit
|
||||
// list. We'll just add it manually to the edit list. There's no
|
||||
// real harm doing so -- worst case is that there's an
|
||||
// additional line when there didn't have to be.
|
||||
if let Some(Edit::ExtraExpected(_)) = path.edits.last() {
|
||||
if path.actual_endpoint < actual.len() {
|
||||
// The edits from the actual should come before the corresponding one from
|
||||
// the expected, so we insert rather than push.
|
||||
path.edits.insert(
|
||||
path.edits.len() - 1,
|
||||
Edit::ExtraActual(actual[path.actual_endpoint]),
|
||||
);
|
||||
}
|
||||
}
|
||||
path.edits.push(Edit::AdditionalActual);
|
||||
return if path.edits.iter().any(|v| !matches!(v, Edit::Both(_))) {
|
||||
Difference::Editable(std::mem::take(&mut path.edits))
|
||||
} else {
|
||||
Difference::Equal
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
paths_last = paths_current;
|
||||
}
|
||||
|
||||
Difference::Unrelated
|
||||
}
|
||||
|
||||
fn index_of_k(k: i32, k_min: i32) -> usize {
|
||||
((k - k_min) / 2) as usize
|
||||
}
|
||||
|
||||
fn compress_prefix_and_suffix<T>(edits: &mut Vec<Edit<T>>) {
|
||||
if let Some(mut first_non_extra_actual_edit) =
|
||||
edits.iter().position(|e| !matches!(e, Edit::ExtraActual(_)))
|
||||
{
|
||||
if first_non_extra_actual_edit > 1
|
||||
&& matches!(edits[first_non_extra_actual_edit], Edit::ExtraExpected(_))
|
||||
{
|
||||
first_non_extra_actual_edit -= 1;
|
||||
}
|
||||
edits.splice(..first_non_extra_actual_edit, [Edit::AdditionalActual]);
|
||||
}
|
||||
|
||||
if let Some(mut last_non_extra_actual_edit) =
|
||||
edits.iter().rposition(|e| !matches!(e, Edit::ExtraActual(_)))
|
||||
{
|
||||
if last_non_extra_actual_edit < edits.len() - 1
|
||||
&& matches!(edits[last_non_extra_actual_edit], Edit::ExtraExpected(_))
|
||||
{
|
||||
last_non_extra_actual_edit += 1;
|
||||
}
|
||||
edits.splice(last_non_extra_actual_edit + 1.., [Edit::AdditionalActual]);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Path<T: Clone> {
|
||||
actual_endpoint: usize,
|
||||
expected_endpoint: usize,
|
||||
edits: Vec<Edit<T>>,
|
||||
}
|
||||
|
||||
impl<T: Clone> Default for Path<T> {
|
||||
fn default() -> Self {
|
||||
Self { actual_endpoint: 0, expected_endpoint: 0, edits: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Path<T> {
|
||||
fn extend_actual_endpoint(&self) -> Self {
|
||||
Self {
|
||||
actual_endpoint: self.actual_endpoint + 1,
|
||||
expected_endpoint: self.expected_endpoint,
|
||||
edits: self.edits.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
use crate::Result;
|
||||
use quickcheck::{quickcheck, Arbitrary, TestResult};
|
||||
|
||||
#[test]
|
||||
fn returns_equal_when_strings_are_equal() -> Result<()> {
|
||||
let result = edit_list(["A string"], ["A string"], Mode::Exact);
|
||||
verify_that!(result, matches_pattern!(&Difference::Equal))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_sequence_of_two_common_parts() -> Result<()> {
|
||||
let result = edit_list(
|
||||
["A string (1)", "A string (2)"],
|
||||
["A string (1)", "A string (2)"],
|
||||
Mode::Exact,
|
||||
);
|
||||
verify_that!(result, matches_pattern!(&Difference::Equal))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_extra_actual_when_only_actual_has_content() -> Result<()> {
|
||||
let result = edit_list(["A string"], [], Mode::Exact);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![matches_pattern!(
|
||||
&Edit::ExtraActual(eq("A string"))
|
||||
)]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_extra_expected_when_only_expected_has_content() -> Result<()> {
|
||||
let result = edit_list([], ["A string"], Mode::Exact);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![matches_pattern!(
|
||||
&Edit::ExtraExpected(eq("A string"))
|
||||
)]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_extra_actual_followed_by_extra_expected_with_two_unequal_strings() -> Result<()> {
|
||||
let result = edit_list(["A string"], ["Another string"], Mode::Exact);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::ExtraActual(eq("A string"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Another string"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interleaves_extra_actual_and_extra_expected_when_multiple_lines_differ() -> Result<()> {
|
||||
let result =
|
||||
edit_list(["A string", "A string"], ["Another string", "Another string"], Mode::Exact);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::ExtraActual(eq("A string"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Another string"))),
|
||||
matches_pattern!(&Edit::ExtraActual(eq("A string"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Another string"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_common_part_plus_difference_when_there_is_common_prefix() -> Result<()> {
|
||||
let result = edit_list(
|
||||
["Common part", "Actual only"],
|
||||
["Common part", "Expected only"],
|
||||
Mode::Exact,
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::Both(eq("Common part"))),
|
||||
matches_pattern!(&Edit::ExtraActual(eq("Actual only"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Expected only"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_common_part_plus_extra_actual_when_actual_has_extra_suffix() -> Result<()> {
|
||||
let result = edit_list(["Common part", "Actual only"], ["Common part"], Mode::Exact);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::Both(eq("Common part"))),
|
||||
matches_pattern!(&Edit::ExtraActual(eq("Actual only"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_common_part_plus_extra_expected_when_expected_has_extra_suffix() -> Result<()> {
|
||||
let result = edit_list(["Common part"], ["Common part", "Expected only"], Mode::Exact);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::Both(eq("Common part"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Expected only"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_difference_plus_common_part_when_there_is_common_suffix() -> Result<()> {
|
||||
let result = edit_list(
|
||||
["Actual only", "Common part"],
|
||||
["Expected only", "Common part"],
|
||||
Mode::Exact,
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::ExtraActual(eq("Actual only"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Expected only"))),
|
||||
matches_pattern!(&Edit::Both(eq("Common part"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_difference_plus_common_part_plus_difference_when_there_is_common_infix() -> Result<()>
|
||||
{
|
||||
let result = edit_list(
|
||||
["Actual only (1)", "Common part", "Actual only (2)"],
|
||||
["Expected only (1)", "Common part", "Expected only (2)"],
|
||||
Mode::Exact,
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::ExtraActual(eq("Actual only (1)"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Expected only (1)"))),
|
||||
matches_pattern!(&Edit::Both(eq("Common part"))),
|
||||
matches_pattern!(&Edit::ExtraActual(eq("Actual only (2)"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Expected only (2)"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_common_part_plus_difference_plus_common_part_when_there_is_common_prefix_and_suffix(
|
||||
) -> Result<()> {
|
||||
let result = edit_list(
|
||||
["Common part (1)", "Actual only", "Common part (2)"],
|
||||
["Common part (1)", "Expected only", "Common part (2)"],
|
||||
Mode::Exact,
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::Both(eq("Common part (1)"))),
|
||||
matches_pattern!(&Edit::ExtraActual(eq("Actual only"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Expected only"))),
|
||||
matches_pattern!(&Edit::Both(eq("Common part (2)"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_common_part_plus_extra_actual_plus_common_part_when_there_is_common_prefix_and_suffix(
|
||||
) -> Result<()> {
|
||||
let result = edit_list(
|
||||
["Common part (1)", "Actual only", "Common part (2)"],
|
||||
["Common part (1)", "Common part (2)"],
|
||||
Mode::Exact,
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::Both(eq("Common part (1)"))),
|
||||
matches_pattern!(&Edit::ExtraActual(eq("Actual only"))),
|
||||
matches_pattern!(&Edit::Both(eq("Common part (2)"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_common_part_plus_extra_expected_plus_common_part_when_there_is_common_prefix_and_suffix(
|
||||
) -> Result<()> {
|
||||
let result = edit_list(
|
||||
["Common part (1)", "Common part (2)"],
|
||||
["Common part (1)", "Expected only", "Common part (2)"],
|
||||
Mode::Exact,
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::Both(eq("Common part (1)"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Expected only"))),
|
||||
matches_pattern!(&Edit::Both(eq("Common part (2)"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_extra_parts_on_actual_at_end_in_prefix_mode() -> Result<()> {
|
||||
let result = edit_list(
|
||||
["Common part", "Actual only"],
|
||||
["Expected only", "Common part"],
|
||||
Mode::Prefix,
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref not(contains(matches_pattern!(
|
||||
&Edit::ExtraActual(eq("Actual only"))
|
||||
)))))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_skip_extra_parts_on_actual_in_prefix_mode_at_end_when_they_are_in_common(
|
||||
) -> Result<()> {
|
||||
let result = edit_list(
|
||||
["Actual only", "Common part"],
|
||||
["Expected only", "Common part"],
|
||||
Mode::Prefix,
|
||||
);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::ExtraActual(eq("Actual only"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Expected only"))),
|
||||
matches_pattern!(&Edit::Both(eq("Common part"))),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_skip_corresponding_line_on_actual_when_actual_and_expected_differ_in_prefix_mode(
|
||||
) -> Result<()> {
|
||||
let result = edit_list(["Actual only"], ["Expected only"], Mode::Prefix);
|
||||
verify_that!(
|
||||
result,
|
||||
matches_pattern!(&Difference::Editable(ref elements_are![
|
||||
matches_pattern!(&Edit::ExtraActual(eq("Actual only"))),
|
||||
matches_pattern!(&Edit::ExtraExpected(eq("Expected only"))),
|
||||
matches_pattern!(&Edit::AdditionalActual),
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_unrelated_when_maximum_distance_exceeded() -> Result<()> {
|
||||
let result = edit_list(0..=50, 60..110, Mode::Exact);
|
||||
verify_that!(result, matches_pattern!(&Difference::Unrelated))
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
fn edit_list_edits_actual_to_expected(
|
||||
actual: Vec<Alphabet>,
|
||||
expected: Vec<Alphabet>
|
||||
) -> TestResult {
|
||||
match edit_list(actual.clone(), expected.clone(), Mode::Exact) {
|
||||
Difference::Equal => TestResult::from_bool(actual == expected),
|
||||
Difference::Editable(edit_list) => {
|
||||
TestResult::from_bool(apply_edits_to_actual(&edit_list, &actual) == expected)
|
||||
}
|
||||
Difference::Unrelated => {
|
||||
if actual == expected {
|
||||
TestResult::failed()
|
||||
} else {
|
||||
TestResult::discard()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
fn edit_list_edits_expected_to_actual(
|
||||
actual: Vec<Alphabet>,
|
||||
expected: Vec<Alphabet>
|
||||
) -> TestResult {
|
||||
match edit_list(actual.clone(), expected.clone(), Mode::Exact) {
|
||||
Difference::Equal => TestResult::from_bool(actual == expected),
|
||||
Difference::Editable(edit_list) => {
|
||||
TestResult::from_bool(apply_edits_to_expected(&edit_list, &expected) == actual)
|
||||
}
|
||||
Difference::Unrelated => {
|
||||
if actual == expected {
|
||||
TestResult::failed()
|
||||
} else {
|
||||
TestResult::discard()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Alphabet {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
|
||||
impl Arbitrary for Alphabet {
|
||||
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||
g.choose(&[Alphabet::A, Alphabet::B, Alphabet::C]).copied().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_edits_to_actual<T: PartialEq + Debug + Copy>(
|
||||
edit_list: &[Edit<T>],
|
||||
actual: &[T],
|
||||
) -> Vec<T> {
|
||||
let mut result = Vec::new();
|
||||
let mut actual_iter = actual.iter();
|
||||
for edit in edit_list {
|
||||
match edit {
|
||||
Edit::ExtraActual(value) => {
|
||||
assert_that!(actual_iter.next(), some(eq(value)));
|
||||
}
|
||||
Edit::ExtraExpected(value) => {
|
||||
result.push(*value);
|
||||
}
|
||||
Edit::Both(value) => {
|
||||
assert_that!(actual_iter.next(), some(eq(value)));
|
||||
result.push(*value);
|
||||
}
|
||||
Edit::AdditionalActual => {
|
||||
fail!("Unexpected Edit::AdditionalActual").unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_that!(actual_iter.next(), none());
|
||||
result
|
||||
}
|
||||
|
||||
fn apply_edits_to_expected<T: PartialEq + Debug + Copy>(
|
||||
edit_list: &[Edit<T>],
|
||||
expected: &[T],
|
||||
) -> Vec<T> {
|
||||
let mut result = Vec::new();
|
||||
let mut expected_iter = expected.iter();
|
||||
for edit in edit_list {
|
||||
match edit {
|
||||
Edit::ExtraActual(value) => {
|
||||
result.push(*value);
|
||||
}
|
||||
Edit::ExtraExpected(value) => {
|
||||
assert_that!(expected_iter.next(), some(eq(value)));
|
||||
}
|
||||
Edit::Both(value) => {
|
||||
assert_that!(expected_iter.next(), some(eq(value)));
|
||||
result.push(*value);
|
||||
}
|
||||
Edit::AdditionalActual => {
|
||||
fail!("Unexpected Edit::AdditionalActual").unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_that!(expected_iter.next(), none());
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
// 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.
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod internal {
|
||||
use crate::description::Description;
|
||||
use crate::matcher::{Matcher, MatcherResult};
|
||||
use crate::matcher_support::count_elements::count_elements;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
/// The requirements of the mapping between matchers and actual values by
|
||||
/// which [`UnorderedElementsAre`] is deemed to match its input.
|
||||
///
|
||||
/// **For internal use only. API stablility is not guaranteed!**
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Requirements {
|
||||
/// There must be a 1:1 correspondence between the actual values and the
|
||||
/// matchers.
|
||||
PerfectMatch,
|
||||
|
||||
/// The mapping from matched actual values to their corresponding
|
||||
/// matchers must be surjective.
|
||||
Superset,
|
||||
|
||||
/// The mapping from matchers to matched actual values must be
|
||||
/// surjective.
|
||||
Subset,
|
||||
}
|
||||
|
||||
impl Requirements {
|
||||
pub fn explain_size_mismatch<ContainerT: IntoIterator + Copy>(
|
||||
&self,
|
||||
actual: ContainerT,
|
||||
expected_size: usize,
|
||||
) -> Option<Description> {
|
||||
let actual_size = count_elements(actual);
|
||||
match self {
|
||||
Requirements::PerfectMatch if actual_size != expected_size => {
|
||||
Some(format!("which has size {actual_size} (expected {expected_size})").into())
|
||||
}
|
||||
|
||||
Requirements::Superset if actual_size < expected_size => Some(
|
||||
format!("which has size {actual_size} (expected at least {expected_size})")
|
||||
.into(),
|
||||
),
|
||||
|
||||
Requirements::Subset if actual_size > expected_size => Some(
|
||||
format!("which has size {actual_size} (expected at most {expected_size})")
|
||||
.into(),
|
||||
),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Requirements {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Requirements::PerfectMatch => {
|
||||
write!(f, "perfect")
|
||||
}
|
||||
Requirements::Superset => {
|
||||
write!(f, "superset")
|
||||
}
|
||||
Requirements::Subset => {
|
||||
write!(f, "subset")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The bipartite matching graph between actual and expected elements.
|
||||
pub(crate) struct MatchMatrix {
|
||||
graph: Vec<Vec<MatcherResult>>, // graph[actual_idx][expected_idx]
|
||||
expected_len: usize,
|
||||
}
|
||||
|
||||
impl MatchMatrix {
|
||||
pub(crate) fn generate<'a, T: Debug + Copy + 'a>(
|
||||
actual: impl IntoIterator<Item = T>,
|
||||
expected: &[Box<dyn Matcher<T> + 'a>],
|
||||
) -> Self {
|
||||
let expected_len = expected.len();
|
||||
let graph = actual
|
||||
.into_iter()
|
||||
.map(|actual| {
|
||||
expected
|
||||
.iter()
|
||||
.map(|expected_matcher| expected_matcher.matches(actual))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
MatchMatrix { graph, expected_len }
|
||||
}
|
||||
|
||||
pub(crate) fn is_match_for(&self, requirements: Requirements) -> bool {
|
||||
match requirements {
|
||||
Requirements::PerfectMatch => {
|
||||
!self.find_unmatchable_elements().has_unmatchable_elements()
|
||||
&& self.find_best_match().is_full_match()
|
||||
}
|
||||
Requirements::Superset => {
|
||||
!self.find_unmatched_expected().has_unmatchable_elements()
|
||||
&& self.find_best_match().is_superset_match()
|
||||
}
|
||||
Requirements::Subset => {
|
||||
!self.find_unmatched_actual().has_unmatchable_elements()
|
||||
&& self.find_best_match().is_subset_match()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn explain_unmatchable(
|
||||
&self,
|
||||
requirements: Requirements,
|
||||
) -> Option<Description> {
|
||||
let unmatchable_elements = match requirements {
|
||||
Requirements::PerfectMatch => self.find_unmatchable_elements(),
|
||||
Requirements::Superset => self.find_unmatched_expected(),
|
||||
Requirements::Subset => self.find_unmatched_actual(),
|
||||
};
|
||||
unmatchable_elements.get_explanation()
|
||||
}
|
||||
|
||||
// Verifies that each actual matches at least one expected and that
|
||||
// each expected matches at least one actual.
|
||||
// This is a necessary condition but not sufficient. But it is faster
|
||||
// than `find_best_match()`.
|
||||
fn find_unmatchable_elements(&self) -> UnmatchableElements {
|
||||
let unmatchable_actual =
|
||||
self.graph.iter().map(|row| row.iter().all(|&e| e.is_no_match())).collect();
|
||||
let mut unmatchable_expected = vec![false; self.expected_len];
|
||||
for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
|
||||
*expected = self.graph.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
|
||||
}
|
||||
UnmatchableElements { unmatchable_actual, unmatchable_expected }
|
||||
}
|
||||
|
||||
fn find_unmatched_expected(&self) -> UnmatchableElements {
|
||||
let mut unmatchable_expected = vec![false; self.expected_len];
|
||||
for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
|
||||
*expected = self.graph.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
|
||||
}
|
||||
UnmatchableElements {
|
||||
unmatchable_actual: vec![false; self.expected_len],
|
||||
unmatchable_expected,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_unmatched_actual(&self) -> UnmatchableElements {
|
||||
let unmatchable_actual =
|
||||
self.graph.iter().map(|row| row.iter().all(|e| e.is_no_match())).collect();
|
||||
UnmatchableElements {
|
||||
unmatchable_actual,
|
||||
unmatchable_expected: vec![false; self.expected_len],
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that a full match exists.
|
||||
//
|
||||
// Uses the well-known Ford-Fulkerson max flow method to find a maximum
|
||||
// bipartite matching. Flow is considered to be from actual to expected.
|
||||
// There is an implicit source node that is connected to all of the actual
|
||||
// nodes, and an implicit sink node that is connected to all of the
|
||||
// expected nodes. All edges have unit capacity.
|
||||
//
|
||||
// Neither the flow graph nor the residual flow graph are represented
|
||||
// explicitly. Instead, they are implied by the information in `self.graph` and
|
||||
// the local `actual_match : vec![Option<usize>; self.expected_len]` whose
|
||||
// elements are initialized to `None`. This represents the initial state
|
||||
// of the algorithm, where the flow graph is empty, and the residual
|
||||
// flow graph has the following edges:
|
||||
// - An edge from source to each actual element node
|
||||
// - An edge from each expected element node to sink
|
||||
// - An edge from each actual element node to each expected element node, if
|
||||
// the actual element matches the expected element, i.e.
|
||||
// `matches!(self.graph[actual_id][expected_id], Matches)`
|
||||
//
|
||||
// When the `try_augment(...)` method adds a flow, it sets `actual_match[l] =
|
||||
// Some(r)` for some nodes l and r. This induces the following changes:
|
||||
// - The edges (source, l), (l, r), and (r, sink) are added to the flow graph.
|
||||
// - The same three edges are removed from the residual flow graph.
|
||||
// - The reverse edges (l, source), (r, l), and (sink, r) are added to the
|
||||
// residual flow graph, which is a directional graph representing unused
|
||||
// flow capacity.
|
||||
//
|
||||
// When the method augments a flow (changing `actual_match[l]` from `Some(r1)`
|
||||
// to `Some(r2)`), this can be thought of as "undoing" the above steps
|
||||
// with respect to r1 and "redoing" them with respect to r2.
|
||||
//
|
||||
// It bears repeating that the flow graph and residual flow graph are
|
||||
// never represented explicitly, but can be derived by looking at the
|
||||
// information in 'self.graph' and in `actual_match`.
|
||||
//
|
||||
// As an optimization, there is a second local `expected_match:
|
||||
// vec![Option<usize>; self.expected_len]` which does not provide any
|
||||
// new information. Instead, it enables more efficient queries about
|
||||
// edges entering or leaving the expected elements nodes of the flow or
|
||||
// residual flow graphs. The following invariants are maintained:
|
||||
//
|
||||
// actual_match[a] == None or expected_match[actual_match[a].unwrap()] ==
|
||||
// Some(a)
|
||||
// expected_match[r] == None or actual_match[expected_match[e].unwrap()] ==
|
||||
// Some(e)
|
||||
//
|
||||
// | [ source ] |
|
||||
// | ||| |
|
||||
// | ||| |
|
||||
// | ||\-> actual_match[0]=Some(1) -\ expected_match[0]=None ---\ |
|
||||
// | || | | |
|
||||
// | |\--> actual_match[1]=None \-> expected_match[1]=Some(0) --\| |
|
||||
// | | || |
|
||||
// | \---> actual_match[2]=Some(2) --> expected_match[2]=Some(2) -\|| |
|
||||
// | ||| |
|
||||
// | elements matchers vvv |
|
||||
// | [ sink ] |
|
||||
//
|
||||
// See Also:
|
||||
// [1] Cormen, et al (2001). "Section 26.2: The Ford-Fulkerson method".
|
||||
// "Introduction to Algorithms (Second ed.)", pp. 651-664.
|
||||
// [2] "Ford-Fulkerson algorithm", Wikipedia,
|
||||
// 'http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm'
|
||||
pub(crate) fn find_best_match(&self) -> BestMatch {
|
||||
let mut actual_match = vec![None; self.graph.len()];
|
||||
let mut expected_match: Vec<Option<usize>> = vec![None; self.expected_len];
|
||||
// Searches the residual flow graph for a path from each actual node to
|
||||
// the sink in the residual flow graph, and if one is found, add this path
|
||||
// to the graph.
|
||||
// It's okay to search through the actual nodes once. The
|
||||
// edge from the implicit source node to each previously-visited actual
|
||||
// node will have flow if that actual node has any path to the sink
|
||||
// whatsoever. Subsequent augmentations can only add flow to the
|
||||
// network, and cannot take away that previous flow unit from the source.
|
||||
// Since the source-to-actual edge can only carry one flow unit (or,
|
||||
// each actual element can be matched to only one expected element), there is no
|
||||
// need to visit the actual nodes more than once looking for
|
||||
// augmented paths. The flow is known to be possible or impossible
|
||||
// by looking at the node once.
|
||||
for actual_idx in 0..self.graph.len() {
|
||||
assert!(actual_match[actual_idx].is_none());
|
||||
let mut seen = vec![false; self.expected_len];
|
||||
self.try_augment(actual_idx, &mut seen, &mut actual_match, &mut expected_match);
|
||||
}
|
||||
BestMatch::new(actual_match, self.expected_len)
|
||||
}
|
||||
|
||||
// Perform a depth-first search from actual node `actual_idx` to the sink by
|
||||
// searching for an unassigned expected node. If a path is found, flow
|
||||
// is added to the network by linking the actual and expected vector elements
|
||||
// corresponding each segment of the path. Returns true if a path to
|
||||
// sink was found, which means that a unit of flow was added to the
|
||||
// network. The 'seen' array elements correspond to expected nodes and are
|
||||
// marked to eliminate cycles from the search.
|
||||
//
|
||||
// Actual nodes will only be explored at most once because they
|
||||
// are accessible from at most one expected node in the residual flow
|
||||
// graph.
|
||||
//
|
||||
// Note that `actual_match[actual_idx]` is the only element of `actual_match`
|
||||
// that `try_augment(...)` will potentially transition from `None` to
|
||||
// `Some(...)`. Any other `actual_match` element holding `None` before
|
||||
// `try_augment(...)` will be holding it when `try_augment(...)`
|
||||
// returns.
|
||||
//
|
||||
fn try_augment(
|
||||
&self,
|
||||
actual_idx: usize,
|
||||
seen: &mut Vec<bool>,
|
||||
actual_match: &mut [Option<usize>],
|
||||
expected_match: &mut Vec<Option<usize>>,
|
||||
) -> bool {
|
||||
for expected_idx in 0..self.expected_len {
|
||||
if seen[expected_idx] {
|
||||
continue;
|
||||
}
|
||||
if self.graph[actual_idx][expected_idx].is_no_match() {
|
||||
continue;
|
||||
}
|
||||
// There is an edge between `actual_idx` and `expected_idx`.
|
||||
seen[expected_idx] = true;
|
||||
// Next a search is performed to determine whether
|
||||
// this edge is a dead end or leads to the sink.
|
||||
//
|
||||
// `expected_match[expected_idx].is_none()` means that there is residual flow
|
||||
// from expected node at index expected_idx to the sink, so we
|
||||
// can use that to finish this flow path and return success.
|
||||
//
|
||||
// Otherwise, we look for a residual flow starting from
|
||||
// `expected_match[expected_idx].unwrap()` by calling
|
||||
// ourselves recursively to see if this ultimately leads to
|
||||
// sink.
|
||||
if expected_match[expected_idx].is_none()
|
||||
|| self.try_augment(
|
||||
expected_match[expected_idx].unwrap(),
|
||||
seen,
|
||||
actual_match,
|
||||
expected_match,
|
||||
)
|
||||
{
|
||||
// We found a residual flow from source to sink. We thus need to add the new
|
||||
// edge to the current flow.
|
||||
// Note: this also remove the potential flow that existed by overwriting the
|
||||
// value in the `expected_match` and `actual_match`.
|
||||
expected_match[expected_idx] = Some(actual_idx);
|
||||
actual_match[actual_idx] = Some(expected_idx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The list of elements that do not match any element in the corresponding
|
||||
/// set.
|
||||
/// TODO - Use BitVec.
|
||||
pub(crate) struct UnmatchableElements {
|
||||
unmatchable_actual: Vec<bool>,
|
||||
unmatchable_expected: Vec<bool>,
|
||||
}
|
||||
|
||||
impl UnmatchableElements {
|
||||
fn has_unmatchable_elements(&self) -> bool {
|
||||
self.unmatchable_actual.iter().any(|b| *b)
|
||||
|| self.unmatchable_expected.iter().any(|b| *b)
|
||||
}
|
||||
|
||||
fn get_explanation(&self) -> Option<Description> {
|
||||
let unmatchable_actual = self.unmatchable_actual();
|
||||
let actual_idx = unmatchable_actual
|
||||
.iter()
|
||||
.map(|idx| format!("#{idx}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let unmatchable_expected = self.unmatchable_expected();
|
||||
let expected_idx = unmatchable_expected
|
||||
.iter()
|
||||
.map(|idx| format!("#{idx}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
match (unmatchable_actual.len(), unmatchable_expected.len()) {
|
||||
(0, 0) => None,
|
||||
(1, 0) => {
|
||||
Some(format!("whose element {actual_idx} does not match any expected elements").into())
|
||||
}
|
||||
(_, 0) => {
|
||||
Some(format!("whose elements {actual_idx} do not match any expected elements",).into())
|
||||
}
|
||||
(0, 1) => Some(format!(
|
||||
"which has no element matching the expected element {expected_idx}"
|
||||
).into()),
|
||||
(0, _) => Some(format!(
|
||||
"which has no elements matching the expected elements {expected_idx}"
|
||||
).into()),
|
||||
(1, 1) => Some(format!(
|
||||
"whose element {actual_idx} does not match any expected elements and no elements match the expected element {expected_idx}"
|
||||
).into()),
|
||||
(_, 1) => Some(format!(
|
||||
"whose elements {actual_idx} do not match any expected elements and no elements match the expected element {expected_idx}"
|
||||
).into()),
|
||||
(1, _) => Some(format!(
|
||||
"whose element {actual_idx} does not match any expected elements and no elements match the expected elements {expected_idx}"
|
||||
).into()),
|
||||
(_, _) => Some(format!(
|
||||
"whose elements {actual_idx} do not match any expected elements and no elements match the expected elements {expected_idx}"
|
||||
).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn unmatchable_actual(&self) -> Vec<usize> {
|
||||
self.unmatchable_actual
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, b)| if *b { Some(idx) } else { None })
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn unmatchable_expected(&self) -> Vec<usize> {
|
||||
self.unmatchable_expected
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, b)| if *b { Some(idx) } else { None })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// The representation of a match between actual and expected.
|
||||
/// The value at idx represents to which expected the actual at idx is
|
||||
/// matched with. For example, `BestMatch::new([Some(0), None, Some(1)],
|
||||
/// ..)` means:
|
||||
/// * The 0th element in actual matches the 0th element in expected.
|
||||
/// * The 1st element in actual does not match.
|
||||
/// * The 2nd element in actual matches the 1st element in expected.
|
||||
pub(crate) struct BestMatch {
|
||||
actual_match: Vec<Option<usize>>,
|
||||
expected_len: usize,
|
||||
}
|
||||
|
||||
impl BestMatch {
|
||||
fn new(actual_match: Vec<Option<usize>>, expected_len: usize) -> BestMatch {
|
||||
BestMatch { actual_match, expected_len }
|
||||
}
|
||||
|
||||
pub(crate) fn is_full_match(&self) -> bool {
|
||||
self.actual_match.iter().all(|o| o.is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn is_subset_match(&self) -> bool {
|
||||
self.is_full_match()
|
||||
}
|
||||
|
||||
pub(crate) fn is_superset_match(&self) -> bool {
|
||||
self.get_unmatched_expected().is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn get_matches(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
|
||||
self.actual_match.iter().enumerate().filter_map(|(actual_idx, maybe_expected_idx)| {
|
||||
maybe_expected_idx.map(|expected_idx| (actual_idx, expected_idx))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_unmatched_actual(&self) -> impl Iterator<Item = usize> + '_ {
|
||||
self.actual_match
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(_, o)| o.is_none())
|
||||
.map(|(actual_idx, _)| actual_idx)
|
||||
}
|
||||
|
||||
pub(crate) fn get_unmatched_expected(&self) -> Vec<usize> {
|
||||
let matched_expected: HashSet<_> = self.actual_match.iter().flatten().collect();
|
||||
(0..self.expected_len)
|
||||
.filter(|expected_idx| !matched_expected.contains(expected_idx))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn get_explanation<'a, T: Debug + Copy>(
|
||||
&self,
|
||||
actual: impl IntoIterator<Item = T>,
|
||||
expected: &[Box<dyn Matcher<T> + 'a>],
|
||||
requirements: Requirements,
|
||||
) -> Option<Description> {
|
||||
let actual: Vec<_> = actual.into_iter().collect();
|
||||
if self.is_full_match() {
|
||||
return None;
|
||||
}
|
||||
let mut error_message =
|
||||
format!("which does not have a {requirements} match with the expected elements.");
|
||||
|
||||
error_message.push_str("\n The best match found was: ");
|
||||
|
||||
let matches = self.get_matches().map(|(actual_idx, expected_idx)|{
|
||||
format!(
|
||||
"Actual element {:?} at index {actual_idx} matched expected element `{}` at index {expected_idx}.",
|
||||
actual[actual_idx],
|
||||
expected[expected_idx].describe(MatcherResult::Match),
|
||||
)});
|
||||
|
||||
let unmatched_actual = self.get_unmatched_actual().map(|actual_idx| {
|
||||
format!(
|
||||
"Actual element {:#?} at index {actual_idx} did not match any remaining expected element.",
|
||||
actual[actual_idx]
|
||||
)
|
||||
});
|
||||
|
||||
let unmatched_expected = self.get_unmatched_expected().into_iter().map(|expected_idx|{format!(
|
||||
"Expected element `{}` at index {expected_idx} did not match any remaining actual element.",
|
||||
expected[expected_idx].describe(MatcherResult::Match)
|
||||
)});
|
||||
|
||||
let best_match = matches
|
||||
.chain(unmatched_actual)
|
||||
.chain(unmatched_expected)
|
||||
.collect::<Description>()
|
||||
.indent();
|
||||
Some(format!(
|
||||
"which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
|
||||
).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn match_matrix_generate() {
|
||||
let actual = vec![1, 2, 3, 4];
|
||||
let expected: Vec<Box<dyn Matcher<i32>>> =
|
||||
vec![Box::new(eq(1)), Box::new(eq(2)), Box::new(eq(3))];
|
||||
let matrix = MatchMatrix::generate(actual, &expected);
|
||||
|
||||
assert_eq!(
|
||||
matrix.graph,
|
||||
vec![
|
||||
vec![true.into(), false.into(), false.into()],
|
||||
vec![false.into(), true.into(), false.into()],
|
||||
vec![false.into(), false.into(), true.into()],
|
||||
vec![false.into(), false.into(), false.into()],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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.
|
||||
|
||||
//! Utilities to facilitate writing matchers.
|
||||
//!
|
||||
//! Tests normally do not need to import anything from this module. Some of
|
||||
//! these facilities could be useful to downstream users writing custom
|
||||
//! matchers.
|
||||
|
||||
mod auto_eq;
|
||||
pub(crate) mod count_elements;
|
||||
pub(crate) mod edit_distance;
|
||||
pub(crate) mod match_matrix;
|
||||
pub(crate) mod summarize_diff;
|
||||
pub(crate) mod zipped_iterator;
|
||||
|
||||
pub mod __internal_unstable_do_not_depend_on_these {
|
||||
pub use super::auto_eq::internal::{ExpectedKind, Wrapper};
|
||||
pub use crate::__auto_eq as auto_eq;
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
// 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::matcher_support::edit_distance;
|
||||
#[rustversion::since(1.70)]
|
||||
use std::io::IsTerminal;
|
||||
use std::{borrow::Cow, cell::Cell, fmt::Display};
|
||||
|
||||
/// Returns a string describing how the expected and actual lines differ.
|
||||
///
|
||||
/// This is included in a match explanation for [`EqMatcher`] and
|
||||
/// [`crate::matchers::str_matcher::StrMatcher`].
|
||||
///
|
||||
/// If the actual value has less than two lines, or the two differ by more than
|
||||
/// the maximum edit distance, then this returns the empty string. If the two
|
||||
/// are equal, it returns a simple statement that they are equal. Otherwise,
|
||||
/// this constructs a unified diff view of the actual and expected values.
|
||||
pub(crate) fn create_diff(
|
||||
actual_debug: &str,
|
||||
expected_debug: &str,
|
||||
diff_mode: edit_distance::Mode,
|
||||
) -> Cow<'static, str> {
|
||||
if actual_debug.lines().count() < 2 {
|
||||
// If the actual debug is only one line, then there is no point in doing a
|
||||
// line-by-line diff.
|
||||
return "".into();
|
||||
}
|
||||
|
||||
match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines(), diff_mode) {
|
||||
edit_distance::Difference::Equal => {
|
||||
// str.lines() is oblivious to the last newline in a
|
||||
// string, so we need to check this to make sure we don't spuriously
|
||||
// claim that 'hello' and 'hello\n' are identical debug strings.
|
||||
//
|
||||
// Although we would have liked to resolve by replacing
|
||||
// str::lines() with str::split('\n'), the potentially
|
||||
// empty last element interferes with good diff output for
|
||||
// "contains" checks.
|
||||
let actual_newline_terminated = actual_debug.ends_with('\n');
|
||||
let expected_newline_terminated = expected_debug.ends_with('\n');
|
||||
if actual_newline_terminated && !expected_newline_terminated {
|
||||
"Actual includes a terminating newline that is absent from expected.".into()
|
||||
} else if !actual_newline_terminated && expected_newline_terminated {
|
||||
"Actual omits a terminating newline that is present in expected.".into()
|
||||
} else {
|
||||
"No difference found between debug strings.".into()
|
||||
}
|
||||
}
|
||||
edit_distance::Difference::Editable(edit_list) => {
|
||||
format!("{}{}", summary_header(), edit_list.into_iter().collect::<BufferedSummary>(),)
|
||||
.into()
|
||||
}
|
||||
edit_distance::Difference::Unrelated => "".into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a string describing how the expected and actual differ after
|
||||
/// reversing the lines in each.
|
||||
///
|
||||
/// This is similar to [`create_diff`] except that it first reverses the lines
|
||||
/// in both the expected and actual values, then reverses the constructed edit
|
||||
/// list. When `diff_mode` is [`edit_distance::Mode::Prefix`], this becomes a
|
||||
/// diff of the suffix for use by [`ends_with`][crate::matchers::ends_with].
|
||||
pub(crate) fn create_diff_reversed(
|
||||
actual_debug: &str,
|
||||
expected_debug: &str,
|
||||
diff_mode: edit_distance::Mode,
|
||||
) -> Cow<'static, str> {
|
||||
if actual_debug.lines().count() < 2 {
|
||||
// If the actual debug is only one line, then there is no point in doing a
|
||||
// line-by-line diff.
|
||||
return "".into();
|
||||
}
|
||||
let mut actual_lines_reversed = actual_debug.lines().collect::<Vec<_>>();
|
||||
let mut expected_lines_reversed = expected_debug.lines().collect::<Vec<_>>();
|
||||
actual_lines_reversed.reverse();
|
||||
expected_lines_reversed.reverse();
|
||||
match edit_distance::edit_list(actual_lines_reversed, expected_lines_reversed, diff_mode) {
|
||||
edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
|
||||
edit_distance::Difference::Editable(mut edit_list) => {
|
||||
edit_list.reverse();
|
||||
format!("{}{}", summary_header(), edit_list.into_iter().collect::<BufferedSummary>(),)
|
||||
.into()
|
||||
}
|
||||
edit_distance::Difference::Unrelated => "".into(),
|
||||
}
|
||||
}
|
||||
|
||||
// Produces the header, with or without coloring depending on
|
||||
// USE_COLOR
|
||||
fn summary_header() -> Cow<'static, str> {
|
||||
if USE_COLOR.with(Cell::get) {
|
||||
format!(
|
||||
"Difference(-{ACTUAL_ONLY_STYLE}actual{RESET_ALL} / +{EXPECTED_ONLY_STYLE}expected{RESET_ALL}):"
|
||||
).into()
|
||||
} else {
|
||||
"Difference(-actual / +expected):".into()
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregator collecting the lines to be printed in the difference summary.
|
||||
//
|
||||
// This is buffered in order to allow a future line to potentially impact how
|
||||
// the current line would be printed.
|
||||
#[derive(Default)]
|
||||
struct BufferedSummary<'a> {
|
||||
summary: SummaryBuilder,
|
||||
buffer: Buffer<'a>,
|
||||
}
|
||||
|
||||
impl<'a> BufferedSummary<'a> {
|
||||
// Appends a new line which is common to both actual and expected.
|
||||
fn feed_common_lines(&mut self, common_line: &'a str) {
|
||||
if let Buffer::CommonLines(ref mut common_lines) = self.buffer {
|
||||
common_lines.push(common_line);
|
||||
} else {
|
||||
self.flush_buffer();
|
||||
self.buffer = Buffer::CommonLines(vec![common_line]);
|
||||
}
|
||||
}
|
||||
|
||||
// Appends a new line which is found only in the actual string.
|
||||
fn feed_extra_actual(&mut self, extra_actual: &'a str) {
|
||||
if let Buffer::ExtraExpectedLineChunk(extra_expected) = self.buffer {
|
||||
self.print_inline_diffs(extra_actual, extra_expected);
|
||||
self.buffer = Buffer::Empty;
|
||||
} else {
|
||||
self.flush_buffer();
|
||||
self.buffer = Buffer::ExtraActualLineChunk(extra_actual);
|
||||
}
|
||||
}
|
||||
|
||||
// Appends a new line which is found only in the expected string.
|
||||
fn feed_extra_expected(&mut self, extra_expected: &'a str) {
|
||||
if let Buffer::ExtraActualLineChunk(extra_actual) = self.buffer {
|
||||
self.print_inline_diffs(extra_actual, extra_expected);
|
||||
self.buffer = Buffer::Empty;
|
||||
} else {
|
||||
self.flush_buffer();
|
||||
self.buffer = Buffer::ExtraExpectedLineChunk(extra_expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Appends a comment for the additional line at the start or the end of the
|
||||
// actual string which should be omitted.
|
||||
fn feed_additional_actual(&mut self) {
|
||||
self.flush_buffer();
|
||||
self.summary.new_line();
|
||||
self.summary.push_str_as_comment("<---- remaining lines omitted ---->");
|
||||
}
|
||||
|
||||
fn flush_buffer(&mut self) {
|
||||
self.buffer.flush(&mut self.summary);
|
||||
}
|
||||
|
||||
fn print_inline_diffs(&mut self, actual_line: &str, expected_line: &str) {
|
||||
let line_edits = edit_distance::edit_list(
|
||||
actual_line.chars(),
|
||||
expected_line.chars(),
|
||||
edit_distance::Mode::Exact,
|
||||
);
|
||||
|
||||
if let edit_distance::Difference::Editable(edit_list) = line_edits {
|
||||
let mut actual_summary = SummaryBuilder::default();
|
||||
actual_summary.new_line_for_actual();
|
||||
let mut expected_summary = SummaryBuilder::default();
|
||||
expected_summary.new_line_for_expected();
|
||||
for edit in &edit_list {
|
||||
match edit {
|
||||
edit_distance::Edit::ExtraActual(c) => actual_summary.push_actual_only(*c),
|
||||
edit_distance::Edit::ExtraExpected(c) => {
|
||||
expected_summary.push_expected_only(*c)
|
||||
}
|
||||
edit_distance::Edit::Both(c) => {
|
||||
actual_summary.push_actual_with_match(*c);
|
||||
expected_summary.push_expected_with_match(*c);
|
||||
}
|
||||
edit_distance::Edit::AdditionalActual => {
|
||||
// Calling edit_distance::edit_list(_, _, Mode::Exact) should never return
|
||||
// this enum
|
||||
panic!("This should not happen. This is a bug in gtest_rust")
|
||||
}
|
||||
}
|
||||
}
|
||||
actual_summary.reset_ansi();
|
||||
expected_summary.reset_ansi();
|
||||
self.summary.push_str(&actual_summary.summary);
|
||||
self.summary.push_str(&expected_summary.summary);
|
||||
} else {
|
||||
self.summary.new_line_for_actual();
|
||||
self.summary.push_str_actual_only(actual_line);
|
||||
self.summary.new_line_for_expected();
|
||||
self.summary.push_str_expected_only(expected_line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<edit_distance::Edit<&'a str>> for BufferedSummary<'a> {
|
||||
fn from_iter<T: IntoIterator<Item = edit_distance::Edit<&'a str>>>(iter: T) -> Self {
|
||||
let mut buffered_summary = BufferedSummary::default();
|
||||
for edit in iter {
|
||||
match edit {
|
||||
edit_distance::Edit::Both(same) => {
|
||||
buffered_summary.feed_common_lines(same);
|
||||
}
|
||||
edit_distance::Edit::ExtraActual(actual) => {
|
||||
buffered_summary.feed_extra_actual(actual);
|
||||
}
|
||||
edit_distance::Edit::ExtraExpected(expected) => {
|
||||
buffered_summary.feed_extra_expected(expected);
|
||||
}
|
||||
edit_distance::Edit::AdditionalActual => {
|
||||
buffered_summary.feed_additional_actual();
|
||||
}
|
||||
};
|
||||
}
|
||||
buffered_summary.flush_buffer();
|
||||
buffered_summary.summary.reset_ansi();
|
||||
|
||||
buffered_summary
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BufferedSummary<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if !matches!(self.buffer, Buffer::Empty) {
|
||||
panic!("Buffer is not empty. This is a bug in gtest_rust.")
|
||||
}
|
||||
if !self.summary.last_ansi_style.is_empty() {
|
||||
panic!("ANSI style has not been reset. This is a bug in gtest_rust.")
|
||||
}
|
||||
self.summary.summary.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
enum Buffer<'a> {
|
||||
Empty,
|
||||
CommonLines(Vec<&'a str>),
|
||||
ExtraActualLineChunk(&'a str),
|
||||
ExtraExpectedLineChunk(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> Buffer<'a> {
|
||||
fn flush(&mut self, summary: &mut SummaryBuilder) {
|
||||
match self {
|
||||
Buffer::Empty => {}
|
||||
Buffer::CommonLines(common_lines) => {
|
||||
Self::flush_common_lines(std::mem::take(common_lines), summary);
|
||||
}
|
||||
Buffer::ExtraActualLineChunk(extra_actual) => {
|
||||
summary.new_line_for_actual();
|
||||
summary.push_str_actual_only(extra_actual);
|
||||
}
|
||||
Buffer::ExtraExpectedLineChunk(extra_expected) => {
|
||||
summary.new_line_for_expected();
|
||||
summary.push_str_expected_only(extra_expected);
|
||||
}
|
||||
};
|
||||
*self = Buffer::Empty;
|
||||
}
|
||||
|
||||
fn flush_common_lines(common_lines: Vec<&'a str>, summary: &mut SummaryBuilder) {
|
||||
// The number of the lines kept before and after the compressed lines.
|
||||
const COMMON_LINES_CONTEXT_SIZE: usize = 2;
|
||||
|
||||
if common_lines.len() <= 2 * COMMON_LINES_CONTEXT_SIZE + 1 {
|
||||
for line in common_lines {
|
||||
summary.new_line();
|
||||
summary.push_str(line);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let start_context = &common_lines[0..COMMON_LINES_CONTEXT_SIZE];
|
||||
|
||||
for line in start_context {
|
||||
summary.new_line();
|
||||
summary.push_str(line);
|
||||
}
|
||||
|
||||
summary.new_line();
|
||||
summary.push_str_as_comment(&format!(
|
||||
"<---- {} common lines omitted ---->",
|
||||
common_lines.len() - 2 * COMMON_LINES_CONTEXT_SIZE,
|
||||
));
|
||||
|
||||
let end_context =
|
||||
&common_lines[common_lines.len() - COMMON_LINES_CONTEXT_SIZE..common_lines.len()];
|
||||
|
||||
for line in end_context {
|
||||
summary.new_line();
|
||||
summary.push_str(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Buffer<'_> {
|
||||
fn default() -> Self {
|
||||
Self::Empty
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub(crate) static USE_COLOR: Cell<bool> = Cell::new(stdout_supports_color());
|
||||
}
|
||||
|
||||
#[rustversion::since(1.70)]
|
||||
fn stdout_supports_color() -> bool {
|
||||
#[allow(clippy::incompatible_msrv)]
|
||||
match (is_env_var_set("NO_COLOR"), is_env_var_set("FORCE_COLOR")) {
|
||||
(true, _) => false,
|
||||
(false, true) => true,
|
||||
(false, false) => std::io::stdout().is_terminal(),
|
||||
}
|
||||
}
|
||||
|
||||
#[rustversion::not(since(1.70))]
|
||||
fn stdout_supports_color() -> bool {
|
||||
is_env_var_set("FORCE_COLOR") && !is_env_var_set("NO_COLOR")
|
||||
}
|
||||
|
||||
fn is_env_var_set(var: &'static str) -> bool {
|
||||
std::env::var(var).map(|s| !s.is_empty()).unwrap_or(false)
|
||||
}
|
||||
|
||||
// Font in italic
|
||||
const COMMENT_STYLE: &str = "\x1B[3m";
|
||||
// Font in green and bold
|
||||
const EXPECTED_ONLY_STYLE: &str = "\x1B[1;32m";
|
||||
// Font in red and bold
|
||||
const ACTUAL_ONLY_STYLE: &str = "\x1B[1;31m";
|
||||
// Font in green onlyh
|
||||
const EXPECTED_WITH_MATCH_STYLE: &str = "\x1B[32m";
|
||||
// Font in red only
|
||||
const ACTUAL_WITH_MATCH_STYLE: &str = "\x1B[31m";
|
||||
// Reset all ANSI formatting
|
||||
const RESET_ALL: &str = "\x1B[0m";
|
||||
|
||||
#[derive(Default)]
|
||||
struct SummaryBuilder {
|
||||
summary: String,
|
||||
last_ansi_style: &'static str,
|
||||
}
|
||||
|
||||
impl SummaryBuilder {
|
||||
fn push_str(&mut self, element: &str) {
|
||||
self.reset_ansi();
|
||||
self.summary.push_str(element);
|
||||
}
|
||||
|
||||
fn push_str_as_comment(&mut self, element: &str) {
|
||||
self.set_ansi(COMMENT_STYLE);
|
||||
self.summary.push_str(element);
|
||||
}
|
||||
|
||||
fn push_str_actual_only(&mut self, element: &str) {
|
||||
self.set_ansi(ACTUAL_ONLY_STYLE);
|
||||
self.summary.push_str(element);
|
||||
}
|
||||
|
||||
fn push_str_expected_only(&mut self, element: &str) {
|
||||
self.set_ansi(EXPECTED_ONLY_STYLE);
|
||||
self.summary.push_str(element);
|
||||
}
|
||||
|
||||
fn push_actual_only(&mut self, element: char) {
|
||||
self.set_ansi(ACTUAL_ONLY_STYLE);
|
||||
self.summary.push(element);
|
||||
}
|
||||
|
||||
fn push_expected_only(&mut self, element: char) {
|
||||
self.set_ansi(EXPECTED_ONLY_STYLE);
|
||||
self.summary.push(element);
|
||||
}
|
||||
|
||||
fn push_actual_with_match(&mut self, element: char) {
|
||||
self.set_ansi(ACTUAL_WITH_MATCH_STYLE);
|
||||
self.summary.push(element);
|
||||
}
|
||||
|
||||
fn push_expected_with_match(&mut self, element: char) {
|
||||
self.set_ansi(EXPECTED_WITH_MATCH_STYLE);
|
||||
self.summary.push(element);
|
||||
}
|
||||
|
||||
fn new_line(&mut self) {
|
||||
self.reset_ansi();
|
||||
self.summary.push_str("\n ");
|
||||
}
|
||||
|
||||
fn new_line_for_actual(&mut self) {
|
||||
self.reset_ansi();
|
||||
self.summary.push_str("\n-");
|
||||
}
|
||||
|
||||
fn new_line_for_expected(&mut self) {
|
||||
self.reset_ansi();
|
||||
self.summary.push_str("\n+");
|
||||
}
|
||||
|
||||
fn reset_ansi(&mut self) {
|
||||
if !self.last_ansi_style.is_empty() && USE_COLOR.with(Cell::get) {
|
||||
self.summary.push_str(RESET_ALL);
|
||||
self.last_ansi_style = "";
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ansi(&mut self, ansi_style: &'static str) {
|
||||
if !USE_COLOR.with(Cell::get) || self.last_ansi_style == ansi_style {
|
||||
return;
|
||||
}
|
||||
if !self.last_ansi_style.is_empty() {
|
||||
self.summary.push_str(RESET_ALL);
|
||||
}
|
||||
self.summary.push_str(ansi_style);
|
||||
self.last_ansi_style = ansi_style;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{matcher_support::edit_distance::Mode, prelude::*, Result};
|
||||
use indoc::indoc;
|
||||
use std::fmt::Write;
|
||||
|
||||
// Make a long text with each element of the iterator on one line.
|
||||
// `collection` must contains at least one element.
|
||||
fn build_text<T: Display>(mut collection: impl Iterator<Item = T>) -> String {
|
||||
let mut text = String::new();
|
||||
write!(&mut text, "{}", collection.next().expect("Provided collection without elements"))
|
||||
.unwrap();
|
||||
for item in collection {
|
||||
write!(&mut text, "\n{item}").unwrap();
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_diff_smaller_than_one_line() -> Result<()> {
|
||||
verify_that!(create_diff("One", "Two", Mode::Exact), eq(""))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_diff_exact_same() -> Result<()> {
|
||||
let expected = indoc! {"
|
||||
One
|
||||
Two
|
||||
"};
|
||||
let actual = indoc! {"
|
||||
One
|
||||
Two
|
||||
"};
|
||||
verify_that!(
|
||||
create_diff(expected, actual, Mode::Exact),
|
||||
eq("No difference found between debug strings.")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_diff_multiline_diff() -> Result<()> {
|
||||
let expected = indoc! {"
|
||||
prefix
|
||||
Actual#1
|
||||
Actual#2
|
||||
Actual#3
|
||||
suffix"};
|
||||
let actual = indoc! {"
|
||||
prefix
|
||||
Expected@one
|
||||
Expected@two
|
||||
suffix"};
|
||||
// TODO: It would be better to have all the Actual together followed by all the
|
||||
// Expected together.
|
||||
verify_that!(
|
||||
create_diff(expected, actual, Mode::Exact),
|
||||
eq(indoc!(
|
||||
"
|
||||
Difference(-actual / +expected):
|
||||
prefix
|
||||
-Actual#1
|
||||
+Expected@one
|
||||
-Actual#2
|
||||
+Expected@two
|
||||
-Actual#3
|
||||
suffix"
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_diff_exact_unrelated() -> Result<()> {
|
||||
verify_that!(create_diff(&build_text(1..500), &build_text(501..1000), Mode::Exact), eq(""))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_diff_exact_small_difference() -> Result<()> {
|
||||
verify_that!(
|
||||
create_diff(&build_text(1..50), &build_text(1..51), Mode::Exact),
|
||||
eq(indoc! {
|
||||
"
|
||||
Difference(-actual / +expected):
|
||||
1
|
||||
2
|
||||
<---- 45 common lines omitted ---->
|
||||
48
|
||||
49
|
||||
+50"
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_diff_exact_small_difference_with_color() -> Result<()> {
|
||||
USE_COLOR.with(|cell| cell.set(true));
|
||||
|
||||
verify_that!(
|
||||
create_diff(&build_text(1..50), &build_text(1..51), Mode::Exact),
|
||||
eq(indoc! {
|
||||
"
|
||||
Difference(-\x1B[1;31mactual\x1B[0m / +\x1B[1;32mexpected\x1B[0m):
|
||||
1
|
||||
2
|
||||
\x1B[3m<---- 45 common lines omitted ---->\x1B[0m
|
||||
48
|
||||
49
|
||||
+\x1B[1;32m50\x1B[0m"
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_diff_exact_difference_with_inline_color() -> Result<()> {
|
||||
USE_COLOR.with(|cell| cell.set(true));
|
||||
|
||||
let actual = indoc!(
|
||||
"There is a home in Nouvelle Orleans
|
||||
They say, it is the rising sons
|
||||
And it has been the ruin of many a po'boy"
|
||||
);
|
||||
|
||||
let expected = indoc!(
|
||||
"There is a house way down in New Orleans
|
||||
They call the rising sun
|
||||
And it has been the ruin of many a poor boy"
|
||||
);
|
||||
|
||||
verify_that!(
|
||||
create_diff(actual, expected, Mode::Exact),
|
||||
eq(indoc! {
|
||||
"
|
||||
Difference(-\x1B[1;31mactual\x1B[0m / +\x1B[1;32mexpected\x1B[0m):
|
||||
-\x1B[31mThere is a ho\x1B[0m\x1B[1;31mm\x1B[0m\x1B[31me in N\x1B[0m\x1B[1;31mouv\x1B[0m\x1B[31me\x1B[0m\x1B[1;31mlle\x1B[0m\x1B[31m Orleans\x1B[0m
|
||||
+\x1B[32mThere is a ho\x1B[0m\x1B[1;32mus\x1B[0m\x1B[32me \x1B[0m\x1B[1;32mway down \x1B[0m\x1B[32min Ne\x1B[0m\x1B[1;32mw\x1B[0m\x1B[32m Orleans\x1B[0m
|
||||
-\x1B[31mThey \x1B[0m\x1B[1;31ms\x1B[0m\x1B[31ma\x1B[0m\x1B[1;31my,\x1B[0m\x1B[31m \x1B[0m\x1B[1;31mi\x1B[0m\x1B[31mt\x1B[0m\x1B[1;31m is t\x1B[0m\x1B[31mhe rising s\x1B[0m\x1B[1;31mo\x1B[0m\x1B[31mn\x1B[0m\x1B[1;31ms\x1B[0m
|
||||
+\x1B[32mThey \x1B[0m\x1B[1;32mc\x1B[0m\x1B[32ma\x1B[0m\x1B[1;32mll\x1B[0m\x1B[32m the rising s\x1B[0m\x1B[1;32mu\x1B[0m\x1B[32mn\x1B[0m
|
||||
-\x1B[31mAnd it has been the ruin of many a po\x1B[0m\x1B[1;31m'\x1B[0m\x1B[31mboy\x1B[0m
|
||||
+\x1B[32mAnd it has been the ruin of many a po\x1B[0m\x1B[1;32mor \x1B[0m\x1B[32mboy\x1B[0m"
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_diff_line_termination_diff() -> Result<()> {
|
||||
verify_that!(
|
||||
create_diff("1\n2\n3", "1\n2\n3\n", Mode::Exact),
|
||||
eq("Actual omits a terminating newline that is present in expected.")
|
||||
)?;
|
||||
verify_that!(
|
||||
create_diff("1\n2\n3\n", "1\n2\n3", Mode::Exact),
|
||||
eq("Actual includes a terminating newline that is absent from expected.")
|
||||
)?;
|
||||
verify_that!(
|
||||
create_diff("1\n2\n3\n", "1\n2\n3\n", Mode::Exact),
|
||||
eq("No difference found between debug strings.")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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.
|
||||
|
||||
/// Zips up two iterators into a single iterator of pairs.
|
||||
///
|
||||
/// This is identical to [`Iterator::zip`] except that this version allows the
|
||||
/// caller to determine whether the two iterators had mismatching sizes using
|
||||
/// the method [`ZippedIterator::has_size_mismatch`].
|
||||
///
|
||||
/// [`Iterator::zip`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.zip
|
||||
pub(crate) fn zip<I1, I2>(left: I1, right: I2) -> ZippedIterator<I1, I2> {
|
||||
ZippedIterator { left, right, has_size_mismatch: false, consumed_elements: 0 }
|
||||
}
|
||||
|
||||
/// An iterator over pairs of the elements of two constituent iterators, which
|
||||
/// keeps track of whether the two iterators have the same size.
|
||||
///
|
||||
/// This is identical to [`Zip`] except that it allows the caller to determine
|
||||
/// whether the two iterators had mismatching sizes using the method
|
||||
/// [`ZippedIterator::has_size_mismatch`].
|
||||
///
|
||||
/// [`Zip`]: https://doc.rust-lang.org/std/iter/struct.Zip.html
|
||||
pub(crate) struct ZippedIterator<I1, I2> {
|
||||
left: I1,
|
||||
right: I2,
|
||||
has_size_mismatch: bool,
|
||||
consumed_elements: usize,
|
||||
}
|
||||
|
||||
impl<I1: Iterator, I2> ZippedIterator<I1, I2> {
|
||||
/// Returns whether a mismatch in the two sizes of the two iterators was
|
||||
/// detected during iteration.
|
||||
///
|
||||
/// This returns `true` if and only if, at some previous call to
|
||||
/// [`Iterator::next`] on this instance, one of the constituent iterators
|
||||
/// had a next element and the other did not.
|
||||
///
|
||||
/// [`Iterator::next`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
|
||||
pub(crate) fn has_size_mismatch(&self) -> bool {
|
||||
self.has_size_mismatch
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the left iterator.
|
||||
///
|
||||
/// This iterates through the remainder of the left iterator if necessary in
|
||||
/// order to get the true number of elements. It therefore consumes `self`.
|
||||
pub(crate) fn left_size(mut self) -> usize {
|
||||
self.consumed_elements + self.left.by_ref().count()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I1: Iterator, I2: Iterator> Iterator for ZippedIterator<I1, I2> {
|
||||
type Item = (I1::Item, I2::Item);
|
||||
|
||||
fn next(&mut self) -> Option<(I1::Item, I2::Item)> {
|
||||
match (self.left.next(), self.right.next()) {
|
||||
(Some(v1), Some(v2)) => {
|
||||
self.consumed_elements += 1;
|
||||
Some((v1, v2))
|
||||
}
|
||||
(Some(_), None) => {
|
||||
// Consumed elements counts only elements from self.left
|
||||
self.consumed_elements += 1;
|
||||
self.has_size_mismatch = true;
|
||||
None
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
self.has_size_mismatch = true;
|
||||
None
|
||||
}
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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