build(rust): vendor googletest crate

This commit was merged in pull request #76.
This commit is contained in:
alexion committed 2026-09-19 14:53:22 -04:00
1 parent a6fe84190e
commit 6887b104d6
576 files changed
+284290 -2

No files matched your search

File diff suppressed because it is too large. Load diff
+406
View File
@@ -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))
}
}
+317
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(&gt(232), MatcherResult::Match),
displays_as(eq("is greater than 232"))
)
}
#[test]
fn gt_describe_does_not_match() -> Result<()> {
verify_that!(
Matcher::<i32>::describe(&gt(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
"
))))
)
}
}
@@ -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\""))
)
}
}
+130
View File
@@ -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]
);
}
@@ -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(())
}
}