Snap for 11399874 from f78e80a01c96543721f1b092a9ad80034dc40f4d to 24D1-release
Change-Id: Iba4380333331664ca7ea73e54a73156fcbf6a640
diff --git a/config.toml b/.cargo/config.toml
similarity index 100%
rename from config.toml
rename to .cargo/config.toml
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index ce5ec95..dae245d 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
{
"git": {
- "sha1": "6e5405db2217d37006720c101beb1b91199a3a26"
+ "sha1": "91a15f5eb416cbf97cd8b6d8831263f2c861b859"
},
"path_in_vcs": "googletest"
}
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index d7fe4a6..dc74442 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6,7 +6,7 @@
host_supported: true,
crate_name: "googletest",
cargo_env_compat: true,
- cargo_pkg_version: "0.10.0",
+ cargo_pkg_version: "0.11.0",
srcs: ["src/lib.rs"],
edition: "2021",
rustlibs: [
diff --git a/Cargo.toml b/Cargo.toml
index 3e898d2..ed9d01c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,9 +11,9 @@
[package]
edition = "2021"
-rust-version = "1.59.0"
+rust-version = "1.66.0"
name = "googletest"
-version = "0.10.0"
+version = "0.11.0"
authors = [
"Bradford Hovinen <hovinen@google.com>",
"Bastien Jacot-Guillarmod <bjacotg@google.com>",
@@ -34,24 +34,23 @@
]
license = "Apache-2.0"
repository = "https://github.com/google/googletest-rust"
-resolver = "1"
[dependencies.anyhow]
version = "1"
optional = true
[dependencies.googletest_macro]
-version = "0.10.0"
+version = "0.11.0"
[dependencies.num-traits]
-version = "0.2.15"
+version = "0.2.17"
[dependencies.proptest]
version = "1.2.0"
optional = true
[dependencies.regex]
-version = "1.6.0"
+version = "1.7.3"
[dependencies.rustversion]
version = "1.0.14"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..9fedcfb
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,44 @@
+# 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.
+
+[package]
+name = "googletest"
+version = "0.11.0"
+keywords = ["unit", "matcher", "testing", "assertions"]
+categories = ["development-tools", "development-tools::testing"]
+description = "A rich assertion and matcher library inspired by GoogleTest for C++"
+repository = "https://github.com/google/googletest-rust"
+readme = "../README.md"
+license = "Apache-2.0"
+edition = "2021"
+rust-version = "1.66.0"
+authors = [
+ "Bradford Hovinen <hovinen@google.com>",
+ "Bastien Jacot-Guillarmod <bjacotg@google.com>",
+ "Maciej Pietrzak <mpi@google.com>",
+ "Martin Geisler <mgeisler@google.com>",
+]
+
+[dependencies]
+googletest_macro = { path = "../googletest_macro", version = "0.11.0" }
+anyhow = { version = "1", optional = true }
+num-traits = "0.2.17"
+proptest = { version = "1.2.0", optional = true }
+regex = "1.7.3"
+rustversion = "1.0.14"
+
+[dev-dependencies]
+indoc = "2"
+quickcheck = "1.0.3"
+serial_test = "2.0.0"
diff --git a/METADATA b/METADATA
index 6c2060b..86fc93a 100644
--- a/METADATA
+++ b/METADATA
@@ -1,19 +1,24 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update external/rust/crates/googletest
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
+
name: "googletest"
description: "A rich assertion and matcher library inspired by GoogleTest for C++"
third_party {
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2024
+ month: 2
+ day: 2
+ }
identifier {
type: "crates.io"
- value: "https://crates.io/crates/googletest"
+ value: "https://static.crates.io/crates/googletest/googletest-0.11.0.crate"
+ version: "0.10.0"
}
identifier {
type: "Archive"
value: "https://static.crates.io/crates/googletest/googletest-0.10.0.crate"
- }
- version: "0.10.0"
- license_type: NOTICE
- last_upgrade_date {
- year: 2023
- month: 9
- day: 1
+ version: "0.11.0"
}
}
diff --git a/README.md b/README.md
index 2a0f918..d442770 100644
--- a/README.md
+++ b/README.md
@@ -25,11 +25,14 @@
* A new set of assertion macros offering similar functionality to those of
[GoogleTest](https://google.github.io/googletest/primer.html#assertions).
-**The minimum supported Rust version is 1.59**. We recommend using at least
-version 1.66 for the best developer experience.
+**The minimum supported Rust version is 1.66**.
> :warning: The API is not fully stable and may still be changed until we
> publish version 1.0.
+>
+> Moreover, any items or modules starting with `__` (double underscores) must
+> not be used directly. Those items or modules are only for internal uses and
+> their API may change without a major version update.
## Assertions and matchers
@@ -162,10 +165,10 @@
fn describe(&self, matcher_result: MatcherResult) -> String {
match matcher_result {
- MatcherResult::Matches => {
+ MatcherResult::Match => {
format!("is equal to {:?} the way I define it", self.expected)
}
- MatcherResult::DoesNotMatch => {
+ MatcherResult::NoMatch => {
format!("isn't equal to {:?} the way I define it", self.expected)
}
}
diff --git a/crate_docs.md b/crate_docs.md
index a00c219..8c8b47c 100644
--- a/crate_docs.md
+++ b/crate_docs.md
@@ -153,38 +153,48 @@
| [`superset_of`] | A container containing all elements of the argument. |
| [`unordered_elements_are!`] | A container whose elements the arguments match, in any order. |
+[`all!`]: matchers::all
+[`any!`]: matchers::any
[`anything`]: matchers::anything
[`approx_eq`]: matchers::approx_eq
[`char_count`]: matchers::char_count
[`container_eq`]: matchers::container_eq
[`contains`]: matchers::contains
+[`contains_each!`]: matchers::contains_each
[`contains_regex`]: matchers::contains_regex
[`contains_substring`]: matchers::contains_substring
[`displays_as`]: matchers::displays_as
[`each`]: matchers::each
+[`elements_are!`]: matchers::elements_are
[`empty`]: matchers::empty
[`ends_with`]: matchers::ends_with
[`eq`]: matchers::eq
[`eq_deref_of`]: matchers::eq_deref_of
[`err`]: matchers::err
+[`field!`]: matchers::field
[`ge`]: matchers::ge
[`gt`]: matchers::gt
[`has_entry`]: matchers::has_entry
+[`is_contained_in!`]: matchers::is_contained_in
[`is_nan`]: matchers::is_nan
[`le`]: matchers::le
[`len`]: matchers::len
[`lt`]: matchers::lt
[`matches_regex`]: matchers::matches_regex
+[`matches_pattern!`]: matchers::matches_pattern
[`near`]: matchers::near
[`none`]: matchers::none
[`not`]: matchers::not
+[`pat!`]: matchers::pat
[`ok`]: matchers::ok
[`points_to`]: matchers::points_to
+[`pointwise!`]: matchers::pointwise
[`predicate`]: matchers::predicate
[`some`]: matchers::some
[`starts_with`]: matchers::starts_with
[`subset_of`]: matchers::subset_of
[`superset_of`]: matchers::superset_of
+[`unordered_elements_are!`]: matchers::unordered_elements_are
[`Deref`]: std::ops::Deref
[`Display`]: std::fmt::Display
[`HashMap`]: std::collections::HashMap
@@ -199,7 +209,7 @@
[`Matcher`]:
```no_run
-use googletest::matcher::{Matcher, MatcherResult};
+use googletest::{description::Description, matcher::{Matcher, MatcherResult}};
use std::fmt::Debug;
struct MyEqMatcher<T> {
@@ -217,13 +227,13 @@
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
MatcherResult::Match => {
- format!("is equal to {:?} the way I define it", self.expected)
+ format!("is equal to {:?} the way I define it", self.expected).into()
}
MatcherResult::NoMatch => {
- format!("isn't equal to {:?} the way I define it", self.expected)
+ format!("isn't equal to {:?} the way I define it", self.expected).into()
}
}
}
@@ -233,7 +243,7 @@
It is recommended to expose a function which constructs the matcher:
```no_run
- # use googletest::matcher::{Matcher, MatcherResult};
+ # use googletest::{description::Description, matcher::{Matcher, MatcherResult}};
# use std::fmt::Debug;
#
# struct MyEqMatcher<T> {
@@ -251,13 +261,13 @@
# }
# }
#
- # fn describe(&self, matcher_result: MatcherResult) -> String {
+ # fn describe(&self, matcher_result: MatcherResult) -> Description {
# match matcher_result {
# MatcherResult::Match => {
- # format!("is equal to {:?} the way I define it", self.expected)
+ # format!("is equal to {:?} the way I define it", self.expected).into()
# }
# MatcherResult::NoMatch => {
- # format!("isn't equal to {:?} the way I define it", self.expected)
+ # format!("isn't equal to {:?} the way I define it", self.expected).into()
# }
# }
# }
@@ -272,7 +282,7 @@
```
# use googletest::prelude::*;
-# use googletest::matcher::{Matcher, MatcherResult};
+# use googletest::{description::Description, matcher::{Matcher, MatcherResult}};
# use std::fmt::Debug;
#
# struct MyEqMatcher<T> {
@@ -290,13 +300,13 @@
# }
# }
#
-# fn describe(&self, matcher_result: MatcherResult) -> String {
+# fn describe(&self, matcher_result: MatcherResult) -> Description {
# match matcher_result {
# MatcherResult::Match => {
-# format!("is equal to {:?} the way I define it", self.expected)
+# format!("is equal to {:?} the way I define it", self.expected).into()
# }
# MatcherResult::NoMatch => {
-# format!("isn't equal to {:?} the way I define it", self.expected)
+# format!("isn't equal to {:?} the way I define it", self.expected).into()
# }
# }
# }
diff --git a/src/assertions.rs b/src/assertions.rs
index 7664486..2668028 100644
--- a/src/assertions.rs
+++ b/src/assertions.rs
@@ -120,23 +120,23 @@
/// not supported; see [Rust by Example](https://doc.rust-lang.org/rust-by-example/primitives/tuples.html#tuples).
#[macro_export]
macro_rules! verify_that {
- ($actual:expr, [$($expecteds:expr),+]) => {
+ ($actual:expr, [$($expecteds:expr),+ $(,)?]) => {
$crate::assertions::internal::check_matcher(
&$actual,
- $crate::elements_are![$($expecteds),+],
+ $crate::matchers::elements_are![$($expecteds),+],
stringify!($actual),
$crate::internal::source_location::SourceLocation::new(file!(), line!(), column!()),
)
};
- ($actual:expr, {$($expecteds:expr),+}) => {
+ ($actual:expr, {$($expecteds:expr),+ $(,)?}) => {
$crate::assertions::internal::check_matcher(
&$actual,
- $crate::unordered_elements_are![$($expecteds),+],
+ $crate::matchers::unordered_elements_are![$($expecteds),+],
stringify!($actual),
$crate::internal::source_location::SourceLocation::new(file!(), line!(), column!()),
)
};
- ($actual:expr, $expected:expr) => {
+ ($actual:expr, $expected:expr $(,)?) => {
$crate::assertions::internal::check_matcher(
&$actual,
$expected,
@@ -309,16 +309,49 @@
/// Matches the given value against the given matcher, panicking if it does not
/// match.
///
+/// ```should_panic
+/// # use googletest::prelude::*;
+/// # fn should_fail() {
+/// let value = 2;
+/// assert_that!(value, eq(3)); // Fails and panics.
+/// # }
+/// # should_fail();
+/// ```
+///
/// This is analogous to assertions in most Rust test libraries, where a failed
/// assertion causes a panic.
///
+/// One may optionally add arguments which will be formatted and appended to a
+/// failure message. For example:
+///
+/// ```should_panic
+/// # use googletest::prelude::*;
+/// # fn should_fail() {
+/// let value = 2;
+/// let extra_information = "Some additional information";
+/// assert_that!(value, eq(3), "Test failed. Extra information: {extra_information}.");
+/// # }
+/// # should_fail();
+/// ```
+///
+/// This is output as follows:
+///
+/// ```text
+/// Value of: value
+/// Expected: is equal to 3
+/// Actual: 2,
+/// which isn't equal to 3
+/// at ...
+/// Test failed. Extra information: Some additional information.
+/// ```
+///
/// **Note for users of [GoogleTest for C++](http://google.github.io/googletest/):**
/// This differs from the `ASSERT_THAT` macro in that it panics rather
/// than triggering an early return from the invoking function. To get behaviour
/// equivalent to `ASSERT_THAT`, use [`verify_that!`] with the `?` operator.
#[macro_export]
macro_rules! assert_that {
- ($actual:expr, $expected:expr) => {
+ ($actual:expr, $expected:expr $(,)?) => {
match $crate::verify_that!($actual, $expected) {
Ok(_) => {}
Err(e) => {
@@ -328,6 +361,19 @@
}
}
};
+
+ ($actual:expr, $expected:expr, $($format_args:expr),* $(,)?) => {
+ match $crate::verify_that!($actual, $expected)
+ .with_failure_message(|| format!($($format_args),*))
+ {
+ Ok(_) => {}
+ Err(e) => {
+ // The extra newline before the assertion failure message makes the failure a
+ // bit easier to read when there's some generic boilerplate from the panic.
+ panic!("\n{}", e);
+ }
+ }
+ };
}
/// Asserts that the given predicate applied to the given arguments returns
@@ -368,12 +414,44 @@
/// ```ignore
/// verify_that!(actual, expected).and_log_failure()
/// ```
+///
+/// One may optionally add arguments which will be formatted and appended to a
+/// failure message. For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_fail() -> std::result::Result<(), googletest::internal::test_outcome::TestFailure> {
+/// # googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+/// let value = 2;
+/// let extra_information = "Some additional information";
+/// expect_that!(value, eq(3), "Test failed. Extra information: {extra_information}.");
+/// # googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
+/// # }
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// This is output as follows:
+///
+/// ```text
+/// Value of: value
+/// Expected: is equal to 3
+/// Actual: 2,
+/// which isn't equal to 3
+/// at ...
+/// Test failed. Extra information: Some additional information.
+/// ```
#[macro_export]
macro_rules! expect_that {
- ($actual:expr, $expected:expr) => {{
+ ($actual:expr, $expected:expr $(,)?) => {{
use $crate::GoogleTestSupport;
$crate::verify_that!($actual, $expected).and_log_failure();
}};
+
+ ($actual:expr, $expected:expr, $($format_args:expr),* $(,)?) => {
+ $crate::verify_that!($actual, $expected)
+ .with_failure_message(|| format!($($format_args),*))
+ .and_log_failure()
+ };
}
/// Asserts that the given predicate applied to the given arguments returns
diff --git a/src/description.rs b/src/description.rs
new file mode 100644
index 0000000..9605559
--- /dev/null
+++ b/src/description.rs
@@ -0,0 +1,362 @@
+// 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,
+}
+
+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()
+ }
+}
+
+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 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"
+ )))
+ )
+ }
+}
diff --git a/src/internal/description_renderer.rs b/src/internal/description_renderer.rs
new file mode 100644
index 0000000..937f0d5
--- /dev/null
+++ b/src/internal/description_renderer.rs
@@ -0,0 +1,614 @@
+// 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()
+ }
+
+ 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!("{:>enumeration_padding$}. ", index).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 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)
+ }
+}
diff --git a/src/internal/mod.rs b/src/internal/mod.rs
index 2f37418..9bccdc3 100644
--- a/src/internal/mod.rs
+++ b/src/internal/mod.rs
@@ -14,5 +14,6 @@
#![doc(hidden)]
+pub(crate) mod description_renderer;
pub mod source_location;
pub mod test_outcome;
diff --git a/src/internal/test_outcome.rs b/src/internal/test_outcome.rs
index 2171cc7..132ba99 100644
--- a/src/internal/test_outcome.rs
+++ b/src/internal/test_outcome.rs
@@ -174,7 +174,7 @@
pub(crate) fn log(&self) {
TestOutcome::fail_current_test();
- print!("{}", self);
+ println!("{}", self);
}
}
diff --git a/src/lib.rs b/src/lib.rs
index cec6da0..51b6345 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -21,6 +21,7 @@
#[macro_use]
pub mod assertions;
+pub mod description;
pub mod internal;
pub mod matcher;
pub mod matcher_support;
@@ -49,11 +50,6 @@
pub use super::Result;
// Assert macros
pub use super::{assert_that, expect_pred, expect_that, fail, verify_pred, verify_that};
- // Matcher macros
- pub use super::{
- all, any, contains_each, elements_are, field, is_contained_in, matches_pattern, pat,
- pointwise, property, unordered_elements_are,
- };
}
pub use googletest_macro::test;
@@ -62,7 +58,10 @@
/// A `Result` whose `Err` variant indicates a test failure.
///
-/// All test functions should return `Result<()>`.
+/// 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:
diff --git a/src/matcher.rs b/src/matcher.rs
index 83a4475..071b0d1 100644
--- a/src/matcher.rs
+++ b/src/matcher.rs
@@ -14,10 +14,11 @@
//! The components required to implement matchers.
+use crate::description::Description;
use crate::internal::source_location::SourceLocation;
use crate::internal::test_outcome::TestAssertionFailure;
-use crate::matchers::conjunction_matcher::ConjunctionMatcher;
-use crate::matchers::disjunction_matcher::DisjunctionMatcher;
+use crate::matchers::__internal_unstable_do_not_depend_on_these::ConjunctionMatcher;
+use crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatcher;
use std::fmt::Debug;
/// An interface for checking an arbitrary condition on a datum.
@@ -57,11 +58,13 @@
/// [`some`][crate::matchers::some] implements `describe` as follows:
///
/// ```ignore
- /// fn describe(&self, matcher_result: MatcherResult) -> String {
+ /// fn describe(&self, matcher_result: MatcherResult) -> Description {
/// match matcher_result {
/// MatcherResult::Matches => {
- /// format!("has a value which {}", self.inner.describe(MatcherResult::Matches))
- /// // Inner matcher invocation: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ /// Description::new()
+ /// .text("has a value which")
+ /// .nested(self.inner.describe(MatcherResult::Matches))
+ /// // Inner matcher: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/// }
/// MatcherResult::DoesNotMatch => {...} // Similar to the above
/// }
@@ -75,7 +78,7 @@
/// 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, matcher_result: MatcherResult) -> String;
+ fn describe(&self, matcher_result: MatcherResult) -> Description;
/// Prepares a [`String`] describing how the expected value
/// encoded in this instance matches or does not match the given value
@@ -114,7 +117,7 @@
/// inner matcher and appears as follows:
///
/// ```ignore
- /// fn explain_match(&self, actual: &Self::ActualT) -> String {
+ /// fn explain_match(&self, actual: &Self::ActualT) -> Description {
/// self.expected.explain_match(actual.deref())
/// }
/// ```
@@ -124,12 +127,14 @@
/// inner matcher at a point where a relative clause would fit. For example:
///
/// ```ignore
- /// fn explain_match(&self, actual: &Self::ActualT) -> String {
- /// format!("which points to a value {}", self.expected.explain_match(actual.deref()))
+ /// fn explain_match(&self, actual: &Self::ActualT) -> Description {
+ /// Description::new()
+ /// .text("which points to a value")
+ /// .nested(self.expected.explain_match(actual.deref()))
/// }
/// ```
- fn explain_match(&self, actual: &Self::ActualT) -> String {
- format!("which {}", self.describe(self.matches(actual)))
+ fn explain_match(&self, actual: &Self::ActualT) -> Description {
+ format!("which {}", self.describe(self.matches(actual))).into()
}
/// Constructs a matcher that matches both `self` and `right`.
@@ -222,10 +227,10 @@
Value of: {actual_expr}
Expected: {}
Actual: {actual_formatted},
- {}
+{}
{source_location}",
matcher.describe(MatcherResult::Match),
- matcher.explain_match(actual),
+ matcher.explain_match(actual).indent(),
))
}
diff --git a/src/matcher_support/description.rs b/src/matcher_support/description.rs
deleted file mode 100644
index ce074e0..0000000
--- a/src/matcher_support/description.rs
+++ /dev/null
@@ -1,410 +0,0 @@
-// 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::{Display, Formatter, Result};
-
-/// Helper structure to build better output of
-/// [`Matcher::describe`][crate::matcher::Matcher::describe] and
-/// [`Matcher::explain_match`][crate::matcher::Matcher::explain_match]. This
-/// is especially useful with composed matchers and matchers over containers.
-///
-/// It provides simple operations to lazily format lists of strings.
-///
-/// Usage:
-/// ```ignore
-/// let iter: impl Iterator<String> = ...
-/// format!("{}", iter.collect::<Description>().indent().bullet_list())
-/// ```
-///
-/// To construct a [`Description`], use `Iterator<Item=String>::collect()`.
-/// Each element of the collected iterator will be separated by a
-/// newline when displayed. The elements may be multi-line, but they will
-/// nevertheless be indented consistently.
-///
-/// Note that a newline will only be added between each element, but not
-/// after the last element. This makes it simpler to keep
-/// [`Matcher::describe`][crate::matcher::Matcher::describe]
-/// and [`Matcher::explain_match`][crate::matcher::Matcher::explain_match]
-/// consistent with simpler [`Matchers`][crate::matcher::Matcher].
-///
-/// They can also be indented, enumerated and or
-/// bullet listed if [`Description::indent`], [`Description::enumerate`], or
-/// respectively [`Description::bullet_list`] has been called.
-#[derive(Debug)]
-pub struct Description {
- elements: Vec<String>,
- indent_mode: IndentMode,
- list_style: ListStyle,
-}
-
-#[derive(Debug)]
-enum IndentMode {
- NoIndent,
- EveryLine,
- AllExceptFirstLine,
-}
-
-#[derive(Debug)]
-enum ListStyle {
- NoList,
- Bullet,
- Enumerate,
-}
-
-struct IndentationSizes {
- first_line_indent: usize,
- first_line_of_element_indent: usize,
- enumeration_padding: usize,
- other_line_indent: usize,
-}
-
-/// Number of space used to indent lines when no alignement is required.
-const INDENTATION_SIZE: usize = 2;
-
-impl Description {
- /// 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::matcher_support::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 { indent_mode: IndentMode::EveryLine, ..self }
- }
-
- /// Indents the lines in elements of this description except for the first
- /// line.
- ///
- /// This is similar to [`Self::indent`] except that the first line is not
- /// indented. This is useful when the first line has already been indented
- /// in the output.
- ///
- /// For example:
- ///
- /// ```
- /// # use googletest::prelude::*;
- /// # use googletest::matcher_support::description::Description;
- /// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
- /// verify_that!(description.indent_except_first_line(), displays_as(eq("A B C\n D E F")))
- /// # .unwrap();
- /// ```
- pub fn indent_except_first_line(self) -> Self {
- Self { indent_mode: IndentMode::AllExceptFirstLine, ..self }
- }
-
- /// Bullet lists the elements of [`self`].
- ///
- /// This operation will be performed lazily when [`self`] is displayed.
- ///
- /// Note that this will only bullet list each element, not each line
- /// in each element.
- ///
- /// For instance:
- ///
- /// ```
- /// # use googletest::prelude::*;
- /// # use googletest::matcher_support::description::Description;
- /// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
- /// verify_that!(description.bullet_list(), displays_as(eq("* A B C\n D E F")))
- /// # .unwrap();
- /// ```
- pub fn bullet_list(self) -> Self {
- Self { list_style: ListStyle::Bullet, ..self }
- }
-
- /// Enumerates the elements of [`self`].
- ///
- /// This operation will be performed lazily when [`self`] is displayed.
- ///
- /// Note that this will only enumerate each element, not each line in
- /// each element.
- ///
- /// For instance:
- ///
- /// ```
- /// # use googletest::prelude::*;
- /// # use googletest::matcher_support::description::Description;
- /// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
- /// verify_that!(description.enumerate(), displays_as(eq("0. A B C\n D E F")))
- /// # .unwrap();
- /// ```
- pub fn enumerate(self) -> Self {
- Self { list_style: ListStyle::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()
- }
-
- fn indentation_sizes(&self) -> IndentationSizes {
- let first_line_indent =
- if matches!(self.indent_mode, IndentMode::EveryLine) { INDENTATION_SIZE } else { 0 };
- let first_line_of_element_indent =
- if !matches!(self.indent_mode, IndentMode::NoIndent) { INDENTATION_SIZE } else { 0 };
- // Number of digit of the last index. For instance, an array of length 13 will
- // have 12 as last index (we start at 0), which have a digit size of 2.
- let enumeration_padding = if self.elements.len() > 1 {
- ((self.elements.len() - 1) as f64).log10().floor() as usize + 1
- } else {
- // Avoid negative logarithm when there is only 0 or 1 element.
- 1
- };
-
- let other_line_indent = first_line_of_element_indent
- + match self.list_style {
- ListStyle::NoList => 0,
- ListStyle::Bullet => "* ".len(),
- ListStyle::Enumerate => enumeration_padding + ". ".len(),
- };
- IndentationSizes {
- first_line_indent,
- first_line_of_element_indent,
- enumeration_padding,
- other_line_indent,
- }
- }
-}
-
-impl Display for Description {
- fn fmt(&self, f: &mut Formatter) -> Result {
- let IndentationSizes {
- mut first_line_indent,
- first_line_of_element_indent,
- enumeration_padding,
- other_line_indent,
- } = self.indentation_sizes();
-
- let mut first = true;
- for (idx, element) in self.elements.iter().enumerate() {
- let mut lines = element.lines();
- if let Some(line) = lines.next() {
- if first {
- first = false;
- } else {
- writeln!(f)?;
- }
- match self.list_style {
- ListStyle::NoList => {
- write!(f, "{:first_line_indent$}{line}", "")?;
- }
- ListStyle::Bullet => {
- write!(f, "{:first_line_indent$}* {line}", "")?;
- }
- ListStyle::Enumerate => {
- write!(
- f,
- "{:first_line_indent$}{:>enumeration_padding$}. {line}",
- "", idx,
- )?;
- }
- }
- }
- for line in lines {
- writeln!(f)?;
- write!(f, "{:other_line_indent$}{line}", "")?;
- }
- first_line_indent = first_line_of_element_indent;
- }
- Ok(())
- }
-}
-
-impl FromIterator<String> for Description {
- fn from_iter<T>(iter: T) -> Self
- where
- T: IntoIterator<Item = String>,
- {
- Self {
- elements: iter.into_iter().collect(),
- indent_mode: IndentMode::NoIndent,
- list_style: ListStyle::NoList,
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::Description;
- use crate::prelude::*;
- use indoc::indoc;
-
- #[test]
- fn description_single_element() -> Result<()> {
- let description = ["A B C".to_string()].into_iter().collect::<Description>();
- verify_that!(description, displays_as(eq("A B C")))
- }
-
- #[test]
- fn description_two_elements() -> 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 description_indent_single_element() -> Result<()> {
- let description = ["A B C".to_string()].into_iter().collect::<Description>().indent();
- verify_that!(description, displays_as(eq(" A B C")))
- }
-
- #[test]
- fn description_indent_two_elements() -> Result<()> {
- let description = ["A B C".to_string(), "D E F".to_string()]
- .into_iter()
- .collect::<Description>()
- .indent();
- verify_that!(description, displays_as(eq(" A B C\n D E F")))
- }
-
- #[test]
- fn description_indent_two_elements_except_first_line() -> Result<()> {
- let description = ["A B C".to_string(), "D E F".to_string()]
- .into_iter()
- .collect::<Description>()
- .indent_except_first_line();
- verify_that!(description, displays_as(eq("A B C\n D E F")))
- }
-
- #[test]
- fn description_indent_single_element_two_lines() -> Result<()> {
- let description =
- ["A B C\nD E F".to_string()].into_iter().collect::<Description>().indent();
- verify_that!(description, displays_as(eq(" A B C\n D E F")))
- }
-
- #[test]
- fn description_indent_single_element_two_lines_except_first_line() -> Result<()> {
- let description = ["A B C\nD E F".to_string()]
- .into_iter()
- .collect::<Description>()
- .indent_except_first_line();
- verify_that!(description, displays_as(eq("A B C\n D E F")))
- }
-
- #[test]
- fn description_bullet_single_element() -> Result<()> {
- let description = ["A B C".to_string()].into_iter().collect::<Description>().bullet_list();
- verify_that!(description, displays_as(eq("* A B C")))
- }
-
- #[test]
- fn description_bullet_two_elements() -> Result<()> {
- let description = ["A B C".to_string(), "D E F".to_string()]
- .into_iter()
- .collect::<Description>()
- .bullet_list();
- verify_that!(description, displays_as(eq("* A B C\n* D E F")))
- }
-
- #[test]
- fn description_bullet_single_element_two_lines() -> Result<()> {
- let description =
- ["A B C\nD E F".to_string()].into_iter().collect::<Description>().bullet_list();
- verify_that!(description, displays_as(eq("* A B C\n D E F")))
- }
-
- #[test]
- fn description_bullet_single_element_two_lines_indent_except_first_line() -> Result<()> {
- let description = ["A B C\nD E F".to_string()]
- .into_iter()
- .collect::<Description>()
- .bullet_list()
- .indent_except_first_line();
- verify_that!(description, displays_as(eq("* A B C\n D E F")))
- }
-
- #[test]
- fn description_bullet_two_elements_indent_except_first_line() -> Result<()> {
- let description = ["A B C".to_string(), "D E F".to_string()]
- .into_iter()
- .collect::<Description>()
- .bullet_list()
- .indent_except_first_line();
- verify_that!(description, displays_as(eq("* A B C\n * D E F")))
- }
-
- #[test]
- fn description_enumerate_single_element() -> Result<()> {
- let description = ["A B C".to_string()].into_iter().collect::<Description>().enumerate();
- verify_that!(description, displays_as(eq("0. A B C")))
- }
-
- #[test]
- fn description_enumerate_two_elements() -> Result<()> {
- let description = ["A B C".to_string(), "D E F".to_string()]
- .into_iter()
- .collect::<Description>()
- .enumerate();
- verify_that!(description, displays_as(eq("0. A B C\n1. D E F")))
- }
-
- #[test]
- fn description_enumerate_single_element_two_lines() -> Result<()> {
- let description =
- ["A B C\nD E F".to_string()].into_iter().collect::<Description>().enumerate();
- verify_that!(description, displays_as(eq("0. A B C\n D E F")))
- }
-
- #[test]
- fn description_enumerate_correct_indentation_with_large_index() -> 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"
- )))
- )
- }
-}
diff --git a/src/matcher_support/edit_distance.rs b/src/matcher_support/edit_distance.rs
index 8469f42..8847bc9 100644
--- a/src/matcher_support/edit_distance.rs
+++ b/src/matcher_support/edit_distance.rs
@@ -19,7 +19,7 @@
///
/// Increasing this limit increases the accuracy of [`edit_list`] while
/// quadratically increasing its worst-case runtime.
-const MAX_DISTANCE: i32 = 25;
+const MAX_DISTANCE: i32 = 50;
/// The difference between two inputs as produced by [`edit_list`].
#[derive(Debug)]
@@ -533,7 +533,7 @@
#[test]
fn returns_unrelated_when_maximum_distance_exceeded() -> Result<()> {
- let result = edit_list(0..=20, 20..40, Mode::Exact);
+ let result = edit_list(0..=50, 60..110, Mode::Exact);
verify_that!(result, matches_pattern!(Difference::Unrelated))
}
diff --git a/src/matcher_support/mod.rs b/src/matcher_support/mod.rs
index 8c30161..8a72ba4 100644
--- a/src/matcher_support/mod.rs
+++ b/src/matcher_support/mod.rs
@@ -19,7 +19,6 @@
//! matchers.
pub(crate) mod count_elements;
-pub mod description;
pub(crate) mod edit_distance;
pub(crate) mod summarize_diff;
pub(crate) mod zipped_iterator;
diff --git a/src/matcher_support/summarize_diff.rs b/src/matcher_support/summarize_diff.rs
index a045282..cd4efa7 100644
--- a/src/matcher_support/summarize_diff.rs
+++ b/src/matcher_support/summarize_diff.rs
@@ -17,10 +17,7 @@
use crate::matcher_support::edit_distance;
#[rustversion::since(1.70)]
use std::io::IsTerminal;
-use std::{
- borrow::Cow,
- fmt::{Display, Write},
-};
+use std::{borrow::Cow, fmt::Display};
/// Returns a string describing how the expected and actual lines differ.
///
@@ -43,13 +40,10 @@
}
match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines(), diff_mode) {
edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
- edit_distance::Difference::Editable(edit_list) => format!(
- "\nDifference({} / {}):{}",
- LineStyle::extra_actual_style().style("actual"),
- LineStyle::extra_expected_style().style("expected"),
- edit_list.into_iter().collect::<BufferedSummary>(),
- )
- .into(),
+ edit_distance::Difference::Editable(edit_list) => {
+ format!("\n{}{}", summary_header(), edit_list.into_iter().collect::<BufferedSummary>(),)
+ .into()
+ }
edit_distance::Difference::Unrelated => "".into(),
}
}
@@ -79,69 +73,125 @@
edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
edit_distance::Difference::Editable(mut edit_list) => {
edit_list.reverse();
- format!(
- "\nDifference({} / {}):{}",
- LineStyle::extra_actual_style().style("actual"),
- LineStyle::extra_expected_style().style("expected"),
- edit_list.into_iter().collect::<BufferedSummary>(),
- )
- .into()
+ format!("\n{}{}", summary_header(), edit_list.into_iter().collect::<BufferedSummary>(),)
+ .into()
}
edit_distance::Difference::Unrelated => "".into(),
}
}
+// Produces the header, with or without coloring depending on
+// stdout_supports_color()
+fn summary_header() -> Cow<'static, str> {
+ if stdout_supports_color() {
+ 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: String,
+ 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) {
- let Buffer::CommonLineBuffer(ref mut common_lines) = self.buffer;
- common_lines.push(common_line);
+ if let Buffer::CommonLineBuffer(ref mut common_lines) = self.buffer {
+ common_lines.push(common_line);
+ } else {
+ self.flush_buffer();
+ self.buffer = Buffer::CommonLineBuffer(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) {
- self.buffer.flush(&mut self.summary).unwrap();
- write!(&mut self.summary, "\n{}", LineStyle::extra_actual_style().style(extra_actual))
- .unwrap();
+ 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: &str) {
- self.flush_buffer();
- write!(&mut self.summary, "\n{}", LineStyle::extra_expected_style().style(extra_expected))
- .unwrap();
+ 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();
- write!(
- &mut self.summary,
- "\n{}",
- LineStyle::comment_style().style("<---- remaining lines omitted ---->")
- )
- .unwrap();
+ self.summary.new_line();
+ self.summary.push_str_as_comment("<---- remaining lines omitted ---->");
}
fn flush_buffer(&mut self) {
- self.buffer.flush(&mut self.summary).unwrap();
+ 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 { summary: String::new(), buffer: Buffer::CommonLineBuffer(vec![]) };
+ let mut buffered_summary = BufferedSummary::default();
for edit in iter {
match edit {
edit_distance::Edit::Both(same) => {
@@ -159,6 +209,7 @@
};
}
buffered_summary.flush_buffer();
+ buffered_summary.summary.reset_ansi();
buffered_summary
}
@@ -166,120 +217,80 @@
impl<'a> Display for BufferedSummary<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- if !matches!(self.buffer, Buffer::CommonLineBuffer(ref b) if b.is_empty()) {
+ if !matches!(self.buffer, Buffer::Empty) {
panic!("Buffer is not empty. This is a bug in gtest_rust.")
}
- self.summary.fmt(f)
+ 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)
}
}
-// This needs to be an enum as there will be in a follow-up PR new types of
-// buffer, most likely actual and expected lines, to be compared with expected
-// and actual lines for line to line comparison.
enum Buffer<'a> {
+ Empty,
CommonLineBuffer(Vec<&'a str>),
+ ExtraActualLineChunk(&'a str),
+ ExtraExpectedLineChunk(&'a str),
}
impl<'a> Buffer<'a> {
- fn flush(&mut self, writer: impl std::fmt::Write) -> std::fmt::Result {
+ fn flush(&mut self, summary: &mut SummaryBuilder) {
match self {
+ Buffer::Empty => {}
Buffer::CommonLineBuffer(common_lines) => {
- Self::flush_common_lines(std::mem::take(common_lines), writer)?
+ 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);
}
};
- Ok(())
+ *self = Buffer::Empty;
}
- fn flush_common_lines(
- common_lines: Vec<&'a str>,
- mut writer: impl std::fmt::Write,
- ) -> std::fmt::Result {
+ 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 {
- write!(writer, "\n{}", LineStyle::unchanged_style().style(line))?;
+ summary.new_line();
+ summary.push_str(line);
}
- return Ok(());
+ return;
}
let start_context = &common_lines[0..COMMON_LINES_CONTEXT_SIZE];
for line in start_context {
- write!(writer, "\n{}", LineStyle::unchanged_style().style(line))?;
+ summary.new_line();
+ summary.push_str(line);
}
- write!(
- writer,
- "\n{}",
- LineStyle::comment_style().style(&format!(
- "<---- {} common lines omitted ---->",
- common_lines.len() - 2 * COMMON_LINES_CONTEXT_SIZE
- )),
- )?;
+ 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 {
- write!(writer, "\n{}", LineStyle::unchanged_style().style(line))?;
+ summary.new_line();
+ summary.push_str(line);
}
- Ok(())
}
}
-// Use ANSI code to enable styling on the summary lines.
-//
-// See https://en.wikipedia.org/wiki/ANSI_escape_code.
-struct LineStyle {
- ansi_prefix: &'static str,
- ansi_suffix: &'static str,
- header: char,
-}
-
-impl LineStyle {
- // Font in red and bold
- fn extra_actual_style() -> Self {
- Self { ansi_prefix: "\x1B[1;31m", ansi_suffix: "\x1B[0m", header: '-' }
- }
-
- // Font in green and bold
- fn extra_expected_style() -> Self {
- Self { ansi_prefix: "\x1B[1;32m", ansi_suffix: "\x1B[0m", header: '+' }
- }
-
- // Font in italic
- fn comment_style() -> Self {
- Self { ansi_prefix: "\x1B[3m", ansi_suffix: "\x1B[0m", header: ' ' }
- }
-
- // No ansi styling
- fn unchanged_style() -> Self {
- Self { ansi_prefix: "", ansi_suffix: "", header: ' ' }
- }
-
- fn style(self, line: &str) -> StyledLine<'_> {
- StyledLine { style: self, line }
- }
-}
-
-struct StyledLine<'a> {
- style: LineStyle,
- line: &'a str,
-}
-
-impl<'a> Display for StyledLine<'a> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- if stdout_supports_color() {
- write!(
- f,
- "{}{}{}{}",
- self.style.header, self.style.ansi_prefix, self.line, self.style.ansi_suffix
- )
- } else {
- write!(f, "{}{}", self.style.header, self.line)
- }
+impl<'a> Default for Buffer<'a> {
+ fn default() -> Self {
+ Self::Empty
}
}
@@ -301,11 +312,107 @@
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() && stdout_supports_color() {
+ self.summary.push_str(RESET_ALL);
+ self.last_ansi_style = "";
+ }
+ }
+
+ fn set_ansi(&mut self, ansi_style: &'static str) {
+ if !stdout_supports_color() || 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::*};
use indoc::indoc;
+ use serial_test::{parallel, serial};
+ use std::fmt::Write;
// Make a long text with each element of the iterator on one line.
// `collection` must contains at least one element.
@@ -320,11 +427,13 @@
}
#[test]
+ #[parallel]
fn create_diff_smaller_than_one_line() -> Result<()> {
verify_that!(create_diff("One", "Two", Mode::Exact), eq(""))
}
#[test]
+ #[parallel]
fn create_diff_exact_same() -> Result<()> {
let expected = indoc! {"
One
@@ -341,14 +450,47 @@
}
#[test]
+ #[parallel]
+ 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]
+ #[parallel]
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_no_color() -> Result<()> {
- std::env::set_var("NO_COLOR", "1");
-
+ #[parallel]
+ fn create_diff_exact_small_difference() -> Result<()> {
verify_that!(
create_diff(&build_text(1..50), &build_text(1..51), Mode::Exact),
eq(indoc! {
@@ -364,4 +506,74 @@
})
)
}
+
+ // Test with color enabled.
+
+ struct ForceColor;
+
+ fn force_color() -> ForceColor {
+ std::env::set_var("FORCE_COLOR", "1");
+ std::env::remove_var("NO_COLOR");
+ ForceColor
+ }
+
+ impl Drop for ForceColor {
+ fn drop(&mut self) {
+ std::env::remove_var("FORCE_COLOR");
+ std::env::set_var("NO_COLOR", "1");
+ }
+ }
+
+ #[test]
+ #[serial]
+ fn create_diff_exact_small_difference_with_color() -> Result<()> {
+ let _keep = force_color();
+
+ 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]
+ #[serial]
+ fn create_diff_exact_difference_with_inline_color() -> Result<()> {
+ let _keep = force_color();
+ 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"
+ })
+ )
+ }
}
diff --git a/src/matchers/all_matcher.rs b/src/matchers/all_matcher.rs
index cc959c7..f2e6d06 100644
--- a/src/matchers/all_matcher.rs
+++ b/src/matchers/all_matcher.rs
@@ -13,7 +13,7 @@
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
-// macro is documented at the top level.
+// macro is documented in the matcher module.
#![doc(hidden)]
/// Matches a value which all of the given matchers match.
@@ -51,9 +51,10 @@
///
/// Assertion failure messages are not guaranteed to be identical, however.
#[macro_export]
-macro_rules! all {
+#[doc(hidden)]
+macro_rules! __all {
($($matcher:expr),* $(,)?) => {{
- use $crate::matchers::all_matcher::internal::AllMatcher;
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::AllMatcher;
AllMatcher::new([$(Box::new($matcher)),*])
}}
}
@@ -63,8 +64,8 @@
/// For internal use only. API stablility is not guaranteed!
#[doc(hidden)]
pub mod internal {
+ use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
- use crate::matcher_support::description::Description;
use crate::matchers::anything;
use std::fmt::Debug;
@@ -101,7 +102,7 @@
MatcherResult::Match
}
- fn explain_match(&self, actual: &Self::ActualT) -> String {
+ fn explain_match(&self, actual: &Self::ActualT) -> Description {
match N {
0 => anything::<T>().explain_match(actual),
1 => self.components[0].explain_match(actual),
@@ -110,36 +111,37 @@
.components
.iter()
.filter(|component| component.matches(actual).is_no_match())
- .map(|component| component.explain_match(actual))
- .collect::<Description>();
+ .collect::<Vec<_>>();
+
if failures.len() == 1 {
- format!("{}", failures)
+ failures[0].explain_match(actual)
} else {
- format!("{}", failures.bullet_list().indent_except_first_line())
+ Description::new()
+ .collect(
+ failures
+ .into_iter()
+ .map(|component| component.explain_match(actual)),
+ )
+ .bullet_list()
}
}
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match N {
0 => anything::<T>().describe(matcher_result),
1 => self.components[0].describe(matcher_result),
_ => {
- let properties = self
- .components
- .iter()
- .map(|m| m.describe(matcher_result))
- .collect::<Description>()
- .bullet_list()
- .indent();
- format!(
- "{}:\n{properties}",
- if matcher_result.into() {
- "has all the following properties"
- } else {
- "has at least one of the following properties"
- }
+ 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(self.components.iter().map(|m| m.describe(matcher_result))),
)
}
}
@@ -162,12 +164,12 @@
verify_that!(
matcher.describe(MatcherResult::Match),
- eq(indoc!(
+ displays_as(eq(indoc!(
"
has all the following properties:
* starts with prefix \"A\"
* ends with suffix \"string\""
- ))
+ )))
)
}
@@ -176,7 +178,10 @@
let first_matcher = starts_with("A");
let matcher: internal::AllMatcher<String, 1> = all!(first_matcher);
- verify_that!(matcher.describe(MatcherResult::Match), eq("starts with prefix \"A\""))
+ verify_that!(
+ matcher.describe(MatcherResult::Match),
+ displays_as(eq("starts with prefix \"A\""))
+ )
}
#[test]
diff --git a/src/matchers/any_matcher.rs b/src/matchers/any_matcher.rs
index 82c8910..95d53fe 100644
--- a/src/matchers/any_matcher.rs
+++ b/src/matchers/any_matcher.rs
@@ -13,7 +13,7 @@
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
-// macro is documented at the top level.
+// macro is documented in the matchers module.
#![doc(hidden)]
/// Matches a value which at least one of the given matchers match.
@@ -53,9 +53,10 @@
///
/// Assertion failure messages are not guaranteed to be identical, however.
#[macro_export]
-macro_rules! any {
+#[doc(hidden)]
+macro_rules! __any {
($($matcher:expr),* $(,)?) => {{
- use $crate::matchers::any_matcher::internal::AnyMatcher;
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::AnyMatcher;
AnyMatcher::new([$(Box::new($matcher)),*])
}}
}
@@ -65,8 +66,8 @@
/// For internal use only. API stablility is not guaranteed!
#[doc(hidden)]
pub mod internal {
+ use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
- use crate::matcher_support::description::Description;
use crate::matchers::anything;
use std::fmt::Debug;
@@ -95,27 +96,33 @@
MatcherResult::from(self.components.iter().any(|c| c.matches(actual).is_match()))
}
- fn explain_match(&self, actual: &Self::ActualT) -> String {
+ fn explain_match(&self, actual: &Self::ActualT) -> Description {
match N {
- 0 => format!("which {}", anything::<T>().describe(MatcherResult::NoMatch)),
+ 0 => format!("which {}", anything::<T>().describe(MatcherResult::NoMatch)).into(),
1 => self.components[0].explain_match(actual),
_ => {
let failures = self
.components
.iter()
.filter(|component| component.matches(actual).is_no_match())
- .map(|component| component.explain_match(actual))
- .collect::<Description>();
+ .collect::<Vec<_>>();
+
if failures.len() == 1 {
- format!("{}", failures)
+ failures[0].explain_match(actual)
} else {
- format!("{}", failures.bullet_list().indent_except_first_line())
+ Description::new()
+ .collect(
+ failures
+ .into_iter()
+ .map(|component| component.explain_match(actual)),
+ )
+ .bullet_list()
}
}
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match N {
0 => anything::<T>().describe(matcher_result),
1 => self.components[0].describe(matcher_result),
@@ -135,6 +142,7 @@
"has none of the following properties"
}
)
+ .into()
}
}
}
@@ -156,12 +164,12 @@
verify_that!(
matcher.describe(MatcherResult::Match),
- eq(indoc!(
+ displays_as(eq(indoc!(
"
has at least one of the following properties:
* starts with prefix \"A\"
* ends with suffix \"string\""
- ))
+ )))
)
}
@@ -170,7 +178,10 @@
let first_matcher = starts_with("A");
let matcher: internal::AnyMatcher<String, 1> = any!(first_matcher);
- verify_that!(matcher.describe(MatcherResult::Match), eq("starts with prefix \"A\""))
+ verify_that!(
+ matcher.describe(MatcherResult::Match),
+ displays_as(eq("starts with prefix \"A\""))
+ )
}
#[test]
diff --git a/src/matchers/anything_matcher.rs b/src/matchers/anything_matcher.rs
index 82de460..36be478 100644
--- a/src/matchers/anything_matcher.rs
+++ b/src/matchers/anything_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches anything. This matcher always succeeds.
@@ -42,10 +45,10 @@
MatcherResult::Match
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => "is anything".to_string(),
- MatcherResult::NoMatch => "never matches".to_string(),
+ MatcherResult::Match => "is anything".into(),
+ MatcherResult::NoMatch => "never matches".into(),
}
}
}
diff --git a/src/matchers/char_count_matcher.rs b/src/matchers/char_count_matcher.rs
index a7765b4..70977d7 100644
--- a/src/matchers/char_count_matcher.rs
+++ b/src/matchers/char_count_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches a string whose number of Unicode scalars matches `expected`.
@@ -71,36 +74,36 @@
self.expected.matches(&actual.as_ref().chars().count())
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => {
- format!(
- "has character count, which {}",
- self.expected.describe(MatcherResult::Match)
- )
- }
- MatcherResult::NoMatch => {
- format!(
- "has character count, which {}",
- self.expected.describe(MatcherResult::NoMatch)
- )
- }
+ 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) -> String {
+ 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 super::char_count;
+ use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use crate::prelude::*;
use indoc::indoc;
@@ -135,11 +138,11 @@
false.into()
}
- fn describe(&self, _: MatcherResult) -> String {
+ fn describe(&self, _: MatcherResult) -> Description {
"called described".into()
}
- fn explain_match(&self, _: &T) -> String {
+ fn explain_match(&self, _: &T) -> Description {
"called explain_match".into()
}
}
diff --git a/src/matchers/conjunction_matcher.rs b/src/matchers/conjunction_matcher.rs
index 1ba59c3..dc50833 100644
--- a/src/matchers/conjunction_matcher.rs
+++ b/src/matchers/conjunction_matcher.rs
@@ -15,7 +15,10 @@
// There are no visible documentation elements in this module.
#![doc(hidden)]
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::fmt::Debug;
/// Matcher created by [`Matcher::and`].
@@ -46,29 +49,24 @@
}
}
- fn explain_match(&self, actual: &M1::ActualT) -> String {
+ fn explain_match(&self, actual: &M1::ActualT) -> Description {
match (self.m1.matches(actual), self.m2.matches(actual)) {
- (MatcherResult::Match, MatcherResult::Match) => {
- format!(
- "{} and\n {}",
- self.m1.explain_match(actual),
- self.m2.explain_match(actual)
- )
- }
+ (MatcherResult::Match, MatcherResult::Match) => Description::new()
+ .nested(self.m1.explain_match(actual))
+ .text("and")
+ .nested(self.m2.explain_match(actual)),
(MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual),
(MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual),
- (MatcherResult::NoMatch, MatcherResult::NoMatch) => {
- format!(
- "{} and\n {}",
- self.m1.explain_match(actual),
- self.m2.explain_match(actual)
- )
- }
+ (MatcherResult::NoMatch, MatcherResult::NoMatch) => Description::new()
+ .nested(self.m1.explain_match(actual))
+ .text("and")
+ .nested(self.m2.explain_match(actual)),
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
format!("{}, and {}", self.m1.describe(matcher_result), self.m2.describe(matcher_result))
+ .into()
}
}
@@ -124,8 +122,9 @@
Value of: 1
Expected: never matches, and never matches
Actual: 1,
- which is anything and
- which is anything
+ which is anything
+ and
+ which is anything
"
))))
)
diff --git a/src/matchers/container_eq_matcher.rs b/src/matchers/container_eq_matcher.rs
index f80cebf..d4f872c 100644
--- a/src/matchers/container_eq_matcher.rs
+++ b/src/matchers/container_eq_matcher.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use std::fmt::Debug;
use std::marker::PhantomData;
@@ -29,9 +30,14 @@
/// Unexpected: [4]
/// ```
///
-/// The type of `expected` must implement `IntoIterator` with an `Item` which
-/// implements `PartialEq`. If the container type is a `Vec`, then the expected
-/// type may be a slice of the same element type. For example:
+/// The actual value must be a container such as a `Vec`, an array, or a
+/// dereferenced slice. More precisely, a shared borrow of the actual value must
+/// implement [`IntoIterator`] whose `Item` type implements
+/// [`PartialEq<ExpectedT>`], where `ExpectedT` is the element type of the
+/// expected value.
+///
+/// If the container type is a `Vec`, then the expected type may be a slice of
+/// the same element type. For example:
///
/// ```
/// # use googletest::prelude::*;
@@ -114,14 +120,14 @@
(*actual == self.expected).into()
}
- fn explain_match(&self, actual: &ActualContainerT) -> String {
- build_explanation(self.get_missing_items(actual), self.get_unexpected_items(actual))
+ 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) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => format!("is equal to {:?}", self.expected),
- MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected),
+ MatcherResult::Match => format!("is equal to {:?}", self.expected).into(),
+ MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected).into(),
}
}
}
diff --git a/src/matchers/contains_matcher.rs b/src/matchers/contains_matcher.rs
index d714c88..1a27ce0 100644
--- a/src/matchers/contains_matcher.rs
+++ b/src/matchers/contains_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches an iterable type whose elements contain a value matched by `inner`.
@@ -101,33 +104,37 @@
}
}
- fn explain_match(&self, actual: &Self::ActualT) -> String {
+ fn explain_match(&self, actual: &Self::ActualT) -> Description {
let count = self.count_matches(actual);
match (count, &self.count) {
- (_, Some(_)) => format!("which contains {} matching elements", count),
- (0, None) => "which does not contain a matching element".to_string(),
- (_, None) => "which contains a matching element".to_string(),
+ (_, Some(_)) => format!("which contains {} matching elements", count).into(),
+ (0, None) => "which does not contain a matching element".into(),
+ (_, None) => "which contains a matching element".into(),
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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()
}
}
}
@@ -193,7 +200,7 @@
#[test]
fn contains_does_not_match_empty_slice() -> Result<()> {
- let matcher = contains(eq(1));
+ let matcher = contains(eq::<i32, _>(1));
let result = matcher.matches(&[]);
@@ -233,7 +240,7 @@
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("contains at least one element which is equal to 1")
+ displays_as(eq("contains at least one element which is equal to 1"))
)
}
@@ -243,7 +250,7 @@
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("contains n elements which is equal to 1\n where n is equal to 2")
+ displays_as(eq("contains n elements which is equal to 1\n where n is equal to 2"))
)
}
diff --git a/src/matchers/contains_regex_matcher.rs b/src/matchers/contains_regex_matcher.rs
index 8cc93a7..6f68168 100644
--- a/src/matchers/contains_regex_matcher.rs
+++ b/src/matchers/contains_regex_matcher.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use regex::Regex;
use std::fmt::Debug;
@@ -77,13 +78,13 @@
self.regex.is_match(actual.as_ref()).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
MatcherResult::Match => {
- format!("contains the regular expression {:#?}", self.regex.as_str())
+ format!("contains the regular expression {:#?}", self.regex.as_str()).into()
}
MatcherResult::NoMatch => {
- format!("doesn't contain the regular expression {:#?}", self.regex.as_str())
+ format!("doesn't contain the regular expression {:#?}", self.regex.as_str()).into()
}
}
}
@@ -142,7 +143,7 @@
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("contains the regular expression \"\\n\"")
+ displays_as(eq("contains the regular expression \"\\n\""))
)
}
}
diff --git a/src/matchers/disjunction_matcher.rs b/src/matchers/disjunction_matcher.rs
index 48bf226..dd56be2 100644
--- a/src/matchers/disjunction_matcher.rs
+++ b/src/matchers/disjunction_matcher.rs
@@ -15,7 +15,10 @@
// There are no visible documentation elements in this module.
#![doc(hidden)]
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::fmt::Debug;
/// Matcher created by [`Matcher::or`].
@@ -46,12 +49,16 @@
}
}
- fn explain_match(&self, actual: &M1::ActualT) -> String {
- format!("{} and\n {}", self.m1.explain_match(actual), self.m2.explain_match(actual))
+ fn explain_match(&self, actual: &M1::ActualT) -> Description {
+ Description::new()
+ .nested(self.m1.explain_match(actual))
+ .text("and")
+ .nested(self.m2.explain_match(actual))
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
format!("{}, or {}", self.m1.describe(matcher_result), self.m2.describe(matcher_result))
+ .into()
}
}
@@ -85,8 +92,9 @@
Value of: 1
Expected: never matches, or never matches
Actual: 1,
- which is anything and
- which is anything
+ which is anything
+ and
+ which is anything
"
))))
)
diff --git a/src/matchers/display_matcher.rs b/src/matchers/display_matcher.rs
index 628769b..51a5bff 100644
--- a/src/matchers/display_matcher.rs
+++ b/src/matchers/display_matcher.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use std::fmt::{Debug, Display};
use std::marker::PhantomData;
@@ -42,21 +43,22 @@
self.inner.matches(&format!("{actual}"))
}
- fn explain_match(&self, actual: &T) -> String {
+ fn explain_match(&self, actual: &T) -> Description {
format!("which displays as a string {}", self.inner.explain_match(&format!("{actual}")))
+ .into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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)
- )
- }
+ MatcherResult::NoMatch => format!(
+ "doesn't display as a string which {}",
+ self.inner.describe(MatcherResult::Match)
+ )
+ .into(),
}
}
}
@@ -107,6 +109,7 @@
result,
err(displays_as(contains_substring(indoc!(
"
+ Actual: \"123\\n234\",
which displays as a string which isn't equal to \"123\\n345\"
Difference(-actual / +expected):
123
diff --git a/src/matchers/each_matcher.rs b/src/matchers/each_matcher.rs
index ce61207..08a0742 100644
--- a/src/matchers/each_matcher.rs
+++ b/src/matchers/each_matcher.rs
@@ -12,14 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
-use crate::matcher_support::description::Description;
use std::{fmt::Debug, marker::PhantomData};
/// Matches a container all of whose elements are matched by the matcher
/// `inner`.
///
-/// `T` can be any container such that `&T` implements `IntoIterator`.
+/// `T` can be any container such that `&T` implements `IntoIterator`. This
+/// includes `Vec`, arrays, and (dereferenced) slices.
///
/// ```
/// # use googletest::prelude::*;
@@ -27,6 +28,10 @@
/// # 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<()> {
@@ -87,7 +92,7 @@
MatcherResult::Match
}
- fn explain_match(&self, actual: &ActualT) -> String {
+ 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() {
@@ -95,11 +100,12 @@
}
}
if non_matching_elements.is_empty() {
- return format!("whose each element {}", self.inner.describe(MatcherResult::Match));
+ 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}");
+ return format!("whose element #{idx} is {element:?}, {explanation}").into();
}
let failed_indexes = non_matching_elements
@@ -112,16 +118,18 @@
.map(|&(_, element, ref explanation)| format!("{element:?}, {explanation}"))
.collect::<Description>()
.indent();
- format!("whose elements {failed_indexes} don't match\n{element_explanations}")
+ format!("whose elements {failed_indexes} don't match\n{element_explanations}").into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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()
}
}
}
@@ -220,8 +228,8 @@
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"
+ 0, which is less than or equal to 1
+ 1, which is less than or equal to 1"
))))
)
}
@@ -233,7 +241,6 @@
result,
err(displays_as(contains_substring(indoc!(
"
- Value of: vec![vec! [1, 2], vec! [1]]
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"
diff --git a/src/matchers/elements_are_matcher.rs b/src/matchers/elements_are_matcher.rs
index 736bf47..3a4b5b2 100644
--- a/src/matchers/elements_are_matcher.rs
+++ b/src/matchers/elements_are_matcher.rs
@@ -13,7 +13,7 @@
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
-// macro is documented at the top level.
+// macro is documented in the matchers module.
#![doc(hidden)]
/// Matches a container's elements to each matcher in order.
@@ -28,8 +28,9 @@
/// # .unwrap();
/// ```
///
-/// The actual value must be a container implementing [`IntoIterator`]. This
-/// includes standard containers, slices (when dereferenced) and arrays.
+/// The actual value must be a container such as a `Vec`, an array, or a
+/// dereferenced slice. More precisely, a shared borrow of the actual value must
+/// implement [`IntoIterator`].
///
/// ```
/// # use googletest::prelude::*;
@@ -50,7 +51,7 @@
///
/// Note: This behavior is only possible in [`verify_that!`] macros. In any
/// other cases, it is still necessary to use the
-/// [`elements_are!`][crate::elements_are] macro.
+/// [`elements_are!`][crate::matchers::elements_are] macro.
///
/// ```compile_fail
/// # use googletest::prelude::*;
@@ -69,7 +70,8 @@
/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`].
///
/// Do not use this with unordered containers, since that will lead to flaky
-/// tests. Use [`unordered_elements_are!`][crate::unordered_elements_are]
+/// tests. Use
+/// [`unordered_elements_are!`][crate::matchers::unordered_elements_are]
/// instead.
///
/// [`IntoIterator`]: std::iter::IntoIterator
@@ -77,9 +79,10 @@
/// [`Iterator::collect`]: std::iter::Iterator::collect
/// [`Vec`]: std::vec::Vec
#[macro_export]
-macro_rules! elements_are {
+#[doc(hidden)]
+macro_rules! __elements_are {
($($matcher:expr),* $(,)?) => {{
- use $crate::matchers::elements_are_matcher::internal::ElementsAre;
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::ElementsAre;
ElementsAre::new(vec![$(Box::new($matcher)),*])
}}
}
@@ -89,8 +92,8 @@
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub mod internal {
+ use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
- use crate::matcher_support::description::Description;
use crate::matcher_support::zipped_iterator::zip;
use std::{fmt::Debug, marker::PhantomData};
@@ -133,7 +136,7 @@
}
}
- fn explain_match(&self, actual: &ContainerT) -> String {
+ 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();
@@ -144,20 +147,20 @@
}
if mismatches.is_empty() {
if !zipped_iterator.has_size_mismatch() {
- "whose elements all match".to_string()
+ "whose elements all match".into()
} else {
- format!("whose size is {}", zipped_iterator.left_size())
+ format!("whose size is {}", zipped_iterator.left_size()).into()
}
} else if mismatches.len() == 1 {
let mismatches = mismatches.into_iter().collect::<Description>();
- format!("where {mismatches}")
+ format!("where {mismatches}").into()
} else {
let mismatches = mismatches.into_iter().collect::<Description>();
- format!("where:\n{}", mismatches.bullet_list().indent())
+ format!("where:\n{}", mismatches.bullet_list().indent()).into()
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
format!(
"{} elements:\n{}",
if matcher_result.into() { "has" } else { "doesn't have" },
@@ -169,6 +172,7 @@
.enumerate()
.indent()
)
+ .into()
}
}
}
diff --git a/src/matchers/empty_matcher.rs b/src/matchers/empty_matcher.rs
index afefcb7..11cb675 100644
--- a/src/matchers/empty_matcher.rs
+++ b/src/matchers/empty_matcher.rs
@@ -12,12 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches an empty container.
///
-/// `T` can be any container such that `&T` implements `IntoIterator`.
+/// `T` can be any container such that `&T` implements `IntoIterator`. For
+/// instance, `T` can be a common container like `Vec` and
+/// [`HashSet`][std::collections::HashSet].
///
/// ```
/// # use googletest::prelude::*;
@@ -66,8 +71,8 @@
actual.into_iter().next().is_none().into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
- if matcher_result.into() { "is empty" } else { "isn't empty" }.to_string()
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
+ if matcher_result.into() { "is empty" } else { "isn't empty" }.into()
}
}
diff --git a/src/matchers/eq_deref_of_matcher.rs b/src/matchers/eq_deref_of_matcher.rs
index 1540905..320aafb 100644
--- a/src/matchers/eq_deref_of_matcher.rs
+++ b/src/matchers/eq_deref_of_matcher.rs
@@ -13,6 +13,7 @@
// limitations under the License.
use crate::{
+ description::Description,
matcher::{Matcher, MatcherResult},
matcher_support::{edit_distance, summarize_diff::create_diff},
};
@@ -76,14 +77,14 @@
(self.expected.deref() == actual).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => format!("is equal to {:?}", self.expected),
- MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected),
+ MatcherResult::Match => format!("is equal to {:?}", self.expected).into(),
+ MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected).into(),
}
}
- fn explain_match(&self, actual: &ActualT) -> String {
+ fn explain_match(&self, actual: &ActualT) -> Description {
format!(
"which {}{}",
&self.describe(self.matches(actual)),
@@ -93,6 +94,7 @@
edit_distance::Mode::Exact,
)
)
+ .into()
}
}
@@ -138,13 +140,13 @@
"
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\",
- }
+ Difference(-actual / +expected):
+ Strukt {
+ - int: 123,
+ + int: 321,
+ - string: \"something\",
+ + string: \"someone\",
+ }
"})))
)
}
diff --git a/src/matchers/eq_matcher.rs b/src/matchers/eq_matcher.rs
index 42684c9..985307f 100644
--- a/src/matchers/eq_matcher.rs
+++ b/src/matchers/eq_matcher.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use crate::matcher_support::edit_distance;
use crate::matcher_support::summarize_diff::create_diff;
@@ -82,21 +83,21 @@
phantom: PhantomData<A>,
}
-impl<A: Debug + ?Sized, T: PartialEq<A> + Debug> Matcher for EqMatcher<A, T> {
+impl<T: Debug, A: Debug + ?Sized + PartialEq<T>> Matcher for EqMatcher<A, T> {
type ActualT = A;
fn matches(&self, actual: &A) -> MatcherResult {
- (self.expected == *actual).into()
+ (*actual == self.expected).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => format!("is equal to {:?}", self.expected),
- MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected),
+ 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) -> String {
+ fn explain_match(&self, actual: &A) -> Description {
let expected_debug = format!("{:#?}", self.expected);
let actual_debug = format!("{:#?}", actual);
let description = self.describe(self.matches(actual));
@@ -117,7 +118,7 @@
create_diff(&actual_debug, &expected_debug, edit_distance::Mode::Exact)
};
- format!("which {description}{diff}")
+ format!("which {description}{diff}").into()
}
}
@@ -178,13 +179,13 @@
"
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\",
- }
+ Difference(-actual / +expected):
+ Strukt {
+ - int: 123,
+ + int: 321,
+ - string: \"something\",
+ + string: \"someone\",
+ }
"})))
)
}
@@ -200,13 +201,13 @@
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,
- ]
+ Difference(-actual / +expected):
+ [
+ 1,
+ - 2,
+ 3,
+ + 4,
+ ]
"})))
)
}
@@ -222,14 +223,14 @@
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,
- ]
+ Difference(-actual / +expected):
+ [
+ 1,
+ - 2,
+ 3,
+ - 4,
+ 5,
+ ]
"})))
)
}
@@ -241,18 +242,20 @@
result,
err(displays_as(contains_substring(indoc! {
"
- Difference(-actual / +expected):
- [
- - 1,
- - 2,
- 3,
- 4,
- <---- 43 common lines omitted ---->
- 48,
- 49,
- + 50,
- + 51,
- ]"})))
+ ],
+ 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,
+ ]"})))
)
}
@@ -263,18 +266,20 @@
result,
err(displays_as(contains_substring(indoc! {
"
- Difference(-actual / +expected):
- [
- - 1,
- - 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- + 8,
- + 9,
- ]"})))
+ 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,
+ ]"})))
)
}
@@ -285,15 +290,17 @@
result,
err(displays_as(contains_substring(indoc! {
"
- Difference(-actual / +expected):
- [
- 1,
- <---- 46 common lines omitted ---->
- 48,
- 49,
- + 50,
- + 51,
- ]"})))
+ ],
+ 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,
+ ]"})))
)
}
@@ -304,15 +311,17 @@
result,
err(displays_as(contains_substring(indoc! {
"
- Difference(-actual / +expected):
- [
- - 1,
- - 2,
- 3,
- 4,
- <---- 46 common lines omitted ---->
- 51,
- ]"})))
+ ],
+ 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,
+ ]"})))
)
}
@@ -354,14 +363,13 @@
verify_that!(
result,
- err(displays_as(contains_substring(indoc!(
- "
- First line
- -Second line
- +Second lines
- Third line
- "
- ))))
+ err(displays_as(contains_substring(
+ "\
+ First line
+ -Second line
+ +Second lines
+ Third line"
+ )))
)
}
diff --git a/src/matchers/err_matcher.rs b/src/matchers/err_matcher.rs
index 022bc8c..3b10de4 100644
--- a/src/matchers/err_matcher.rs
+++ b/src/matchers/err_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches a `Result` containing `Err` with a value matched by `inner`.
@@ -56,24 +59,25 @@
actual.as_ref().err().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
}
- fn explain_match(&self, actual: &Self::ActualT) -> String {
+ fn explain_match(&self, actual: &Self::ActualT) -> Description {
match actual {
- Err(e) => format!("which is an error {}", self.inner.explain_match(e)),
- Ok(_) => "which is a success".to_string(),
+ 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) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
MatcherResult::Match => {
- format!("is an error which {}", self.inner.describe(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)
- )
- }
+ MatcherResult::NoMatch => format!(
+ "is a success or is an error containing a value which {}",
+ self.inner.describe(MatcherResult::NoMatch)
+ )
+ .into(),
}
}
}
@@ -126,7 +130,8 @@
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
+ which is an error
+ which isn't equal to 2
"
))))
)
@@ -139,6 +144,9 @@
phantom_t: Default::default(),
phantom_e: Default::default(),
};
- verify_that!(matcher.describe(MatcherResult::Match), eq("is an error which is equal to 1"))
+ verify_that!(
+ matcher.describe(MatcherResult::Match),
+ displays_as(eq("is an error which is equal to 1"))
+ )
}
}
diff --git a/src/matchers/field_matcher.rs b/src/matchers/field_matcher.rs
index 0d1d4fe..fc37ce6 100644
--- a/src/matchers/field_matcher.rs
+++ b/src/matchers/field_matcher.rs
@@ -13,7 +13,7 @@
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
-// macro is documented at the top level.
+// macro is documented in the matchers module.
#![doc(hidden)]
/// Matches a structure or enum with a given field which is matched by a given
@@ -105,10 +105,11 @@
/// # }
/// ```
///
-/// See also the macro [`property`][crate::property] for an analogous mechanism
-/// to extract a datum by invoking a method.
+/// See also the macro [`property`][crate::matchers::property] for an analogous
+/// mechanism to extract a datum by invoking a method.
#[macro_export]
-macro_rules! field {
+#[doc(hidden)]
+macro_rules! __field {
($($t:tt)*) => { $crate::field_internal!($($t)*) }
}
@@ -118,7 +119,7 @@
#[macro_export]
macro_rules! field_internal {
($($t:ident)::+.$field:tt, $m:expr) => {{
- use $crate::matchers::field_matcher::internal::field_matcher;
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::field_matcher;
field_matcher(
|o| {
match o {
@@ -140,7 +141,10 @@
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub mod internal {
- use crate::matcher::{Matcher, MatcherResult};
+ use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+ };
use std::fmt::Debug;
/// Creates a matcher to verify a specific field of the actual struct using
@@ -175,27 +179,29 @@
}
}
- fn explain_match(&self, actual: &OuterT) -> String {
+ 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}`")
+ format!("which has the wrong enum variant `{without_fields}`").into()
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
format!(
"has field `{}`, which {}",
self.field_path,
self.inner.describe(matcher_result)
)
+ .into()
}
}
}
diff --git a/src/matchers/ge_matcher.rs b/src/matchers/ge_matcher.rs
index 33e847e..cb2a91f 100644
--- a/src/matchers/ge_matcher.rs
+++ b/src/matchers/ge_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches a value greater than or equal to (in the sense of `>=`) `expected`.
@@ -91,10 +94,12 @@
(*actual >= self.expected).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => format!("is greater than or equal to {:?}", self.expected),
- MatcherResult::NoMatch => format!("is less than {:?}", self.expected),
+ MatcherResult::Match => {
+ format!("is greater than or equal to {:?}", self.expected).into()
+ }
+ MatcherResult::NoMatch => format!("is less than {:?}", self.expected).into(),
}
}
}
diff --git a/src/matchers/gt_matcher.rs b/src/matchers/gt_matcher.rs
index 699bf2a..b52c6e3 100644
--- a/src/matchers/gt_matcher.rs
+++ b/src/matchers/gt_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches a value greater (in the sense of `>`) than `expected`.
@@ -91,10 +94,12 @@
(*actual > self.expected).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => format!("is greater than {:?}", self.expected),
- MatcherResult::NoMatch => format!("is less than or equal to {:?}", self.expected),
+ MatcherResult::Match => format!("is greater than {:?}", self.expected).into(),
+ MatcherResult::NoMatch => {
+ format!("is less than or equal to {:?}", self.expected).into()
+ }
}
}
}
@@ -175,14 +180,17 @@
#[test]
fn gt_describe_matches() -> Result<()> {
- verify_that!(gt::<i32, i32>(232).describe(MatcherResult::Match), eq("is greater than 232"))
+ verify_that!(
+ gt::<i32, i32>(232).describe(MatcherResult::Match),
+ displays_as(eq("is greater than 232"))
+ )
}
#[test]
fn gt_describe_does_not_match() -> Result<()> {
verify_that!(
gt::<i32, i32>(232).describe(MatcherResult::NoMatch),
- eq("is less than or equal to 232")
+ displays_as(eq("is less than or equal to 232"))
)
}
diff --git a/src/matchers/has_entry_matcher.rs b/src/matchers/has_entry_matcher.rs
index 67a44cd..7f9d2ad 100644
--- a/src/matchers/has_entry_matcher.rs
+++ b/src/matchers/has_entry_matcher.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use std::collections::HashMap;
use std::fmt::Debug;
@@ -87,7 +88,7 @@
}
}
- fn explain_match(&self, actual: &HashMap<KeyT, ValueT>) -> String {
+ fn explain_match(&self, actual: &HashMap<KeyT, ValueT>) -> Description {
if let Some(value) = actual.get(&self.key) {
format!(
"which contains key {:?}, but is mapped to value {:#?}, {}",
@@ -95,24 +96,27 @@
value,
self.inner.explain_match(value)
)
+ .into()
} else {
- format!("which doesn't contain key {:?}", self.key)
+ format!("which doesn't contain key {:?}", self.key).into()
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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(),
}
}
}
diff --git a/src/matchers/is_encoded_string_matcher.rs b/src/matchers/is_encoded_string_matcher.rs
new file mode 100644
index 0000000..d2fb259
--- /dev/null
+++ b/src/matchers/is_encoded_string_matcher.rs
@@ -0,0 +1,180 @@
+// 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, MatcherResult},
+};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// 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<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT>(
+ inner: InnerMatcherT,
+) -> impl Matcher<ActualT = ActualT>
+where
+ InnerMatcherT: Matcher<ActualT = String>,
+{
+ IsEncodedStringMatcher { inner, phantom: Default::default() }
+}
+
+struct IsEncodedStringMatcher<ActualT, InnerMatcherT> {
+ inner: InnerMatcherT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT> Matcher
+ for IsEncodedStringMatcher<ActualT, InnerMatcherT>
+where
+ InnerMatcherT: Matcher<ActualT = String>,
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ String::from_utf8(actual.as_ref().to_vec())
+ .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: &Self::ActualT) -> Description {
+ match String::from_utf8(actual.as_ref().to_vec()) {
+ 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::*;
+
+ #[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::<&[u8], _>(eq("A string"));
+
+ verify_that!(
+ matcher.describe(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::<&[u8], _>(eq("A string"));
+
+ verify_that!(
+ matcher.describe(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\""))
+ )
+ }
+}
diff --git a/src/matchers/is_matcher.rs b/src/matchers/is_matcher.rs
index 51fd3b2..336ce53 100644
--- a/src/matchers/is_matcher.rs
+++ b/src/matchers/is_matcher.rs
@@ -14,7 +14,10 @@
#![doc(hidden)]
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches precisely values matched by `inner`.
@@ -44,22 +47,24 @@
self.inner.matches(actual)
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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: &Self::ActualT) -> String {
+ fn explain_match(&self, actual: &Self::ActualT) -> Description {
self.inner.explain_match(actual)
}
}
diff --git a/src/matchers/is_nan_matcher.rs b/src/matchers/is_nan_matcher.rs
index 3cbe694..0af4338 100644
--- a/src/matchers/is_nan_matcher.rs
+++ b/src/matchers/is_nan_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use num_traits::float::Float;
use std::{fmt::Debug, marker::PhantomData};
@@ -30,8 +33,8 @@
actual.is_nan().into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
- if matcher_result.into() { "is NaN" } else { "isn't NaN" }.to_string()
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
+ if matcher_result.into() { "is NaN" } else { "isn't NaN" }.into()
}
}
diff --git a/src/matchers/le_matcher.rs b/src/matchers/le_matcher.rs
index 6e7a16f..cfc5781 100644
--- a/src/matchers/le_matcher.rs
+++ b/src/matchers/le_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches a value less than or equal to (in the sense of `<=`) `expected`.
@@ -91,10 +94,10 @@
(*actual <= self.expected).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => format!("is less than or equal to {:?}", self.expected),
- MatcherResult::NoMatch => format!("is greater than {:?}", self.expected),
+ MatcherResult::Match => format!("is less than or equal to {:?}", self.expected).into(),
+ MatcherResult::NoMatch => format!("is greater than {:?}", self.expected).into(),
}
}
}
diff --git a/src/matchers/len_matcher.rs b/src/matchers/len_matcher.rs
index 3a31873..be903c9 100644
--- a/src/matchers/len_matcher.rs
+++ b/src/matchers/len_matcher.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use crate::matcher_support::count_elements::count_elements;
use std::{fmt::Debug, marker::PhantomData};
@@ -19,7 +20,9 @@
/// 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 (when dereferenced) slices.
+/// the standard Rust containers, arrays, and (when dereferenced) slices. More
+/// precisely, a shared borrow of the actual type must implement
+/// [`IntoIterator`].
///
/// ```
/// # use googletest::prelude::*;
@@ -68,26 +71,29 @@
self.expected.matches(&count_elements(actual))
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
MatcherResult::Match => {
- format!("has length, which {}", self.expected.describe(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) -> String {
+ 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 super::len;
+ use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use crate::prelude::*;
use indoc::indoc;
@@ -180,11 +186,11 @@
false.into()
}
- fn describe(&self, _: MatcherResult) -> String {
+ fn describe(&self, _: MatcherResult) -> Description {
"called described".into()
}
- fn explain_match(&self, _: &T) -> String {
+ fn explain_match(&self, _: &T) -> Description {
"called explain_match".into()
}
}
diff --git a/src/matchers/lt_matcher.rs b/src/matchers/lt_matcher.rs
index 96df00c..08bc563 100644
--- a/src/matchers/lt_matcher.rs
+++ b/src/matchers/lt_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches a value less (in the sense of `<`) than `expected`.
@@ -91,11 +94,11 @@
(*actual < self.expected).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => format!("is less than {:?}", self.expected),
+ MatcherResult::Match => format!("is less than {:?}", self.expected).into(),
MatcherResult::NoMatch => {
- format!("is greater than or equal to {:?}", self.expected)
+ format!("is greater than or equal to {:?}", self.expected).into()
}
}
}
diff --git a/src/matchers/matches_pattern.rs b/src/matchers/matches_pattern.rs
index 9c252e5..106a5d7 100644
--- a/src/matchers/matches_pattern.rs
+++ b/src/matchers/matches_pattern.rs
@@ -13,7 +13,7 @@
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
-// macro is documented at the top level.
+// macro is documented in the matchers module.
#![doc(hidden)]
/// Matches a value according to a pattern of matchers.
@@ -91,7 +91,8 @@
/// # .unwrap();
/// ```
///
-/// One can use the alias [`pat`][crate::pat] to make this less verbose:
+/// One can use the alias [`pat`][crate::matchers::pat] to make this less
+/// verbose:
///
/// ```
/// # use googletest::prelude::*;
@@ -162,7 +163,7 @@
/// # .unwrap();
/// ```
///
-/// If the method returns a reference, precede it with the keyword `ref`:
+/// If the method returns a reference, precede it with a `*`:
///
/// ```
/// # use googletest::prelude::*;
@@ -177,7 +178,7 @@
///
/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
/// verify_that!(my_struct, matches_pattern!(MyStruct {
-/// ref get_a_field_ref(): starts_with("Something"),
+/// *get_a_field_ref(): starts_with("Something"),
/// }))
/// # .unwrap();
/// ```
@@ -246,7 +247,8 @@
/// # .unwrap();
/// ```
#[macro_export]
-macro_rules! matches_pattern {
+#[doc(hidden)]
+macro_rules! __matches_pattern {
($($t:tt)*) => { $crate::matches_pattern_internal!($($t)*) }
}
@@ -259,7 +261,7 @@
[$($struct_name:tt)*],
{ $field_name:ident : $matcher:expr $(,)? }
) => {
- $crate::matchers::is_matcher::is(
+ $crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(field!($($struct_name)*.$field_name, $matcher))
)
@@ -269,7 +271,7 @@
[$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
) => {
- $crate::matchers::is_matcher::is(
+ $crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(property!($($struct_name)*.$property_name($($argument),*), $matcher))
)
@@ -277,11 +279,11 @@
(
[$($struct_name:tt)*],
- { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+ { * $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
) => {
- $crate::matchers::is_matcher::is(
+ $crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
- all!(property!(ref $($struct_name)*.$property_name($($argument),*), $matcher))
+ all!(property!(* $($struct_name)*.$property_name($($argument),*), $matcher))
)
};
@@ -309,10 +311,10 @@
(
[$($struct_name:tt)*],
- { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+ { * $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
- all!(property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)),
+ all!(property!(* $($struct_name)*.$property_name($($argument),*), $matcher)),
[$($struct_name)*],
{ $($rest)* }
)
@@ -323,7 +325,7 @@
[$($struct_name:tt)*],
{ $field_name:ident : $matcher:expr $(,)? }
) => {
- $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+ $crate::matchers::__internal_unstable_do_not_depend_on_these::is(stringify!($($struct_name)*), all!(
$($processed)*,
field!($($struct_name)*.$field_name, $matcher)
))
@@ -334,7 +336,7 @@
[$($struct_name:tt)*],
{ $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
) => {
- $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+ $crate::matchers::__internal_unstable_do_not_depend_on_these::is(stringify!($($struct_name)*), all!(
$($processed)*,
property!($($struct_name)*.$property_name($($argument),*), $matcher)
))
@@ -343,11 +345,11 @@
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
- { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+ { * $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
) => {
- $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+ $crate::matchers::__internal_unstable_do_not_depend_on_these::is(stringify!($($struct_name)*), all!(
$($processed)*,
- property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)
+ property!(* $($struct_name)*.$property_name($($argument),*), $matcher)
))
};
@@ -384,12 +386,12 @@
(
all!($($processed:tt)*),
[$($struct_name:tt)*],
- { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+ { * $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
) => {
$crate::matches_pattern_internal!(
all!(
$($processed)*,
- property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)
+ property!(* $($struct_name)*.$property_name($($argument),*), $matcher)
),
[$($struct_name)*],
{ $($rest)* }
@@ -410,7 +412,7 @@
[$($struct_name:tt)*],
($matcher:expr $(,)?)
) => {
- $crate::matchers::is_matcher::is(
+ $crate::matchers::__internal_unstable_do_not_depend_on_these::is(
stringify!($($struct_name)*),
all!(field!($($struct_name)*.0, $matcher))
)
@@ -436,7 +438,7 @@
$field:tt,
($matcher:expr $(,)?)
) => {
- $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+ $crate::matchers::__internal_unstable_do_not_depend_on_these::is(stringify!($($struct_name)*), all!(
$($processed)*,
field!($($struct_name)*.$field, $matcher)
))
@@ -587,13 +589,14 @@
($first:tt $($rest:tt)*) => {{
#[allow(unused)]
- use $crate::{all, field, property};
+ use $crate::matchers::{all, field, property};
$crate::matches_pattern_internal!([$first], $($rest)*)
}};
}
-/// An alias for [`matches_pattern`].
+/// An alias for [`matches_pattern`][crate::matchers::matches_pattern!].
#[macro_export]
-macro_rules! pat {
+#[doc(hidden)]
+macro_rules! __pat {
($($t:tt)*) => { $crate::matches_pattern_internal!($($t)*) }
}
diff --git a/src/matchers/matches_regex_matcher.rs b/src/matchers/matches_regex_matcher.rs
index d0001e2..32b053b 100644
--- a/src/matchers/matches_regex_matcher.rs
+++ b/src/matchers/matches_regex_matcher.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use regex::Regex;
use std::fmt::Debug;
@@ -94,13 +95,13 @@
self.regex.is_match(actual.as_ref()).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
MatcherResult::Match => {
- format!("matches the regular expression {:#?}", self.pattern.deref())
+ format!("matches the regular expression {:#?}", self.pattern.deref()).into()
}
MatcherResult::NoMatch => {
- format!("doesn't match the regular expression {:#?}", self.pattern.deref())
+ format!("doesn't match the regular expression {:#?}", self.pattern.deref()).into()
}
}
}
@@ -204,7 +205,7 @@
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("matches the regular expression \"\\n\"")
+ displays_as(eq("matches the regular expression \"\\n\""))
)
}
}
diff --git a/src/matchers/mod.rs b/src/matchers/mod.rs
index f8aef10..1e028b9 100644
--- a/src/matchers/mod.rs
+++ b/src/matchers/mod.rs
@@ -14,27 +14,28 @@
//! All built-in matchers of this crate are in submodules of this module.
-pub mod all_matcher;
-pub mod any_matcher;
+mod all_matcher;
+mod any_matcher;
mod anything_matcher;
mod char_count_matcher;
-pub mod conjunction_matcher;
+mod conjunction_matcher;
mod container_eq_matcher;
mod contains_matcher;
mod contains_regex_matcher;
-pub mod disjunction_matcher;
+mod disjunction_matcher;
mod display_matcher;
mod each_matcher;
-pub mod elements_are_matcher;
+mod elements_are_matcher;
mod empty_matcher;
mod eq_deref_of_matcher;
mod eq_matcher;
mod err_matcher;
-pub mod field_matcher;
+mod field_matcher;
mod ge_matcher;
mod gt_matcher;
mod has_entry_matcher;
-pub mod is_matcher;
+mod is_encoded_string_matcher;
+mod is_matcher;
mod is_nan_matcher;
mod le_matcher;
mod len_matcher;
@@ -46,15 +47,15 @@
mod not_matcher;
mod ok_matcher;
mod points_to_matcher;
-pub mod pointwise_matcher;
+mod pointwise_matcher;
mod predicate_matcher;
-pub mod property_matcher;
+mod property_matcher;
mod some_matcher;
mod str_matcher;
mod subset_of_matcher;
mod superset_of_matcher;
mod tuple_matcher;
-pub mod unordered_elements_are_matcher;
+mod unordered_elements_are_matcher;
pub use anything_matcher::anything;
pub use char_count_matcher::char_count;
@@ -70,6 +71,7 @@
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_nan_matcher::is_nan;
pub use le_matcher::le;
pub use len_matcher::len;
@@ -87,3 +89,32 @@
};
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,
+ __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::all_matcher::internal::AllMatcher;
+ pub use super::any_matcher::internal::AnyMatcher;
+ 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::pointwise_matcher::internal::PointwiseMatcher;
+ pub use super::property_matcher::internal::{property_matcher, property_ref_matcher};
+ pub use super::unordered_elements_are_matcher::internal::{
+ Requirements, UnorderedElementsAreMatcher, UnorderedElementsOfMapAreMatcher,
+ };
+}
diff --git a/src/matchers/near_matcher.rs b/src/matchers/near_matcher.rs
index 484939c..ca7cbdf 100644
--- a/src/matchers/near_matcher.rs
+++ b/src/matchers/near_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use num_traits::{Float, FloatConst};
use std::fmt::Debug;
@@ -179,13 +182,13 @@
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
MatcherResult::Match => {
- format!("is within {:?} of {:?}", self.max_abs_error, self.expected)
+ format!("is within {:?} of {:?}", self.max_abs_error, self.expected).into()
}
MatcherResult::NoMatch => {
- format!("isn't within {:?} of {:?}", self.max_abs_error, self.expected)
+ format!("isn't within {:?} of {:?}", self.max_abs_error, self.expected).into()
}
}
}
diff --git a/src/matchers/none_matcher.rs b/src/matchers/none_matcher.rs
index e48d549..af28932 100644
--- a/src/matchers/none_matcher.rs
+++ b/src/matchers/none_matcher.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use std::fmt::Debug;
use std::marker::PhantomData;
@@ -46,10 +47,10 @@
(actual.is_none()).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => "is none".to_string(),
- MatcherResult::NoMatch => "is some(_)".to_string(),
+ MatcherResult::Match => "is none".into(),
+ MatcherResult::NoMatch => "is some(_)".into(),
}
}
}
diff --git a/src/matchers/not_matcher.rs b/src/matchers/not_matcher.rs
index 1dff791..f03d4ce 100644
--- a/src/matchers/not_matcher.rs
+++ b/src/matchers/not_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches the actual value exactly when the inner matcher does _not_ match.
@@ -51,11 +54,11 @@
}
}
- fn explain_match(&self, actual: &T) -> String {
+ fn explain_match(&self, actual: &T) -> Description {
self.inner.explain_match(actual)
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
self.inner.describe(if matcher_result.into() {
MatcherResult::NoMatch
} else {
diff --git a/src/matchers/ok_matcher.rs b/src/matchers/ok_matcher.rs
index 5c6fa51..8425b93 100644
--- a/src/matchers/ok_matcher.rs
+++ b/src/matchers/ok_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches a `Result` containing `Ok` with a value matched by `inner`.
@@ -56,27 +59,27 @@
actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
}
- fn explain_match(&self, actual: &Self::ActualT) -> String {
+ fn explain_match(&self, actual: &Self::ActualT) -> Description {
match actual {
- Ok(o) => format!("which is a success {}", self.inner.explain_match(o)),
- Err(_) => "which is an error".to_string(),
+ 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) -> String {
+ 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)
- )
- }
- MatcherResult::NoMatch => {
- format!(
- "is an error or a success containing a value, which {}",
- self.inner.describe(MatcherResult::NoMatch)
- )
- }
+ 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(),
}
}
}
@@ -129,7 +132,8 @@
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
+ which is a success
+ which isn't equal to 2
"
))))
)
@@ -144,7 +148,7 @@
};
verify_that!(
matcher.describe(MatcherResult::Match),
- eq("is a success containing a value, which is equal to 1")
+ displays_as(eq("is a success containing a value, which is equal to 1"))
)
}
}
diff --git a/src/matchers/points_to_matcher.rs b/src/matchers/points_to_matcher.rs
index 08c7343..2c516d0 100644
--- a/src/matchers/points_to_matcher.rs
+++ b/src/matchers/points_to_matcher.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use std::fmt::Debug;
use std::marker::PhantomData;
@@ -59,11 +60,11 @@
self.expected.matches(actual.deref())
}
- fn explain_match(&self, actual: &ActualT) -> String {
+ fn explain_match(&self, actual: &ActualT) -> Description {
self.expected.explain_match(actual.deref())
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
self.expected.describe(matcher_result)
}
}
diff --git a/src/matchers/pointwise_matcher.rs b/src/matchers/pointwise_matcher.rs
index 5ee0f22..01e70c0 100644
--- a/src/matchers/pointwise_matcher.rs
+++ b/src/matchers/pointwise_matcher.rs
@@ -13,7 +13,7 @@
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
-// macro is documented at the top level.
+// macro is documented in the matcher module.
#![doc(hidden)]
/// Generates a matcher which matches a container each of whose elements match
@@ -79,8 +79,9 @@
/// 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 implementing [`IntoIterator`]. This
-/// includes standard containers, slices (when dereferenced) and arrays.
+/// The actual value must be a container such as a `Vec`, an array, or a
+/// dereferenced slice. More precisely, a shared borrow of the actual value must
+/// implement [`IntoIterator`].
///
/// ```
/// # use googletest::prelude::*;
@@ -115,14 +116,15 @@
/// [`Pointwise`]: https://google.github.io/googletest/reference/matchers.html#container-matchers
/// [`Vec`]: std::vec::Vec
#[macro_export]
-macro_rules! pointwise {
+#[doc(hidden)]
+macro_rules! __pointwise {
($matcher:expr, $container:expr) => {{
- use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::PointwiseMatcher;
PointwiseMatcher::new($container.into_iter().map($matcher).collect())
}};
($matcher:expr, $left_container:expr, $right_container:expr) => {{
- use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::PointwiseMatcher;
PointwiseMatcher::new(
$left_container
.into_iter()
@@ -133,7 +135,7 @@
}};
($matcher:expr, $left_container:expr, $middle_container:expr, $right_container:expr) => {{
- use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::PointwiseMatcher;
PointwiseMatcher::new(
$left_container
.into_iter()
@@ -149,8 +151,8 @@
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub mod internal {
+ use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
- use crate::matcher_support::description::Description;
use crate::matcher_support::zipped_iterator::zip;
use std::{fmt::Debug, marker::PhantomData};
@@ -190,7 +192,7 @@
}
}
- fn explain_match(&self, actual: &ContainerT) -> String {
+ 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)
@@ -204,23 +206,24 @@
}
if mismatches.is_empty() {
if !zipped_iterator.has_size_mismatch() {
- "which matches all elements".to_string()
+ "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])
+ format!("where {}", mismatches[0]).into()
} else {
let mismatches = mismatches.into_iter().collect::<Description>();
- format!("where:\n{}", mismatches.bullet_list().indent())
+ format!("where:\n{}", mismatches.bullet_list().indent()).into()
}
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
format!(
"{} elements satisfying respectively:\n{}",
if matcher_result.into() { "has" } else { "doesn't have" },
@@ -231,6 +234,7 @@
.enumerate()
.indent()
)
+ .into()
}
}
}
diff --git a/src/matchers/predicate_matcher.rs b/src/matchers/predicate_matcher.rs
index fabd6c3..5bc067d 100644
--- a/src/matchers/predicate_matcher.rs
+++ b/src/matchers/predicate_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Creates a matcher based on the predicate provided.
@@ -92,18 +95,18 @@
///
/// See [`PredicateMatcher::with_description`]
pub trait PredicateDescription {
- fn to_description(&self) -> String;
+ fn to_description(&self) -> Description;
}
impl PredicateDescription for &str {
- fn to_description(&self) -> String {
- self.to_string()
+ fn to_description(&self) -> Description {
+ self.to_string().into()
}
}
impl PredicateDescription for String {
- fn to_description(&self) -> String {
- self.clone()
+ fn to_description(&self) -> Description {
+ self.to_string().into()
}
}
@@ -112,8 +115,8 @@
T: Fn() -> S,
S: Into<String>,
{
- fn to_description(&self) -> String {
- self().into()
+ fn to_description(&self) -> Description {
+ self().into().into()
}
}
@@ -131,10 +134,10 @@
(self.predicate)(actual).into()
}
- fn describe(&self, result: MatcherResult) -> String {
+ fn describe(&self, result: MatcherResult) -> Description {
match result {
- MatcherResult::Match => "matches".to_string(),
- MatcherResult::NoMatch => "does not match".to_string(),
+ MatcherResult::Match => "matches".into(),
+ MatcherResult::NoMatch => "does not match".into(),
}
}
}
@@ -150,7 +153,7 @@
(self.predicate)(actual).into()
}
- fn describe(&self, result: MatcherResult) -> String {
+ fn describe(&self, result: MatcherResult) -> Description {
match result {
MatcherResult::Match => self.positive_description.to_description(),
MatcherResult::NoMatch => self.negative_description.to_description(),
diff --git a/src/matchers/property_matcher.rs b/src/matchers/property_matcher.rs
index d69ba1d..19b4862 100644
--- a/src/matchers/property_matcher.rs
+++ b/src/matchers/property_matcher.rs
@@ -13,7 +13,7 @@
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
-// macro is documented at the top level.
+// macro is documented in the matcher module.
#![doc(hidden)]
/// Matches an object which, upon calling the given method on it with the given
@@ -44,8 +44,7 @@
/// failure, it will be invoked a second time, with the assertion failure output
/// reflecting the *second* invocation.
///
-/// If the method returns a *reference*, then it must be preceded by the keyword
-/// `ref`:
+/// If the method returns a *reference*, then it must be preceded by a `*`:
///
/// ```
/// # use googletest::prelude::*;
@@ -58,7 +57,7 @@
/// }
///
/// # let value = vec![MyStruct { a_field: 100 }];
-/// verify_that!(value, contains(property!(ref MyStruct.get_a_field(), eq(100))))
+/// verify_that!(value, contains(property!(*MyStruct.get_a_field(), eq(100))))
/// # .unwrap();
/// ```
///
@@ -93,17 +92,18 @@
/// }
///
/// let value = MyStruct { a_string: "A string".into() };
-/// verify_that!(value, property!(ref MyStruct.get_a_string(), eq("A string"))) // Does not compile
+/// verify_that!(value, property!(*MyStruct.get_a_string(), eq("A string"))) // Does not compile
/// # .unwrap();
/// ```
///
-/// This macro is analogous to [`field`][crate::field], except that it extracts
-/// the datum to be matched from the given object by invoking a method rather
-/// than accessing a field.
+/// 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]
-macro_rules! property {
+#[doc(hidden)]
+macro_rules! __property {
($($t:tt)*) => { $crate::property_internal!($($t)*) }
}
@@ -113,15 +113,15 @@
#[macro_export]
macro_rules! property_internal {
($($t:ident)::+.$method:tt($($argument:tt),* $(,)?), $m:expr) => {{
- use $crate::matchers::property_matcher::internal::property_matcher;
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::property_matcher;
property_matcher(
|o: &$($t)::+| o.$method($($argument),*),
&stringify!($method($($argument),*)),
$m)
}};
- (ref $($t:ident)::+.$method:tt($($argument:tt),* $(,)?), $m:expr) => {{
- use $crate::matchers::property_matcher::internal::property_ref_matcher;
+ (* $($t:ident)::+.$method:tt($($argument:tt),* $(,)?), $m:expr) => {{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::property_ref_matcher;
property_ref_matcher(
|o: &$($t)::+| o.$method($($argument),*),
&stringify!($method($($argument),*)),
@@ -134,7 +134,10 @@
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub mod internal {
- use crate::matcher::{Matcher, MatcherResult};
+ use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+ };
use std::{fmt::Debug, marker::PhantomData};
/// **For internal use only. API stablility is not guaranteed!**
@@ -167,15 +170,16 @@
self.inner.matches(&(self.extractor)(actual))
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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) -> String {
+ fn explain_match(&self, actual: &OuterT) -> Description {
let actual_inner = (self.extractor)(actual);
format!(
"whose property `{}` is `{:#?}`, {}",
@@ -183,6 +187,7 @@
actual_inner,
self.inner.explain_match(&actual_inner)
)
+ .into()
}
}
@@ -216,15 +221,16 @@
self.inner.matches((self.extractor)(actual))
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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) -> String {
+ fn explain_match(&self, actual: &OuterT) -> Description {
let actual_inner = (self.extractor)(actual);
format!(
"whose property `{}` is `{:#?}`, {}",
@@ -232,6 +238,7 @@
actual_inner,
self.inner.explain_match(actual_inner)
)
+ .into()
}
}
}
diff --git a/src/matchers/some_matcher.rs b/src/matchers/some_matcher.rs
index a6ce021..905aa17 100644
--- a/src/matchers/some_matcher.rs
+++ b/src/matchers/some_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches an `Option` containing a value matched by `inner`.
@@ -51,24 +54,25 @@
actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
}
- fn explain_match(&self, actual: &Option<T>) -> String {
+ fn explain_match(&self, actual: &Option<T>) -> Description {
match (self.matches(actual), actual) {
- (_, Some(t)) => format!("which has a value {}", self.inner.explain_match(t)),
- (_, None) => "which is None".to_string(),
+ (_, 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) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
MatcherResult::Match => {
- format!("has a value which {}", self.inner.describe(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)
- )
- }
+ MatcherResult::NoMatch => format!(
+ "is None or has a value which {}",
+ self.inner.describe(MatcherResult::NoMatch)
+ )
+ .into(),
}
}
}
@@ -100,7 +104,7 @@
#[test]
fn some_does_not_match_option_with_none() -> Result<()> {
- let matcher = some(eq(1));
+ let matcher = some(eq::<i32, _>(1));
let result = matcher.matches(&None);
@@ -117,7 +121,8 @@
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
+ which has a value
+ which isn't equal to 1
"
))))
)
@@ -126,29 +131,29 @@
#[test]
fn some_describe_matches() -> Result<()> {
verify_that!(
- some(eq(1)).describe(MatcherResult::Match),
- eq("has a value which is equal to 1")
+ some(eq::<i32, _>(1)).describe(MatcherResult::Match),
+ displays_as(eq("has a value which is equal to 1"))
)
}
#[test]
fn some_describe_does_not_match() -> Result<()> {
verify_that!(
- some(eq(1)).describe(MatcherResult::NoMatch),
- eq("is None or has a value which isn't equal to 1")
+ some(eq::<i32, _>(1)).describe(MatcherResult::NoMatch),
+ displays_as(eq("is None or has a value which isn't equal to 1"))
)
}
#[test]
fn some_explain_match_with_none() -> Result<()> {
- verify_that!(some(eq(1)).explain_match(&None), displays_as(eq("which is None")))
+ verify_that!(some(eq::<i32, _>(1)).explain_match(&None), 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 which is equal to 1"))
+ displays_as(eq("which has a value\n which is equal to 1"))
)
}
@@ -156,7 +161,7 @@
fn some_explain_match_with_some_fail() -> Result<()> {
verify_that!(
some(eq(1)).explain_match(&Some(2)),
- displays_as(eq("which has a value which isn't equal to 1"))
+ displays_as(eq("which has a value\n which isn't equal to 1"))
)
}
}
diff --git a/src/matchers/str_matcher.rs b/src/matchers/str_matcher.rs
index 3a4e2e9..b624d44 100644
--- a/src/matchers/str_matcher.rs
+++ b/src/matchers/str_matcher.rs
@@ -13,6 +13,7 @@
// limitations under the License.
use crate::{
+ description::Description,
matcher::{Matcher, MatcherResult},
matcher_support::{
edit_distance,
@@ -303,11 +304,11 @@
self.configuration.do_strings_match(self.expected.deref(), actual.as_ref()).into()
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
self.configuration.describe(matcher_result, self.expected.deref())
}
- fn explain_match(&self, actual: &ActualT) -> String {
+ fn explain_match(&self, actual: &ActualT) -> Description {
self.configuration.explain_match(self.expected.deref(), actual.as_ref())
}
}
@@ -467,7 +468,7 @@
}
// StrMatcher::describe redirects immediately to this function.
- fn describe(&self, matcher_result: MatcherResult, expected: &str) -> String {
+ fn describe(&self, matcher_result: MatcherResult, expected: &str) -> Description {
let mut addenda: Vec<Cow<'static, str>> = Vec::with_capacity(3);
match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
(true, true) => addenda.push("ignoring leading and trailing whitespace".into()),
@@ -502,14 +503,15 @@
MatcherResult::NoMatch => "does not end with",
},
};
- format!("{match_mode_description} {expected:?}{extra}")
+ format!("{match_mode_description} {expected:?}{extra}").into()
}
- fn explain_match(&self, expected: &str, actual: &str) -> String {
+ fn explain_match(&self, expected: &str, actual: &str) -> Description {
let default_explanation = format!(
"which {}",
self.describe(self.do_strings_match(expected, actual).into(), expected)
- );
+ )
+ .into();
if !expected.contains('\n') || !actual.contains('\n') {
return default_explanation;
}
@@ -549,7 +551,7 @@
MatchMode::EndsWith => create_diff_reversed(actual, expected, self.mode.to_diff_mode()),
};
- format!("{default_explanation}\n{diff}",)
+ format!("{default_explanation}\n{diff}").into()
}
fn ignoring_leading_whitespace(self) -> Self {
@@ -811,7 +813,7 @@
let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string");
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("is equal to \"A string\"")
+ displays_as(eq("is equal to \"A string\""))
)
}
@@ -820,7 +822,7 @@
let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string");
verify_that!(
Matcher::describe(&matcher, MatcherResult::NoMatch),
- eq("isn't equal to \"A string\"")
+ displays_as(eq("isn't equal to \"A string\""))
)
}
@@ -830,7 +832,7 @@
StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("is equal to \"A string\" (ignoring leading whitespace)")
+ displays_as(eq("is equal to \"A string\" (ignoring leading whitespace)"))
)
}
@@ -840,7 +842,7 @@
StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
verify_that!(
Matcher::describe(&matcher, MatcherResult::NoMatch),
- eq("isn't equal to \"A string\" (ignoring leading whitespace)")
+ displays_as(eq("isn't equal to \"A string\" (ignoring leading whitespace)"))
)
}
@@ -850,7 +852,7 @@
StrMatcher::with_default_config("A string").ignoring_trailing_whitespace();
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("is equal to \"A string\" (ignoring trailing whitespace)")
+ displays_as(eq("is equal to \"A string\" (ignoring trailing whitespace)"))
)
}
@@ -861,7 +863,7 @@
StrMatcher::with_default_config("A string").ignoring_outer_whitespace();
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("is equal to \"A string\" (ignoring leading and trailing whitespace)")
+ displays_as(eq("is equal to \"A string\" (ignoring leading and trailing whitespace)"))
)
}
@@ -871,7 +873,7 @@
StrMatcher::with_default_config("A string").ignoring_ascii_case();
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("is equal to \"A string\" (ignoring ASCII case)")
+ displays_as(eq("is equal to \"A string\" (ignoring ASCII case)"))
)
}
@@ -883,7 +885,9 @@
.ignoring_ascii_case();
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("is equal to \"A string\" (ignoring leading whitespace, ignoring ASCII case)")
+ displays_as(eq(
+ "is equal to \"A string\" (ignoring leading whitespace, ignoring ASCII case)"
+ ))
)
}
@@ -892,7 +896,7 @@
let matcher: StrMatcher<&str, _> = contains_substring("A string");
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("contains a substring \"A string\"")
+ displays_as(eq("contains a substring \"A string\""))
)
}
@@ -901,7 +905,7 @@
let matcher: StrMatcher<&str, _> = contains_substring("A string");
verify_that!(
Matcher::describe(&matcher, MatcherResult::NoMatch),
- eq("does not contain a substring \"A string\"")
+ displays_as(eq("does not contain a substring \"A string\""))
)
}
@@ -910,7 +914,7 @@
let matcher: StrMatcher<&str, _> = contains_substring("A string").times(gt(2));
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("contains a substring \"A string\" (count is greater than 2)")
+ displays_as(eq("contains a substring \"A string\" (count is greater than 2)"))
)
}
@@ -919,7 +923,7 @@
let matcher: StrMatcher<&str, _> = starts_with("A string");
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("starts with prefix \"A string\"")
+ displays_as(eq("starts with prefix \"A string\""))
)
}
@@ -928,7 +932,7 @@
let matcher: StrMatcher<&str, _> = starts_with("A string");
verify_that!(
Matcher::describe(&matcher, MatcherResult::NoMatch),
- eq("does not start with \"A string\"")
+ displays_as(eq("does not start with \"A string\""))
)
}
@@ -937,7 +941,7 @@
let matcher: StrMatcher<&str, _> = ends_with("A string");
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq("ends with suffix \"A string\"")
+ displays_as(eq("ends with suffix \"A string\""))
)
}
@@ -946,7 +950,7 @@
let matcher: StrMatcher<&str, _> = ends_with("A string");
verify_that!(
Matcher::describe(&matcher, MatcherResult::NoMatch),
- eq("does not end with \"A string\"")
+ displays_as(eq("does not end with \"A string\""))
)
}
@@ -971,14 +975,13 @@
verify_that!(
result,
- err(displays_as(contains_substring(indoc!(
- "
- First line
- -Second line
- +Second lines
- Third line
- "
- ))))
+ err(displays_as(contains_substring(
+ "\
+ First line
+ -Second line
+ +Second lines
+ Third line"
+ )))
)
}
@@ -1004,15 +1007,14 @@
verify_that!(
result,
- err(displays_as(contains_substring(indoc!(
+ err(displays_as(contains_substring(
"
- First line
- -Second line
- +Second lines
- Third line
- <---- remaining lines omitted ---->
- "
- ))))
+ First line
+ -Second line
+ +Second lines
+ Third line
+ <---- remaining lines omitted ---->"
+ )))
)
}
@@ -1037,14 +1039,13 @@
verify_that!(
result,
- err(displays_as(contains_substring(indoc!(
- "
- First line
- -Second line
- +Second lines
- <---- remaining lines omitted ---->
- "
- ))))
+ err(displays_as(contains_substring(
+ "\
+ First line
+ -Second line
+ +Second lines
+ <---- remaining lines omitted ---->"
+ )))
)
}
@@ -1070,16 +1071,15 @@
verify_that!(
result,
- err(displays_as(contains_substring(indoc!(
+ err(displays_as(contains_substring(
"
- Difference(-actual / +expected):
- <---- remaining lines omitted ---->
- Second line
- +Third lines
- -Third line
- Fourth line
- "
- ))))
+ Difference(-actual / +expected):
+ <---- remaining lines omitted ---->
+ Second line
+ -Third line
+ +Third lines
+ Fourth line"
+ )))
)
}
@@ -1107,16 +1107,16 @@
verify_that!(
result,
- err(displays_as(contains_substring(indoc!(
+ err(displays_as(contains_substring(
"
- Difference(-actual / +expected):
- <---- remaining lines omitted ---->
- Second line
- +Third lines
- -Third line
- Fourth line
- <---- remaining lines omitted ---->"
- ))))
+ Difference(-actual / +expected):
+ <---- remaining lines omitted ---->
+ Second line
+ -Third line
+ +Third lines
+ Fourth line
+ <---- remaining lines omitted ---->"
+ )))
)
}
@@ -1144,20 +1144,19 @@
verify_that!(
result,
- err(displays_as(contains_substring(indoc!(
+ err(displays_as(contains_substring(
"
- Difference(-actual / +expected):
- <---- remaining lines omitted ---->
- +line
- -Second line
- Third line
- +Foorth line
- -Fourth line
- +Fifth
- -Fifth line
- <---- remaining lines omitted ---->
- "
- ))))
+ Difference(-actual / +expected):
+ <---- remaining lines omitted ---->
+ -Second line
+ +line
+ Third line
+ -Fourth line
+ +Foorth line
+ -Fifth line
+ +Fifth
+ <---- remaining lines omitted ---->"
+ )))
)
}
@@ -1183,15 +1182,14 @@
verify_that!(
result,
- err(displays_as(contains_substring(indoc!(
- "
- First line
- -Second line
- +Second lines
- Third line
- -Fourth line
- "
- ))))
+ err(displays_as(contains_substring(
+ "\
+ First line
+ -Second line
+ +Second lines
+ Third line
+ -Fourth line"
+ )))
)
}
diff --git a/src/matchers/subset_of_matcher.rs b/src/matchers/subset_of_matcher.rs
index facee4f..24c00d8 100644
--- a/src/matchers/subset_of_matcher.rs
+++ b/src/matchers/subset_of_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches a container all of whose items are in the given container
@@ -22,7 +25,8 @@
/// comparison.
///
/// `ActualT` and `ExpectedT` can each be any container a reference to which
-/// implements `IntoIterator`. They need not be the same container type.
+/// 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::*;
@@ -30,6 +34,8 @@
/// # 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<()> {
@@ -109,7 +115,7 @@
MatcherResult::Match
}
- fn explain_match(&self, actual: &ActualT) -> String {
+ fn explain_match(&self, actual: &ActualT) -> Description {
let unexpected_elements = actual
.into_iter()
.enumerate()
@@ -118,16 +124,16 @@
.collect::<Vec<_>>();
match unexpected_elements.len() {
- 0 => "which no element is unexpected".to_string(),
- 1 => format!("whose element {} is unexpected", &unexpected_elements[0]),
- _ => format!("whose elements {} are unexpected", unexpected_elements.join(", ")),
+ 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) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => format!("is a subset of {:#?}", self.superset),
- MatcherResult::NoMatch => format!("isn't a subset of {:#?}", self.superset),
+ MatcherResult::Match => format!("is a subset of {:#?}", self.superset).into(),
+ MatcherResult::NoMatch => format!("isn't a subset of {:#?}", self.superset).into(),
}
}
}
diff --git a/src/matchers/superset_of_matcher.rs b/src/matchers/superset_of_matcher.rs
index 8e98015..d1e9d72 100644
--- a/src/matchers/superset_of_matcher.rs
+++ b/src/matchers/superset_of_matcher.rs
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::matcher::{Matcher, MatcherResult};
+use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+};
use std::{fmt::Debug, marker::PhantomData};
/// Matches a container containing all of the items in the given container
@@ -22,7 +25,9 @@
/// comparison.
///
/// `ActualT` and `ExpectedT` can each be any container a reference to which
-/// implements `IntoIterator`. They need not be the same container type.
+/// implements `IntoIterator`. For instance, `ActualT` and `ExpectedT` can be a
+/// common container like `Vec` or arrays. They need not be the same container
+/// type.
///
/// ```
/// # use googletest::prelude::*;
@@ -30,6 +35,8 @@
/// # 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<()> {
@@ -109,7 +116,7 @@
MatcherResult::Match
}
- fn explain_match(&self, actual: &ActualT) -> String {
+ fn explain_match(&self, actual: &ActualT) -> Description {
let missing_items: Vec<_> = self
.subset
.into_iter()
@@ -117,16 +124,16 @@
.map(|expected_item| format!("{expected_item:#?}"))
.collect();
match missing_items.len() {
- 0 => "whose no element is missing".to_string(),
- 1 => format!("whose element {} is missing", &missing_items[0]),
- _ => format!("whose elements {} are missing", missing_items.join(", ")),
+ 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) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
- MatcherResult::Match => format!("is a superset of {:#?}", self.subset),
- MatcherResult::NoMatch => format!("isn't a superset of {:#?}", self.subset),
+ MatcherResult::Match => format!("is a superset of {:#?}", self.subset).into(),
+ MatcherResult::NoMatch => format!("isn't a superset of {:#?}", self.subset).into(),
}
}
}
diff --git a/src/matchers/tuple_matcher.rs b/src/matchers/tuple_matcher.rs
index a2e325b..af55cbf 100644
--- a/src/matchers/tuple_matcher.rs
+++ b/src/matchers/tuple_matcher.rs
@@ -21,22 +21,11 @@
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub mod internal {
- use crate::matcher::{Matcher, MatcherResult};
- use std::fmt::{Debug, Write};
-
- /// Replaces the first expression with the second at compile time.
- ///
- /// This is used below in repetition sequences where the output must only
- /// include the same expression repeated the same number of times as the
- /// macro input.
- ///
- /// **For internal use only. API stablility is not guaranteed!**
- #[doc(hidden)]
- macro_rules! replace_expr {
- ($_ignored:tt, $replacement:expr) => {
- $replacement
- };
- }
+ use crate::{
+ description::Description,
+ matcher::{Matcher, MatcherResult},
+ };
+ use std::fmt::Debug;
// This implementation is provided for completeness, but is completely trivial.
// The only actual value which can be supplied is (), which must match.
@@ -47,7 +36,7 @@
MatcherResult::Match
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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(),
@@ -76,41 +65,30 @@
MatcherResult::Match
}
- fn explain_match(&self, actual: &($($field_type,)*)) -> String {
- let mut explanation = format!("which {}", self.describe(self.matches(actual)));
+ 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 => {
- writeln!(
- &mut explanation,
- concat!("Element #", $field_number, " is {:?}, {}"),
- actual.$field_number,
- self.$field_number.explain_match(&actual.$field_number)
- ).unwrap();
+ explanation = explanation
+ .text(format!(concat!("Element #", $field_number, " is {:?},"), actual.$field_number))
+ .nested(self.$field_number.explain_match(&actual.$field_number));
}
})*
- (explanation)
+ explanation
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
match matcher_result {
MatcherResult::Match => {
- format!(
- concat!(
- "is a tuple whose values respectively match:\n",
- $(replace_expr!($field_number, " {},\n")),*
- ),
- $(self.$field_number.describe(matcher_result)),*
- )
+ 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 => {
- format!(
- concat!(
- "is a tuple whose values do not respectively match:\n",
- $(replace_expr!($field_number, " {},\n")),*
- ),
- $(self.$field_number.describe(MatcherResult::Match)),*
- )
+ 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
}
}
}
diff --git a/src/matchers/unordered_elements_are_matcher.rs b/src/matchers/unordered_elements_are_matcher.rs
index 1930697..f4585a4 100644
--- a/src/matchers/unordered_elements_are_matcher.rs
+++ b/src/matchers/unordered_elements_are_matcher.rs
@@ -13,7 +13,7 @@
// limitations under the License.
// There are no visible documentation elements in this module; the declarative
-// macro is documented at the top level.
+// macro is documented in the matchers module.
#![doc(hidden)]
/// Matches a container whose elements in any order have a 1:1 correspondence
@@ -43,8 +43,9 @@
/// # should_fail_3().unwrap_err();
/// ```
///
-/// The actual value must be a container implementing [`IntoIterator`]. This
-/// includes standard containers, slices (when dereferenced) and arrays.
+/// The actual value must be a container such as a `Vec`, an array, or a
+/// dereferenced slice. More precisely, a shared borrow of the actual value must
+/// implement [`IntoIterator`].
///
/// This can also match against [`HashMap`][std::collections::HashMap] and
/// similar collections. The arguments are a sequence of pairs of matchers
@@ -73,7 +74,7 @@
///
/// 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::unordered_elements_are] macro.
+/// [`unordered_elements_are!`][crate::matchers::unordered_elements_are] macro.
///
/// ```compile_fail
/// # use googletest::prelude::*;
@@ -115,9 +116,10 @@
/// [`Iterator::collect`]: std::iter::Iterator::collect
/// [`Vec`]: std::vec::Vec
#[macro_export]
-macro_rules! unordered_elements_are {
+#[doc(hidden)]
+macro_rules! __unordered_elements_are {
($(,)?) => {{
- use $crate::matchers::unordered_elements_are_matcher::internal::{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::{
UnorderedElementsAreMatcher, Requirements
};
UnorderedElementsAreMatcher::new([], Requirements::PerfectMatch)
@@ -126,7 +128,7 @@
// TODO: Consider an alternative map-like syntax here similar to that used in
// https://crates.io/crates/maplit.
($(($key_matcher:expr, $value_matcher:expr)),* $(,)?) => {{
- use $crate::matchers::unordered_elements_are_matcher::internal::{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::{
UnorderedElementsOfMapAreMatcher, Requirements
};
UnorderedElementsOfMapAreMatcher::new(
@@ -136,7 +138,7 @@
}};
($($matcher:expr),* $(,)?) => {{
- use $crate::matchers::unordered_elements_are_matcher::internal::{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::{
UnorderedElementsAreMatcher, Requirements
};
UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::PerfectMatch)
@@ -151,7 +153,9 @@
/// 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`] would match.
+/// container which
+/// [`unordered_elements_are`][crate::matchers::unordered_elements_are] would
+/// match.
///
/// ```
/// # use googletest::prelude::*;
@@ -178,8 +182,9 @@
/// # should_fail_3().unwrap_err();
/// ```
///
-/// The actual value must be a container implementing [`IntoIterator`]. This
-/// includes standard containers, slices (when dereferenced) and arrays.
+/// The actual value must be a container such as a `Vec`, an array, or a
+/// dereferenced slice. More precisely, a shared borrow of the actual value must
+/// implement [`IntoIterator`].
///
/// This can also match against [`HashMap`][std::collections::HashMap] and
/// similar collections. The arguments are a sequence of pairs of matchers
@@ -217,9 +222,10 @@
/// [`Iterator::collect`]: std::iter::Iterator::collect
/// [`Vec`]: std::vec::Vec
#[macro_export]
-macro_rules! contains_each {
+#[doc(hidden)]
+macro_rules! __contains_each {
($(,)?) => {{
- use $crate::matchers::unordered_elements_are_matcher::internal::{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::{
UnorderedElementsAreMatcher, Requirements
};
UnorderedElementsAreMatcher::new([], Requirements::Superset)
@@ -228,7 +234,7 @@
// TODO: Consider an alternative map-like syntax here similar to that used in
// https://crates.io/crates/maplit.
($(($key_matcher:expr, $value_matcher:expr)),* $(,)?) => {{
- use $crate::matchers::unordered_elements_are_matcher::internal::{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::{
UnorderedElementsOfMapAreMatcher, Requirements
};
UnorderedElementsOfMapAreMatcher::new(
@@ -238,7 +244,7 @@
}};
($($matcher:expr),* $(,)?) => {{
- use $crate::matchers::unordered_elements_are_matcher::internal::{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::{
UnorderedElementsAreMatcher, Requirements
};
UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::Superset)
@@ -255,7 +261,8 @@
/// container.
///
/// Put another way, `is_contained_in!` matches if there is a subset of the
-/// matchers which would match with [`unordered_elements_are`].
+/// matchers which would match with
+/// [`unordered_elements_are`][crate::matchers::unordered_elements_are].
///
/// ```
/// # use googletest::prelude::*;
@@ -282,8 +289,9 @@
/// # should_fail_3().unwrap_err();
/// ```
///
-/// The actual value must be a container implementing [`IntoIterator`]. This
-/// includes standard containers, slices (when dereferenced) and arrays.
+/// The actual value must be a container such as a `Vec`, an array, or a
+/// dereferenced slice. More precisely, a shared borrow of the actual value must
+/// implement [`IntoIterator`].
///
/// This can also match against [`HashMap`][std::collections::HashMap] and
/// similar collections. The arguments are a sequence of pairs of matchers
@@ -323,9 +331,10 @@
/// [`Iterator::collect`]: std::iter::Iterator::collect
/// [`Vec`]: std::vec::Vec
#[macro_export]
-macro_rules! is_contained_in {
+#[doc(hidden)]
+macro_rules! __is_contained_in {
($(,)?) => {{
- use $crate::matchers::unordered_elements_are_matcher::internal::{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::{
UnorderedElementsAreMatcher, Requirements
};
UnorderedElementsAreMatcher::new([], Requirements::Subset)
@@ -334,7 +343,7 @@
// TODO: Consider an alternative map-like syntax here similar to that used in
// https://crates.io/crates/maplit.
($(($key_matcher:expr, $value_matcher:expr)),* $(,)?) => {{
- use $crate::matchers::unordered_elements_are_matcher::internal::{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::{
UnorderedElementsOfMapAreMatcher, Requirements
};
UnorderedElementsOfMapAreMatcher::new(
@@ -344,7 +353,7 @@
}};
($($matcher:expr),* $(,)?) => {{
- use $crate::matchers::unordered_elements_are_matcher::internal::{
+ use $crate::matchers::__internal_unstable_do_not_depend_on_these::{
UnorderedElementsAreMatcher, Requirements
};
UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::Subset)
@@ -356,9 +365,9 @@
/// **For internal use only. API stablility is not guaranteed!**
#[doc(hidden)]
pub mod internal {
+ use crate::description::Description;
use crate::matcher::{Matcher, MatcherResult};
use crate::matcher_support::count_elements::count_elements;
- use crate::matcher_support::description::Description;
use std::collections::HashSet;
use std::fmt::{Debug, Display};
use std::marker::PhantomData;
@@ -406,7 +415,7 @@
match_matrix.is_match_for(self.requirements).into()
}
- fn explain_match(&self, actual: &ContainerT) -> String {
+ fn explain_match(&self, actual: &ContainerT) -> Description {
if let Some(size_mismatch_explanation) =
self.requirements.explain_size_mismatch(actual, N)
{
@@ -423,10 +432,10 @@
let best_match = match_matrix.find_best_match();
best_match
.get_explanation(actual, &self.elements, self.requirements)
- .unwrap_or("whose elements all match".to_string())
+ .unwrap_or("whose elements all match".into())
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
format!(
"{} elements matching in any order:\n{}",
if matcher_result.into() { "contains" } else { "doesn't contain" },
@@ -437,6 +446,7 @@
.enumerate()
.indent()
)
+ .into()
}
}
@@ -482,7 +492,7 @@
match_matrix.is_match_for(self.requirements).into()
}
- fn explain_match(&self, actual: &ContainerT) -> String {
+ fn explain_match(&self, actual: &ContainerT) -> Description {
if let Some(size_mismatch_explanation) =
self.requirements.explain_size_mismatch(actual, N)
{
@@ -500,10 +510,10 @@
best_match
.get_explanation_for_map(actual, &self.elements, self.requirements)
- .unwrap_or("whose elements all match".to_string())
+ .unwrap_or("whose elements all match".into())
}
- fn describe(&self, matcher_result: MatcherResult) -> String {
+ fn describe(&self, matcher_result: MatcherResult) -> Description {
format!(
"{} elements matching in any order:\n{}",
if matcher_result.into() { "contains" } else { "doesn't contain" },
@@ -517,6 +527,7 @@
.collect::<Description>()
.indent()
)
+ .into()
}
}
@@ -545,25 +556,25 @@
&self,
actual: &ContainerT,
expected_size: usize,
- ) -> Option<String>
+ ) -> Option<Description>
where
for<'b> &'b ContainerT: IntoIterator,
{
let actual_size = count_elements(actual);
match self {
- Requirements::PerfectMatch if actual_size != expected_size => {
- Some(format!("which has size {} (expected {})", actual_size, expected_size))
- }
+ Requirements::PerfectMatch if actual_size != expected_size => Some(
+ format!("which has size {} (expected {})", actual_size, expected_size).into(),
+ ),
- Requirements::Superset if actual_size < expected_size => Some(format!(
- "which has size {} (expected at least {})",
- actual_size, expected_size
- )),
+ Requirements::Superset if actual_size < expected_size => Some(
+ format!("which has size {} (expected at least {})", actual_size, expected_size)
+ .into(),
+ ),
- Requirements::Subset if actual_size > expected_size => Some(format!(
- "which has size {} (expected at most {})",
- actual_size, expected_size
- )),
+ Requirements::Subset if actual_size > expected_size => Some(
+ format!("which has size {} (expected at most {})", actual_size, expected_size)
+ .into(),
+ ),
_ => None,
}
@@ -641,7 +652,7 @@
}
}
- fn explain_unmatchable(&self, requirements: Requirements) -> Option<String> {
+ 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(),
@@ -848,7 +859,7 @@
|| self.unmatchable_expected.iter().any(|b| *b)
}
- fn get_explanation(&self) -> Option<String> {
+ fn get_explanation(&self) -> Option<Description> {
let unmatchable_actual = self.unmatchable_actual();
let actual_idx = unmatchable_actual
.iter()
@@ -864,29 +875,29 @@
match (unmatchable_actual.len(), unmatchable_expected.len()) {
(0, 0) => None,
(1, 0) => {
- Some(format!("whose element {actual_idx} does not match any expected elements"))
+ 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",))
+ 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()),
}
}
@@ -953,7 +964,7 @@
actual: &ContainerT,
expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N],
requirements: Requirements,
- ) -> Option<String>
+ ) -> Option<Description>
where
for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
{
@@ -992,7 +1003,7 @@
.indent();
Some(format!(
"which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
- ))
+ ).into())
}
fn get_explanation_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>(
@@ -1000,7 +1011,7 @@
actual: &ContainerT,
expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N],
requirements: Requirements,
- ) -> Option<String>
+ ) -> Option<Description>
where
for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
{
@@ -1050,7 +1061,7 @@
.indent();
Some(format!(
"which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
- ))
+ ).into())
}
}
}
@@ -1078,13 +1089,13 @@
];
verify_that!(
Matcher::describe(&matcher, MatcherResult::Match),
- eq(indoc!(
+ displays_as(eq(indoc!(
"
contains elements matching in any order:
is equal to 2 => is equal to \"Two\"
is equal to 1 => is equal to \"One\"
is equal to 3 => is equal to \"Three\""
- ))
+ )))
)
}
diff --git a/tests/all_matcher_test.rs b/tests/all_matcher_test.rs
index 6d7e3f8..8b36fc0 100644
--- a/tests/all_matcher_test.rs
+++ b/tests/all_matcher_test.rs
@@ -61,9 +61,7 @@
fn mismatch_description_two_failed_matchers() -> Result<()> {
verify_that!(
all!(starts_with("One"), starts_with("Two")).explain_match("Three"),
- displays_as(eq(
- "* which does not start with \"One\"\n * which does not start with \"Two\""
- ))
+ displays_as(eq("* which does not start with \"One\"\n* which does not start with \"Two\""))
)
}
@@ -91,3 +89,67 @@
))))
)
}
+
+#[test]
+fn formats_error_message_correctly_when_all_is_inside_some() -> Result<()> {
+ let value = Some(4);
+ let result = verify_that!(value, some(all![eq(1), eq(2), eq(3)]));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: value
+ Expected: has a value which has all the following properties:
+ * is equal to 1
+ * is equal to 2
+ * is equal to 3
+ Actual: Some(4),
+ which has a value
+ * which isn't equal to 1
+ * which isn't equal to 2
+ * which isn't equal to 3"
+ ))))
+ )
+}
+
+#[test]
+fn formats_error_message_correctly_when_all_is_inside_ok() -> Result<()> {
+ let value: std::result::Result<i32, std::io::Error> = Ok(4);
+ let result = verify_that!(value, ok(all![eq(1), eq(2), eq(3)]));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: value
+ Expected: is a success containing a value, which has all the following properties:
+ * is equal to 1
+ * is equal to 2
+ * is equal to 3
+ Actual: Ok(4),
+ which is a success
+ * which isn't equal to 1
+ * which isn't equal to 2
+ * which isn't equal to 3"
+ ))))
+ )
+}
+
+#[test]
+fn formats_error_message_correctly_when_all_is_inside_err() -> Result<()> {
+ let value: std::result::Result<(), &'static str> = Err("An error");
+ let result = verify_that!(value, err(all![starts_with("Not"), ends_with("problem")]));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ r#"
+ Value of: value
+ Expected: is an error which has all the following properties:
+ * starts with prefix "Not"
+ * ends with suffix "problem"
+ Actual: Err("An error"),
+ which is an error
+ * which does not start with "Not"
+ * which does not end with "problem""#
+ ))))
+ )
+}
diff --git a/tests/any_matcher_test.rs b/tests/any_matcher_test.rs
index 1bdd794..82ed046 100644
--- a/tests/any_matcher_test.rs
+++ b/tests/any_matcher_test.rs
@@ -61,9 +61,7 @@
fn mismatch_description_two_failed_matchers() -> Result<()> {
verify_that!(
any!(starts_with("One"), starts_with("Two")).explain_match("Three"),
- displays_as(eq(
- "* which does not start with \"One\"\n * which does not start with \"Two\""
- ))
+ displays_as(eq("* which does not start with \"One\"\n* which does not start with \"Two\""))
)
}
@@ -91,3 +89,67 @@
))))
)
}
+
+#[test]
+fn formats_error_message_correctly_when_any_is_inside_some() -> Result<()> {
+ let value = Some(4);
+ let result = verify_that!(value, some(any![eq(1), eq(2), eq(3)]));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: value
+ Expected: has a value which has at least one of the following properties:
+ * is equal to 1
+ * is equal to 2
+ * is equal to 3
+ Actual: Some(4),
+ which has a value
+ * which isn't equal to 1
+ * which isn't equal to 2
+ * which isn't equal to 3"
+ ))))
+ )
+}
+
+#[test]
+fn formats_error_message_correctly_when_any_is_inside_ok() -> Result<()> {
+ let value: std::result::Result<i32, std::io::Error> = Ok(4);
+ let result = verify_that!(value, ok(any![eq(1), eq(2), eq(3)]));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: value
+ Expected: is a success containing a value, which has at least one of the following properties:
+ * is equal to 1
+ * is equal to 2
+ * is equal to 3
+ Actual: Ok(4),
+ which is a success
+ * which isn't equal to 1
+ * which isn't equal to 2
+ * which isn't equal to 3"
+ ))))
+ )
+}
+
+#[test]
+fn formats_error_message_correctly_when_any_is_inside_err() -> Result<()> {
+ let value: std::result::Result<(), &'static str> = Err("An error");
+ let result = verify_that!(value, err(any![starts_with("Not"), ends_with("problem")]));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ r#"
+ Value of: value
+ Expected: is an error which has at least one of the following properties:
+ * starts with prefix "Not"
+ * ends with suffix "problem"
+ Actual: Err("An error"),
+ which is an error
+ * which does not start with "Not"
+ * which does not end with "problem""#
+ ))))
+ )
+}
diff --git a/tests/colorized_diff_test.rs b/tests/colorized_diff_test.rs
index d1b4ecb..d056020 100644
--- a/tests/colorized_diff_test.rs
+++ b/tests/colorized_diff_test.rs
@@ -13,7 +13,6 @@
// limitations under the License.
use googletest::prelude::*;
-use indoc::indoc;
use std::fmt::{Display, Write};
// Make a long text with each element of the iterator on one line.
@@ -37,16 +36,15 @@
verify_that!(
result,
- err(displays_as(contains_substring(indoc! {
+ err(displays_as(contains_substring(
"
-
- 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"
- })))
+ 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"
+ )))
)
}
diff --git a/tests/elements_are_matcher_test.rs b/tests/elements_are_matcher_test.rs
index 99e9fe7..4de2314 100644
--- a/tests/elements_are_matcher_test.rs
+++ b/tests/elements_are_matcher_test.rs
@@ -82,7 +82,6 @@
result,
err(displays_as(contains_substring(indoc!(
"
- Value of: vec![vec! [0, 1], vec! [1, 2]]
Expected: has elements:
0. has elements:
0. is equal to 1
@@ -92,12 +91,12 @@
1. is equal to 3
Actual: [[0, 1], [1, 2]],
where:
- * element #0 is [0, 1], where:
- * element #0 is 0, which isn't equal to 1
- * element #1 is 1, which isn't equal to 2
- * element #1 is [1, 2], where:
- * element #0 is 1, which isn't equal to 2
- * element #1 is 2, which isn't equal to 3"
+ * element #0 is [0, 1], where:
+ * element #0 is 0, which isn't equal to 1
+ * element #1 is 1, which isn't equal to 2
+ * element #1 is [1, 2], where:
+ * element #0 is 1, which isn't equal to 2
+ * element #1 is 2, which isn't equal to 3"
))))
)
}
diff --git a/tests/field_matcher_test.rs b/tests/field_matcher_test.rs
index f5d85c5..f585c21 100644
--- a/tests/field_matcher_test.rs
+++ b/tests/field_matcher_test.rs
@@ -41,7 +41,7 @@
verify_that!(
matcher.describe(MatcherResult::Match),
- eq("has field `int`, which is equal to 31")
+ displays_as(eq("has field `int`, which is equal to 31"))
)
}
diff --git a/tests/matches_pattern_test.rs b/tests/matches_pattern_test.rs
index 4904cf5..3298e36 100644
--- a/tests/matches_pattern_test.rs
+++ b/tests/matches_pattern_test.rs
@@ -202,8 +202,6 @@
verify_that!(
result,
err(displays_as(contains_substring(indoc! {"
- Value of: actual
- Expected: is AnEnum :: B which has field `0`, which is equal to 123
Actual: A(123),
which has the wrong enum variant `A`
"
@@ -399,6 +397,12 @@
verify_that!(actual, matches_pattern!(AnEnum::A))
}
+#[rustversion::before(1.76)]
+const ANENUM_A_REPR: &str = "AnEnum :: A";
+
+#[rustversion::since(1.76)]
+const ANENUM_A_REPR: &str = "AnEnum::A";
+
#[test]
fn generates_correct_failure_output_when_enum_variant_without_field_is_not_matched() -> Result<()> {
#[derive(Debug)]
@@ -411,7 +415,7 @@
let result = verify_that!(actual, matches_pattern!(AnEnum::A));
- verify_that!(result, err(displays_as(contains_substring("is not AnEnum :: A"))))
+ verify_that!(result, err(displays_as(contains_substring(format!("is not {ANENUM_A_REPR}")))))
}
#[test]
@@ -424,7 +428,7 @@
let result = verify_that!(actual, not(matches_pattern!(AnEnum::A)));
- verify_that!(result, err(displays_as(contains_substring("is AnEnum :: A"))))
+ verify_that!(result, err(displays_as(contains_substring(format!("is {ANENUM_A_REPR}")))))
}
#[test]
@@ -463,7 +467,9 @@
verify_that!(
result,
- err(displays_as(contains_substring("Expected: is AnEnum :: A which has field `0`")))
+ err(displays_as(contains_substring(format!(
+ "Expected: is {ANENUM_A_REPR} which has field `0`"
+ ))))
)
}
@@ -479,9 +485,9 @@
verify_that!(
result,
- err(displays_as(contains_substring(
- "Expected: is not AnEnum :: A which has field `0`, which is equal to"
- )))
+ err(displays_as(contains_substring(format!(
+ "Expected: is not {ANENUM_A_REPR} which has field `0`, which is equal to"
+ ))))
)
}
@@ -497,9 +503,9 @@
verify_that!(
result,
- err(displays_as(contains_substring(
- "Expected: is AnEnum :: A which has all the following properties"
- )))
+ err(displays_as(contains_substring(format!(
+ "Expected: is {ANENUM_A_REPR} which has all the following properties"
+ ))))
)
}
@@ -515,9 +521,9 @@
verify_that!(
result,
- err(displays_as(contains_substring(
- "Expected: is AnEnum :: A which has all the following properties"
- )))
+ err(displays_as(contains_substring(format!(
+ "Expected: is {ANENUM_A_REPR} which has all the following properties"
+ ))))
)
}
@@ -533,7 +539,9 @@
verify_that!(
result,
- err(displays_as(contains_substring("Expected: is AnEnum :: A which has field `field`")))
+ err(displays_as(contains_substring(format!(
+ "Expected: is {ANENUM_A_REPR} which has field `field`"
+ ))))
)
}
@@ -552,9 +560,9 @@
verify_that!(
result,
- err(displays_as(contains_substring(
- "Expected: is AnEnum :: A which has all the following properties"
- )))
+ err(displays_as(contains_substring(format!(
+ "Expected: is {ANENUM_A_REPR} which has all the following properties"
+ ))))
)
}
@@ -594,7 +602,7 @@
}
let actual = AStruct { field: 123 };
- let result = verify_that!(actual, matches_pattern!(AStruct { ref get_field(): eq(234) }));
+ let result = verify_that!(actual, matches_pattern!(AStruct { *get_field(): eq(234) }));
verify_that!(
result,
@@ -641,10 +649,8 @@
}
let actual = AStruct { field: 123 };
- let result = verify_that!(
- actual,
- matches_pattern!(AStruct { field: eq(123), ref get_field(): eq(234) })
- );
+ let result =
+ verify_that!(actual, matches_pattern!(AStruct { field: eq(123), *get_field(): eq(234) }));
verify_that!(
result,
@@ -781,7 +787,7 @@
let actual = AStruct { a_field: 123 };
- verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(): eq(123) }))
+ verify_that!(actual, matches_pattern!(AStruct { *get_field_ref(): eq(123) }))
}
#[test]
@@ -799,7 +805,7 @@
let actual = AStruct { a_field: 123 };
- verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(): eq(123), }))
+ verify_that!(actual, matches_pattern!(AStruct { *get_field_ref(): eq(123), }))
}
#[test]
@@ -817,7 +823,7 @@
let actual = AStruct { a_field: 1 };
- verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(2, 3): eq(1) }))
+ verify_that!(actual, matches_pattern!(AStruct { *get_field_ref(2, 3): eq(1) }))
}
#[test]
@@ -839,7 +845,7 @@
let actual = AStruct { a_field: 1 };
- verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(AnEnum::AVariant): eq(1) }))
+ verify_that!(actual, matches_pattern!(AStruct { *get_field_ref(AnEnum::AVariant): eq(1) }))
}
#[test]
@@ -857,7 +863,7 @@
let actual = AStruct { a_field: 1 };
- verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(2, 3,): eq(1) }))
+ verify_that!(actual, matches_pattern!(AStruct { *get_field_ref(2, 3,): eq(1) }))
}
#[test]
@@ -990,7 +996,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { ref get_field_ref(): eq(123), another_field: eq(234) })
+ matches_pattern!(AStruct { *get_field_ref(): eq(123), another_field: eq(234) })
)
}
@@ -1013,7 +1019,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { ref get_field_ref(): eq(123), another_field: eq(234), })
+ matches_pattern!(AStruct { *get_field_ref(): eq(123), another_field: eq(234), })
)
}
@@ -1035,7 +1041,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { ref get_field_ref(2, 3): eq(1), another_field: eq(123) })
+ matches_pattern!(AStruct { *get_field_ref(2, 3): eq(1), another_field: eq(123) })
)
}
@@ -1061,7 +1067,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { ref get_field_ref(AnEnum::AVariant): eq(1), another_field: eq(2) })
+ matches_pattern!(AStruct { *get_field_ref(AnEnum::AVariant): eq(1), another_field: eq(2) })
)
}
@@ -1084,7 +1090,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { ref get_field_ref(2, 3,): eq(1), another_field: eq(123) })
+ matches_pattern!(AStruct { *get_field_ref(2, 3,): eq(1), another_field: eq(123) })
)
}
@@ -1217,7 +1223,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(): eq(123) })
+ matches_pattern!(AStruct { another_field: eq(234), *get_field_ref(): eq(123) })
)
}
@@ -1240,7 +1246,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(): eq(123), })
+ matches_pattern!(AStruct { another_field: eq(234), *get_field_ref(): eq(123), })
)
}
@@ -1262,7 +1268,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(2, 3): eq(123) })
+ matches_pattern!(AStruct { another_field: eq(234), *get_field_ref(2, 3): eq(123) })
)
}
@@ -1288,7 +1294,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { another_field: eq(2), ref get_field_ref(AnEnum::AVariant): eq(1) })
+ matches_pattern!(AStruct { another_field: eq(2), *get_field_ref(AnEnum::AVariant): eq(1) })
)
}
@@ -1311,7 +1317,7 @@
verify_that!(
actual,
- matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(2, 3,): eq(123) })
+ matches_pattern!(AStruct { another_field: eq(234), *get_field_ref(2, 3,): eq(123) })
)
}
@@ -1479,7 +1485,7 @@
actual,
matches_pattern!(AStruct {
another_field: eq(234),
- ref get_field_ref(): eq(123),
+ *get_field_ref(): eq(123),
a_third_field: eq(345)
})
)
@@ -1507,7 +1513,7 @@
actual,
matches_pattern!(AStruct {
another_field: eq(234),
- ref get_field_ref(): eq(123),
+ *get_field_ref(): eq(123),
a_third_field: eq(345),
})
)
@@ -1535,7 +1541,7 @@
actual,
matches_pattern!(AStruct {
another_field: eq(234),
- ref get_field_ref(2, 3): eq(123),
+ *get_field_ref(2, 3): eq(123),
a_third_field: eq(345),
})
)
@@ -1567,7 +1573,7 @@
actual,
matches_pattern!(AStruct {
another_field: eq(2),
- ref get_field_ref(AnEnum::AVariant): eq(1),
+ *get_field_ref(AnEnum::AVariant): eq(1),
a_third_field: eq(3),
})
)
@@ -1595,7 +1601,7 @@
actual,
matches_pattern!(AStruct {
another_field: eq(234),
- ref get_field_ref(2, 3,): eq(123),
+ *get_field_ref(2, 3,): eq(123),
a_third_field: eq(345),
})
)
diff --git a/tests/pointwise_matcher_test.rs b/tests/pointwise_matcher_test.rs
index 85d16ff..cb8ef3b 100644
--- a/tests/pointwise_matcher_test.rs
+++ b/tests/pointwise_matcher_test.rs
@@ -142,8 +142,8 @@
2. is equal to 3
Actual: [1, 2, 3],
where:
- * element #0 is 1, which isn't equal to 2
- * element #1 is 2, which isn't equal to 3"
+ * element #0 is 1, which isn't equal to 2
+ * element #1 is 2, which isn't equal to 3"
))))
)
}
diff --git a/tests/property_matcher_test.rs b/tests/property_matcher_test.rs
index f9a88be..7092446 100644
--- a/tests/property_matcher_test.rs
+++ b/tests/property_matcher_test.rs
@@ -67,7 +67,7 @@
#[test]
fn matches_struct_with_matching_property_ref() -> Result<()> {
let value = SomeStruct { a_property: 10 };
- verify_that!(value, property!(ref SomeStruct.get_property_ref(), eq(10)))
+ verify_that!(value, property!(*SomeStruct.get_property_ref(), eq(10)))
}
#[test]
@@ -82,7 +82,7 @@
}
}
let value = StructWithString { property: "Something".into() };
- verify_that!(value, property!(ref StructWithString.get_property_ref(), eq("Something")))
+ verify_that!(value, property!(*StructWithString.get_property_ref(), eq("Something")))
}
#[test]
@@ -97,19 +97,19 @@
}
}
let value = StructWithVec { property: vec![1, 2, 3] };
- verify_that!(value, property!(ref StructWithVec.get_property_ref(), eq([1, 2, 3])))
+ verify_that!(value, property!(*StructWithVec.get_property_ref(), eq([1, 2, 3])))
}
#[test]
fn matches_struct_with_matching_property_ref_with_parameters() -> Result<()> {
let value = SomeStruct { a_property: 10 };
- verify_that!(value, property!(ref SomeStruct.get_property_ref_with_params(2, 3), eq(10)))
+ verify_that!(value, property!(*SomeStruct.get_property_ref_with_params(2, 3), eq(10)))
}
#[test]
fn matches_struct_with_matching_property_ref_with_parameters_and_trailing_comma() -> Result<()> {
let value = SomeStruct { a_property: 10 };
- verify_that!(value, property!(ref SomeStruct.get_property_ref_with_params(2, 3,), eq(10)))
+ verify_that!(value, property!(*SomeStruct.get_property_ref_with_params(2, 3,), eq(10)))
}
#[test]
@@ -122,7 +122,7 @@
fn describes_itself_in_matching_case() -> Result<()> {
verify_that!(
property!(SomeStruct.get_property(), eq(1)).describe(MatcherResult::Match),
- eq("has property `get_property()`, which is equal to 1")
+ displays_as(eq("has property `get_property()`, which is equal to 1"))
)
}
@@ -130,7 +130,7 @@
fn describes_itself_in_not_matching_case() -> Result<()> {
verify_that!(
property!(SomeStruct.get_property(), eq(1)).describe(MatcherResult::NoMatch),
- eq("has property `get_property()`, which isn't equal to 1")
+ displays_as(eq("has property `get_property()`, which isn't equal to 1"))
)
}
@@ -155,16 +155,16 @@
#[test]
fn describes_itself_in_matching_case_for_ref() -> Result<()> {
verify_that!(
- property!(ref SomeStruct.get_property_ref(), eq(1)).describe(MatcherResult::Match),
- eq("has property `get_property_ref()`, which is equal to 1")
+ property!(*SomeStruct.get_property_ref(), eq(1)).describe(MatcherResult::Match),
+ displays_as(eq("has property `get_property_ref()`, which is equal to 1"))
)
}
#[test]
fn describes_itself_in_not_matching_case_for_ref() -> Result<()> {
verify_that!(
- property!(ref SomeStruct.get_property_ref(), eq(1)).describe(MatcherResult::NoMatch),
- eq("has property `get_property_ref()`, which isn't equal to 1")
+ property!(*SomeStruct.get_property_ref(), eq(1)).describe(MatcherResult::NoMatch),
+ displays_as(eq("has property `get_property_ref()`, which isn't equal to 1"))
)
}
@@ -178,7 +178,7 @@
}
let value = SomeStruct { a_property: 2 };
let result =
- verify_that!(value, property!(ref SomeStruct.get_a_collection_ref(), container_eq([1])));
+ verify_that!(value, property!(*SomeStruct.get_a_collection_ref(), container_eq([1])));
verify_that!(
result,
diff --git a/tests/tuple_matcher_test.rs b/tests/tuple_matcher_test.rs
index 0c1eb01..a93ffee 100644
--- a/tests/tuple_matcher_test.rs
+++ b/tests/tuple_matcher_test.rs
@@ -176,54 +176,50 @@
#[test]
fn tuple_matcher_1_has_correct_description_for_match() -> Result<()> {
verify_that!(
- (eq(1),).describe(MatcherResult::Match),
- eq(indoc!(
+ (eq::<i32, _>(1),).describe(MatcherResult::Match),
+ displays_as(eq(indoc!(
"
is a tuple whose values respectively match:
- is equal to 1,
- "
- ))
+ is equal to 1"
+ )))
)
}
#[test]
fn tuple_matcher_1_has_correct_description_for_mismatch() -> Result<()> {
verify_that!(
- (eq(1),).describe(MatcherResult::NoMatch),
- eq(indoc!(
+ (eq::<i32, _>(1),).describe(MatcherResult::NoMatch),
+ displays_as(eq(indoc!(
"
is a tuple whose values do not respectively match:
- is equal to 1,
- "
- ))
+ is equal to 1"
+ )))
)
}
#[test]
fn tuple_matcher_2_has_correct_description_for_match() -> Result<()> {
verify_that!(
- (eq(1), eq(2)).describe(MatcherResult::Match),
- eq(indoc!(
+ (eq::<i32, _>(1), eq::<i32, _>(2)).describe(MatcherResult::Match),
+ displays_as(eq(indoc!(
"
is a tuple whose values respectively match:
- is equal to 1,
- is equal to 2,
- "
- ))
+ is equal to 1
+ is equal to 2"
+ )))
)
}
#[test]
fn tuple_matcher_2_has_correct_description_for_mismatch() -> Result<()> {
verify_that!(
- (eq(1), eq(2)).describe(MatcherResult::NoMatch),
- eq(indoc!(
+ (eq::<i32, _>(1), eq::<i32, _>(2)).describe(MatcherResult::NoMatch),
+ displays_as(eq(indoc!(
"
is a tuple whose values do not respectively match:
- is equal to 1,
- is equal to 2,
- "
- ))
+ is equal to 1
+ is equal to 2"
+ )))
)
}
@@ -233,11 +229,12 @@
(eq(1), eq(2)).explain_match(&(1, 3)),
displays_as(eq(indoc!(
"
- which is a tuple whose values do not respectively match:
- is equal to 1,
- is equal to 2,
- Element #1 is 3, which isn't equal to 2
- "
+ which
+ is a tuple whose values do not respectively match:
+ is equal to 1
+ is equal to 2
+ Element #1 is 3,
+ which isn't equal to 2"
)))
)
}
@@ -248,12 +245,14 @@
(eq(1), eq(2)).explain_match(&(2, 3)),
displays_as(eq(indoc!(
"
- which is a tuple whose values do not respectively match:
- is equal to 1,
- is equal to 2,
- Element #0 is 2, which isn't equal to 1
- Element #1 is 3, which isn't equal to 2
- "
+ which
+ is a tuple whose values do not respectively match:
+ is equal to 1
+ is equal to 2
+ Element #0 is 2,
+ which isn't equal to 1
+ Element #1 is 3,
+ which isn't equal to 2"
)))
)
}
diff --git a/tests/unordered_elements_are_matcher_test.rs b/tests/unordered_elements_are_matcher_test.rs
index a105b70..bd61417 100644
--- a/tests/unordered_elements_are_matcher_test.rs
+++ b/tests/unordered_elements_are_matcher_test.rs
@@ -352,7 +352,7 @@
#[test]
fn is_contained_in_matches_when_container_is_empty() -> Result<()> {
- verify_that!(vec![], is_contained_in!(eq(2), eq(3), eq(4)))
+ verify_that!(vec![], is_contained_in!(eq::<i32, _>(2), eq(3), eq(4)))
}
#[test]