aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Geisler <mgeisler@google.com>2023-09-07 08:26:12 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-09-07 08:26:12 +0000
commit8a97854a1cd062cbfbf0981abae19746f65e2177 (patch)
treebf32c7b6a23a37fa66e7cbb1e627cf30b4376daf
parent501a385ccace04c1f79ee7c42f546256ea8be258 (diff)
parentfeface21b7006ea632c1d1b405c58bbcf83c2c54 (diff)
downloadgoogletest-8a97854a1cd062cbfbf0981abae19746f65e2177.tar.gz
Import googletest 0.10.0 crate am: feface21b7
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/googletest/+/2737240 Change-Id: I02e827e4a9f1aecaf15907509bb96b367b6fc2f2 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Android.bp28
-rw-r--r--Cargo.toml66
-rw-r--r--LICENSE202
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--README.md354
-rw-r--r--cargo2android.json5
-rw-r--r--config.toml2
-rw-r--r--crate_docs.md485
-rw-r--r--src/assertions.rs455
-rw-r--r--src/internal/mod.rs18
-rw-r--r--src/internal/source_location.rs44
-rw-r--r--src/internal/test_outcome.rs211
-rw-r--r--src/lib.rs273
-rw-r--r--src/matcher.rs265
-rw-r--r--src/matcher_support/count_elements.rs32
-rw-r--r--src/matcher_support/description.rs410
-rw-r--r--src/matcher_support/edit_distance.rs648
-rw-r--r--src/matcher_support/mod.rs25
-rw-r--r--src/matcher_support/summarize_diff.rs367
-rw-r--r--src/matcher_support/zipped_iterator.rs85
-rw-r--r--src/matchers/all_matcher.rs205
-rw-r--r--src/matchers/any_matcher.rs199
-rw-r--r--src/matchers/anything_matcher.rs78
-rw-r--r--src/matchers/char_count_matcher.rs166
-rw-r--r--src/matchers/conjunction_matcher.rs157
-rw-r--r--src/matchers/container_eq_matcher.rs312
-rw-r--r--src/matchers/contains_matcher.rs273
-rw-r--r--src/matchers/contains_regex_matcher.rs148
-rw-r--r--src/matchers/disjunction_matcher.rs109
-rw-r--r--src/matchers/display_matcher.rs119
-rw-r--r--src/matchers/each_matcher.rs243
-rw-r--r--src/matchers/elements_are_matcher.rs174
-rw-r--r--src/matchers/empty_matcher.rs103
-rw-r--r--src/matchers/eq_deref_of_matcher.rs151
-rw-r--r--src/matchers/eq_matcher.rs405
-rw-r--r--src/matchers/err_matcher.rs144
-rw-r--r--src/matchers/field_matcher.rs201
-rw-r--r--src/matchers/ge_matcher.rs218
-rw-r--r--src/matchers/gt_matcher.rs254
-rw-r--r--src/matchers/has_entry_matcher.rs184
-rw-r--r--src/matchers/is_matcher.rs65
-rw-r--r--src/matchers/is_nan_matcher.rs62
-rw-r--r--src/matchers/le_matcher.rs219
-rw-r--r--src/matchers/len_matcher.rs211
-rw-r--r--src/matchers/lt_matcher.rs228
-rw-r--r--src/matchers/matches_pattern.rs599
-rw-r--r--src/matchers/matches_regex_matcher.rs210
-rw-r--r--src/matchers/mod.rs89
-rw-r--r--src/matchers/near_matcher.rs347
-rw-r--r--src/matchers/none_matcher.rs80
-rw-r--r--src/matchers/not_matcher.rs106
-rw-r--r--src/matchers/ok_matcher.rs150
-rw-r--r--src/matchers/points_to_matcher.rs107
-rw-r--r--src/matchers/pointwise_matcher.rs236
-rw-r--r--src/matchers/predicate_matcher.rs228
-rw-r--r--src/matchers/property_matcher.rs237
-rw-r--r--src/matchers/some_matcher.rs162
-rw-r--r--src/matchers/str_matcher.rs1234
-rw-r--r--src/matchers/subset_of_matcher.rs267
-rw-r--r--src/matchers/superset_of_matcher.rs267
-rw-r--r--src/matchers/tuple_matcher.rs207
-rw-r--r--src/matchers/unordered_elements_are_matcher.rs1116
-rw-r--r--tests/all_matcher_test.rs93
-rw-r--r--tests/any_matcher_test.rs93
-rw-r--r--tests/colorized_diff_test.rs52
-rw-r--r--tests/composition_test.rs71
-rw-r--r--tests/elements_are_matcher_test.rs125
-rw-r--r--tests/field_matcher_test.rs178
-rw-r--r--tests/lib.rs27
-rw-r--r--tests/matches_pattern_test.rs1602
-rw-r--r--tests/no_color_test.rs54
-rw-r--r--tests/pointwise_matcher_test.rs170
-rw-r--r--tests/property_matcher_test.rs189
-rw-r--r--tests/proptest_integration_test.rs30
-rw-r--r--tests/tuple_matcher_test.rs259
-rw-r--r--tests/unordered_elements_are_matcher_test.rs403
79 files changed, 17617 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..ce5ec95
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "6e5405db2217d37006720c101beb1b91199a3a26"
+ },
+ "path_in_vcs": "googletest"
+} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..9f2e45e
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,28 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+
+
+rust_library {
+ name: "libgoogletest_rust",
+ host_supported: true,
+ crate_name: "googletest",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.10.0",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libnum_traits",
+ "libregex",
+ ],
+ proc_macros: [
+ "libgoogletest_macro",
+ "librustversion",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+}
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..3e898d2
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,66 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.59.0"
+name = "googletest"
+version = "0.10.0"
+authors = [
+ "Bradford Hovinen <hovinen@google.com>",
+ "Bastien Jacot-Guillarmod <bjacotg@google.com>",
+ "Maciej Pietrzak <mpi@google.com>",
+ "Martin Geisler <mgeisler@google.com>",
+]
+description = "A rich assertion and matcher library inspired by GoogleTest for C++"
+readme = "README.md"
+keywords = [
+ "unit",
+ "matcher",
+ "testing",
+ "assertions",
+]
+categories = [
+ "development-tools",
+ "development-tools::testing",
+]
+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"
+
+[dependencies.num-traits]
+version = "0.2.15"
+
+[dependencies.proptest]
+version = "1.2.0"
+optional = true
+
+[dependencies.regex]
+version = "1.6.0"
+
+[dependencies.rustversion]
+version = "1.0.14"
+
+[dev-dependencies.indoc]
+version = "2"
+
+[dev-dependencies.quickcheck]
+version = "1.0.3"
+
+[dev-dependencies.serial_test]
+version = "2.0.0"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..6c2060b
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "googletest"
+description: "A rich assertion and matcher library inspired by GoogleTest for C++"
+third_party {
+ identifier {
+ type: "crates.io"
+ value: "https://crates.io/crates/googletest"
+ }
+ 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
+ }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2a0f918
--- /dev/null
+++ b/README.md
@@ -0,0 +1,354 @@
+# GoogleTest Rust
+
+[![crates.io][crates-badge]][crates-url]
+[![docs.rs][docs-badge]][docs-url]
+[![Apache licensed][license-badge]][license-url]
+[![Build Status][actions-badge]][actions-url]
+
+[crates-badge]: https://img.shields.io/crates/v/googletest.svg
+[crates-url]: https://crates.io/crates/googletest
+[docs-badge]: https://img.shields.io/badge/docs.rs-googletest-66c2a5
+[docs-url]: https://docs.rs/googletest/*/googletest/
+[license-badge]: https://img.shields.io/badge/license-Apache-blue.svg
+[license-url]: https://github.com/google/googletest-rust/blob/main/LICENSE
+[actions-badge]: https://github.com/google/googletest-rust/workflows/CI/badge.svg
+[actions-url]: https://github.com/google/googletest-rust/actions?query=workflow%3ACI+branch%3Amain
+
+This library brings the rich assertion types of Google's C++ testing library
+[GoogleTest](https://github.com/google/googletest) to Rust. It provides:
+
+ * A framework for writing matchers which can be combined to make a wide range
+ of assertions on data,
+ * A rich set of matchers providing similar functionality to those included in
+ [GoogleTest](https://google.github.io/googletest/reference/matchers.html),
+ and
+ * 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.
+
+> :warning: The API is not fully stable and may still be changed until we
+> publish version 1.0.
+
+## Assertions and matchers
+
+The core of GoogleTest is its *matchers*. Matchers indicate what aspect of an
+actual value one is asserting: (in-)equality, containment, regular expression
+matching, and so on.
+
+To make an assertion using a matcher, GoogleTest offers three macros:
+
+ * [`assert_that!`] panics if the assertion fails, aborting the test.
+ * [`expect_that!`] logs an assertion failure, marking the test as having
+ failed, but allows the test to continue running (called a _non-fatal
+ assertion_). It requires the use of the [`googletest::test`] attribute macro
+ on the test itself.
+ * [`verify_that!`] has no side effects and evaluates to a [`Result<()>`] whose
+ `Err` variant describes the assertion failure, if there is one. In
+ combination with the
+ [`?` operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator),
+ this can be used to abort the test on assertion failure without panicking. It
+ is also the building block for the other two macros above.
+
+For example:
+
+```rust
+use googletest::prelude::*;
+
+#[test]
+fn fails_and_panics() {
+ let value = 2;
+ assert_that!(value, eq(4));
+}
+
+#[googletest::test]
+fn two_logged_failures() {
+ let value = 2;
+ expect_that!(value, eq(4)); // Test now failed, but continues executing.
+ expect_that!(value, eq(5)); // Second failure is also logged.
+}
+
+#[test]
+fn fails_immediately_without_panic() -> Result<()> {
+ let value = 2;
+ verify_that!(value, eq(4))?; // Test fails and aborts.
+ verify_that!(value, eq(2))?; // Never executes.
+ Ok(())
+}
+
+#[test]
+fn simple_assertion() -> Result<()> {
+ let value = 2;
+ verify_that!(value, eq(4)) // One can also just return the last assertion.
+}
+```
+
+This library includes a rich set of matchers, covering:
+
+ * Equality, numeric inequality, and approximate equality;
+ * Strings and regular expressions;
+ * Containers and set-theoretic matching.
+
+Matchers are composable:
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn contains_at_least_one_item_at_least_3() {
+ let value = vec![1, 2, 3];
+ expect_that!(value, contains(ge(3)));
+}
+```
+
+They can also be logically combined:
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn strictly_between_9_and_11() {
+ let value = 10;
+ expect_that!(value, gt(9).and(not(ge(11))));
+}
+```
+
+## Pattern-matching
+
+One can use the macro [`matches_pattern!`] to create a composite matcher for a
+struct or enum that matches fields with other matchers:
+
+```rust
+use googletest::prelude::*;
+
+struct AStruct {
+ a_field: i32,
+ another_field: i32,
+ a_third_field: &'static str,
+}
+
+#[test]
+fn struct_has_expected_values() {
+ let value = AStruct {
+ a_field: 10,
+ another_field: 100,
+ a_third_field: "A correct value",
+ };
+ expect_that!(value, matches_pattern!(AStruct {
+ a_field: eq(10),
+ another_field: gt(50),
+ a_third_field: contains_substring("correct"),
+ }));
+}
+```
+
+## Writing matchers
+
+One can extend the library by writing additional matchers. To do so, create a
+struct holding the matcher's data and have it implement the trait [`Matcher`]:
+
+```rust
+struct MyEqMatcher<T> {
+ expected: T,
+}
+
+impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
+ type ActualT = T;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ (self.expected == *actual).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Matches => {
+ format!("is equal to {:?} the way I define it", self.expected)
+ }
+ MatcherResult::DoesNotMatch => {
+ format!("isn't equal to {:?} the way I define it", self.expected)
+ }
+ }
+ }
+}
+```
+
+It is recommended to expose a function which constructs the matcher:
+
+```rust
+pub fn eq_my_way<T: PartialEq + Debug>(expected: T) -> impl Matcher<ActualT = T> {
+ MyEqMatcher { expected }
+}
+```
+
+The new matcher can then be used in the assertion macros:
+
+```rust
+#[googletest::test]
+fn should_be_equal_by_my_definition() {
+ expect_that!(10, eq_my_way(10));
+}
+```
+
+## Non-fatal assertions
+
+Using non-fatal assertions, a single test is able to log multiple assertion
+failures. Any single assertion failure causes the test to be considered having
+failed, but execution continues until the test completes or otherwise aborts.
+
+This is analogous to the `EXPECT_*` family of macros in GoogleTest.
+
+To make a non-fatal assertion, use the macro [`expect_that!`]. The test must
+also be marked with [`googletest::test`] instead of the Rust-standard `#[test]`.
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn three_non_fatal_assertions() {
+ let value = 2;
+ expect_that!(value, eq(2)); // Passes; test still considered passing.
+ expect_that!(value, eq(3)); // Fails; logs failure and marks the test failed.
+ expect_that!(value, eq(4)); // A second failure, also logged.
+}
+```
+
+This can be used in the same tests as `verify_that!`, in which case the test
+function must also return [`Result<()>`]:
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn failing_non_fatal_assertion() -> Result<()> {
+ let value = 2;
+ expect_that!(value, eq(3)); // Just marks the test as having failed.
+ verify_that!(value, eq(2))?; // Passes, so does not abort the test.
+ Ok(()) // Because of the failing expect_that! call above, the
+ // test fails despite returning Ok(())
+}
+```
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn failing_fatal_assertion_after_non_fatal_assertion() -> Result<()> {
+ let value = 2;
+ verify_that!(value, eq(3))?; // Fails and aborts the test.
+ expect_that!(value, eq(3)); // Never executes, since the test already aborted.
+ Ok(())
+}
+```
+
+### Interoperability
+
+You can use the `#[googletest::test]` macro together with many other libraries
+such as [rstest](https://crates.io/crates/rstest). Just apply both attribute
+macros to the test:
+
+```rust
+#[googletest::test]
+#[rstest]
+#[case(1)]
+#[case(2)]
+#[case(3)]
+fn rstest_works_with_google_test(#[case] value: u32) -> Result<()> {
+ verify_that!(value, gt(0))
+}
+```
+
+Make sure to put `#[googletest::test]` *before* `#[rstest]`. Otherwise the
+annotated test will run twice, since both macros will attempt to register a test
+with the Rust test harness.
+
+The macro also works together with
+[async tests with Tokio](https://docs.rs/tokio/latest/tokio/attr.test.html) in
+the same way:
+
+```rust
+#[googletest::test]
+#[tokio::test]
+async fn should_work_with_tokio() -> Result<()> {
+ verify_that!(3, gt(0))
+}
+```
+
+There is one caveat when running async tests: test failure reporting through
+`and_log_failure` will not work properly if the assertion occurs on a different
+thread than runs the test.
+
+## Predicate assertions
+
+The macro [`verify_pred!`] provides predicate assertions analogous to
+GoogleTest's `EXPECT_PRED` family of macros. Wrap an invocation of a predicate
+in a `verify_pred!` invocation to turn that into a test assertion which passes
+precisely when the predicate returns `true`:
+
+```rust
+fn stuff_is_correct(x: i32, y: i32) -> bool {
+ x == y
+}
+
+let x = 3;
+let y = 4;
+verify_pred!(stuff_is_correct(x, y))?;
+```
+
+The assertion failure message shows the arguments and the values to which they
+evaluate:
+
+```
+stuff_is_correct(x, y) was false with
+ x = 3,
+ y = 4
+```
+
+The `verify_pred!` invocation evaluates to a [`Result<()>`] just like
+[`verify_that!`]. There is also a macro [`expect_pred!`] to make a non-fatal
+predicaticate assertion.
+
+## Unconditionally generating a test failure
+
+The macro [`fail!`] unconditionally evaluates to a `Result` indicating a test
+failure. It can be used analogously to [`verify_that!`] and [`verify_pred!`] to
+cause a test to fail, with an optional formatted message:
+
+```rust
+#[test]
+fn always_fails() -> Result<()> {
+ fail!("This test must fail with {}", "today")
+}
+```
+
+## Configuration
+
+This library is configurable through environment variables. Since the
+configuration does not impact whether a test fails or not but how a failure is
+displayed, we recommend setting those variables in the personal
+`~/.cargo/config.toml` instead of in the project-scoped `Cargo.toml`.
+
+### Configuration variable list
+
+| Variable name | Description |
+| ------------- | ------------------------------------------------------- |
+| NO_COLOR | Disables colored output. See <https://no-color.org/>. |
+| FORCE_COLOR | Forces colors even when the output is piped to a file. |
+
+## Contributing Changes
+
+Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
+to this project.
+
+[`and_log_failure()`]: https://docs.rs/googletest/*/googletest/trait.GoogleTestSupport.html#tymethod.and_log_failure
+[`assert_that!`]: https://docs.rs/googletest/*/googletest/macro.assert_that.html
+[`expect_pred!`]: https://docs.rs/googletest/*/googletest/macro.expect_pred.html
+[`expect_that!`]: https://docs.rs/googletest/*/googletest/macro.expect_that.html
+[`fail!`]: https://docs.rs/googletest/*/googletest/macro.fail.html
+[`googletest::test`]: https://docs.rs/googletest/*/googletest/attr.test.html
+[`matches_pattern!`]: https://docs.rs/googletest/*/googletest/macro.matches_pattern.html
+[`verify_pred!`]: https://docs.rs/googletest/*/googletest/macro.verify_pred.html
+[`verify_that!`]: https://docs.rs/googletest/*/googletest/macro.verify_that.html
+[`Describe`]: https://docs.rs/googletest/*/googletest/matcher/trait.Describe.html
+[`Matcher`]: https://docs.rs/googletest/*/googletest/matcher/trait.Matcher.html
+[`Result<()>`]: https://docs.rs/googletest/*/googletest/type.Result.html
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..c76e391
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,5 @@
+{
+ "device": true,
+ "run": true,
+ "suffix": "_rust"
+} \ No newline at end of file
diff --git a/config.toml b/config.toml
new file mode 100644
index 0000000..7bc2412
--- /dev/null
+++ b/config.toml
@@ -0,0 +1,2 @@
+[env]
+NO_COLOR = "1"
diff --git a/crate_docs.md b/crate_docs.md
new file mode 100644
index 0000000..a00c219
--- /dev/null
+++ b/crate_docs.md
@@ -0,0 +1,485 @@
+A rich test assertion library for Rust.
+
+This library provides:
+
+ * A framework for writing matchers which can be combined to make a wide
+ range of assertions on data,
+ * A rich set of matchers, and
+ * A new set of test assertion macros.
+
+## Assertions and matchers
+
+The core of GoogleTest is its *matchers*. Matchers indicate what aspect of an
+actual value one is asserting: (in-)equality, containment, regular expression
+matching, and so on.
+
+To make an assertion using a matcher, GoogleTest offers three macros:
+
+ * [`assert_that!`] panics if the assertion fails, aborting the test.
+ * [`expect_that!`] logs an assertion failure, marking the test as having
+ failed, but allows the test to continue running (called a _non-fatal
+ assertion_). It requires the use of the [`googletest::test`][crate::test]
+ attribute macro on the test itself.
+ * [`verify_that!`] has no side effects and evaluates to a [`Result`] whose
+ `Err` variant describes the assertion failure, if there is one. In
+ combination with the
+ [`?` operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator),
+ this can be used to abort the test on assertion failure without panicking. It
+ is also the building block for the other two macros above.
+
+For example:
+
+```
+use googletest::prelude::*;
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn fails_and_panics() {
+ let value = 2;
+ assert_that!(value, eq(4));
+}
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[googletest::test]
+# */
+fn two_logged_failures() {
+ let value = 2;
+ expect_that!(value, eq(4)); // Test now failed, but continues executing.
+ expect_that!(value, eq(5)); // Second failure is also logged.
+}
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn fails_immediately_without_panic() -> Result<()> {
+ let value = 2;
+ verify_that!(value, eq(4))?; // Test fails and aborts.
+ verify_that!(value, eq(2))?; // Never executes.
+ Ok(())
+}
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn simple_assertion() -> Result<()> {
+ let value = 2;
+ verify_that!(value, eq(4)) // One can also just return the last assertion.
+}
+```
+
+Matchers are composable:
+
+```
+use googletest::prelude::*;
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[googletest::test]
+# */
+fn contains_at_least_one_item_at_least_3() {
+# googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+ let value = vec![1, 2, 3];
+ expect_that!(value, contains(ge(3)));
+# googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
+# .unwrap();
+}
+# contains_at_least_one_item_at_least_3();
+```
+
+They can also be logically combined:
+
+```
+use googletest::prelude::*;
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[googletest::test]
+# */
+fn strictly_between_9_and_11() {
+# googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+ let value = 10;
+ expect_that!(value, gt(9).and(not(ge(11))));
+# googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
+# .unwrap();
+}
+# strictly_between_9_and_11();
+```
+
+## Available matchers
+
+The following matchers are provided in GoogleTest Rust:
+
+| Matcher | What it matches |
+|----------------------|--------------------------------------------------------------------------|
+| [`all!`] | Anything matched by all given matchers. |
+| [`any!`] | Anything matched by at least one of the given matchers. |
+| [`anything`] | Any input. |
+| [`approx_eq`] | A floating point number within a standard tolerance of the argument. |
+| [`char_count`] | A string with a Unicode scalar count matching the argument. |
+| [`container_eq`] | Same as [`eq`], but for containers (with a better mismatch description). |
+| [`contains`] | A container containing an element matched by the given matcher. |
+| [`contains_each!`] | A container containing distinct elements each of the arguments match. |
+| [`contains_regex`] | A string containing a substring matching the given regular expression. |
+| [`contains_substring`] | A string containing the given substring. |
+| [`displays_as`] | A [`Display`] value whose formatted string is matched by the argument. |
+| [`each`] | A container all of whose elements the given argument matches. |
+| [`elements_are!`] | A container whose elements the arguments match, in order. |
+| [`empty`] | An empty collection. |
+| [`ends_with`] | A string ending with the given suffix. |
+| [`eq`] | A value equal to the argument, in the sense of the [`PartialEq`] trait. |
+| [`eq_deref_of`] | A value equal to the dereferenced value of the argument. |
+| [`err`] | A [`Result`][std::result::Result] containing an `Err` variant the argument matches. |
+| [`field!`] | A struct or enum with a given field whose value the argument matches. |
+| [`ge`] | A [`PartialOrd`] value greater than or equal to the given value. |
+| [`gt`] | A [`PartialOrd`] value strictly greater than the given value. |
+| [`has_entry`] | A [`HashMap`] containing a given key whose value the argument matches. |
+| [`is_contained_in!`] | A container each of whose elements is matched by some given matcher. |
+| [`is_nan`] | A floating point number which is NaN. |
+| [`le`] | A [`PartialOrd`] value less than or equal to the given value. |
+| [`len`] | A container whose number of elements the argument matches. |
+| [`lt`] | A [`PartialOrd`] value strictly less than the given value. |
+| [`matches_pattern!`] | A struct or enum whose fields are matched according to the arguments. |
+| [`matches_regex`] | A string matched by the given regular expression. |
+| [`near`] | A floating point number within a given tolerance of the argument. |
+| [`none`] | An [`Option`] containing `None`. |
+| [`not`] | Any value the argument does not match. |
+| [`ok`] | A [`Result`][std::result::Result] containing an `Ok` variant the argument matches. |
+| [`pat!`] | Alias for [`matches_pattern!`]. |
+| [`points_to`] | Any [`Deref`] such as `&`, `Rc`, etc. whose value the argument matches. |
+| [`pointwise!`] | A container whose contents the arguments match in a pointwise fashion. |
+| [`predicate`] | A value on which the given predicate returns true. |
+| [`some`] | An [`Option`] containing `Some` whose value the argument matches. |
+| [`starts_with`] | A string starting with the given prefix. |
+| [`subset_of`] | A container all of whose elements are contained in the argument. |
+| [`superset_of`] | A container containing all elements of the argument. |
+| [`unordered_elements_are!`] | A container whose elements the arguments match, in any order. |
+
+[`anything`]: matchers::anything
+[`approx_eq`]: matchers::approx_eq
+[`char_count`]: matchers::char_count
+[`container_eq`]: matchers::container_eq
+[`contains`]: matchers::contains
+[`contains_regex`]: matchers::contains_regex
+[`contains_substring`]: matchers::contains_substring
+[`displays_as`]: matchers::displays_as
+[`each`]: matchers::each
+[`empty`]: matchers::empty
+[`ends_with`]: matchers::ends_with
+[`eq`]: matchers::eq
+[`eq_deref_of`]: matchers::eq_deref_of
+[`err`]: matchers::err
+[`ge`]: matchers::ge
+[`gt`]: matchers::gt
+[`has_entry`]: matchers::has_entry
+[`is_nan`]: matchers::is_nan
+[`le`]: matchers::le
+[`len`]: matchers::len
+[`lt`]: matchers::lt
+[`matches_regex`]: matchers::matches_regex
+[`near`]: matchers::near
+[`none`]: matchers::none
+[`not`]: matchers::not
+[`ok`]: matchers::ok
+[`points_to`]: matchers::points_to
+[`predicate`]: matchers::predicate
+[`some`]: matchers::some
+[`starts_with`]: matchers::starts_with
+[`subset_of`]: matchers::subset_of
+[`superset_of`]: matchers::superset_of
+[`Deref`]: std::ops::Deref
+[`Display`]: std::fmt::Display
+[`HashMap`]: std::collections::HashMap
+[`Option`]: std::option::Option
+[`PartialEq`]: std::cmp::PartialEq
+[`PartialOrd`]: std::cmp::PartialOrd
+
+## Writing matchers
+
+One can extend the library by writing additional matchers. To do so, create
+a struct holding the matcher's data and have it implement the trait
+[`Matcher`]:
+
+```no_run
+use googletest::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+
+struct MyEqMatcher<T> {
+ expected: T,
+}
+
+impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
+ type ActualT = T;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ if self.expected == *actual {
+ MatcherResult::Match
+ } else {
+ MatcherResult::NoMatch
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!("is equal to {:?} the way I define it", self.expected)
+ }
+ MatcherResult::NoMatch => {
+ format!("isn't equal to {:?} the way I define it", self.expected)
+ }
+ }
+ }
+}
+```
+
+ It is recommended to expose a function which constructs the matcher:
+
+ ```no_run
+ # use googletest::matcher::{Matcher, MatcherResult};
+ # use std::fmt::Debug;
+ #
+ # struct MyEqMatcher<T> {
+ # expected: T,
+ # }
+ #
+ # impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
+ # type ActualT = T;
+ #
+ # fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ # if self.expected == *actual {
+ # MatcherResult::Match
+ # } else {
+ # MatcherResult::NoMatch
+ # }
+ # }
+ #
+ # fn describe(&self, matcher_result: MatcherResult) -> String {
+ # match matcher_result {
+ # MatcherResult::Match => {
+ # format!("is equal to {:?} the way I define it", self.expected)
+ # }
+ # MatcherResult::NoMatch => {
+ # format!("isn't equal to {:?} the way I define it", self.expected)
+ # }
+ # }
+ # }
+ # }
+ #
+ pub fn eq_my_way<T: PartialEq + Debug>(expected: T) -> impl Matcher<ActualT = T> {
+ MyEqMatcher { expected }
+ }
+ ```
+
+ The new matcher can then be used in the assertion macros:
+
+```
+# use googletest::prelude::*;
+# use googletest::matcher::{Matcher, MatcherResult};
+# use std::fmt::Debug;
+#
+# struct MyEqMatcher<T> {
+# expected: T,
+# }
+#
+# impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
+# type ActualT = T;
+#
+# fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+# if self.expected == *actual {
+# MatcherResult::Match
+# } else {
+# MatcherResult::NoMatch
+# }
+# }
+#
+# fn describe(&self, matcher_result: MatcherResult) -> String {
+# match matcher_result {
+# MatcherResult::Match => {
+# format!("is equal to {:?} the way I define it", self.expected)
+# }
+# MatcherResult::NoMatch => {
+# format!("isn't equal to {:?} the way I define it", self.expected)
+# }
+# }
+# }
+# }
+#
+# pub fn eq_my_way<T: PartialEq + Debug>(expected: T) -> impl Matcher<ActualT = T> {
+# MyEqMatcher { expected }
+# }
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[googletest::test]
+# */
+fn should_be_equal_by_my_definition() {
+# googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+ expect_that!(10, eq_my_way(10));
+# googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
+# .unwrap();
+}
+# should_be_equal_by_my_definition();
+```
+
+## Non-fatal assertions
+
+Using non-fatal assertions, a single test is able to log multiple assertion
+failures. Any single assertion failure causes the test to be considered
+having failed, but execution continues until the test completes or otherwise
+aborts.
+
+To make a non-fatal assertion, use the macro [`expect_that!`]. The test must
+also be marked with [`googletest::test`][crate::test] instead of the
+Rust-standard `#[test]`.
+
+```no_run
+use googletest::prelude::*;
+
+#[googletest::test]
+fn three_non_fatal_assertions() {
+ let value = 2;
+ expect_that!(value, eq(2)); // Passes; test still considered passing.
+ expect_that!(value, eq(3)); // Fails; logs failure and marks the test failed.
+ expect_that!(value, eq(4)); // A second failure, also logged.
+}
+```
+
+This can be used in the same tests as `verify_that!`, in which case the test
+function must also return [`Result<()>`]:
+
+```no_run
+use googletest::prelude::*;
+
+# /* Make sure this also compiles as a doctest.
+#[googletest::test]
+# */
+fn failing_non_fatal_assertion() -> Result<()> {
+ let value = 2;
+ expect_that!(value, eq(3)); // Just marks the test as having failed.
+ verify_that!(value, eq(2))?; // Passes, so does not abort the test.
+ Ok(()) // Because of the failing expect_that! call above, the
+ // test fails despite returning Ok(())
+}
+```
+
+```no_run
+use googletest::prelude::*;
+
+#[googletest::test]
+fn failing_fatal_assertion_after_non_fatal_assertion() -> Result<()> {
+ let value = 2;
+ expect_that!(value, eq(2)); // Passes; test still considered passing.
+ verify_that!(value, eq(3))?; // Fails and aborts the test.
+ expect_that!(value, eq(3)); // Never executes, since the test already aborted.
+ Ok(())
+}
+```
+
+## Predicate assertions
+
+The macro [`verify_pred!`] provides predicate assertions analogous to
+GoogleTest's `EXPECT_PRED` family of macros. Wrap an invocation of a
+predicate in a `verify_pred!` invocation to turn that into a test assertion
+which passes precisely when the predicate returns `true`:
+
+```
+# use googletest::prelude::*;
+fn stuff_is_correct(x: i32, y: i32) -> bool {
+ x == y
+}
+
+# fn run_test() -> Result<()> {
+let x = 3;
+let y = 4;
+verify_pred!(stuff_is_correct(x, y))?;
+# Ok(())
+# }
+# run_test().unwrap_err();
+```
+
+The assertion failure message shows the arguments and the values to which
+they evaluate:
+
+```text
+stuff_is_correct(x, y) was false with
+ x = 3,
+ y = 4
+```
+
+The `verify_pred!` invocation evaluates to a [`Result<()>`] just like
+[`verify_that!`]. There is also a macro [`expect_pred!`] to make a non-fatal
+predicaticate assertion.
+
+## Unconditionally generating a test failure
+
+The macro [`fail!`] unconditionally evaluates to a `Result` indicating a
+test failure. It can be used analogously to [`verify_that!`] and
+[`verify_pred!`] to cause a test to fail, with an optional formatted
+message:
+
+```
+# use googletest::prelude::*;
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn always_fails() -> Result<()> {
+ fail!("This test must fail with {}", "today")
+}
+# always_fails().unwrap_err();
+```
+
+## Integrations with other crates
+
+GoogleTest Rust includes integrations with the
+[Anyhow](https://crates.io/crates/anyhow) and
+[Proptest](https://crates.io/crates/proptest) crates to simplify turning
+errors from those crates into test failures.
+
+To use this, activate the `anyhow`, respectively `proptest` feature in
+GoogleTest Rust and invoke the extension method [`into_test_result()`] on a
+`Result` value in your test. For example:
+
+```
+# use googletest::prelude::*;
+# #[cfg(feature = "anyhow")]
+# use anyhow::anyhow;
+# #[cfg(feature = "anyhow")]
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn has_anyhow_failure() -> Result<()> {
+ Ok(just_return_error().into_test_result()?)
+}
+
+# #[cfg(feature = "anyhow")]
+fn just_return_error() -> anyhow::Result<()> {
+ anyhow::Result::Err(anyhow!("This is an error"))
+}
+# #[cfg(feature = "anyhow")]
+# has_anyhow_failure().unwrap_err();
+```
+
+One can convert Proptest test failures into GoogleTest test failures when the
+test is invoked with
+[`TestRunner::run`](https://docs.rs/proptest/latest/proptest/test_runner/struct.TestRunner.html#method.run):
+
+```
+# use googletest::prelude::*;
+# #[cfg(feature = "proptest")]
+# use proptest::test_runner::{Config, TestRunner};
+# #[cfg(feature = "proptest")]
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn numbers_are_greater_than_zero() -> Result<()> {
+ let mut runner = TestRunner::new(Config::default());
+ runner.run(&(1..100i32), |v| Ok(verify_that!(v, gt(0))?)).into_test_result()
+}
+# #[cfg(feature = "proptest")]
+# numbers_are_greater_than_zero().unwrap();
+```
+
+Similarly, when the `proptest` feature is enabled, GoogleTest assertion failures
+can automatically be converted into Proptest
+[`TestCaseError`](https://docs.rs/proptest/latest/proptest/test_runner/enum.TestCaseError.html)
+through the `?` operator as the example above shows.
+
+[`and_log_failure()`]: GoogleTestSupport::and_log_failure
+[`into_test_result()`]: IntoTestResult::into_test_result
+[`Matcher`]: matcher::Matcher
diff --git a/src/assertions.rs b/src/assertions.rs
new file mode 100644
index 0000000..7664486
--- /dev/null
+++ b/src/assertions.rs
@@ -0,0 +1,455 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macros are documented at the top level.
+#![doc(hidden)]
+
+/// Checks whether the `Matcher` given by the second argument matches the first
+/// argument.
+///
+/// Evaluates to `Result::Ok(())` if the matcher matches and
+/// `Result::Err(TestAssertionFailure)` if it does not. The caller must then
+/// decide how to handle the `Err` variant. It has a few options:
+///
+/// * Abort the current function with the `?` operator. This requires that the
+/// function return a suitable `Result`.
+/// * Log the test failure and continue by calling the method
+/// `and_log_failure`.
+///
+/// Of course, one can also use all other standard methods on `Result`.
+///
+/// **Invoking this macro by itself does not cause a test failure to be recorded
+/// or output.** The resulting `Result` must be handled as described above to
+/// cause the test to be recorded as a failure.
+///
+/// Example:
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(42, eq(42))?; // This will pass.
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # fn should_fail() -> Result<()> {
+/// # googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+/// verify_that!(42, eq(123)).and_log_failure();
+/// // This will log a test failure and allow execution to continue.
+/// let _ = verify_that!(42, eq(123)); // This will do nothing.
+/// verify_that!(42, eq(123))?; // This will fail, returning immediately.
+/// verify_that!(42, eq(0))?; // This will not run.
+/// # googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
+/// # .unwrap_err();
+/// # Ok(())
+/// # }
+/// # verify_that!(should_fail(), err(displays_as(contains_substring("Expected: is equal to 123"))))
+/// # .unwrap();
+/// ```
+///
+/// This macro has special support for matching against container. Namely:
+/// * `verify_that!(actual, [m1, m2, ...])` is equivalent to
+/// `verify_that!(actual, elements_are![m1, m2, ...])`
+/// * `verify_that!(actual, {m1, m2, ...})` is equivalent to
+/// `verify_that!(actual, unordered_elements_are![m1, m2, ...])`
+///
+/// ## Matching against tuples
+///
+/// One can match against a tuple by constructing a tuple of matchers as
+/// follows:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!((123, 456), (eq(123), eq(456)))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!((123, 456), (eq(123), eq(0)))?; // Fails: second matcher does not match
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// This also works with composed matchers:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!((123, 456), not((eq(456), eq(123))))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Matchers must correspond to the actual tuple in count and type. Otherwise
+/// the test will fail to compile.
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!((123, 456), (eq(123),))?; // Does not compile: wrong tuple size
+/// verify_that!((123, "A string"), (eq(123), eq(456)))?; // Does not compile: wrong type
+/// # Ok(())
+/// # }
+/// ```
+///
+/// All fields must be covered by matchers. Use
+/// [`anything`][crate::matchers::anything] for fields which are not relevant
+/// for the test.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!((123, 456), (eq(123), anything()))
+/// # .unwrap();
+/// ```
+///
+/// This supports tuples of up to 12 elements. Tuples longer than that do not
+/// automatically inherit the `Debug` trait from their members, so are generally
+/// 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),+]) => {
+ $crate::assertions::internal::check_matcher(
+ &$actual,
+ $crate::elements_are![$($expecteds),+],
+ stringify!($actual),
+ $crate::internal::source_location::SourceLocation::new(file!(), line!(), column!()),
+ )
+ };
+ ($actual:expr, {$($expecteds:expr),+}) => {
+ $crate::assertions::internal::check_matcher(
+ &$actual,
+ $crate::unordered_elements_are![$($expecteds),+],
+ stringify!($actual),
+ $crate::internal::source_location::SourceLocation::new(file!(), line!(), column!()),
+ )
+ };
+ ($actual:expr, $expected:expr) => {
+ $crate::assertions::internal::check_matcher(
+ &$actual,
+ $expected,
+ stringify!($actual),
+ $crate::internal::source_location::SourceLocation::new(file!(), line!(), column!()),
+ )
+ };
+}
+
+/// Asserts that the given predicate applied to the given arguments returns
+/// true.
+///
+/// Similarly to [`verify_that`], this evaluates to a `Result` whose `Ok`
+/// variant indicates that the given predicate returned true and whose `Err`
+/// variant indicates that it returned false.
+///
+/// The failure message contains detailed information about the arguments. For
+/// example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// fn equals_modulo(a: i32, b: i32, n: i32) -> bool { a % n == b % n }
+///
+/// # /* The attribute macro would prevent the function from being compiled in a doctest.
+/// #[test]
+/// # */
+/// fn test() -> Result<()> {
+/// let a = 1;
+/// let b = 7;
+/// let n = 5;
+/// verify_pred!(equals_modulo(a, b, n))?;
+/// Ok(())
+/// }
+/// # verify_that!(
+/// # test(),
+/// # err(displays_as(contains_substring("equals_modulo(a, b, n) was false with")))
+/// # ).unwrap();
+/// ```
+///
+/// This results in the following message:
+///
+/// ```text
+/// equals_modulo(a, b, n) was false with
+/// a = 1,
+/// b = 7,
+/// n = 5
+/// ```
+///
+/// The function passed to this macro must return `bool`. Each of the arguments
+/// must evaluate to a type implementing [`std::fmt::Debug`]. The debug output
+/// is used to construct the failure message.
+///
+/// The predicate can also be a method on a struct, e.g.:
+///
+/// ```ignore
+/// struct AStruct {};
+///
+/// impl AStruct {
+/// fn equals_modulo(...) {...}
+/// }
+///
+/// verify_pred!((AStruct {}).equals_modulo(a, b, n))?;
+/// ```
+///
+/// **Warning:** This macro assumes that the arguments passed to the predicate
+/// are either *variables* or *calls to pure functions*. If two subsequent
+/// invocations to any of the expresssions passed as arguments result in
+/// different values, then the output message of a test failure will deviate
+/// from the values actually passed to the predicate. For this reason, *always
+/// assign the outputs of non-pure functions to variables before using them in
+/// this macro. For example:
+///
+/// ```ignore
+/// let output = generate_random_number(); // Assigned outside of verify_pred.
+/// verify_pred!(is_sufficiently_random(output))?;
+/// ```
+#[macro_export]
+macro_rules! verify_pred {
+ ([$($predicate:tt)*]($($arg:tt),* $(,)?)) => {
+ if !$($predicate)*($($arg),*) {
+ $crate::assertions::internal::report_failed_predicate(
+ concat!(stringify!($($predicate)*), stringify!(($($arg),*))),
+ vec![$((format!(concat!(stringify!($arg), " = {:?}"), $arg))),*],
+ $crate::internal::source_location::SourceLocation::new(
+ file!(),
+ line!(),
+ column!(),
+ ),
+ )
+ } else {
+ Ok(())
+ }
+ };
+
+ ([$($predicate:tt)*] $first:tt $($rest:tt)*) => {
+ $crate::verify_pred!([$($predicate)* $first] $($rest)*)
+ };
+
+ ($first:tt $($rest:tt)*) => {
+ $crate::verify_pred!([$first] $($rest)*)
+ };
+}
+
+/// Evaluates to a `Result` which contains an `Err` variant with the given test
+/// failure message.
+///
+/// This can be used to force the test to fail if its execution reaches a
+/// particular point. For example:
+///
+/// ```ignore
+/// match some_value {
+/// ExpectedVariant => {...}
+/// UnwantedVaraint => {
+/// fail!("This thing which should not happen happened anyway")?;
+/// }
+/// }
+/// ```
+///
+/// One may include formatted arguments in the failure message:
+///
+/// ```ignore
+/// match some_value {
+/// ExpectedVariant => {...}
+/// UnwantedVaraint => {
+/// fail!("This thing which should not happen happened anyway: {}", some_value)?;
+/// }
+/// }
+/// ```
+///
+/// One may also omit the message, in which case the test failure message will
+/// be generic:
+///
+/// ```ignore
+/// match some_value {
+/// ExpectedVariant => {...}
+/// UnwantedVaraint => {
+/// fail!()?;
+/// }
+/// }
+/// ```
+///
+/// Unlike `panic!` but analogously to [`verify_that`] and [`verify_pred`], this
+/// macro has no effect on the flow of control but instead returns a `Result`
+/// which must be handled by the invoking function. This can be done with the
+/// question mark operator (as above) or the method
+/// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure).
+#[macro_export]
+macro_rules! fail {
+ ($($message:expr),+) => {{
+ // We wrap this in a function so that we can annotate it with the must_use attribute.
+ // must_use on expressions is still experimental.
+ #[must_use = "The assertion result must be evaluated to affect the test result."]
+ fn create_fail_result(message: String) -> $crate::Result<()> {
+ Err($crate::internal::test_outcome::TestAssertionFailure::create(format!(
+ "{}\n{}",
+ message,
+ $crate::internal::source_location::SourceLocation::new(
+ file!(),
+ line!(),
+ column!(),
+ ),
+ )))
+ }
+ create_fail_result(format!($($message),*))
+ }};
+
+ () => { fail!("Test failed") };
+}
+
+/// Matches the given value against the given matcher, panicking if it does not
+/// match.
+///
+/// This is analogous to assertions in most Rust test libraries, where a failed
+/// assertion causes a panic.
+///
+/// **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) => {
+ match $crate::verify_that!($actual, $expected) {
+ 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
+/// true, panicking if it does not.
+///
+/// **Note for users of [GoogleTest for C++](http://google.github.io/googletest/):**
+/// This differs from the `ASSERT_PRED*` family of macros in that it panics
+/// rather than triggering an early return from the invoking function. To get
+/// behaviour equivalent to `ASSERT_PRED*`, use [`verify_pred!`] with the `?`
+/// operator.
+#[macro_export]
+macro_rules! assert_pred {
+ ($($content:tt)*) => {
+ match $crate::verify_pred!($($content)*) {
+ 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);
+ }
+ }
+ };
+}
+
+/// Matches the given value against the given matcher, marking the test as
+/// failed but continuing execution if it does not match.
+///
+/// This is a *non-fatal* assertion: the test continues
+/// execution in the event of assertion failure.
+///
+/// This can only be invoked inside tests with the
+/// [`googletest::test`][crate::test] attribute. The assertion must
+/// occur in the same thread as that running the test itself.
+///
+/// Invoking this macro is equivalent to using
+/// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure) as follows:
+///
+/// ```ignore
+/// verify_that!(actual, expected).and_log_failure()
+/// ```
+#[macro_export]
+macro_rules! expect_that {
+ ($actual:expr, $expected:expr) => {{
+ use $crate::GoogleTestSupport;
+ $crate::verify_that!($actual, $expected).and_log_failure();
+ }};
+}
+
+/// Asserts that the given predicate applied to the given arguments returns
+/// true, failing the test but continuing execution if not.
+///
+/// This is a *non-fatal* predicate assertion: the test
+/// continues execution in the event of assertion failure.
+///
+/// This can only be invoked inside tests with the
+/// [`googletest::test`][crate::test] attribute. The assertion must
+/// occur in the same thread as that running the test itself.
+///
+/// Invoking this macro is equivalent to using
+/// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure) as follows:
+///
+/// ```ignore
+/// verify_pred!(predicate(...)).and_log_failure()
+/// ```
+#[macro_export]
+macro_rules! expect_pred {
+ ($($content:tt)*) => {{
+ use $crate::GoogleTestSupport;
+ $crate::verify_pred!($($content)*).and_log_failure();
+ }};
+}
+
+/// Functions for use only by the procedural macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+ use crate::{
+ internal::{source_location::SourceLocation, test_outcome::TestAssertionFailure},
+ matcher::{create_assertion_failure, Matcher, MatcherResult},
+ };
+ use std::fmt::Debug;
+
+ /// Checks whether the matcher `expected` matches the value `actual`, adding
+ /// a test failure report if it does not match.
+ ///
+ /// Returns `Ok(())` if the value matches and `Err(())` if it does not
+ /// match.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[must_use = "The assertion result must be evaluated to affect the test result."]
+ pub fn check_matcher<T: Debug + ?Sized>(
+ actual: &T,
+ expected: impl Matcher<ActualT = T>,
+ actual_expr: &'static str,
+ source_location: SourceLocation,
+ ) -> Result<(), TestAssertionFailure> {
+ match expected.matches(actual) {
+ MatcherResult::Match => Ok(()),
+ MatcherResult::NoMatch => {
+ Err(create_assertion_failure(&expected, actual, actual_expr, source_location))
+ }
+ }
+ }
+
+ /// Constructs a `Result::Err(TestAssertionFailure)` for a predicate failure
+ /// as produced by the macro [`crate::verify_pred`].
+ ///
+ /// This intended only for use by the macro [`crate::verify_pred`].
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[must_use = "The assertion result must be evaluated to affect the test result."]
+ pub fn report_failed_predicate(
+ actual_expr: &'static str,
+ formatted_arguments: Vec<String>,
+ source_location: SourceLocation,
+ ) -> Result<(), TestAssertionFailure> {
+ Err(TestAssertionFailure::create(format!(
+ "{} was false with\n {}\n{}",
+ actual_expr,
+ formatted_arguments.join(",\n "),
+ source_location,
+ )))
+ }
+}
diff --git a/src/internal/mod.rs b/src/internal/mod.rs
new file mode 100644
index 0000000..2f37418
--- /dev/null
+++ b/src/internal/mod.rs
@@ -0,0 +1,18 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![doc(hidden)]
+
+pub mod source_location;
+pub mod test_outcome;
diff --git a/src/internal/source_location.rs b/src/internal/source_location.rs
new file mode 100644
index 0000000..4520c92
--- /dev/null
+++ b/src/internal/source_location.rs
@@ -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.
+
+use std::fmt::{Display, Error, Formatter};
+
+/// Encapsulates a location in source code.
+///
+/// This is intended to report the location of an assertion which failed to
+/// stdout.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub struct SourceLocation {
+ file: &'static str,
+ line: u32,
+ column: u32,
+}
+
+impl SourceLocation {
+ /// Constructs a new [`SourceLocation`].
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub fn new(file: &'static str, line: u32, column: u32) -> Self {
+ Self { file, line, column }
+ }
+}
+
+impl Display for SourceLocation {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+ write!(f, " at {}:{}:{}", self.file, self.line, self.column)
+ }
+}
diff --git a/src/internal/test_outcome.rs b/src/internal/test_outcome.rs
new file mode 100644
index 0000000..2171cc7
--- /dev/null
+++ b/src/internal/test_outcome.rs
@@ -0,0 +1,211 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::cell::{RefCell, RefMut};
+use std::fmt::{Debug, Display, Error, Formatter};
+use std::thread_local;
+
+/// The outcome hitherto of running a test.
+///
+/// This is kept as a running record as the test progresses. One can access it
+/// with `TestOutcome::with_current_test_outcome`.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub enum TestOutcome {
+ /// The test ran or is currently running and no assertions have failed.
+ Success,
+ /// The test ran or is currently running and at least one assertion has
+ /// failed.
+ Failure,
+}
+
+thread_local! {
+ static CURRENT_TEST_OUTCOME: RefCell<Option<TestOutcome>> = RefCell::new(None);
+}
+
+impl TestOutcome {
+ /// Resets the current test's [`TestOutcome`].
+ ///
+ /// This is intended only for use by the attribute macro
+ /// `#[googletest::test]`.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub fn init_current_test_outcome() {
+ Self::with_current_test_outcome(|mut current_test_outcome| {
+ *current_test_outcome = Some(TestOutcome::Success);
+ })
+ }
+
+ /// Evaluates the current test's [`TestOutcome`], producing a suitable
+ /// `Result`.
+ ///
+ /// The parameter `result` is the value returned by the test function
+ /// itself. This returns `Result::Err` with a `Display`-formatted string of
+ /// the error if `result` is `Result::Err`.
+ ///
+ /// Otherwise, this returns `Result::Err` precisely when a test failure has
+ /// been recorded with
+ /// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure).
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub fn close_current_test_outcome<E: Display>(
+ inner_result: Result<(), E>,
+ ) -> Result<(), TestFailure> {
+ TestOutcome::with_current_test_outcome(|mut outcome| {
+ let outer_result = match &*outcome {
+ Some(TestOutcome::Success) => match inner_result {
+ Ok(()) => Ok(()),
+ Err(_) => Err(TestFailure),
+ },
+ Some(TestOutcome::Failure) => Err(TestFailure),
+ None => {
+ panic!("No test context found. This indicates a bug in GoogleTest.")
+ }
+ };
+ if let Err(fatal_assertion_failure) = inner_result {
+ println!("{fatal_assertion_failure}");
+ }
+ *outcome = None;
+ outer_result
+ })
+ }
+
+ /// Returns a `Result` corresponding to the outcome of the currently running
+ /// test.
+ pub(crate) fn get_current_test_outcome() -> Result<(), TestAssertionFailure> {
+ TestOutcome::with_current_test_outcome(|mut outcome| {
+ let outcome = outcome
+ .as_mut()
+ .expect("No test context found. This indicates a bug in GoogleTest.");
+ match outcome {
+ TestOutcome::Success => Ok(()),
+ TestOutcome::Failure => Err(TestAssertionFailure::create("Test failed".into())),
+ }
+ })
+ }
+
+ /// Records that the currently running test has failed.
+ fn fail_current_test() {
+ TestOutcome::with_current_test_outcome(|mut outcome| {
+ let outcome = outcome
+ .as_mut()
+ .expect("No test context found. This indicates a bug in GoogleTest.");
+ *outcome = TestOutcome::Failure;
+ })
+ }
+
+ /// Runs `action` with the [`TestOutcome`] for the currently running test.
+ ///
+ /// This is primarily intended for use by assertion macros like
+ /// `expect_that!`.
+ fn with_current_test_outcome<T>(action: impl FnOnce(RefMut<Option<TestOutcome>>) -> T) -> T {
+ CURRENT_TEST_OUTCOME.with(|current_test_outcome| action(current_test_outcome.borrow_mut()))
+ }
+
+ /// Ensure that there is a test context present and panic if there is not.
+ pub(crate) fn ensure_text_context_present() {
+ TestOutcome::with_current_test_outcome(|outcome| {
+ outcome.as_ref().expect(
+ "
+No test context found.
+ * Did you annotate the test with googletest::test?
+ * Is the assertion running in the original test thread?
+",
+ );
+ })
+ }
+}
+
+/// A marking struct indicating that a test has failed.
+///
+/// This exists to implement the [Error][std::error::Error] trait. It displays
+/// to a message indicating that the actual test assertion failure messages are
+/// in the text above.
+pub struct TestFailure;
+
+impl std::error::Error for TestFailure {}
+
+impl std::fmt::Debug for TestFailure {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+ writeln!(f, "See failure output above")?;
+ Ok(())
+ }
+}
+
+impl std::fmt::Display for TestFailure {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+ writeln!(f, "See failure output above")?;
+ Ok(())
+ }
+}
+
+/// A report that a single test assertion failed.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+#[derive(Clone)]
+pub struct TestAssertionFailure {
+ /// A human-readable formatted string describing the error.
+ pub description: String,
+ pub custom_message: Option<String>,
+}
+
+impl TestAssertionFailure {
+ /// Creates a new instance with the given `description`.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ pub fn create(description: String) -> Self {
+ Self { description, custom_message: None }
+ }
+
+ pub(crate) fn log(&self) {
+ TestOutcome::fail_current_test();
+ print!("{}", self);
+ }
+}
+
+impl Display for TestAssertionFailure {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+ writeln!(f, "{}", self.description)?;
+ if let Some(custom_message) = &self.custom_message {
+ writeln!(f, "{}", custom_message)?;
+ }
+ Ok(())
+ }
+}
+
+// The standard Rust test harness outputs the TestAssertionFailure with the
+// Debug trait. We want the output to be formatted, so we use a custom Debug
+// implementation which defers to Display.
+impl Debug for TestAssertionFailure {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+ Display::fmt(self, f)
+ }
+}
+
+impl<T: std::error::Error> From<T> for TestAssertionFailure {
+ fn from(value: T) -> Self {
+ TestAssertionFailure::create(format!("{value}"))
+ }
+}
+
+#[cfg(feature = "proptest")]
+impl From<TestAssertionFailure> for proptest::test_runner::TestCaseError {
+ fn from(value: TestAssertionFailure) -> Self {
+ proptest::test_runner::TestCaseError::Fail(format!("{value}").into())
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..cec6da0
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,273 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![doc = include_str!("../crate_docs.md")]
+
+extern crate googletest_macro;
+
+#[cfg(test)]
+extern crate quickcheck;
+
+#[macro_use]
+pub mod assertions;
+pub mod internal;
+pub mod matcher;
+pub mod matcher_support;
+pub mod matchers;
+
+/// Re-exports of the symbols in this crate which are most likely to be used.
+///
+/// This includes:
+/// * All assertion macros,
+/// * Traits and type definitions normally used by tests, and
+/// * All built-in matchers.
+///
+/// Typically, one imports everything in the prelude in one's test module:
+///
+/// ```
+/// mod tests {
+/// use googletest::prelude::*;
+/// }
+/// ```
+pub mod prelude {
+ pub use super::matcher::Matcher;
+ pub use super::matchers::*;
+ pub use super::verify_current_test_outcome;
+ pub use super::GoogleTestSupport;
+ pub use super::IntoTestResult;
+ 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;
+
+use internal::test_outcome::{TestAssertionFailure, TestOutcome};
+
+/// A `Result` whose `Err` variant indicates a test failure.
+///
+/// All test functions should return `Result<()>`.
+///
+/// This can be used with subroutines which may cause the test to fatally fail
+/// and which return some value needed by the caller. For example:
+///
+/// ```ignore
+/// fn load_file_content_as_string() -> Result<String> {
+/// let file_stream = load_file().err_to_test_failure()?;
+/// Ok(file_stream.to_string())
+/// }
+/// ```
+///
+/// The `Err` variant contains a [`TestAssertionFailure`] which carries the data
+/// of the (fatal) assertion failure which generated this result. Non-fatal
+/// assertion failures, which log the failure and report the test as having
+/// failed but allow it to continue running, are not encoded in this type.
+pub type Result<T> = std::result::Result<T, TestAssertionFailure>;
+
+/// Returns a [`Result`] corresponding to the outcome of the currently running
+/// test.
+///
+/// This returns `Result::Err` precisely if the current test has recorded at
+/// least one test assertion failure via [`expect_that!`][crate::expect_that],
+/// [`expect_pred!`][crate::expect_pred], or
+/// [`GoogleTestSupport::and_log_failure`]. It can be used in concert with the
+/// `?` operator to continue execution of the test conditionally on there not
+/// having been any failure yet.
+///
+/// This requires the use of the [`#[googletest::test]`][crate::test] attribute
+/// macro.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # /* Make sure this also compiles as a doctest.
+/// #[googletest::test]
+/// # */
+/// # fn foo() -> u32 { 1 }
+/// # fn bar() -> u32 { 2 }
+/// fn should_fail_and_not_execute_last_assertion() -> Result<()> {
+/// # googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+/// expect_that!(foo(), eq(2)); // May fail, but will not abort the test.
+/// expect_that!(bar(), gt(1)); // May fail, but will not abort the test.
+/// verify_current_test_outcome()?; // Aborts the test if one of the previous assertions failed.
+/// verify_that!(foo(), gt(0)) // Does not execute if the line above aborts.
+/// }
+/// # verify_that!(should_fail_and_not_execute_last_assertion(), err(displays_as(contains_substring("Test failed")))).unwrap();
+/// ```
+pub fn verify_current_test_outcome() -> Result<()> {
+ TestOutcome::get_current_test_outcome()
+}
+
+/// Adds to `Result` support for GoogleTest Rust functionality.
+pub trait GoogleTestSupport {
+ /// If `self` is a `Result::Err`, writes to `stdout` a failure report
+ /// and marks the test failed. Otherwise, does nothing.
+ ///
+ /// This can be used for non-fatal test assertions, for example:
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # use googletest::internal::test_outcome::TestOutcome;
+ /// # TestOutcome::init_current_test_outcome();
+ /// let actual = 42;
+ /// verify_that!(actual, eq(42)).and_log_failure();
+ /// // Test still passing; nothing happens
+ /// verify_that!(actual, eq(10)).and_log_failure();
+ /// // Test now fails and failure output to stdout
+ /// verify_that!(actual, eq(100)).and_log_failure();
+ /// // Test still fails and new failure also output to stdout
+ /// # TestOutcome::close_current_test_outcome::<&str>(Ok(())).unwrap_err();
+ /// ```
+ fn and_log_failure(self);
+
+ /// Adds `message` to the logged failure message if `self` is a
+ /// `Result::Err`. Otherwise, does nothing.
+ ///
+ /// If this method is called more than once, only `message` from the last
+ /// invocation is output.
+ ///
+ /// For example:
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_fail() -> Result<()> {
+ /// let actual = 0;
+ /// verify_that!(actual, eq(42)).failure_message("Actual was wrong!")?;
+ /// # Ok(())
+ /// # }
+ /// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual was wrong"))))
+ /// # .unwrap();
+ /// ```
+ ///
+ /// results in the following failure message:
+ ///
+ /// ```text
+ /// Expected: actual equal to 42
+ /// but was: 0
+ /// Actual was wrong!
+ /// ```
+ ///
+ /// One can pass a `String` too:
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_fail() -> Result<()> {
+ /// let actual = 0;
+ /// verify_that!(actual, eq(42))
+ /// .failure_message(format!("Actual {} was wrong!", actual))?;
+ /// # Ok(())
+ /// # }
+ /// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
+ /// # .unwrap();
+ /// ```
+ ///
+ /// However, consider using [`GoogleTestSupport::with_failure_message`]
+ /// instead in that case to avoid unnecessary memory allocation when the
+ /// message is not needed.
+ fn failure_message(self, message: impl Into<String>) -> Self;
+
+ /// Adds the output of the closure `provider` to the logged failure message
+ /// if `self` is a `Result::Err`. Otherwise, does nothing.
+ ///
+ /// This is analogous to [`GoogleTestSupport::failure_message`] but
+ /// only executes the closure `provider` if it actually produces the
+ /// message, thus saving possible memory allocation.
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_fail() -> Result<()> {
+ /// let actual = 0;
+ /// verify_that!(actual, eq(42))
+ /// .with_failure_message(|| format!("Actual {} was wrong!", actual))?;
+ /// # Ok(())
+ /// # }
+ /// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
+ /// # .unwrap();
+ /// ```
+ fn with_failure_message(self, provider: impl FnOnce() -> String) -> Self;
+}
+
+impl<T> GoogleTestSupport for std::result::Result<T, TestAssertionFailure> {
+ fn and_log_failure(self) {
+ TestOutcome::ensure_text_context_present();
+ if let Err(failure) = self {
+ failure.log();
+ }
+ }
+
+ fn failure_message(mut self, message: impl Into<String>) -> Self {
+ if let Err(ref mut failure) = self {
+ failure.custom_message = Some(message.into());
+ }
+ self
+ }
+
+ fn with_failure_message(mut self, provider: impl FnOnce() -> String) -> Self {
+ if let Err(ref mut failure) = self {
+ failure.custom_message = Some(provider());
+ }
+ self
+ }
+}
+
+/// Provides an extension method for converting an arbitrary type into a
+/// [`Result`].
+///
+/// A type can implement this trait to provide an easy way to return immediately
+/// from a test in conjunction with the `?` operator. This is useful for
+/// [`Result`][std::result::Result] types whose `Result::Err` variant does not
+/// implement [`std::error::Error`].
+///
+/// There is an implementation of this trait for [`anyhow::Error`] (which does
+/// not implement `std::error::Error`) when the `anyhow` feature is enabled.
+/// Importing this trait allows one to easily map [`anyhow::Error`] to a test
+/// failure:
+///
+/// ```ignore
+/// #[test]
+/// fn should_work() -> Result<()> {
+/// let value = something_which_can_fail().into_test_result()?;
+/// ...
+/// }
+///
+/// fn something_which_can_fail() -> anyhow::Result<...> { ... }
+/// ```
+pub trait IntoTestResult<T> {
+ /// Converts this instance into a [`Result`].
+ ///
+ /// Typically, the `Self` type is itself a [`std::result::Result`]. This
+ /// method should then map the `Err` variant to a [`TestAssertionFailure`]
+ /// and leave the `Ok` variant unchanged.
+ fn into_test_result(self) -> Result<T>;
+}
+
+#[cfg(feature = "anyhow")]
+impl<T> IntoTestResult<T> for std::result::Result<T, anyhow::Error> {
+ fn into_test_result(self) -> std::result::Result<T, TestAssertionFailure> {
+ self.map_err(|e| TestAssertionFailure::create(format!("{e}")))
+ }
+}
+
+#[cfg(feature = "proptest")]
+impl<OkT, CaseT: std::fmt::Debug> IntoTestResult<OkT>
+ for std::result::Result<OkT, proptest::test_runner::TestError<CaseT>>
+{
+ fn into_test_result(self) -> std::result::Result<OkT, TestAssertionFailure> {
+ self.map_err(|e| TestAssertionFailure::create(format!("{e}")))
+ }
+}
diff --git a/src/matcher.rs b/src/matcher.rs
new file mode 100644
index 0000000..83a4475
--- /dev/null
+++ b/src/matcher.rs
@@ -0,0 +1,265 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! The components required to implement matchers.
+
+use crate::internal::source_location::SourceLocation;
+use crate::internal::test_outcome::TestAssertionFailure;
+use crate::matchers::conjunction_matcher::ConjunctionMatcher;
+use crate::matchers::disjunction_matcher::DisjunctionMatcher;
+use std::fmt::Debug;
+
+/// An interface for checking an arbitrary condition on a datum.
+pub trait Matcher {
+ /// The type against which this matcher matches.
+ type ActualT: Debug + ?Sized;
+
+ /// Returns whether the condition matches the datum `actual`.
+ ///
+ /// The trait implementation defines what it means to "match". Often the
+ /// matching condition is based on data stored in the matcher. For example,
+ /// `eq` matches when its stored expected value is equal (in the sense of
+ /// the `==` operator) to the value `actual`.
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult;
+
+ /// Returns a description of `self` or a negative description if
+ /// `matcher_result` is `DoesNotMatch`.
+ ///
+ /// The function should print a verb phrase that describes the property a
+ /// value matching, respectively not matching, this matcher should have.
+ /// The subject of the verb phrase is the value being matched.
+ ///
+ /// The output appears next to `Expected` in an assertion failure message.
+ /// For example:
+ ///
+ /// ```text
+ /// Value of: ...
+ /// Expected: is equal to 7
+ /// ^^^^^^^^^^^^^
+ /// Actual: ...
+ /// ```
+ ///
+ /// When the matcher contains one or more inner matchers, the implementation
+ /// should invoke [`Self::describe`] on the inner matchers to complete the
+ /// description. It should place the inner description at a point where a
+ /// verb phrase would fit. For example, the matcher
+ /// [`some`][crate::matchers::some] implements `describe` as follows:
+ ///
+ /// ```ignore
+ /// fn describe(&self, matcher_result: MatcherResult) -> String {
+ /// match matcher_result {
+ /// MatcherResult::Matches => {
+ /// format!("has a value which {}", self.inner.describe(MatcherResult::Matches))
+ /// // Inner matcher invocation: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ /// }
+ /// MatcherResult::DoesNotMatch => {...} // Similar to the above
+ /// }
+ /// }
+ /// ```
+ ///
+ /// The output expectation differs from that of
+ /// [`explain_match`][Self::explain_match] in that it is a verb phrase
+ /// (beginning with a verb like "is") rather than a relative clause
+ /// (beginning with "which" or "whose"). This difference is because the
+ /// output of `explain_match` is always used adjectivally to describe the
+ /// actual value, while `describe` is used in contexts where a relative
+ /// clause would not make sense.
+ fn describe(&self, matcher_result: MatcherResult) -> String;
+
+ /// Prepares a [`String`] describing how the expected value
+ /// encoded in this instance matches or does not match the given value
+ /// `actual`.
+ ///
+ /// This should be in the form of a relative clause, i.e. something starting
+ /// with a relative pronoun such as "which" or "whose". It will appear next
+ /// to the actual value in an assertion failure. For example:
+ ///
+ /// ```text
+ /// Value of: ...
+ /// Expected: ...
+ /// Actual: ["Something"], which does not contain "Something else"
+ /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ /// ```
+ ///
+ /// The default implementation relies on [`describe`][Self::describe]. Thus
+ /// it does not make any use of the actual value itself, but rather only
+ /// whether the value is matched.
+ ///
+ /// Override the default implementation to provide additional context on why
+ /// a particular value matched or did not match. For example, the
+ /// [`container_eq`][crate::matchers::container_eq] matcher displays
+ /// information on which elements of the actual value were not present in
+ /// the expected value and vice versa.
+ ///
+ /// This implementation should be overridden in any matcher which contains
+ /// one or more inner matchers. The implementation should invoke
+ /// `explain_match` on the inner matchers, so that the generated match
+ /// explanation also reflects their implementation. Without this, the match
+ /// explanation of the inner matchers will not be able to make use of the
+ /// actual value at all.
+ ///
+ /// For example, the `explain_match` implementation of the matcher
+ /// [`points_to`][crate::matchers::points_to] defers immediately to the
+ /// inner matcher and appears as follows:
+ ///
+ /// ```ignore
+ /// fn explain_match(&self, actual: &Self::ActualT) -> String {
+ /// self.expected.explain_match(actual.deref())
+ /// }
+ /// ```
+ ///
+ /// The matcher can also provide some additional context before deferring to
+ /// an inner matcher. In that case it should invoke `explain_match` on the
+ /// inner matcher at a point where a relative clause would fit. For example:
+ ///
+ /// ```ignore
+ /// fn explain_match(&self, actual: &Self::ActualT) -> String {
+ /// format!("which points to a value {}", self.expected.explain_match(actual.deref()))
+ /// }
+ /// ```
+ fn explain_match(&self, actual: &Self::ActualT) -> String {
+ format!("which {}", self.describe(self.matches(actual)))
+ }
+
+ /// Constructs a matcher that matches both `self` and `right`.
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_pass() -> Result<()> {
+ /// verify_that!("A string", starts_with("A").and(ends_with("string")))?; // Passes
+ /// # Ok(())
+ /// # }
+ /// # fn should_fail_1() -> Result<()> {
+ /// verify_that!("A string", starts_with("Another").and(ends_with("string")))?; // Fails
+ /// # Ok(())
+ /// # }
+ /// # fn should_fail_2() -> Result<()> {
+ /// verify_that!("A string", starts_with("A").and(ends_with("non-string")))?; // Fails
+ /// # Ok(())
+ /// # }
+ /// # should_pass().unwrap();
+ /// # should_fail_1().unwrap_err();
+ /// # should_fail_2().unwrap_err();
+ /// ```
+ // TODO(b/264518763): Replace the return type with impl Matcher and reduce
+ // visibility of ConjunctionMatcher once impl in return position in trait
+ // methods is stable.
+ fn and<Right: Matcher<ActualT = Self::ActualT>>(
+ self,
+ right: Right,
+ ) -> ConjunctionMatcher<Self, Right>
+ where
+ Self: Sized,
+ {
+ ConjunctionMatcher::new(self, right)
+ }
+
+ /// Constructs a matcher that matches when at least one of `self` or `right`
+ /// matches the input.
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_pass() -> Result<()> {
+ /// verify_that!(10, eq(2).or(ge(5)))?; // Passes
+ /// verify_that!(10, eq(2).or(eq(5)).or(ge(9)))?; // Passes
+ /// # Ok(())
+ /// # }
+ /// # fn should_fail() -> Result<()> {
+ /// verify_that!(10, eq(2).or(ge(15)))?; // Fails
+ /// # Ok(())
+ /// # }
+ /// # should_pass().unwrap();
+ /// # should_fail().unwrap_err();
+ /// ```
+ // TODO(b/264518763): Replace the return type with impl Matcher and reduce
+ // visibility of DisjunctionMatcher once impl in return position in trait
+ // methods is stable.
+ fn or<Right: Matcher<ActualT = Self::ActualT>>(
+ self,
+ right: Right,
+ ) -> DisjunctionMatcher<Self, Right>
+ where
+ Self: Sized,
+ {
+ DisjunctionMatcher::new(self, right)
+ }
+}
+
+/// Any actual value whose debug length is greater than this value will be
+/// pretty-printed. Otherwise, it will have normal debug output formatting.
+const PRETTY_PRINT_LENGTH_THRESHOLD: usize = 60;
+
+/// Constructs a [`TestAssertionFailure`] reporting that the given `matcher`
+/// does not match the value `actual`.
+///
+/// The parameter `actual_expr` contains the expression which was evaluated to
+/// obtain `actual`.
+pub(crate) fn create_assertion_failure<T: Debug + ?Sized>(
+ matcher: &impl Matcher<ActualT = T>,
+ actual: &T,
+ actual_expr: &'static str,
+ source_location: SourceLocation,
+) -> TestAssertionFailure {
+ let actual_formatted = format!("{actual:?}");
+ let actual_formatted = if actual_formatted.len() > PRETTY_PRINT_LENGTH_THRESHOLD {
+ format!("{actual:#?}")
+ } else {
+ actual_formatted
+ };
+ TestAssertionFailure::create(format!(
+ "\
+Value of: {actual_expr}
+Expected: {}
+Actual: {actual_formatted},
+ {}
+{source_location}",
+ matcher.describe(MatcherResult::Match),
+ matcher.explain_match(actual),
+ ))
+}
+
+/// The result of applying a [`Matcher`] on an actual value.
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum MatcherResult {
+ /// The actual value matches according to the [`Matcher`] definition.
+ Match,
+ /// The actual value does not match according to the [`Matcher`] definition.
+ NoMatch,
+}
+
+impl From<bool> for MatcherResult {
+ fn from(b: bool) -> Self {
+ if b { MatcherResult::Match } else { MatcherResult::NoMatch }
+ }
+}
+
+impl From<MatcherResult> for bool {
+ fn from(matcher_result: MatcherResult) -> Self {
+ matcher_result.is_match()
+ }
+}
+
+impl MatcherResult {
+ /// Returns `true` if `self` is [`MatcherResult::Match`], otherwise
+ /// `false`.
+ pub fn is_match(self) -> bool {
+ matches!(self, MatcherResult::Match)
+ }
+
+ /// Returns `true` if `self` is [`MatcherResult::NoMatch`], otherwise
+ /// `false`.
+ pub fn is_no_match(self) -> bool {
+ matches!(self, MatcherResult::NoMatch)
+ }
+}
diff --git a/src/matcher_support/count_elements.rs b/src/matcher_support/count_elements.rs
new file mode 100644
index 0000000..662dcc9
--- /dev/null
+++ b/src/matcher_support/count_elements.rs
@@ -0,0 +1,32 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Counts the number of elements in `value`.
+///
+/// This uses [`Iterator::size_hint`] when that function returns an
+/// unambiguous answer, i.e., the upper bound exists and the lower and upper
+/// bounds agree. Otherwise it iterates through `value` and counts the
+/// elements.
+pub(crate) fn count_elements<ContainerT: ?Sized>(value: &ContainerT) -> usize
+where
+ for<'b> &'b ContainerT: IntoIterator,
+{
+ let iterator = value.into_iter();
+ if let (lower, Some(higher)) = iterator.size_hint() {
+ if lower == higher {
+ return lower;
+ }
+ }
+ iterator.count()
+}
diff --git a/src/matcher_support/description.rs b/src/matcher_support/description.rs
new file mode 100644
index 0000000..ce074e0
--- /dev/null
+++ b/src/matcher_support/description.rs
@@ -0,0 +1,410 @@
+// 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
new file mode 100644
index 0000000..8469f42
--- /dev/null
+++ b/src/matcher_support/edit_distance.rs
@@ -0,0 +1,648 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::fmt::Debug;
+
+/// Maximum number of edits which can exist before [`edit_list`] falls back to a
+/// complete rewrite to produce the edit list.
+///
+/// Increasing this limit increases the accuracy of [`edit_list`] while
+/// quadratically increasing its worst-case runtime.
+const MAX_DISTANCE: i32 = 25;
+
+/// The difference between two inputs as produced by [`edit_list`].
+#[derive(Debug)]
+pub(crate) enum Difference<T> {
+ /// No differences were detected at all.
+ Equal,
+
+ /// At most [`MAX_DISTANCE`] edits are required to convert one input to the
+ /// other.
+ ///
+ /// Contains the list of [`Edit`] to perform the transformation.
+ Editable(Vec<Edit<T>>),
+
+ /// More than [`MAX_DISTANCE`] edits are required to convert one input to
+ /// the other.
+ ///
+ /// The inputs are therefore considered unrelated and no edit list is
+ /// provided.
+ Unrelated,
+}
+
+/// An edit operation on two sequences of `T`.
+#[derive(Debug, Clone)]
+pub(crate) enum Edit<T> {
+ /// An extra `T` was added to the actual sequence.
+ ExtraActual(T),
+
+ /// An extra `T` was added to the expected sequence.
+ ExtraExpected(T),
+
+ /// An element was added to each sequence.
+ Both(T),
+
+ /// Additional (unlisted) elements are present in the actual sequence.
+ ///
+ /// This is only output in the mode [`Mode::Prefix`]. Its presence precludes
+ /// reconstructing the actual sequence from the expected sequence.
+ AdditionalActual,
+}
+
+/// Controls the termination condition of [`edit_list`].
+#[derive(Clone, Copy)]
+pub(crate) enum Mode {
+ /// Indicates that the two arguments are intended to be equal.
+ ///
+ /// The entire edit list to transform between `actual` and `expected` is
+ /// returned.
+ Exact,
+
+ /// Indicates that `expected` is inteded to be a prefix of `actual`.
+ ///
+ /// Any additional parts of `actual` after the prefix `expected` are omitted
+ /// from the output.
+ Prefix,
+
+ /// Similar to [`Mode::Prefix`], except it is also assumed that `actual` has
+ /// some number of initial lines which should not be in the output.
+ ///
+ /// Any initial [`Edit::ExtraActual`] entries are replaced with
+ /// [`Edit::AdditionalActual`] in the edit list. If the first entry which is
+ /// not an [`Edit::ExtraActual`] is [`Edit::ExtraExpected`], then the last
+ /// [`Edit::ExtraActual`] is actual in the output.
+ Contains,
+}
+
+/// Computes the edit list of `actual` and `expected`.
+///
+/// If `actual` and `expected` are equal, then this returns
+/// [`Difference::Equal`]. If they are different but have an
+/// [edit distance](https://en.wikipedia.org/wiki/Edit_distance)
+/// of at most [`MAX_DISTANCE`], this returns [`Difference::Editable`] with the
+/// sequence of [`Edit`] which can be applied to `actual` to obtain `expected`.
+/// Otherwise this returns [`Difference::Unrelated`].
+///
+/// This uses [Myers Algorithm](https://neil.fraser.name/writing/diff/myers.pdf)
+/// with a maximum edit distance of [`MAX_DISTANCE`]. Thus the worst-case
+/// runtime is linear in both the input length and [`MAX_DISTANCE`].
+pub(crate) fn edit_list<T: PartialEq + Copy>(
+ actual: impl IntoIterator<Item = T>,
+ expected: impl IntoIterator<Item = T>,
+ mode: Mode,
+) -> Difference<T> {
+ let actual: Vec<_> = actual.into_iter().collect();
+ let expected: Vec<_> = expected.into_iter().collect();
+
+ let mut paths_last: Vec<Path<T>> = Vec::new();
+
+ for distance in 0..=MAX_DISTANCE {
+ let mut paths_current = Vec::new();
+ for k in (-distance..=distance).step_by(2) {
+ // The following will be None when k is at the edges of the range,
+ // since no paths have been created for k outside the range in the
+ // previous iteration.
+ let path_k_minus_1 = paths_last.get(index_of_k(k - 1, -distance + 1));
+ let path_k_plus_1 = paths_last.get(index_of_k(k + 1, -distance + 1));
+
+ let (mut path, edit) = match (path_k_minus_1, path_k_plus_1) {
+ // This the first (outer) iteration.
+ (None, None) => (Path::default(), None),
+
+ // k = -distance. There is no previous parent path yet.
+ (None, Some(path_k_plus_1)) => (
+ path_k_plus_1.clone(),
+ expected.get(path_k_plus_1.expected_endpoint).copied().map(Edit::ExtraExpected),
+ ),
+
+ // k = distance. There is no next parent path yet.
+ (Some(path_k_minus_1), None) => (
+ path_k_minus_1.extend_actual_endpoint(),
+ actual.get(path_k_minus_1.actual_endpoint).copied().map(Edit::ExtraActual),
+ ),
+
+ // k is strictly between -distance and distance. Both parent paths were set in the
+ // last iteration.
+ (Some(path_k_minus_1), Some(path_k_plus_1)) => {
+ // This decides whether the algorithm prefers to add an edit
+ // from the actual or from the expected when the rows differ. We
+ // alternate so that the elements of differing blocks
+ // interleave rather than all elements of each respective
+ // side being output in a single block.
+ if (distance % 2 == 0
+ && path_k_plus_1.actual_endpoint > path_k_minus_1.actual_endpoint)
+ || (distance % 2 == 1
+ && path_k_plus_1.expected_endpoint > path_k_minus_1.expected_endpoint)
+ {
+ (
+ path_k_plus_1.clone(),
+ expected
+ .get(path_k_plus_1.expected_endpoint)
+ .copied()
+ .map(Edit::ExtraExpected),
+ )
+ } else {
+ (
+ path_k_minus_1.extend_actual_endpoint(),
+ actual
+ .get(path_k_minus_1.actual_endpoint)
+ .copied()
+ .map(Edit::ExtraActual),
+ )
+ }
+ }
+ };
+ path.edits.extend(edit);
+
+ // Advance through any common elements starting at the current path.
+ let (mut actual_endpoint, mut expected_endpoint) =
+ (path.actual_endpoint, (path.actual_endpoint as i32 - k) as usize);
+ while actual_endpoint < actual.len()
+ && expected_endpoint < expected.len()
+ && actual[actual_endpoint] == expected[expected_endpoint]
+ {
+ path.edits.push(Edit::Both(actual[actual_endpoint]));
+ (actual_endpoint, expected_endpoint) = (actual_endpoint + 1, expected_endpoint + 1);
+ }
+
+ // If we have exhausted both inputs, we are done.
+ if actual_endpoint == actual.len() && expected_endpoint == expected.len() {
+ return if path.edits.iter().any(|v| !matches!(v, Edit::Both(_))) {
+ if matches!(mode, Mode::Contains) {
+ compress_prefix_and_suffix(&mut path.edits);
+ }
+ Difference::Editable(path.edits)
+ } else {
+ Difference::Equal
+ };
+ }
+
+ path.actual_endpoint = actual_endpoint;
+ path.expected_endpoint = expected_endpoint;
+ paths_current.push(path);
+ }
+
+ if matches!(mode, Mode::Prefix) {
+ if let Some(path) = paths_current
+ .iter_mut()
+ .filter(|p| p.expected_endpoint == expected.len())
+ .max_by(|p1, p2| p1.edits.len().cmp(&p2.edits.len()))
+ {
+ // We've reached the end of the expected side but there could still be a
+ // corresponding line on the actual which we haven't picked up into the edit
+ // list. We'll just add it manually to the edit list. There's no
+ // real harm doing so -- worst case is that there's an
+ // additional line when there didn't have to be.
+ if let Some(Edit::ExtraExpected(_)) = path.edits.last() {
+ if path.actual_endpoint < actual.len() {
+ // The edits from the actual should come before the corresponding one from
+ // the expected, so we insert rather than push.
+ path.edits.insert(
+ path.edits.len() - 1,
+ Edit::ExtraActual(actual[path.actual_endpoint]),
+ );
+ }
+ }
+ path.edits.push(Edit::AdditionalActual);
+ return if path.edits.iter().any(|v| !matches!(v, Edit::Both(_))) {
+ Difference::Editable(std::mem::take(&mut path.edits))
+ } else {
+ Difference::Equal
+ };
+ }
+ }
+
+ paths_last = paths_current;
+ }
+
+ Difference::Unrelated
+}
+
+fn index_of_k(k: i32, k_min: i32) -> usize {
+ ((k - k_min) / 2) as usize
+}
+
+fn compress_prefix_and_suffix<T>(edits: &mut Vec<Edit<T>>) {
+ if let Some(mut first_non_extra_actual_edit) =
+ edits.iter().position(|e| !matches!(e, Edit::ExtraActual(_)))
+ {
+ if first_non_extra_actual_edit > 1
+ && matches!(edits[first_non_extra_actual_edit], Edit::ExtraExpected(_))
+ {
+ first_non_extra_actual_edit -= 1;
+ }
+ edits.splice(..first_non_extra_actual_edit, [Edit::AdditionalActual]);
+ }
+
+ if let Some(mut last_non_extra_actual_edit) =
+ edits.iter().rposition(|e| !matches!(e, Edit::ExtraActual(_)))
+ {
+ if last_non_extra_actual_edit < edits.len() - 1
+ && matches!(edits[last_non_extra_actual_edit], Edit::ExtraExpected(_))
+ {
+ last_non_extra_actual_edit += 1;
+ }
+ edits.splice(last_non_extra_actual_edit + 1.., [Edit::AdditionalActual]);
+ }
+}
+
+#[derive(Clone)]
+struct Path<T: Clone> {
+ actual_endpoint: usize,
+ expected_endpoint: usize,
+ edits: Vec<Edit<T>>,
+}
+
+impl<T: Clone> Default for Path<T> {
+ fn default() -> Self {
+ Self { actual_endpoint: 0, expected_endpoint: 0, edits: vec![] }
+ }
+}
+
+impl<T: Clone> Path<T> {
+ fn extend_actual_endpoint(&self) -> Self {
+ Self {
+ actual_endpoint: self.actual_endpoint + 1,
+ expected_endpoint: self.expected_endpoint,
+ edits: self.edits.clone(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::prelude::*;
+ use quickcheck::{quickcheck, Arbitrary, TestResult};
+
+ #[test]
+ fn returns_equal_when_strings_are_equal() -> Result<()> {
+ let result = edit_list(["A string"], ["A string"], Mode::Exact);
+ verify_that!(result, matches_pattern!(Difference::Equal))
+ }
+
+ #[test]
+ fn returns_sequence_of_two_common_parts() -> Result<()> {
+ let result = edit_list(
+ ["A string (1)", "A string (2)"],
+ ["A string (1)", "A string (2)"],
+ Mode::Exact,
+ );
+ verify_that!(result, matches_pattern!(Difference::Equal))
+ }
+
+ #[test]
+ fn returns_extra_actual_when_only_actual_has_content() -> Result<()> {
+ let result = edit_list(["A string"], [], Mode::Exact);
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![matches_pattern!(
+ Edit::ExtraActual(eq("A string"))
+ )]))
+ )
+ }
+
+ #[test]
+ fn returns_extra_expected_when_only_expected_has_content() -> Result<()> {
+ let result = edit_list([], ["A string"], Mode::Exact);
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![matches_pattern!(
+ Edit::ExtraExpected(eq("A string"))
+ )]))
+ )
+ }
+
+ #[test]
+ fn returns_extra_actual_followed_by_extra_expected_with_two_unequal_strings() -> Result<()> {
+ let result = edit_list(["A string"], ["Another string"], Mode::Exact);
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::ExtraActual(eq("A string"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Another string"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn interleaves_extra_actual_and_extra_expected_when_multiple_lines_differ() -> Result<()> {
+ let result =
+ edit_list(["A string", "A string"], ["Another string", "Another string"], Mode::Exact);
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::ExtraActual(eq("A string"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Another string"))),
+ matches_pattern!(Edit::ExtraActual(eq("A string"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Another string"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn returns_common_part_plus_difference_when_there_is_common_prefix() -> Result<()> {
+ let result = edit_list(
+ ["Common part", "Actual only"],
+ ["Common part", "Expected only"],
+ Mode::Exact,
+ );
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::Both(eq("Common part"))),
+ matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn returns_common_part_plus_extra_actual_when_actual_has_extra_suffix() -> Result<()> {
+ let result = edit_list(["Common part", "Actual only"], ["Common part"], Mode::Exact);
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::Both(eq("Common part"))),
+ matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn returns_common_part_plus_extra_expected_when_expected_has_extra_suffix() -> Result<()> {
+ let result = edit_list(["Common part"], ["Common part", "Expected only"], Mode::Exact);
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::Both(eq("Common part"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn returns_difference_plus_common_part_when_there_is_common_suffix() -> Result<()> {
+ let result = edit_list(
+ ["Actual only", "Common part"],
+ ["Expected only", "Common part"],
+ Mode::Exact,
+ );
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+ matches_pattern!(Edit::Both(eq("Common part"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn returns_difference_plus_common_part_plus_difference_when_there_is_common_infix() -> Result<()>
+ {
+ let result = edit_list(
+ ["Actual only (1)", "Common part", "Actual only (2)"],
+ ["Expected only (1)", "Common part", "Expected only (2)"],
+ Mode::Exact,
+ );
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::ExtraActual(eq("Actual only (1)"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Expected only (1)"))),
+ matches_pattern!(Edit::Both(eq("Common part"))),
+ matches_pattern!(Edit::ExtraActual(eq("Actual only (2)"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Expected only (2)"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn returns_common_part_plus_difference_plus_common_part_when_there_is_common_prefix_and_suffix()
+ -> Result<()> {
+ let result = edit_list(
+ ["Common part (1)", "Actual only", "Common part (2)"],
+ ["Common part (1)", "Expected only", "Common part (2)"],
+ Mode::Exact,
+ );
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::Both(eq("Common part (1)"))),
+ matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+ matches_pattern!(Edit::Both(eq("Common part (2)"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn returns_common_part_plus_extra_actual_plus_common_part_when_there_is_common_prefix_and_suffix()
+ -> Result<()> {
+ let result = edit_list(
+ ["Common part (1)", "Actual only", "Common part (2)"],
+ ["Common part (1)", "Common part (2)"],
+ Mode::Exact,
+ );
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::Both(eq("Common part (1)"))),
+ matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+ matches_pattern!(Edit::Both(eq("Common part (2)"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn returns_common_part_plus_extra_expected_plus_common_part_when_there_is_common_prefix_and_suffix()
+ -> Result<()> {
+ let result = edit_list(
+ ["Common part (1)", "Common part (2)"],
+ ["Common part (1)", "Expected only", "Common part (2)"],
+ Mode::Exact,
+ );
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::Both(eq("Common part (1)"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+ matches_pattern!(Edit::Both(eq("Common part (2)"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn skips_extra_parts_on_actual_at_end_in_prefix_mode() -> Result<()> {
+ let result = edit_list(
+ ["Common part", "Actual only"],
+ ["Expected only", "Common part"],
+ Mode::Prefix,
+ );
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(not(contains(matches_pattern!(
+ Edit::ExtraActual(eq("Actual only"))
+ )))))
+ )
+ }
+
+ #[test]
+ fn does_not_skip_extra_parts_on_actual_in_prefix_mode_at_end_when_they_are_in_common()
+ -> Result<()> {
+ let result = edit_list(
+ ["Actual only", "Common part"],
+ ["Expected only", "Common part"],
+ Mode::Prefix,
+ );
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+ matches_pattern!(Edit::Both(eq("Common part"))),
+ ]))
+ )
+ }
+
+ #[test]
+ fn does_not_skip_corresponding_line_on_actual_when_actual_and_expected_differ_in_prefix_mode()
+ -> Result<()> {
+ let result = edit_list(["Actual only"], ["Expected only"], Mode::Prefix);
+ verify_that!(
+ result,
+ matches_pattern!(Difference::Editable(elements_are![
+ matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+ matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+ matches_pattern!(Edit::AdditionalActual),
+ ]))
+ )
+ }
+
+ #[test]
+ fn returns_unrelated_when_maximum_distance_exceeded() -> Result<()> {
+ let result = edit_list(0..=20, 20..40, Mode::Exact);
+ verify_that!(result, matches_pattern!(Difference::Unrelated))
+ }
+
+ quickcheck! {
+ fn edit_list_edits_actual_to_expected(
+ actual: Vec<Alphabet>,
+ expected: Vec<Alphabet>
+ ) -> TestResult {
+ match edit_list(actual.clone(), expected.clone(), Mode::Exact) {
+ Difference::Equal => TestResult::from_bool(actual == expected),
+ Difference::Editable(edit_list) => {
+ TestResult::from_bool(apply_edits_to_actual(&edit_list, &actual) == expected)
+ }
+ Difference::Unrelated => {
+ if actual == expected {
+ TestResult::failed()
+ } else {
+ TestResult::discard()
+ }
+ }
+ }
+ }
+ }
+
+ quickcheck! {
+ fn edit_list_edits_expected_to_actual(
+ actual: Vec<Alphabet>,
+ expected: Vec<Alphabet>
+ ) -> TestResult {
+ match edit_list(actual.clone(), expected.clone(), Mode::Exact) {
+ Difference::Equal => TestResult::from_bool(actual == expected),
+ Difference::Editable(edit_list) => {
+ TestResult::from_bool(apply_edits_to_expected(&edit_list, &expected) == actual)
+ }
+ Difference::Unrelated => {
+ if actual == expected {
+ TestResult::failed()
+ } else {
+ TestResult::discard()
+ }
+ }
+ }
+ }
+ }
+
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ enum Alphabet {
+ A,
+ B,
+ C,
+ }
+
+ impl Arbitrary for Alphabet {
+ fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+ g.choose(&[Alphabet::A, Alphabet::B, Alphabet::C]).copied().unwrap()
+ }
+ }
+
+ fn apply_edits_to_actual<T: PartialEq + Debug + Copy>(
+ edit_list: &[Edit<T>],
+ actual: &[T],
+ ) -> Vec<T> {
+ let mut result = Vec::new();
+ let mut actual_iter = actual.iter();
+ for edit in edit_list {
+ match edit {
+ Edit::ExtraActual(value) => {
+ assert_that!(actual_iter.next(), some(eq(value)));
+ }
+ Edit::ExtraExpected(value) => {
+ result.push(*value);
+ }
+ Edit::Both(value) => {
+ assert_that!(actual_iter.next(), some(eq(value)));
+ result.push(*value);
+ }
+ Edit::AdditionalActual => {
+ fail!("Unexpected Edit::AdditionalActual").unwrap();
+ }
+ }
+ }
+ assert_that!(actual_iter.next(), none());
+ result
+ }
+
+ fn apply_edits_to_expected<T: PartialEq + Debug + Copy>(
+ edit_list: &[Edit<T>],
+ expected: &[T],
+ ) -> Vec<T> {
+ let mut result = Vec::new();
+ let mut expected_iter = expected.iter();
+ for edit in edit_list {
+ match edit {
+ Edit::ExtraActual(value) => {
+ result.push(*value);
+ }
+ Edit::ExtraExpected(value) => {
+ assert_that!(expected_iter.next(), some(eq(value)));
+ }
+ Edit::Both(value) => {
+ assert_that!(expected_iter.next(), some(eq(value)));
+ result.push(*value);
+ }
+ Edit::AdditionalActual => {
+ fail!("Unexpected Edit::AdditionalActual").unwrap();
+ }
+ }
+ }
+ assert_that!(expected_iter.next(), none());
+ result
+ }
+}
diff --git a/src/matcher_support/mod.rs b/src/matcher_support/mod.rs
new file mode 100644
index 0000000..8c30161
--- /dev/null
+++ b/src/matcher_support/mod.rs
@@ -0,0 +1,25 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Utilities to facilitate writing matchers.
+//!
+//! Tests normally do not need to import anything from this module. Some of
+//! these facilities could be useful to downstream users writing custom
+//! matchers.
+
+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
new file mode 100644
index 0000000..a045282
--- /dev/null
+++ b/src/matcher_support/summarize_diff.rs
@@ -0,0 +1,367 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![doc(hidden)]
+
+use crate::matcher_support::edit_distance;
+#[rustversion::since(1.70)]
+use std::io::IsTerminal;
+use std::{
+ borrow::Cow,
+ fmt::{Display, Write},
+};
+
+/// Returns a string describing how the expected and actual lines differ.
+///
+/// This is included in a match explanation for [`EqMatcher`] and
+/// [`crate::matchers::str_matcher::StrMatcher`].
+///
+/// If the actual value has less than two lines, or the two differ by more than
+/// the maximum edit distance, then this returns the empty string. If the two
+/// are equal, it returns a simple statement that they are equal. Otherwise,
+/// this constructs a unified diff view of the actual and expected values.
+pub(crate) fn create_diff(
+ actual_debug: &str,
+ expected_debug: &str,
+ diff_mode: edit_distance::Mode,
+) -> Cow<'static, str> {
+ if actual_debug.lines().count() < 2 {
+ // If the actual debug is only one line, then there is no point in doing a
+ // line-by-line diff.
+ return "".into();
+ }
+ match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines(), diff_mode) {
+ edit_distance::Difference::Equal => "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::Unrelated => "".into(),
+ }
+}
+
+/// Returns a string describing how the expected and actual differ after
+/// reversing the lines in each.
+///
+/// This is similar to [`create_diff`] except that it first reverses the lines
+/// in both the expected and actual values, then reverses the constructed edit
+/// list. When `diff_mode` is [`edit_distance::Mode::Prefix`], this becomes a
+/// diff of the suffix for use by [`ends_with`][crate::matchers::ends_with].
+pub(crate) fn create_diff_reversed(
+ actual_debug: &str,
+ expected_debug: &str,
+ diff_mode: edit_distance::Mode,
+) -> Cow<'static, str> {
+ if actual_debug.lines().count() < 2 {
+ // If the actual debug is only one line, then there is no point in doing a
+ // line-by-line diff.
+ return "".into();
+ }
+ let mut actual_lines_reversed = actual_debug.lines().collect::<Vec<_>>();
+ let mut expected_lines_reversed = expected_debug.lines().collect::<Vec<_>>();
+ actual_lines_reversed.reverse();
+ expected_lines_reversed.reverse();
+ match edit_distance::edit_list(actual_lines_reversed, expected_lines_reversed, diff_mode) {
+ edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
+ edit_distance::Difference::Editable(mut edit_list) => {
+ edit_list.reverse();
+ format!(
+ "\nDifference({} / {}):{}",
+ LineStyle::extra_actual_style().style("actual"),
+ LineStyle::extra_expected_style().style("expected"),
+ edit_list.into_iter().collect::<BufferedSummary>(),
+ )
+ .into()
+ }
+ edit_distance::Difference::Unrelated => "".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.
+struct BufferedSummary<'a> {
+ summary: String,
+ 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);
+ }
+
+ // 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();
+ }
+
+ // 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();
+ }
+
+ // 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();
+ }
+
+ fn flush_buffer(&mut self) {
+ self.buffer.flush(&mut self.summary).unwrap();
+ }
+}
+
+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![]) };
+ for edit in iter {
+ match edit {
+ edit_distance::Edit::Both(same) => {
+ buffered_summary.feed_common_lines(same);
+ }
+ edit_distance::Edit::ExtraActual(actual) => {
+ buffered_summary.feed_extra_actual(actual);
+ }
+ edit_distance::Edit::ExtraExpected(expected) => {
+ buffered_summary.feed_extra_expected(expected);
+ }
+ edit_distance::Edit::AdditionalActual => {
+ buffered_summary.feed_additional_actual();
+ }
+ };
+ }
+ buffered_summary.flush_buffer();
+
+ buffered_summary
+ }
+}
+
+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()) {
+ panic!("Buffer is not empty. This is a bug in gtest_rust.")
+ }
+ self.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> {
+ CommonLineBuffer(Vec<&'a str>),
+}
+
+impl<'a> Buffer<'a> {
+ fn flush(&mut self, writer: impl std::fmt::Write) -> std::fmt::Result {
+ match self {
+ Buffer::CommonLineBuffer(common_lines) => {
+ Self::flush_common_lines(std::mem::take(common_lines), writer)?
+ }
+ };
+ Ok(())
+ }
+
+ fn flush_common_lines(
+ common_lines: Vec<&'a str>,
+ mut writer: impl std::fmt::Write,
+ ) -> std::fmt::Result {
+ // 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))?;
+ }
+ return Ok(());
+ }
+
+ let start_context = &common_lines[0..COMMON_LINES_CONTEXT_SIZE];
+
+ for line in start_context {
+ write!(writer, "\n{}", LineStyle::unchanged_style().style(line))?;
+ }
+
+ write!(
+ writer,
+ "\n{}",
+ LineStyle::comment_style().style(&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))?;
+ }
+ 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)
+ }
+ }
+}
+
+#[rustversion::since(1.70)]
+fn stdout_supports_color() -> bool {
+ match (is_env_var_set("NO_COLOR"), is_env_var_set("FORCE_COLOR")) {
+ (true, _) => false,
+ (false, true) => true,
+ (false, false) => std::io::stdout().is_terminal(),
+ }
+}
+
+#[rustversion::not(since(1.70))]
+fn stdout_supports_color() -> bool {
+ is_env_var_set("FORCE_COLOR")
+}
+
+fn is_env_var_set(var: &'static str) -> bool {
+ std::env::var(var).map(|s| !s.is_empty()).unwrap_or(false)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{matcher_support::edit_distance::Mode, prelude::*};
+ use indoc::indoc;
+
+ // Make a long text with each element of the iterator on one line.
+ // `collection` must contains at least one element.
+ fn build_text<T: Display>(mut collection: impl Iterator<Item = T>) -> String {
+ let mut text = String::new();
+ write!(&mut text, "{}", collection.next().expect("Provided collection without elements"))
+ .unwrap();
+ for item in collection {
+ write!(&mut text, "\n{}", item).unwrap();
+ }
+ text
+ }
+
+ #[test]
+ fn create_diff_smaller_than_one_line() -> Result<()> {
+ verify_that!(create_diff("One", "Two", Mode::Exact), eq(""))
+ }
+
+ #[test]
+ fn create_diff_exact_same() -> Result<()> {
+ let expected = indoc! {"
+ One
+ Two
+ "};
+ let actual = indoc! {"
+ One
+ Two
+ "};
+ verify_that!(
+ create_diff(expected, actual, Mode::Exact),
+ eq("No difference found between debug strings.")
+ )
+ }
+
+ #[test]
+ fn create_diff_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");
+
+ verify_that!(
+ create_diff(&build_text(1..50), &build_text(1..51), Mode::Exact),
+ eq(indoc! {
+ "
+
+ Difference(-actual / +expected):
+ 1
+ 2
+ <---- 45 common lines omitted ---->
+ 48
+ 49
+ +50"
+ })
+ )
+ }
+}
diff --git a/src/matcher_support/zipped_iterator.rs b/src/matcher_support/zipped_iterator.rs
new file mode 100644
index 0000000..d1cbaf3
--- /dev/null
+++ b/src/matcher_support/zipped_iterator.rs
@@ -0,0 +1,85 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Zips up two iterators into a single iterator of pairs.
+///
+/// This is identical to [`Iterator::zip`] except that this version allows the
+/// caller to determine whether the two iterators had mismatching sizes using
+/// the method [`ZippedIterator::has_size_mismatch`].
+///
+/// [`Iterator::zip`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.zip
+pub(crate) fn zip<I1, I2>(left: I1, right: I2) -> ZippedIterator<I1, I2> {
+ ZippedIterator { left, right, has_size_mismatch: false, consumed_elements: 0 }
+}
+
+/// An iterator over pairs of the elements of two constituent iterators, which
+/// keeps track of whether the two iterators have the same size.
+///
+/// This is identical to [`Zip`] except that it allows the caller to determine
+/// whether the two iterators had mismatching sizes using the method
+/// [`ZippedIterator::has_size_mismatch`].
+///
+/// [`Zip`]: https://doc.rust-lang.org/std/iter/struct.Zip.html
+pub(crate) struct ZippedIterator<I1, I2> {
+ left: I1,
+ right: I2,
+ has_size_mismatch: bool,
+ consumed_elements: usize,
+}
+
+impl<I1: Iterator, I2> ZippedIterator<I1, I2> {
+ /// Returns whether a mismatch in the two sizes of the two iterators was
+ /// detected during iteration.
+ ///
+ /// This returns `true` if and only if, at some previous call to
+ /// [`Iterator::next`] on this instance, one of the constituent iterators
+ /// had a next element and the other did not.
+ ///
+ /// [`Iterator::next`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
+ pub(crate) fn has_size_mismatch(&self) -> bool {
+ self.has_size_mismatch
+ }
+
+ /// Returns the number of elements in the left iterator.
+ ///
+ /// This iterates through the remainder of the left iterator if necessary in
+ /// order to get the true number of elements. It therefore consumes `self`.
+ pub(crate) fn left_size(mut self) -> usize {
+ self.consumed_elements + self.left.by_ref().count()
+ }
+}
+
+impl<I1: Iterator, I2: Iterator> Iterator for ZippedIterator<I1, I2> {
+ type Item = (I1::Item, I2::Item);
+
+ fn next(&mut self) -> Option<(I1::Item, I2::Item)> {
+ match (self.left.next(), self.right.next()) {
+ (Some(v1), Some(v2)) => {
+ self.consumed_elements += 1;
+ Some((v1, v2))
+ }
+ (Some(_), None) => {
+ // Consumed elements counts only elements from self.left
+ self.consumed_elements += 1;
+ self.has_size_mismatch = true;
+ None
+ }
+ (None, Some(_)) => {
+ self.has_size_mismatch = true;
+ None
+ }
+ (None, None) => None,
+ }
+ }
+}
diff --git a/src/matchers/all_matcher.rs b/src/matchers/all_matcher.rs
new file mode 100644
index 0000000..cc959c7
--- /dev/null
+++ b/src/matchers/all_matcher.rs
@@ -0,0 +1,205 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a value which all of the given matchers match.
+///
+/// Each argument is a [`Matcher`][crate::matcher::Matcher] which matches
+/// against the actual value.
+///
+/// For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!("A string", all!(starts_with("A"), ends_with("string")))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!("A string", all!(starts_with("A"), ends_with("not a string")))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// Using this macro is equivalent to using the
+/// [`and`][crate::matcher::Matcher::and] method:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(10, gt(9).and(lt(11)))?; // Also passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Assertion failure messages are not guaranteed to be identical, however.
+#[macro_export]
+macro_rules! all {
+ ($($matcher:expr),* $(,)?) => {{
+ use $crate::matchers::all_matcher::internal::AllMatcher;
+ AllMatcher::new([$(Box::new($matcher)),*])
+ }}
+}
+
+/// Functionality needed by the [`all`] macro.
+///
+/// For internal use only. API stablility is not guaranteed!
+#[doc(hidden)]
+pub mod internal {
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::matcher_support::description::Description;
+ use crate::matchers::anything;
+ use std::fmt::Debug;
+
+ /// A matcher which matches an input value matched by all matchers in the
+ /// array `components`.
+ ///
+ /// For internal use only. API stablility is not guaranteed!
+ #[doc(hidden)]
+ pub struct AllMatcher<'a, T: Debug + ?Sized, const N: usize> {
+ components: [Box<dyn Matcher<ActualT = T> + 'a>; N],
+ }
+
+ impl<'a, T: Debug + ?Sized, const N: usize> AllMatcher<'a, T, N> {
+ /// Constructs an [`AllMatcher`] with the given component matchers.
+ ///
+ /// Intended for use only by the [`all`] macro.
+ pub fn new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self {
+ Self { components }
+ }
+ }
+
+ impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AllMatcher<'a, T, N> {
+ type ActualT = T;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ for component in &self.components {
+ match component.matches(actual) {
+ MatcherResult::NoMatch => {
+ return MatcherResult::NoMatch;
+ }
+ MatcherResult::Match => {}
+ }
+ }
+ MatcherResult::Match
+ }
+
+ fn explain_match(&self, actual: &Self::ActualT) -> String {
+ match N {
+ 0 => anything::<T>().explain_match(actual),
+ 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>();
+ if failures.len() == 1 {
+ format!("{}", failures)
+ } else {
+ format!("{}", failures.bullet_list().indent_except_first_line())
+ }
+ }
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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"
+ }
+ )
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::internal;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn description_shows_more_than_one_matcher() -> Result<()> {
+ let first_matcher = starts_with("A");
+ let second_matcher = ends_with("string");
+ let matcher: internal::AllMatcher<String, 2> = all!(first_matcher, second_matcher);
+
+ verify_that!(
+ matcher.describe(MatcherResult::Match),
+ eq(indoc!(
+ "
+ has all the following properties:
+ * starts with prefix \"A\"
+ * ends with suffix \"string\""
+ ))
+ )
+ }
+
+ #[test]
+ fn description_shows_one_matcher_directly() -> Result<()> {
+ let first_matcher = starts_with("A");
+ let matcher: internal::AllMatcher<String, 1> = all!(first_matcher);
+
+ verify_that!(matcher.describe(MatcherResult::Match), eq("starts with prefix \"A\""))
+ }
+
+ #[test]
+ fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()>
+ {
+ let first_matcher = starts_with("Another");
+ let second_matcher = ends_with("string");
+ let matcher: internal::AllMatcher<str, 2> = all!(first_matcher, second_matcher);
+
+ verify_that!(
+ matcher.explain_match("A string"),
+ displays_as(eq("which does not start with \"Another\""))
+ )
+ }
+
+ #[test]
+ fn mismatch_description_is_simple_when_only_one_consistuent() -> Result<()> {
+ let first_matcher = starts_with("Another");
+ let matcher: internal::AllMatcher<str, 1> = all!(first_matcher);
+
+ verify_that!(
+ matcher.explain_match("A string"),
+ displays_as(eq("which does not start with \"Another\""))
+ )
+ }
+}
diff --git a/src/matchers/any_matcher.rs b/src/matchers/any_matcher.rs
new file mode 100644
index 0000000..82c8910
--- /dev/null
+++ b/src/matchers/any_matcher.rs
@@ -0,0 +1,199 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a value which at least one of the given matchers match.
+///
+/// Each argument is a [`Matcher`][crate::matcher::Matcher] which matches
+/// against the actual value.
+///
+/// For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!("A string", any!(starts_with("A"), ends_with("string")))?; // Passes
+/// verify_that!("A string", any!(starts_with("A"), starts_with("string")))?; // Passes
+/// verify_that!("A string", any!(ends_with("A"), ends_with("string")))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!("A string", any!(starts_with("An"), ends_with("not a string")))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// Using this macro is equivalent to using the
+/// [`or`][crate::matcher::Matcher::or] method:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(10, gt(9).or(lt(8)))?; // Also passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Assertion failure messages are not guaranteed to be identical, however.
+#[macro_export]
+macro_rules! any {
+ ($($matcher:expr),* $(,)?) => {{
+ use $crate::matchers::any_matcher::internal::AnyMatcher;
+ AnyMatcher::new([$(Box::new($matcher)),*])
+ }}
+}
+
+/// Functionality needed by the [`any`] macro.
+///
+/// For internal use only. API stablility is not guaranteed!
+#[doc(hidden)]
+pub mod internal {
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::matcher_support::description::Description;
+ use crate::matchers::anything;
+ use std::fmt::Debug;
+
+ /// A matcher which matches an input value matched by all matchers in the
+ /// array `components`.
+ ///
+ /// For internal use only. API stablility is not guaranteed!
+ #[doc(hidden)]
+ pub struct AnyMatcher<'a, T: Debug + ?Sized, const N: usize> {
+ components: [Box<dyn Matcher<ActualT = T> + 'a>; N],
+ }
+
+ impl<'a, T: Debug + ?Sized, const N: usize> AnyMatcher<'a, T, N> {
+ /// Constructs an [`AnyMatcher`] with the given component matchers.
+ ///
+ /// Intended for use only by the [`all`] macro.
+ pub fn new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self {
+ Self { components }
+ }
+ }
+
+ impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AnyMatcher<'a, T, N> {
+ type ActualT = T;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ MatcherResult::from(self.components.iter().any(|c| c.matches(actual).is_match()))
+ }
+
+ fn explain_match(&self, actual: &Self::ActualT) -> String {
+ match N {
+ 0 => format!("which {}", anything::<T>().describe(MatcherResult::NoMatch)),
+ 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>();
+ if failures.len() == 1 {
+ format!("{}", failures)
+ } else {
+ format!("{}", failures.bullet_list().indent_except_first_line())
+ }
+ }
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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 at least one of the following properties"
+ } else {
+ "has none of the following properties"
+ }
+ )
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::internal;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn description_shows_more_than_one_matcher() -> Result<()> {
+ let first_matcher = starts_with("A");
+ let second_matcher = ends_with("string");
+ let matcher: internal::AnyMatcher<String, 2> = any!(first_matcher, second_matcher);
+
+ verify_that!(
+ matcher.describe(MatcherResult::Match),
+ eq(indoc!(
+ "
+ has at least one of the following properties:
+ * starts with prefix \"A\"
+ * ends with suffix \"string\""
+ ))
+ )
+ }
+
+ #[test]
+ fn description_shows_one_matcher_directly() -> Result<()> {
+ let first_matcher = starts_with("A");
+ let matcher: internal::AnyMatcher<String, 1> = any!(first_matcher);
+
+ verify_that!(matcher.describe(MatcherResult::Match), eq("starts with prefix \"A\""))
+ }
+
+ #[test]
+ fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()>
+ {
+ let first_matcher = starts_with("Another");
+ let second_matcher = ends_with("string");
+ let matcher: internal::AnyMatcher<str, 2> = any!(first_matcher, second_matcher);
+
+ verify_that!(
+ matcher.explain_match("A string"),
+ displays_as(eq("which does not start with \"Another\""))
+ )
+ }
+
+ #[test]
+ fn mismatch_description_is_simple_when_only_one_constituent() -> Result<()> {
+ let first_matcher = starts_with("Another");
+ let matcher: internal::AnyMatcher<str, 1> = any!(first_matcher);
+
+ verify_that!(
+ matcher.explain_match("A string"),
+ displays_as(eq("which does not start with \"Another\""))
+ )
+ }
+}
diff --git a/src/matchers/anything_matcher.rs b/src/matchers/anything_matcher.rs
new file mode 100644
index 0000000..82de460
--- /dev/null
+++ b/src/matchers/anything_matcher.rs
@@ -0,0 +1,78 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches anything. This matcher always succeeds.
+///
+/// This is useful to check if `actual` matches the specific structure (like
+/// `Some(...)`) but without caring about the internal value.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let option = Some("Some value");
+/// verify_that!(option, some(anything()))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn anything<T: Debug + ?Sized>() -> impl Matcher<ActualT = T> {
+ Anything::<T>(Default::default())
+}
+
+struct Anything<T: ?Sized>(PhantomData<T>);
+
+impl<T: Debug + ?Sized> Matcher for Anything<T> {
+ type ActualT = T;
+
+ fn matches(&self, _: &T) -> MatcherResult {
+ MatcherResult::Match
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => "is anything".to_string(),
+ MatcherResult::NoMatch => "never matches".to_string(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::anything;
+ use crate::prelude::*;
+
+ #[test]
+ fn anything_matches_i32() -> Result<()> {
+ let value = 32;
+ verify_that!(value, anything())?;
+ Ok(())
+ }
+
+ #[test]
+ fn anything_matches_str() -> Result<()> {
+ let value = "32";
+ verify_that!(value, anything())?;
+ Ok(())
+ }
+
+ #[test]
+ fn anything_matches_option() -> Result<()> {
+ let value = Some(32);
+ verify_that!(value, some(anything()))?;
+ Ok(())
+ }
+}
diff --git a/src/matchers/char_count_matcher.rs b/src/matchers/char_count_matcher.rs
new file mode 100644
index 0000000..a7765b4
--- /dev/null
+++ b/src/matchers/char_count_matcher.rs
@@ -0,0 +1,166 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a string whose number of Unicode scalars matches `expected`.
+///
+/// In other words, the argument must match the output of
+/// [`actual_string.chars().count()`][std::str::Chars].
+///
+/// This can have surprising effects when what appears to be a single character
+/// is composed of multiple Unicode scalars. See [Rust documentation on
+/// character
+/// representation](https://doc.rust-lang.org/std/primitive.char.html#representation)
+/// for more information.
+///
+/// This matches against owned strings and string slices.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let string_slice = "A string";
+/// verify_that!(string_slice, char_count(eq(8)))?;
+/// let non_ascii_string_slice = "Ä ſtřiɲğ";
+/// verify_that!(non_ascii_string_slice, char_count(eq(8)))?;
+/// let owned_string = String::from("A string");
+/// verify_that!(owned_string, char_count(eq(8)))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// The parameter `expected` can be any integer numeric matcher.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let string_slice = "A string";
+/// verify_that!(string_slice, char_count(gt(4)))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn char_count<T: Debug + ?Sized + AsRef<str>, E: Matcher<ActualT = usize>>(
+ expected: E,
+) -> impl Matcher<ActualT = T> {
+ CharLenMatcher { expected, phantom: Default::default() }
+}
+
+struct CharLenMatcher<T: ?Sized, E> {
+ expected: E,
+ phantom: PhantomData<T>,
+}
+
+impl<T: Debug + ?Sized + AsRef<str>, E: Matcher<ActualT = usize>> Matcher for CharLenMatcher<T, E> {
+ type ActualT = T;
+
+ fn matches(&self, actual: &T) -> MatcherResult {
+ self.expected.matches(&actual.as_ref().chars().count())
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!(
+ "has character count, which {}",
+ self.expected.describe(MatcherResult::Match)
+ )
+ }
+ MatcherResult::NoMatch => {
+ format!(
+ "has character count, which {}",
+ self.expected.describe(MatcherResult::NoMatch)
+ )
+ }
+ }
+ }
+
+ fn explain_match(&self, actual: &T) -> String {
+ let actual_size = actual.as_ref().chars().count();
+ format!(
+ "which has character count {}, {}",
+ actual_size,
+ self.expected.explain_match(&actual_size)
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::char_count;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::fmt::Debug;
+ use std::marker::PhantomData;
+
+ #[test]
+ fn char_count_matches_string_slice() -> Result<()> {
+ let value = "abcd";
+ verify_that!(value, char_count(eq(4)))
+ }
+
+ #[test]
+ fn char_count_matches_owned_string() -> Result<()> {
+ let value = String::from("abcd");
+ verify_that!(value, char_count(eq(4)))
+ }
+
+ #[test]
+ fn char_count_counts_non_ascii_characters_correctly() -> Result<()> {
+ let value = "äöüß";
+ verify_that!(value, char_count(eq(4)))
+ }
+
+ #[test]
+ fn char_count_explains_match() -> Result<()> {
+ struct TestMatcher<T>(PhantomData<T>);
+ impl<T: Debug> Matcher for TestMatcher<T> {
+ type ActualT = T;
+
+ fn matches(&self, _: &T) -> MatcherResult {
+ false.into()
+ }
+
+ fn describe(&self, _: MatcherResult) -> String {
+ "called described".into()
+ }
+
+ fn explain_match(&self, _: &T) -> String {
+ "called explain_match".into()
+ }
+ }
+ verify_that!(
+ char_count(TestMatcher(Default::default())).explain_match(&"A string"),
+ displays_as(eq("which has character count 8, called explain_match"))
+ )
+ }
+
+ #[test]
+ fn char_count_has_correct_failure_message() -> Result<()> {
+ let result = verify_that!("äöüß", char_count(eq(3)));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ r#"
+ Value of: "äöüß"
+ Expected: has character count, which is equal to 3
+ Actual: "äöüß",
+ which has character count 4, which isn't equal to 3"#
+ ))))
+ )
+ }
+}
diff --git a/src/matchers/conjunction_matcher.rs b/src/matchers/conjunction_matcher.rs
new file mode 100644
index 0000000..1ba59c3
--- /dev/null
+++ b/src/matchers/conjunction_matcher.rs
@@ -0,0 +1,157 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module.
+#![doc(hidden)]
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+
+/// Matcher created by [`Matcher::and`].
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub struct ConjunctionMatcher<M1, M2> {
+ m1: M1,
+ m2: M2,
+}
+
+impl<M1, M2> ConjunctionMatcher<M1, M2> {
+ pub(crate) fn new(m1: M1, m2: M2) -> Self {
+ Self { m1, m2 }
+ }
+}
+
+impl<M1: Matcher, M2: Matcher<ActualT = M1::ActualT>> Matcher for ConjunctionMatcher<M1, M2>
+where
+ M1::ActualT: Debug,
+{
+ type ActualT = M1::ActualT;
+
+ fn matches(&self, actual: &M1::ActualT) -> MatcherResult {
+ match (self.m1.matches(actual), self.m2.matches(actual)) {
+ (MatcherResult::Match, MatcherResult::Match) => MatcherResult::Match,
+ _ => MatcherResult::NoMatch,
+ }
+ }
+
+ fn explain_match(&self, actual: &M1::ActualT) -> String {
+ 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::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)
+ )
+ }
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ format!("{}, and {}", self.m1.describe(matcher_result), self.m2.describe(matcher_result))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn and_true_true_matches() -> Result<()> {
+ verify_that!(1, anything().and(anything()))
+ }
+
+ #[test]
+ fn and_true_false_does_not_match() -> Result<()> {
+ let result = verify_that!(1, anything().and(not(anything())));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: 1
+ Expected: is anything, and never matches
+ Actual: 1,
+ which is anything
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn and_false_true_does_not_match() -> Result<()> {
+ let result = verify_that!(1, not(anything()).and(anything()));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: 1
+ Expected: never matches, and is anything
+ Actual: 1,
+ which is anything
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn and_false_false_does_not_match() -> Result<()> {
+ let result = verify_that!(1, not(anything()).and(not(anything())));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: 1
+ Expected: never matches, and never matches
+ Actual: 1,
+ which is anything and
+ which is anything
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn chained_and_matches() -> Result<()> {
+ #[derive(Debug)]
+ struct Struct {
+ a: i32,
+ b: i32,
+ c: i32,
+ }
+ verify_that!(
+ Struct { a: 1, b: 2, c: 3 },
+ field!(Struct.a, eq(1)).and(field!(Struct.b, eq(2))).and(field!(Struct.c, eq(3)))
+ )
+ }
+
+ #[test]
+ fn works_with_str_slices() -> Result<()> {
+ verify_that!("A string", starts_with("A").and(ends_with("string")))
+ }
+
+ #[test]
+ fn works_with_owned_strings() -> Result<()> {
+ verify_that!("A string".to_string(), starts_with("A").and(ends_with("string")))
+ }
+}
diff --git a/src/matchers/container_eq_matcher.rs b/src/matchers/container_eq_matcher.rs
new file mode 100644
index 0000000..f80cebf
--- /dev/null
+++ b/src/matchers/container_eq_matcher.rs
@@ -0,0 +1,312 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+use std::marker::PhantomData;
+
+/// Matches a container equal (in the sense of `==`) to `expected`.
+///
+/// This is similar to [`crate::matchers::eq`] except that an assertion failure
+/// message generated from this matcher will include the missing and unexpected
+/// items in the actual value, e.g.:
+///
+/// ```text
+/// Expected container to equal [1, 2, 3]
+/// but was: [1, 2, 4]
+/// Missing: [3]
+/// Unexpected: [4]
+/// ```
+///
+/// The 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:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let vec = vec![1, 2, 3];
+/// verify_that!(vec, container_eq([1, 2, 3]))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// As an exception, if the actual type is a `Vec<String>`, the expected type
+/// may be a slice of `&str`:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let vec: Vec<String> = vec!["A string".into(), "Another string".into()];
+/// verify_that!(vec, container_eq(["A string", "Another string"]))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// These exceptions allow one to avoid unnecessary allocations in test
+/// assertions.
+///
+/// One can also check container equality of a slice with an array. To do so,
+/// dereference the slice:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = &[1, 2, 3];
+/// verify_that!(*value, container_eq([1, 2, 3]))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Otherwise, the actual and expected types must be identical.
+///
+/// *Performance note*: In the event of a mismatch leading to an assertion
+/// failure, the construction of the lists of missing and unexpected values
+/// uses a naive algorithm requiring time proportional to the product of the
+/// sizes of the expected and actual values. This should therefore only be used
+/// when the containers are small enough that this is not a problem.
+// This returns ContainerEqMatcher and not impl Matcher because
+// ContainerEqMatcher has some specialisations for slice types (see
+// documentation above). Returning impl Matcher would hide those from the
+// compiler.
+pub fn container_eq<ActualContainerT, ExpectedContainerT>(
+ expected: ExpectedContainerT,
+) -> ContainerEqMatcher<ActualContainerT, ExpectedContainerT>
+where
+ ActualContainerT: PartialEq<ExpectedContainerT> + Debug + ?Sized,
+ ExpectedContainerT: Debug,
+{
+ ContainerEqMatcher { expected, phantom: Default::default() }
+}
+
+pub struct ContainerEqMatcher<ActualContainerT: ?Sized, ExpectedContainerT> {
+ expected: ExpectedContainerT,
+ phantom: PhantomData<ActualContainerT>,
+}
+
+impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT> Matcher
+ for ContainerEqMatcher<ActualContainerT, ExpectedContainerT>
+where
+ ActualElementT: PartialEq<ExpectedElementT> + Debug + ?Sized,
+ ActualContainerT: PartialEq<ExpectedContainerT> + Debug + ?Sized,
+ ExpectedElementT: Debug,
+ ExpectedContainerT: Debug,
+ for<'a> &'a ActualContainerT: IntoIterator<Item = &'a ActualElementT>,
+ for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
+{
+ type ActualT = ActualContainerT;
+
+ fn matches(&self, actual: &ActualContainerT) -> MatcherResult {
+ (*actual == self.expected).into()
+ }
+
+ fn explain_match(&self, actual: &ActualContainerT) -> String {
+ build_explanation(self.get_missing_items(actual), self.get_unexpected_items(actual))
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!("is equal to {:?}", self.expected),
+ MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected),
+ }
+ }
+}
+
+impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
+ ContainerEqMatcher<ActualContainerT, ExpectedContainerT>
+where
+ ActualElementT: PartialEq<ExpectedElementT> + ?Sized,
+ ActualContainerT: PartialEq<ExpectedContainerT> + ?Sized,
+ for<'a> &'a ActualContainerT: IntoIterator<Item = &'a ActualElementT>,
+ for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
+{
+ fn get_missing_items(&self, actual: &ActualContainerT) -> Vec<&ExpectedElementT> {
+ self.expected.into_iter().filter(|&i| !actual.into_iter().any(|j| j == i)).collect()
+ }
+
+ fn get_unexpected_items<'a>(&self, actual: &'a ActualContainerT) -> Vec<&'a ActualElementT> {
+ actual.into_iter().filter(|&i| !self.expected.into_iter().any(|j| i == j)).collect()
+ }
+}
+
+fn build_explanation<T: Debug, U: Debug>(missing: Vec<T>, unexpected: Vec<U>) -> String {
+ match (missing.len(), unexpected.len()) {
+ // TODO(b/261175849) add more data here (out of order elements, duplicated elements, etc...)
+ (0, 0) => "which contains all the elements".to_string(),
+ (0, 1) => format!("which contains the unexpected element {:?}", unexpected[0]),
+ (0, _) => format!("which contains the unexpected elements {unexpected:?}",),
+ (1, 0) => format!("which is missing the element {:?}", missing[0]),
+ (1, 1) => {
+ format!(
+ "which is missing the element {:?} and contains the unexpected element {:?}",
+ missing[0], unexpected[0]
+ )
+ }
+ (1, _) => {
+ format!(
+ "which is missing the element {:?} and contains the unexpected elements {unexpected:?}",
+ missing[0]
+ )
+ }
+ (_, 0) => format!("which is missing the elements {missing:?}"),
+ (_, 1) => {
+ format!(
+ "which is missing the elements {missing:?} and contains the unexpected element {:?}",
+ unexpected[0]
+ )
+ }
+ (_, _) => {
+ format!(
+ "which is missing the elements {missing:?} and contains the unexpected elements {unexpected:?}",
+ )
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::container_eq;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::collections::HashSet;
+
+ #[test]
+ fn container_eq_returns_match_when_containers_match() -> Result<()> {
+ verify_that!(vec![1, 2, 3], container_eq(vec![1, 2, 3]))
+ }
+
+ #[test]
+ fn container_eq_matches_array_with_slice() -> Result<()> {
+ let value = &[1, 2, 3];
+ verify_that!(*value, container_eq([1, 2, 3]))
+ }
+
+ #[test]
+ fn container_eq_matches_hash_set() -> Result<()> {
+ let value: HashSet<i32> = [1, 2, 3].into();
+ verify_that!(value, container_eq([1, 2, 3].into()))
+ }
+
+ #[test]
+ fn container_eq_full_error_message() -> Result<()> {
+ let result = verify_that!(vec![1, 3, 2], container_eq(vec![1, 2, 3]));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 3, 2]
+ Expected: is equal to [1, 2, 3]
+ Actual: [1, 3, 2],
+ which contains all the elements
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn container_eq_returns_mismatch_when_elements_out_of_order() -> Result<()> {
+ verify_that!(
+ container_eq(vec![1, 2, 3]).explain_match(&vec![1, 3, 2]),
+ displays_as(eq("which contains all the elements"))
+ )
+ }
+
+ #[test]
+ fn container_eq_mismatch_shows_missing_elements_in_container() -> Result<()> {
+ verify_that!(
+ container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2]),
+ displays_as(eq("which is missing the element 3"))
+ )
+ }
+
+ #[test]
+ fn container_eq_mismatch_shows_surplus_elements_in_container() -> Result<()> {
+ verify_that!(
+ container_eq(vec![1, 2]).explain_match(&vec![1, 2, 3]),
+ displays_as(eq("which contains the unexpected element 3"))
+ )
+ }
+
+ #[test]
+ fn container_eq_mismatch_shows_missing_and_surplus_elements_in_container() -> Result<()> {
+ verify_that!(
+ container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 4]),
+ displays_as(eq("which is missing the element 3 and contains the unexpected element 4"))
+ )
+ }
+
+ #[test]
+ fn container_eq_mismatch_does_not_show_duplicated_element() -> Result<()> {
+ verify_that!(
+ container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 3, 3]),
+ displays_as(eq("which contains all the elements"))
+ )
+ }
+
+ #[test]
+ fn container_eq_matches_owned_vec_with_array() -> Result<()> {
+ let vector = vec![123, 234];
+ verify_that!(vector, container_eq([123, 234]))
+ }
+
+ #[test]
+ fn container_eq_matches_owned_vec_of_owned_strings_with_slice_of_string_references()
+ -> Result<()> {
+ let vector = vec!["A string".to_string(), "Another string".to_string()];
+ verify_that!(vector, container_eq(["A string", "Another string"]))
+ }
+
+ #[test]
+ fn container_eq_matches_owned_vec_of_owned_strings_with_shorter_slice_of_string_references()
+ -> Result<()> {
+ let actual = vec!["A string".to_string(), "Another string".to_string()];
+ let matcher = container_eq(["A string"]);
+
+ let result = matcher.matches(&actual);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn container_eq_mismatch_with_slice_shows_missing_elements_in_container() -> Result<()> {
+ verify_that!(
+ container_eq([1, 2, 3]).explain_match(&vec![1, 2]),
+ displays_as(eq("which is missing the element 3"))
+ )
+ }
+
+ #[test]
+ fn container_eq_mismatch_with_str_slice_shows_missing_elements_in_container() -> Result<()> {
+ verify_that!(
+ container_eq(["A", "B", "C"]).explain_match(&vec!["A".to_string(), "B".to_string()]),
+ displays_as(eq("which is missing the element \"C\""))
+ )
+ }
+
+ #[test]
+ fn container_eq_mismatch_with_str_slice_shows_surplus_elements_in_container() -> Result<()> {
+ verify_that!(
+ container_eq(["A", "B"]).explain_match(&vec![
+ "A".to_string(),
+ "B".to_string(),
+ "C".to_string()
+ ]),
+ displays_as(eq("which contains the unexpected element \"C\""))
+ )
+ }
+}
diff --git a/src/matchers/contains_matcher.rs b/src/matchers/contains_matcher.rs
new file mode 100644
index 0000000..d714c88
--- /dev/null
+++ b/src/matchers/contains_matcher.rs
@@ -0,0 +1,273 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches an iterable type whose elements contain a value matched by `inner`.
+///
+/// By default, this matches a container with any number of elements matched
+/// by `inner`. Use the method [`ContainsMatcher::times`] to constrain the
+/// matched containers to a specific number of matching elements.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(["Some value"], contains(eq("Some value")))?; // Passes
+/// verify_that!(vec!["Some value"], contains(eq("Some value")))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!([] as [String; 0], contains(eq("Some value")))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(["Some value"], contains(eq("Some other value")))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+pub fn contains<T, InnerMatcherT>(inner: InnerMatcherT) -> ContainsMatcher<T, InnerMatcherT> {
+ ContainsMatcher { inner, count: None, phantom: Default::default() }
+}
+
+/// A matcher which matches a container containing one or more elements a given
+/// inner [`Matcher`] matches.
+pub struct ContainsMatcher<T, InnerMatcherT> {
+ inner: InnerMatcherT,
+ count: Option<Box<dyn Matcher<ActualT = usize>>>,
+ phantom: PhantomData<T>,
+}
+
+impl<T, InnerMatcherT> ContainsMatcher<T, InnerMatcherT> {
+ /// Configures this instance to match containers which contain a number of
+ /// matching items matched by `count`.
+ ///
+ /// For example, to assert that exactly three matching items must be
+ /// present, use:
+ ///
+ /// ```ignore
+ /// contains(...).times(eq(3))
+ /// ```
+ ///
+ /// One can also use `times(eq(0))` to test for the *absence* of an item
+ /// matching the expected value.
+ pub fn times(mut self, count: impl Matcher<ActualT = usize> + 'static) -> Self {
+ self.count = Some(Box::new(count));
+ self
+ }
+}
+
+// TODO(hovinen): Revisit the trait bounds to see whether this can be made more
+// flexible. Namely, the following doesn't compile currently:
+//
+// let matcher = contains(eq(&42));
+// let val = 42;
+// let _ = matcher.matches(&vec![&val]);
+//
+// because val is dropped before matcher but the trait bound requires that
+// the argument to matches outlive the matcher. It works fine if one defines
+// val before matcher.
+impl<T: Debug, InnerMatcherT: Matcher<ActualT = T>, ContainerT: Debug> Matcher
+ for ContainsMatcher<ContainerT, InnerMatcherT>
+where
+ for<'a> &'a ContainerT: IntoIterator<Item = &'a T>,
+{
+ type ActualT = ContainerT;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ if let Some(count) = &self.count {
+ count.matches(&self.count_matches(actual))
+ } else {
+ for v in actual.into_iter() {
+ if self.inner.matches(v).into() {
+ return MatcherResult::Match;
+ }
+ }
+ MatcherResult::NoMatch
+ }
+ }
+
+ fn explain_match(&self, actual: &Self::ActualT) -> String {
+ 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(),
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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)
+ ),
+ (MatcherResult::NoMatch, Some(count)) => format!(
+ "doesn't contain n elements which {}\n where n {}",
+ self.inner.describe(MatcherResult::Match),
+ count.describe(MatcherResult::Match)
+ ),
+ (MatcherResult::Match, None) => format!(
+ "contains at least one element which {}",
+ self.inner.describe(MatcherResult::Match)
+ ),
+ (MatcherResult::NoMatch, None) => {
+ format!("contains no element which {}", self.inner.describe(MatcherResult::Match))
+ }
+ }
+ }
+}
+
+impl<ActualT, InnerMatcherT> ContainsMatcher<ActualT, InnerMatcherT> {
+ fn count_matches<T: Debug, ContainerT>(&self, actual: &ContainerT) -> usize
+ where
+ for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+ InnerMatcherT: Matcher<ActualT = T>,
+ {
+ let mut count = 0;
+ for v in actual.into_iter() {
+ if self.inner.matches(v).into() {
+ count += 1;
+ }
+ }
+ count
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{contains, ContainsMatcher};
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+
+ #[test]
+ fn contains_matches_singleton_slice_with_value() -> Result<()> {
+ let matcher = contains(eq(1));
+
+ let result = matcher.matches(&vec![1]);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn contains_matches_singleton_vec_with_value() -> Result<()> {
+ let matcher = contains(eq(1));
+
+ let result = matcher.matches(&vec![1]);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn contains_matches_two_element_slice_with_value() -> Result<()> {
+ let matcher = contains(eq(1));
+
+ let result = matcher.matches(&[0, 1]);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn contains_does_not_match_singleton_slice_with_wrong_value() -> Result<()> {
+ let matcher = contains(eq(1));
+
+ let result = matcher.matches(&[0]);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn contains_does_not_match_empty_slice() -> Result<()> {
+ let matcher = contains(eq(1));
+
+ let result = matcher.matches(&[]);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn contains_matches_slice_with_repeated_value() -> Result<()> {
+ let matcher = contains(eq(1)).times(eq(2));
+
+ let result = matcher.matches(&[1, 1]);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn contains_does_not_match_slice_with_too_few_of_value() -> Result<()> {
+ let matcher = contains(eq(1)).times(eq(2));
+
+ let result = matcher.matches(&[0, 1]);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn contains_does_not_match_slice_with_too_many_of_value() -> Result<()> {
+ let matcher = contains(eq(1)).times(eq(1));
+
+ let result = matcher.matches(&[1, 1]);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn contains_formats_without_multiplicity_by_default() -> Result<()> {
+ let matcher: ContainsMatcher<Vec<i32>, _> = contains(eq(1));
+
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ eq("contains at least one element which is equal to 1")
+ )
+ }
+
+ #[test]
+ fn contains_formats_with_multiplicity_when_specified() -> Result<()> {
+ let matcher: ContainsMatcher<Vec<i32>, _> = contains(eq(1)).times(eq(2));
+
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ eq("contains n elements which is equal to 1\n where n is equal to 2")
+ )
+ }
+
+ #[test]
+ fn contains_mismatch_shows_number_of_times_element_was_found() -> Result<()> {
+ verify_that!(
+ contains(eq(3)).times(eq(1)).explain_match(&vec![1, 2, 3, 3]),
+ displays_as(eq("which contains 2 matching elements"))
+ )
+ }
+
+ #[test]
+ fn contains_mismatch_shows_when_matches() -> Result<()> {
+ verify_that!(
+ contains(eq(3)).explain_match(&vec![1, 2, 3, 3]),
+ displays_as(eq("which contains a matching element"))
+ )
+ }
+
+ #[test]
+ fn contains_mismatch_shows_when_no_matches() -> Result<()> {
+ verify_that!(
+ contains(eq(3)).explain_match(&vec![1, 2]),
+ displays_as(eq("which does not contain a matching element"))
+ )
+ }
+}
diff --git a/src/matchers/contains_regex_matcher.rs b/src/matchers/contains_regex_matcher.rs
new file mode 100644
index 0000000..8cc93a7
--- /dev/null
+++ b/src/matchers/contains_regex_matcher.rs
@@ -0,0 +1,148 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use regex::Regex;
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::ops::Deref;
+
+/// Matches a string containing a substring which matches the given regular
+/// expression.
+///
+/// Both the actual value and the expected regular expression may be either a
+/// `String` or a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", contains_regex("S.*e"))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!("Another value", contains_regex("Some"))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), contains_regex("v.*e"))?; // Passes
+/// verify_that!("Some value", contains_regex("v.*e".to_string()))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// Panics if the given `pattern` is not a syntactically valid regular
+/// expression.
+// N.B. This returns the concrete type rather than an impl Matcher so that it
+// can act simultaneously as a Matcher<str> and a Matcher<String>. Otherwise the
+// compiler treats it as a Matcher<str> only and the code
+// verify_that!("Some value".to_string(), contains_regex(".*value"))?;
+// doesn't compile.
+pub fn contains_regex<ActualT: ?Sized, PatternT: Deref<Target = str>>(
+ pattern: PatternT,
+) -> ContainsRegexMatcher<ActualT> {
+ ContainsRegexMatcher {
+ regex: Regex::new(pattern.deref()).unwrap(),
+ phantom: Default::default(),
+ }
+}
+
+/// A matcher matching a string-like type containing a substring matching a
+/// given regular expression.
+///
+/// Intended only to be used from the function [`contains_regex`] only.
+/// Should not be referenced by code outside this library.
+pub struct ContainsRegexMatcher<ActualT: ?Sized> {
+ regex: Regex,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: AsRef<str> + Debug + ?Sized> Matcher for ContainsRegexMatcher<ActualT> {
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ self.regex.is_match(actual.as_ref()).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!("contains the regular expression {:#?}", self.regex.as_str())
+ }
+ MatcherResult::NoMatch => {
+ format!("doesn't contain the regular expression {:#?}", self.regex.as_str())
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{contains_regex, ContainsRegexMatcher};
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+
+ #[test]
+ fn contains_regex_matches_string_reference_with_pattern() -> Result<()> {
+ let matcher = contains_regex("S.*val");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn contains_regex_does_not_match_string_without_pattern() -> Result<()> {
+ let matcher = contains_regex("Another");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn contains_regex_matches_owned_string_with_pattern() -> Result<()> {
+ let matcher = contains_regex("value");
+
+ let result = matcher.matches(&"Some value".to_string());
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn contains_regex_matches_string_reference_with_owned_string() -> Result<()> {
+ let matcher = contains_regex("value");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn verify_that_works_with_owned_string() -> Result<()> {
+ verify_that!("Some value".to_string(), contains_regex("value"))
+ }
+
+ #[test]
+ fn contains_regex_displays_quoted_debug_of_pattern() -> Result<()> {
+ let matcher: ContainsRegexMatcher<&str> = contains_regex("\n");
+
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ eq("contains the regular expression \"\\n\"")
+ )
+ }
+}
diff --git a/src/matchers/disjunction_matcher.rs b/src/matchers/disjunction_matcher.rs
new file mode 100644
index 0000000..48bf226
--- /dev/null
+++ b/src/matchers/disjunction_matcher.rs
@@ -0,0 +1,109 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module.
+#![doc(hidden)]
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+
+/// Matcher created by [`Matcher::or`].
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub struct DisjunctionMatcher<M1, M2> {
+ m1: M1,
+ m2: M2,
+}
+
+impl<M1, M2> DisjunctionMatcher<M1, M2> {
+ pub(crate) fn new(m1: M1, m2: M2) -> Self {
+ Self { m1, m2 }
+ }
+}
+
+impl<M1: Matcher, M2: Matcher<ActualT = M1::ActualT>> Matcher for DisjunctionMatcher<M1, M2>
+where
+ M1::ActualT: Debug,
+{
+ type ActualT = M1::ActualT;
+
+ fn matches(&self, actual: &M1::ActualT) -> MatcherResult {
+ match (self.m1.matches(actual), self.m2.matches(actual)) {
+ (MatcherResult::NoMatch, MatcherResult::NoMatch) => MatcherResult::NoMatch,
+ _ => MatcherResult::Match,
+ }
+ }
+
+ fn explain_match(&self, actual: &M1::ActualT) -> String {
+ format!("{} and\n {}", self.m1.explain_match(actual), self.m2.explain_match(actual))
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ format!("{}, or {}", self.m1.describe(matcher_result), self.m2.describe(matcher_result))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn or_true_true_matches() -> Result<()> {
+ verify_that!(1, anything().or(anything()))
+ }
+
+ #[test]
+ fn or_true_false_matches() -> Result<()> {
+ verify_that!(1, anything().or(not(anything())))
+ }
+
+ #[test]
+ fn or_false_true_matches() -> Result<()> {
+ verify_that!(1, not(anything()).or(anything()))
+ }
+
+ #[test]
+ fn or_false_false_does_not_match() -> Result<()> {
+ let result = verify_that!(1, not(anything()).or(not(anything())));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: 1
+ Expected: never matches, or never matches
+ Actual: 1,
+ which is anything and
+ which is anything
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn chained_or_matches() -> Result<()> {
+ verify_that!(10, eq(1).or(eq(5)).or(ge(9)))
+ }
+
+ #[test]
+ fn works_with_str_slices() -> Result<()> {
+ verify_that!("A string", ends_with("A").or(ends_with("string")))
+ }
+
+ #[test]
+ fn works_with_owned_strings() -> Result<()> {
+ verify_that!("A string".to_string(), ends_with("A").or(ends_with("string")))
+ }
+}
diff --git a/src/matchers/display_matcher.rs b/src/matchers/display_matcher.rs
new file mode 100644
index 0000000..628769b
--- /dev/null
+++ b/src/matchers/display_matcher.rs
@@ -0,0 +1,119 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::{Debug, Display};
+use std::marker::PhantomData;
+
+/// Matches the string representation of types that implement `Display`.
+///
+/// ```ignore
+/// let result: impl Display = ...;
+/// verify_that!(result, displays_as(eq(format!("{}", result))))?;
+/// ```
+pub fn displays_as<T: Debug + Display, InnerMatcher: Matcher<ActualT = String>>(
+ inner: InnerMatcher,
+) -> impl Matcher<ActualT = T> {
+ DisplayMatcher::<T, _> { inner, phantom: Default::default() }
+}
+
+struct DisplayMatcher<T, InnerMatcher: Matcher> {
+ inner: InnerMatcher,
+ phantom: PhantomData<T>,
+}
+
+impl<T: Debug + Display, InnerMatcher: Matcher<ActualT = String>> Matcher
+ for DisplayMatcher<T, InnerMatcher>
+{
+ type ActualT = T;
+
+ fn matches(&self, actual: &T) -> MatcherResult {
+ self.inner.matches(&format!("{actual}"))
+ }
+
+ fn explain_match(&self, actual: &T) -> String {
+ format!("which displays as a string {}", self.inner.explain_match(&format!("{actual}")))
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!("displays as a string which {}", self.inner.describe(MatcherResult::Match))
+ }
+ MatcherResult::NoMatch => {
+ format!(
+ "doesn't display as a string which {}",
+ self.inner.describe(MatcherResult::Match)
+ )
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::displays_as;
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::fmt::{Debug, Display, Error, Formatter};
+
+ #[test]
+ fn display_matches_i32() -> Result<()> {
+ let value = 32;
+ verify_that!(value, displays_as(eq("32")))?;
+ Ok(())
+ }
+
+ #[test]
+ fn display_matches_str() -> Result<()> {
+ let value = "32";
+ verify_that!(value, displays_as(eq("32")))?;
+ Ok(())
+ }
+
+ #[test]
+ fn display_matches_struct() -> Result<()> {
+ #[allow(dead_code)]
+ #[derive(Debug)]
+ struct Struct {
+ a: i32,
+ b: i64,
+ }
+ impl Display for Struct {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
+ write!(f, "{:?}", self)
+ }
+ }
+ verify_that!(Struct { a: 123, b: 321 }, displays_as(eq("Struct { a: 123, b: 321 }")))?;
+ Ok(())
+ }
+
+ #[test]
+ fn display_displays_error_message_with_explanation_from_inner_matcher() -> Result<()> {
+ let result = verify_that!("123\n234", displays_as(eq("123\n345")));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ which displays as a string which isn't equal to \"123\\n345\"
+ Difference(-actual / +expected):
+ 123
+ -234
+ +345
+ "
+ ))))
+ )
+ }
+}
diff --git a/src/matchers/each_matcher.rs b/src/matchers/each_matcher.rs
new file mode 100644
index 0000000..ce61207
--- /dev/null
+++ b/src/matchers/each_matcher.rs
@@ -0,0 +1,243 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::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`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass_1() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(value, each(gt(0)))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// # let value = vec![1, 2, 3];
+/// verify_that!(value, each(lt(2)))?; // Fails: 2 and 3 are not less than 2
+/// # Ok(())
+/// # }
+///
+/// # fn should_pass_2() -> Result<()> {
+/// let value: HashSet<i32> = [1, 2, 3].into();
+/// verify_that!(value, each(gt(0)))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// One can also verify the contents of a slice by dereferencing it:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = &[1, 2, 3];
+/// verify_that!(*value, each(gt(0)))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn each<ElementT: Debug, ActualT: Debug + ?Sized, MatcherT>(
+ inner: MatcherT,
+) -> impl Matcher<ActualT = ActualT>
+where
+ for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+ MatcherT: Matcher<ActualT = ElementT>,
+{
+ EachMatcher { inner, phantom: Default::default() }
+}
+
+struct EachMatcher<ActualT: ?Sized, MatcherT> {
+ inner: MatcherT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ElementT: Debug, ActualT: Debug + ?Sized, MatcherT> Matcher for EachMatcher<ActualT, MatcherT>
+where
+ for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+ MatcherT: Matcher<ActualT = ElementT>,
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ for element in actual {
+ if self.inner.matches(element).is_no_match() {
+ return MatcherResult::NoMatch;
+ }
+ }
+ MatcherResult::Match
+ }
+
+ fn explain_match(&self, actual: &ActualT) -> String {
+ let mut non_matching_elements = Vec::new();
+ for (index, element) in actual.into_iter().enumerate() {
+ if self.inner.matches(element).is_no_match() {
+ non_matching_elements.push((index, element, self.inner.explain_match(element)));
+ }
+ }
+ if non_matching_elements.is_empty() {
+ return format!("whose each element {}", self.inner.describe(MatcherResult::Match));
+ }
+ if non_matching_elements.len() == 1 {
+ let (idx, element, explanation) = non_matching_elements.remove(0);
+ return format!("whose element #{idx} is {element:?}, {explanation}");
+ }
+
+ let failed_indexes = non_matching_elements
+ .iter()
+ .map(|&(idx, _, _)| format!("#{idx}"))
+ .collect::<Vec<_>>()
+ .join(", ");
+ let element_explanations = non_matching_elements
+ .iter()
+ .map(|&(_, element, ref explanation)| format!("{element:?}, {explanation}"))
+ .collect::<Description>()
+ .indent();
+ format!("whose elements {failed_indexes} don't match\n{element_explanations}")
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!("only contains elements that {}", self.inner.describe(MatcherResult::Match))
+ }
+ MatcherResult::NoMatch => {
+ format!("contains no element that {}", self.inner.describe(MatcherResult::Match))
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::each;
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::collections::HashSet;
+
+ #[test]
+ fn each_matches_empty_vec() -> Result<()> {
+ let value: Vec<i32> = vec![];
+ verify_that!(value, each(gt(0)))
+ }
+
+ #[test]
+ fn each_matches_vec_with_one_element() -> Result<()> {
+ let value = vec![1];
+ verify_that!(value, each(gt(0)))
+ }
+
+ #[test]
+ fn each_matches_vec_with_two_elements() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, each(gt(0)))
+ }
+
+ #[test]
+ fn each_matches_slice_with_one_element() -> Result<()> {
+ let value = &[1];
+ verify_that!(*value, each(gt(0)))
+ }
+
+ #[test]
+ fn each_matches_hash_set_with_one_element() -> Result<()> {
+ let value: HashSet<i32> = [1].into();
+ verify_that!(value, each(gt(0)))
+ }
+
+ #[test]
+ fn each_does_not_match_when_first_element_does_not_match() -> Result<()> {
+ let value = vec![0];
+ verify_that!(value, not(each(gt(1))))
+ }
+
+ #[test]
+ fn each_does_not_match_when_second_element_does_not_match() -> Result<()> {
+ let value = vec![2, 0];
+ verify_that!(value, not(each(gt(1))))
+ }
+
+ #[test]
+ fn each_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
+ let result = verify_that!(vec![0, 2, 3], each(gt(0)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![0, 2, 3]
+ Expected: only contains elements that is greater than 0
+ Actual: [0, 2, 3],
+ whose element #0 is 0, which is less than or equal to 0"
+ ))))
+ )
+ }
+
+ #[test]
+ fn each_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
+ let result = verify_that!(vec![1, 0, 3], each(gt(0)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 0, 3]
+ Expected: only contains elements that is greater than 0
+ Actual: [1, 0, 3],
+ whose element #1 is 0, which is less than or equal to 0"
+ ))))
+ )
+ }
+
+ #[test]
+ fn each_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
+ let result = verify_that!(vec![0, 1, 3], each(gt(1)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![0, 1, 3]
+ Expected: only contains elements that is greater than 1
+ Actual: [0, 1, 3],
+ whose elements #0, #1 don't match
+ 0, which is less than or equal to 1
+ 1, which is less than or equal to 1"
+ ))))
+ )
+ }
+ #[test]
+ fn each_shows_inner_explanation() -> Result<()> {
+ let result = verify_that!(vec![vec![1, 2], vec![1]], each(each(eq(1))));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ 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
new file mode 100644
index 0000000..736bf47
--- /dev/null
+++ b/src/matchers/elements_are_matcher.rs
@@ -0,0 +1,174 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a container's elements to each matcher in order.
+///
+/// This macro produces a matcher against a container. It takes as arguments a
+/// sequence of matchers each of which should respectively match the
+/// corresponding element of the actual value.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!(vec![1, 2, 3], elements_are![eq(1), anything(), gt(0).and(lt(123))])
+/// # .unwrap();
+/// ```
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// let vector = vec![1, 2, 3];
+/// let slice = vector.as_slice();
+/// verify_that!(*slice, elements_are![eq(1), anything(), gt(0).and(lt(123))])
+/// # .unwrap();
+/// ```
+///
+/// This can also be omitted in [`verify_that!`] macros and replaced with square
+/// brackets.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!(vec![1, 2], [eq(1), eq(2)])
+/// # .unwrap();
+/// ```
+///
+/// Note: This behavior is only possible in [`verify_that!`] macros. In any
+/// other cases, it is still necessary to use the
+/// [`elements_are!`][crate::elements_are] macro.
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// verify_that!(vec![vec![1,2], vec![3]], [[eq(1), eq(2)], [eq(3)]])
+/// # .unwrap();
+/// ```
+///
+/// Use this instead:
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!(vec![vec![1,2], vec![3]], [elements_are![eq(1), eq(2)], elements_are![eq(3)]])
+/// # .unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// 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]
+/// instead.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! elements_are {
+ ($($matcher:expr),* $(,)?) => {{
+ use $crate::matchers::elements_are_matcher::internal::ElementsAre;
+ ElementsAre::new(vec![$(Box::new($matcher)),*])
+ }}
+}
+
+/// Module for use only by the procedural macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::matcher_support::description::Description;
+ use crate::matcher_support::zipped_iterator::zip;
+ use std::{fmt::Debug, marker::PhantomData};
+
+ /// This struct is meant to be used only by the macro `elements_are!`.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub struct ElementsAre<'a, ContainerT: ?Sized, T: Debug> {
+ elements: Vec<Box<dyn Matcher<ActualT = T> + 'a>>,
+ phantom: PhantomData<ContainerT>,
+ }
+
+ impl<'a, ContainerT: ?Sized, T: Debug> ElementsAre<'a, ContainerT, T> {
+ /// Factory only intended for use in the macro `elements_are!`.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub fn new(elements: Vec<Box<dyn Matcher<ActualT = T> + 'a>>) -> Self {
+ Self { elements, phantom: Default::default() }
+ }
+ }
+
+ impl<'a, T: Debug, ContainerT: Debug + ?Sized> Matcher for ElementsAre<'a, ContainerT, T>
+ where
+ for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+ {
+ type ActualT = ContainerT;
+
+ fn matches(&self, actual: &ContainerT) -> MatcherResult {
+ let mut zipped_iterator = zip(actual.into_iter(), self.elements.iter());
+ for (a, e) in zipped_iterator.by_ref() {
+ if e.matches(a).is_no_match() {
+ return MatcherResult::NoMatch;
+ }
+ }
+ if !zipped_iterator.has_size_mismatch() {
+ MatcherResult::Match
+ } else {
+ MatcherResult::NoMatch
+ }
+ }
+
+ fn explain_match(&self, actual: &ContainerT) -> String {
+ let actual_iterator = actual.into_iter();
+ let mut zipped_iterator = zip(actual_iterator, self.elements.iter());
+ let mut mismatches = Vec::new();
+ for (idx, (a, e)) in zipped_iterator.by_ref().enumerate() {
+ if e.matches(a).is_no_match() {
+ mismatches.push(format!("element #{idx} is {a:?}, {}", e.explain_match(a)));
+ }
+ }
+ if mismatches.is_empty() {
+ if !zipped_iterator.has_size_mismatch() {
+ "whose elements all match".to_string()
+ } else {
+ format!("whose size is {}", zipped_iterator.left_size())
+ }
+ } else if mismatches.len() == 1 {
+ let mismatches = mismatches.into_iter().collect::<Description>();
+ format!("where {mismatches}")
+ } else {
+ let mismatches = mismatches.into_iter().collect::<Description>();
+ format!("where:\n{}", mismatches.bullet_list().indent())
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ format!(
+ "{} elements:\n{}",
+ if matcher_result.into() { "has" } else { "doesn't have" },
+ &self
+ .elements
+ .iter()
+ .map(|matcher| matcher.describe(MatcherResult::Match))
+ .collect::<Description>()
+ .enumerate()
+ .indent()
+ )
+ }
+ }
+}
diff --git a/src/matchers/empty_matcher.rs b/src/matchers/empty_matcher.rs
new file mode 100644
index 0000000..afefcb7
--- /dev/null
+++ b/src/matchers/empty_matcher.rs
@@ -0,0 +1,103 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches an empty container.
+///
+/// `T` can be any container such that `&T` implements `IntoIterator`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass() -> Result<()> {
+/// let value: Vec<i32> = vec![];
+/// verify_that!(value, empty())?;
+/// let value: HashSet<i32> = HashSet::new();
+/// verify_that!(value, empty())?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// One can also check whether a slice is empty by dereferencing it:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass() -> Result<()> {
+/// let value: &[u32] = &[];
+/// verify_that!(*value, empty())?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+
+pub fn empty<T: Debug + ?Sized>() -> impl Matcher<ActualT = T>
+where
+ for<'a> &'a T: IntoIterator,
+{
+ EmptyMatcher { phantom: Default::default() }
+}
+
+struct EmptyMatcher<T: ?Sized> {
+ phantom: PhantomData<T>,
+}
+
+impl<T: Debug + ?Sized> Matcher for EmptyMatcher<T>
+where
+ for<'a> &'a T: IntoIterator,
+{
+ type ActualT = T;
+
+ fn matches(&self, actual: &T) -> MatcherResult {
+ 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()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::empty;
+ use crate::prelude::*;
+ use std::collections::HashSet;
+
+ #[test]
+ fn empty_matcher_match_empty_vec() -> Result<()> {
+ let value: Vec<i32> = vec![];
+ verify_that!(value, empty())
+ }
+
+ #[test]
+ fn empty_matcher_does_not_match_empty_vec() -> Result<()> {
+ let value = vec![1, 2, 3];
+ verify_that!(value, not(empty()))
+ }
+
+ #[test]
+ fn empty_matcher_matches_empty_slice() -> Result<()> {
+ let value: &[i32] = &[];
+ verify_that!(*value, empty())
+ }
+
+ #[test]
+ fn empty_matcher_matches_empty_hash_set() -> Result<()> {
+ let value: HashSet<i32> = HashSet::new();
+ verify_that!(value, empty())
+ }
+}
diff --git a/src/matchers/eq_deref_of_matcher.rs b/src/matchers/eq_deref_of_matcher.rs
new file mode 100644
index 0000000..1540905
--- /dev/null
+++ b/src/matchers/eq_deref_of_matcher.rs
@@ -0,0 +1,151 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{
+ matcher::{Matcher, MatcherResult},
+ matcher_support::{edit_distance, summarize_diff::create_diff},
+};
+use std::{fmt::Debug, marker::PhantomData, ops::Deref};
+
+/// Matches a value equal (in the sense of `==`) to the dereferenced value of
+/// `expected`.
+///
+/// This is similar to [`eq`][crate::matchers::eq] but takes a reference or
+/// smart pointer to the expected value rather than consuming it. This is useful
+/// when:
+///
+/// * one has only a reference to the expected value, and
+/// * the expected value cannot or should not be copied or cloned to create an
+/// owned value from it.
+///
+/// ```
+/// # use googletest::{matchers::eq_deref_of, verify_that};
+/// #[derive(Debug, PartialEq)]
+/// struct NonCloneableStruct(i32);
+/// let expected = NonCloneableStruct(123);
+/// verify_that!(NonCloneableStruct(123), eq_deref_of(&expected))
+/// # .unwrap()
+/// ```
+///
+/// **Note**: while one can use `eq_deref_of` with the configuration methods of
+/// [`StrMatcherConfigurator`][crate::matchers::str_matcher::StrMatcherConfigurator]
+/// to configure string equality, it is not possible to do so when the input is
+/// a smart pointer to a string.
+///
+/// ```compile_fail
+/// # use googletest::{matchers::{eq_deref_of, str_matcher::StrMatcherConfigurator}, verify_that};
+/// verify_that!("A string", eq_deref_of(Box::new("A STRING")).ignoring_ascii_case()) // Does not compile
+/// # .unwrap()
+/// ```
+///
+/// Otherwise, this has the same behaviour as [`eq`][crate::matchers::eq].
+pub fn eq_deref_of<ActualT: ?Sized, ExpectedRefT>(
+ expected: ExpectedRefT,
+) -> EqDerefOfMatcher<ActualT, ExpectedRefT> {
+ EqDerefOfMatcher { expected, phantom: Default::default() }
+}
+
+/// A matcher which matches a value equal to the derefenced value of `expected`.
+///
+/// See [`eq_deref_of`].
+pub struct EqDerefOfMatcher<ActualT: ?Sized, ExpectedRefT> {
+ pub(crate) expected: ExpectedRefT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT, ExpectedRefT, ExpectedT> Matcher for EqDerefOfMatcher<ActualT, ExpectedRefT>
+where
+ ActualT: Debug + ?Sized,
+ ExpectedRefT: Deref<Target = ExpectedT> + Debug,
+ ExpectedT: PartialEq<ActualT> + Debug,
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ (self.expected.deref() == actual).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!("is equal to {:?}", self.expected),
+ MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected),
+ }
+ }
+
+ fn explain_match(&self, actual: &ActualT) -> String {
+ format!(
+ "which {}{}",
+ &self.describe(self.matches(actual)),
+ create_diff(
+ &format!("{:#?}", actual),
+ &format!("{:#?}", self.expected.deref()),
+ edit_distance::Mode::Exact,
+ )
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::eq_deref_of;
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[derive(Debug, PartialEq)]
+ struct NonCloneNonCopyStruct(i32);
+
+ #[test]
+ fn matches_value_with_ref_to_equal_value() -> Result<()> {
+ verify_that!(NonCloneNonCopyStruct(123), eq_deref_of(&NonCloneNonCopyStruct(123)))
+ }
+
+ #[test]
+ fn matches_value_with_box_of_equal_value() -> Result<()> {
+ verify_that!(NonCloneNonCopyStruct(123), eq_deref_of(Box::new(NonCloneNonCopyStruct(123))))
+ }
+
+ #[test]
+ fn does_not_match_value_with_non_equal_value() -> Result<()> {
+ verify_that!(NonCloneNonCopyStruct(123), not(eq_deref_of(&NonCloneNonCopyStruct(234))))
+ }
+
+ #[test]
+ fn shows_structured_diff() -> Result<()> {
+ #[derive(Debug, PartialEq)]
+ struct Strukt {
+ int: i32,
+ string: String,
+ }
+
+ let result = verify_that!(
+ Strukt { int: 123, string: "something".into() },
+ eq_deref_of(Box::new(Strukt { int: 321, string: "someone".into() }))
+ );
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ "
+ Actual: Strukt { int: 123, string: \"something\" },
+ which isn't equal to Strukt { int: 321, string: \"someone\" }
+ Difference(-actual / +expected):
+ Strukt {
+ - int: 123,
+ + int: 321,
+ - string: \"something\",
+ + string: \"someone\",
+ }
+ "})))
+ )
+ }
+}
diff --git a/src/matchers/eq_matcher.rs b/src/matchers/eq_matcher.rs
new file mode 100644
index 0000000..42684c9
--- /dev/null
+++ b/src/matchers/eq_matcher.rs
@@ -0,0 +1,405 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use crate::matcher_support::edit_distance;
+use crate::matcher_support::summarize_diff::create_diff;
+
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value equal (in the sense of `==`) to `expected`.
+///
+/// The type of `expected` must implement the [`PartialEq`] trait so that the
+/// expected and actual values can be compared.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(123, eq(123))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(123, eq(234))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// `expected` to `actual` must be comparable with one another via the
+/// [`PartialEq`] trait. In most cases, this means that they must be of the same
+/// type. However, there are a few cases where different but closely related
+/// types are comparable, for example `String` with `&str`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(String::from("Some value"), eq("Some value"))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// In most cases however, one must convert one of the arguments explicitly.
+/// This can be surprising when comparing integer types or references.
+///
+/// ```compile_fail
+/// verify_that!(123u32, eq(123u64))?; // Does not compile
+/// verify_that!(123u32 as u64, eq(123u64))?; // Passes
+/// ```
+///
+/// ```ignore
+/// let actual: &T = ...;
+/// let expected: T = T{...};
+/// verify_that(actual, eq(expected))?; // Does not compile
+/// verify_that(actual, eq(&expected))?; // Compiles
+/// ```
+///
+/// When matching with string types (`&str` and `String`), one can set more
+/// options on how equality is checked through the
+/// [`StrMatcherConfigurator`][crate::matchers::str_matcher::StrMatcherConfigurator]
+/// extension trait, which is implemented for this matcher.
+pub fn eq<A: ?Sized, T>(expected: T) -> EqMatcher<A, T> {
+ EqMatcher { expected, phantom: Default::default() }
+}
+
+/// A matcher which matches a value equal to `expected`.
+///
+/// See [`eq`].
+pub struct EqMatcher<A: ?Sized, T> {
+ pub(crate) expected: T,
+ phantom: PhantomData<A>,
+}
+
+impl<A: Debug + ?Sized, T: PartialEq<A> + Debug> Matcher for EqMatcher<A, T> {
+ type ActualT = A;
+
+ fn matches(&self, actual: &A) -> MatcherResult {
+ (self.expected == *actual).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!("is equal to {:?}", self.expected),
+ MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected),
+ }
+ }
+
+ fn explain_match(&self, actual: &A) -> String {
+ let expected_debug = format!("{:#?}", self.expected);
+ let actual_debug = format!("{:#?}", actual);
+ let description = self.describe(self.matches(actual));
+
+ let diff = if is_multiline_string_debug(&actual_debug)
+ && is_multiline_string_debug(&expected_debug)
+ {
+ create_diff(
+ // The two calls below return None if and only if the strings expected_debug
+ // respectively actual_debug are not enclosed in ". The calls to
+ // is_multiline_string_debug above ensure that they are. So the calls cannot
+ // actually return None and unwrap() should not panic.
+ &to_display_output(&actual_debug).unwrap(),
+ &to_display_output(&expected_debug).unwrap(),
+ edit_distance::Mode::Exact,
+ )
+ } else {
+ create_diff(&actual_debug, &expected_debug, edit_distance::Mode::Exact)
+ };
+
+ format!("which {description}{diff}")
+ }
+}
+
+fn is_multiline_string_debug(string: &str) -> bool {
+ string.starts_with('"')
+ && string.ends_with('"')
+ && !string.contains('\n')
+ && string.contains("\\n")
+}
+
+fn to_display_output(string: &str) -> Option<String> {
+ Some(string.strip_prefix('"')?.strip_suffix('"')?.split("\\n").collect::<Vec<_>>().join("\n"))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::eq;
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn eq_matches_string_reference_with_string_reference() -> Result<()> {
+ verify_that!("A string", eq("A string"))
+ }
+
+ #[test]
+ fn eq_matches_owned_string_with_string_reference() -> Result<()> {
+ let value = "A string".to_string();
+ verify_that!(value, eq("A string"))
+ }
+
+ #[test]
+ fn eq_matches_owned_string_reference_with_string_reference() -> Result<()> {
+ let value = "A string".to_string();
+ verify_that!(&value, eq("A string"))
+ }
+
+ #[test]
+ fn eq_matches_i32_with_i32() -> Result<()> {
+ verify_that!(123, eq(123))
+ }
+
+ #[test]
+ fn eq_struct_debug_diff() -> Result<()> {
+ #[derive(Debug, PartialEq)]
+ struct Strukt {
+ int: i32,
+ string: String,
+ }
+
+ let result = verify_that!(
+ Strukt { int: 123, string: "something".into() },
+ eq(Strukt { int: 321, string: "someone".into() })
+ );
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ "
+ Actual: Strukt { int: 123, string: \"something\" },
+ which isn't equal to Strukt { int: 321, string: \"someone\" }
+ Difference(-actual / +expected):
+ Strukt {
+ - int: 123,
+ + int: 321,
+ - string: \"something\",
+ + string: \"someone\",
+ }
+ "})))
+ )
+ }
+
+ #[test]
+ fn eq_vec_debug_diff() -> Result<()> {
+ let result = verify_that!(vec![1, 2, 3], eq(vec![1, 3, 4]));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ "
+ Value of: vec![1, 2, 3]
+ Expected: is equal to [1, 3, 4]
+ Actual: [1, 2, 3],
+ which isn't equal to [1, 3, 4]
+ Difference(-actual / +expected):
+ [
+ 1,
+ - 2,
+ 3,
+ + 4,
+ ]
+ "})))
+ )
+ }
+
+ #[test]
+ fn eq_vec_debug_diff_length_mismatch() -> Result<()> {
+ let result = verify_that!(vec![1, 2, 3, 4, 5], eq(vec![1, 3, 5]));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ "
+ Value of: vec![1, 2, 3, 4, 5]
+ Expected: is equal to [1, 3, 5]
+ Actual: [1, 2, 3, 4, 5],
+ which isn't equal to [1, 3, 5]
+ Difference(-actual / +expected):
+ [
+ 1,
+ - 2,
+ 3,
+ - 4,
+ 5,
+ ]
+ "})))
+ )
+ }
+
+ #[test]
+ fn eq_debug_diff_common_lines_omitted() -> Result<()> {
+ let result = verify_that!((1..50).collect::<Vec<_>>(), eq((3..52).collect::<Vec<_>>()));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ "
+ Difference(-actual / +expected):
+ [
+ - 1,
+ - 2,
+ 3,
+ 4,
+ <---- 43 common lines omitted ---->
+ 48,
+ 49,
+ + 50,
+ + 51,
+ ]"})))
+ )
+ }
+
+ #[test]
+ fn eq_debug_diff_5_common_lines_not_omitted() -> Result<()> {
+ let result = verify_that!((1..8).collect::<Vec<_>>(), eq((3..10).collect::<Vec<_>>()));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ "
+ Difference(-actual / +expected):
+ [
+ - 1,
+ - 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ + 8,
+ + 9,
+ ]"})))
+ )
+ }
+
+ #[test]
+ fn eq_debug_diff_start_common_lines_omitted() -> Result<()> {
+ let result = verify_that!((1..50).collect::<Vec<_>>(), eq((1..52).collect::<Vec<_>>()));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ "
+ Difference(-actual / +expected):
+ [
+ 1,
+ <---- 46 common lines omitted ---->
+ 48,
+ 49,
+ + 50,
+ + 51,
+ ]"})))
+ )
+ }
+
+ #[test]
+ fn eq_debug_diff_end_common_lines_omitted() -> Result<()> {
+ let result = verify_that!((1..52).collect::<Vec<_>>(), eq((3..52).collect::<Vec<_>>()));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ "
+ Difference(-actual / +expected):
+ [
+ - 1,
+ - 2,
+ 3,
+ 4,
+ <---- 46 common lines omitted ---->
+ 51,
+ ]"})))
+ )
+ }
+
+ #[test]
+ fn eq_multi_line_string_debug_diff() -> Result<()> {
+ let result = verify_that!("One\nTwo\nThree", eq("One\nSix\nThree"));
+ // TODO: b/257454450 - Make this more useful, by potentially unescaping the
+ // line return.
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ r#"
+ Value of: "One\nTwo\nThree"
+ Expected: is equal to "One\nSix\nThree"
+ Actual: "One\nTwo\nThree",
+ which isn't equal to "One\nSix\nThree"
+ "#})))
+ )
+ }
+
+ #[test]
+ fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()> {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ "
+ ),
+ eq(indoc!(
+ "
+ First line
+ Second lines
+ Third line
+ "
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ First line
+ -Second line
+ +Second lines
+ Third line
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()> {
+ let result = verify_that!(
+ "First line",
+ eq(indoc!(
+ "
+ First line
+ Second line
+ Third line
+ "
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()> {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ "
+ ),
+ eq("First line")
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
+ )
+ }
+}
diff --git a/src/matchers/err_matcher.rs b/src/matchers/err_matcher.rs
new file mode 100644
index 0000000..022bc8c
--- /dev/null
+++ b/src/matchers/err_matcher.rs
@@ -0,0 +1,144 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a `Result` containing `Err` with a value matched by `inner`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> googletest::Result<()> {
+/// verify_that!(Err::<(), _>("Some error"), err(eq("Some error")))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> googletest::Result<()> {
+/// verify_that!(Ok::<_, &str>("A value"), err(eq("A value")))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> googletest::Result<()> {
+/// verify_that!(Err::<(), _>("Some error"), err(eq("Some other error")))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+pub fn err<T: Debug, E: Debug>(
+ inner: impl Matcher<ActualT = E>,
+) -> impl Matcher<ActualT = std::result::Result<T, E>> {
+ ErrMatcher::<T, E, _> { inner, phantom_t: Default::default(), phantom_e: Default::default() }
+}
+
+struct ErrMatcher<T, E, InnerMatcherT> {
+ inner: InnerMatcherT,
+ phantom_t: PhantomData<T>,
+ phantom_e: PhantomData<E>,
+}
+
+impl<T: Debug, E: Debug, InnerMatcherT: Matcher<ActualT = E>> Matcher
+ for ErrMatcher<T, E, InnerMatcherT>
+{
+ type ActualT = std::result::Result<T, E>;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ actual.as_ref().err().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
+ }
+
+ fn explain_match(&self, actual: &Self::ActualT) -> String {
+ match actual {
+ Err(e) => format!("which is an error {}", self.inner.explain_match(e)),
+ Ok(_) => "which is a success".to_string(),
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!("is an error which {}", self.inner.describe(MatcherResult::Match))
+ }
+ MatcherResult::NoMatch => {
+ format!(
+ "is a success or is an error containing a value which {}",
+ self.inner.describe(MatcherResult::NoMatch)
+ )
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::err;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn err_matches_result_with_err_value() -> Result<()> {
+ let matcher = err(eq(1));
+ let value: std::result::Result<i32, i32> = Err(1);
+
+ let result = matcher.matches(&value);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn err_does_not_match_result_with_wrong_err_value() -> Result<()> {
+ let matcher = err(eq(1));
+ let value: std::result::Result<i32, i32> = Err(0);
+
+ let result = matcher.matches(&value);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn err_does_not_match_result_with_ok() -> Result<()> {
+ let matcher = err(eq(1));
+ let value: std::result::Result<i32, i32> = Ok(1);
+
+ let result = matcher.matches(&value);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn err_full_error_message() -> Result<()> {
+ let result = verify_that!(Err::<i32, i32>(1), err(eq(2)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: Err::<i32, i32>(1)
+ Expected: is an error which is equal to 2
+ Actual: Err(1),
+ which is an error which isn't equal to 2
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn err_describe_matches() -> Result<()> {
+ let matcher = super::ErrMatcher::<i32, i32, _> {
+ inner: eq(1),
+ phantom_t: Default::default(),
+ phantom_e: Default::default(),
+ };
+ verify_that!(matcher.describe(MatcherResult::Match), eq("is an error which is equal to 1"))
+ }
+}
diff --git a/src/matchers/field_matcher.rs b/src/matchers/field_matcher.rs
new file mode 100644
index 0000000..0d1d4fe
--- /dev/null
+++ b/src/matchers/field_matcher.rs
@@ -0,0 +1,201 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a structure or enum with a given field which is matched by a given
+/// matcher.
+///
+/// This takes two arguments:
+///
+/// * a specification of the field against which to match, and
+/// * an inner [`Matcher`][crate::matcher::Matcher] to apply to that field.
+///
+/// For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct IntField {
+/// int: i32
+/// }
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(IntField{int: 32}, field!(IntField.int, eq(32)))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Tuple structs are also supported via the index syntax:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct IntField(i32);
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(IntField(32), field!(IntField.0, eq(32)))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Enums are also supported, in which case only the specified variant is
+/// matched:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// enum MyEnum {
+/// A(i32),
+/// B,
+/// }
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(MyEnum::A(32), field!(MyEnum::A.0, eq(32)))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(MyEnum::B, field!(MyEnum::A.0, eq(32)))?; // Fails: wrong enum variant
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// The structure or enum may also be referenced from a separate module:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// mod a_module {
+/// #[derive(Debug)]
+/// pub struct AStruct(pub i32);
+/// }
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(a_module::AStruct(32), field!(a_module::AStruct.0, eq(32)))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Nested structures are *not supported*, however:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct InnerStruct(i32);
+/// #[derive(Debug)]
+/// struct OuterStruct {
+/// inner: InnerStruct,
+/// }
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(value, field!(OuterStruct.inner.0, eq(32)))?; // Does not compile
+/// # Ok(())
+/// # }
+/// ```
+///
+/// See also the macro [`property`][crate::property] for an analogous mechanism
+/// to extract a datum by invoking a method.
+#[macro_export]
+macro_rules! field {
+ ($($t:tt)*) => { $crate::field_internal!($($t)*) }
+}
+
+// Internal-only macro created so that the macro definition does not appear in
+// generated documentation.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! field_internal {
+ ($($t:ident)::+.$field:tt, $m:expr) => {{
+ use $crate::matchers::field_matcher::internal::field_matcher;
+ field_matcher(
+ |o| {
+ match o {
+ $($t)::* { $field: value, .. } => Some(value),
+ // The pattern below is unreachable if the type is a struct (as opposed to an
+ // enum). Since the macro can't know which it is, we always include it and just
+ // tell the compiler not to complain.
+ #[allow(unreachable_patterns)]
+ _ => None,
+ }
+ },
+ &stringify!($field),
+ $m)
+ }};
+}
+
+/// Functions for use only by the declarative macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+ use crate::matcher::{Matcher, MatcherResult};
+ use std::fmt::Debug;
+
+ /// Creates a matcher to verify a specific field of the actual struct using
+ /// the provided inner matcher.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub fn field_matcher<OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<ActualT = InnerT>>(
+ field_accessor: fn(&OuterT) -> Option<&InnerT>,
+ field_path: &'static str,
+ inner: InnerMatcher,
+ ) -> impl Matcher<ActualT = OuterT> {
+ FieldMatcher { field_accessor, field_path, inner }
+ }
+
+ struct FieldMatcher<OuterT, InnerT, InnerMatcher> {
+ field_accessor: fn(&OuterT) -> Option<&InnerT>,
+ field_path: &'static str,
+ inner: InnerMatcher,
+ }
+
+ impl<OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<ActualT = InnerT>> Matcher
+ for FieldMatcher<OuterT, InnerT, InnerMatcher>
+ {
+ type ActualT = OuterT;
+
+ fn matches(&self, actual: &OuterT) -> MatcherResult {
+ if let Some(value) = (self.field_accessor)(actual) {
+ self.inner.matches(value)
+ } else {
+ MatcherResult::NoMatch
+ }
+ }
+
+ fn explain_match(&self, actual: &OuterT) -> String {
+ if let Some(actual) = (self.field_accessor)(actual) {
+ format!(
+ "which has field `{}`, {}",
+ self.field_path,
+ self.inner.explain_match(actual)
+ )
+ } 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}`")
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ format!(
+ "has field `{}`, which {}",
+ self.field_path,
+ self.inner.describe(matcher_result)
+ )
+ }
+ }
+}
diff --git a/src/matchers/ge_matcher.rs b/src/matchers/ge_matcher.rs
new file mode 100644
index 0000000..33e847e
--- /dev/null
+++ b/src/matchers/ge_matcher.rs
@@ -0,0 +1,218 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value greater than or equal to (in the sense of `>=`) `expected`.
+///
+/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
+/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
+/// `PartialOrd<ExpectedT>`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(1, ge(0))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(0, ge(1))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// In most cases the params neeed to be the same type or they need to be cast
+/// explicitly. This can be surprising when comparing integer types or
+/// references:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(123u32, ge(0u64))?; // Does not compile
+/// verify_that!(123u32 as u64, ge(0u64))?; // Passes
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 0;
+/// verify_that!(actual, ge(expected))?; // Does not compile
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 0;
+/// verify_that!(actual, ge(&expected))?; // Compiles and passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// You can find the standard library `PartialOrd` implementation in
+/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
+pub fn ge<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug>(
+ expected: ExpectedT,
+) -> impl Matcher<ActualT = ActualT> {
+ GeMatcher::<ActualT, _> { expected, phantom: Default::default() }
+}
+
+struct GeMatcher<ActualT, ExpectedT> {
+ expected: ExpectedT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug> Matcher
+ for GeMatcher<ActualT, ExpectedT>
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ (*actual >= self.expected).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!("is greater than or equal to {:?}", self.expected),
+ MatcherResult::NoMatch => format!("is less than {:?}", self.expected),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::ge;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::ffi::OsString;
+
+ #[test]
+ fn ge_matches_i32_with_i32() -> Result<()> {
+ let actual: i32 = 0;
+ let expected: i32 = 0;
+ verify_that!(actual, ge(expected))
+ }
+
+ #[test]
+ fn ge_does_not_match_smaller_i32() -> Result<()> {
+ let matcher = ge(10);
+ let result = matcher.matches(&9);
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn ge_matches_bigger_str() -> Result<()> {
+ verify_that!("B", ge("A"))
+ }
+
+ #[test]
+ fn ge_does_not_match_lesser_str() -> Result<()> {
+ let matcher = ge("z");
+ let result = matcher.matches(&"a");
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn ge_mismatch_contains_actual_and_expected() -> Result<()> {
+ let result = verify_that!(591, ge(927));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: 591
+ Expected: is greater than or equal to 927
+ Actual: 591,
+ which is less than 927
+ "
+ ))))
+ )
+ }
+
+ // Test `ge` matcher where actual is `&OsString` and expected is `&str`.
+ // Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
+ // `OsString` and `str`, but only in one direction: it's only possible to
+ // compare `OsString` with `str` if `OsString` is on the left side of the
+ // ">=" operator (`impl PartialOrd<str> for OsString`).
+ //
+ // The comparison in the other direction is not defined.
+ //
+ // This means that the test case bellow effectively ensures that
+ // `verify_that(actual, ge(expected))` works if `actual >= expected` works
+ // (regardless whether the `expected >= actual` works`).
+ #[test]
+ fn ge_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
+ let expected = "A";
+ let actual: OsString = "B".to_string().into();
+ verify_that!(&actual, ge(expected))
+ }
+
+ #[test]
+ fn ge_matches_ipv6addr_with_ipaddr() -> Result<()> {
+ use std::net::IpAddr;
+ use std::net::Ipv6Addr;
+ let actual: Ipv6Addr = "2001:4860:4860::8844".parse().unwrap();
+ let expected: IpAddr = "127.0.0.1".parse().unwrap();
+ verify_that!(actual, ge(expected))
+ }
+
+ #[test]
+ fn ge_matches_with_custom_partial_ord() -> Result<()> {
+ /// A custom "number" that is lower than all other numbers. The only
+ /// things we define about this "special" number is `PartialOrd` and
+ /// `PartialEq` against `u32`.
+ #[derive(Debug)]
+ struct VeryLowNumber {}
+
+ impl std::cmp::PartialEq<u32> for VeryLowNumber {
+ fn eq(&self, _other: &u32) -> bool {
+ false
+ }
+ }
+
+ // PartialOrd (required for >) requires PartialEq.
+ impl std::cmp::PartialOrd<u32> for VeryLowNumber {
+ fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
+ Some(std::cmp::Ordering::Less)
+ }
+ }
+
+ impl std::cmp::PartialEq<VeryLowNumber> for u32 {
+ fn eq(&self, _other: &VeryLowNumber) -> bool {
+ false
+ }
+ }
+
+ impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
+ fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
+ Some(std::cmp::Ordering::Greater)
+ }
+ }
+
+ let actual: u32 = 42;
+ let expected = VeryLowNumber {};
+
+ verify_that!(actual, ge(expected))
+ }
+}
diff --git a/src/matchers/gt_matcher.rs b/src/matchers/gt_matcher.rs
new file mode 100644
index 0000000..699bf2a
--- /dev/null
+++ b/src/matchers/gt_matcher.rs
@@ -0,0 +1,254 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value greater (in the sense of `>`) than `expected`.
+///
+/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
+/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
+/// `PartialOrd<ExpectedT>`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(38, gt(1))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(234, gt(234))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// In most cases the params neeed to be the same type or they need to be cast
+/// explicitly. This can be surprising when comparing integer types or
+/// references:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(123u32, gt(0u64))?; // Does not compile
+/// verify_that!(123u32 as u64, gt(0u64))?; // Passes
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 1;
+/// verify_that!(actual, gt(expected))?; // Does not compile
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 1;
+/// verify_that!(actual, gt(&expected))?; // Compiles and passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// You can find the standard library `PartialOrd` implementation in
+/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
+pub fn gt<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug>(
+ expected: ExpectedT,
+) -> impl Matcher<ActualT = ActualT> {
+ GtMatcher::<ActualT, _> { expected, phantom: Default::default() }
+}
+
+struct GtMatcher<ActualT, ExpectedT> {
+ expected: ExpectedT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug> Matcher
+ for GtMatcher<ActualT, ExpectedT>
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ (*actual > self.expected).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!("is greater than {:?}", self.expected),
+ MatcherResult::NoMatch => format!("is less than or equal to {:?}", self.expected),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::gt;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::ffi::OsString;
+
+ #[test]
+ fn gt_matches_i32_with_i32() -> Result<()> {
+ let actual: i32 = 321;
+ let expected: i32 = 123;
+ verify_that!(actual, gt(expected))
+ }
+
+ #[test]
+ fn gt_does_not_match_equal_i32() -> Result<()> {
+ let matcher = gt(10);
+ let result = matcher.matches(&10);
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn gt_does_not_match_lower_i32() -> Result<()> {
+ let matcher = gt(-50);
+ let result = matcher.matches(&-51);
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn gt_matches_greater_str() -> Result<()> {
+ verify_that!("B", gt("A"))
+ }
+
+ #[test]
+ fn gt_does_not_match_lesser_str() -> Result<()> {
+ let matcher = gt("B");
+ let result = matcher.matches(&"A");
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn gt_mismatch_contains_actual_and_expected() -> Result<()> {
+ let result = verify_that!(481, gt(632));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: 481
+ Expected: is greater than 632
+ Actual: 481,
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn gt_mismatch_combined_with_each() -> Result<()> {
+ let result = verify_that!(vec![19, 23, 11], each(gt(15)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![19, 23, 11]
+ Expected: only contains elements that is greater than 15
+ Actual: [19, 23, 11],
+ whose element #2 is 11, which is less than or equal to 15
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn gt_describe_matches() -> Result<()> {
+ verify_that!(gt::<i32, i32>(232).describe(MatcherResult::Match), 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")
+ )
+ }
+
+ // Test `gt` matcher where actual is `&OsString` and expected is `&str`.
+ // Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
+ // `OsString` and `str`, but only in one direction: it's only possible to
+ // compare `OsString` with `str` if `OsString` is on the left side of the
+ // ">" operator (`impl PartialOrd<str> for OsString`).
+ //
+ // The comparison in the other direction is not defined.
+ //
+ // This means that the test case bellow effectively ensures that
+ // `verify_that(actual, gt(expected))` works if `actual > expected` works
+ // (regardless whether the `expected > actual` works`).
+ #[test]
+ fn gt_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
+ let expected = "A";
+ let actual: OsString = "B".to_string().into();
+ verify_that!(&actual, gt(expected))
+ }
+
+ #[test]
+ fn gt_matches_ipv6addr_with_ipaddr() -> Result<()> {
+ use std::net::IpAddr;
+ use std::net::Ipv6Addr;
+ let actual: Ipv6Addr = "2001:4860:4860::8888".parse().unwrap();
+ let expected: IpAddr = "127.0.0.1".parse().unwrap();
+ verify_that!(actual, gt(expected))
+ }
+
+ #[test]
+ fn gt_matches_with_custom_partial_ord() -> Result<()> {
+ /// A custom "number" that is smaller than all other numbers. The only
+ /// things we define about this "special" number is `PartialOrd` and
+ /// `PartialEq` against `u32`.
+ #[derive(Debug)]
+ struct VeryLowNumber {}
+
+ impl std::cmp::PartialEq<u32> for VeryLowNumber {
+ fn eq(&self, _other: &u32) -> bool {
+ false
+ }
+ }
+
+ // PartialOrd (required for >) requires PartialEq.
+ impl std::cmp::PartialOrd<u32> for VeryLowNumber {
+ fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
+ Some(std::cmp::Ordering::Less)
+ }
+ }
+
+ impl std::cmp::PartialEq<VeryLowNumber> for u32 {
+ fn eq(&self, _other: &VeryLowNumber) -> bool {
+ false
+ }
+ }
+
+ impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
+ fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
+ Some(std::cmp::Ordering::Greater)
+ }
+ }
+
+ let actual: u32 = 42;
+ let expected = VeryLowNumber {};
+
+ verify_that!(actual, gt(expected))
+ }
+}
diff --git a/src/matchers/has_entry_matcher.rs b/src/matchers/has_entry_matcher.rs
new file mode 100644
index 0000000..67a44cd
--- /dev/null
+++ b/src/matchers/has_entry_matcher.rs
@@ -0,0 +1,184 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::collections::HashMap;
+use std::fmt::Debug;
+use std::hash::Hash;
+use std::marker::PhantomData;
+
+/// Matches a HashMap containing the given `key` whose value is matched by the
+/// matcher `inner`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// # fn should_pass() -> Result<()> {
+/// let value = HashMap::from([(0, 1), (1, -1)]);
+/// verify_that!(value, has_entry(0, eq(1)))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// # let value = HashMap::from([(0, 1), (1, -1)]);
+/// verify_that!(value, has_entry(1, gt(0)))?; // Fails: value not matched
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// # let value = HashMap::from([(0, 1), (1, -1)]);
+/// verify_that!(value, has_entry(2, eq(0)))?; // Fails: key not present
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+///
+/// Note: One could obtain the same effect by collecting entries into a `Vec`
+/// and using `contains`:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// # fn should_pass() -> Result<()> {
+/// let value = HashMap::from([(0, 1), (1, -1)]);
+/// verify_that!(value.into_iter().collect::<Vec<_>>(), contains(eq((0, 1))))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// However, `has_entry` will offer somewhat better diagnostic messages in the
+/// case of assertion failure. And it avoid the extra allocation hidden in the
+/// code above.
+pub fn has_entry<KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<ActualT = ValueT>>(
+ key: KeyT,
+ inner: MatcherT,
+) -> impl Matcher<ActualT = HashMap<KeyT, ValueT>> {
+ HasEntryMatcher { key, inner, phantom: Default::default() }
+}
+
+struct HasEntryMatcher<KeyT, ValueT, MatcherT> {
+ key: KeyT,
+ inner: MatcherT,
+ phantom: PhantomData<ValueT>,
+}
+
+impl<KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<ActualT = ValueT>> Matcher
+ for HasEntryMatcher<KeyT, ValueT, MatcherT>
+{
+ type ActualT = HashMap<KeyT, ValueT>;
+
+ fn matches(&self, actual: &HashMap<KeyT, ValueT>) -> MatcherResult {
+ if let Some(value) = actual.get(&self.key) {
+ self.inner.matches(value)
+ } else {
+ MatcherResult::NoMatch
+ }
+ }
+
+ fn explain_match(&self, actual: &HashMap<KeyT, ValueT>) -> String {
+ if let Some(value) = actual.get(&self.key) {
+ format!(
+ "which contains key {:?}, but is mapped to value {:#?}, {}",
+ self.key,
+ value,
+ self.inner.explain_match(value)
+ )
+ } else {
+ format!("which doesn't contain key {:?}", self.key)
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!(
+ "contains key {:?}, which value {}",
+ self.key,
+ self.inner.describe(MatcherResult::Match)
+ ),
+ MatcherResult::NoMatch => format!(
+ "doesn't contain key {:?} or contains key {:?}, which value {}",
+ self.key,
+ self.key,
+ self.inner.describe(MatcherResult::NoMatch)
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::has_entry;
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::collections::HashMap;
+
+ #[test]
+ fn has_entry_does_not_match_empty_hash_map() -> Result<()> {
+ let value: HashMap<i32, i32> = HashMap::new();
+ verify_that!(value, not(has_entry(0, eq(0))))
+ }
+
+ #[test]
+ fn has_entry_matches_hash_map_with_value() -> Result<()> {
+ let value: HashMap<i32, i32> = HashMap::from([(0, 0)]);
+ verify_that!(value, has_entry(0, eq(0)))
+ }
+
+ #[test]
+ fn has_entry_does_not_match_hash_map_with_wrong_value() -> Result<()> {
+ let value: HashMap<i32, i32> = HashMap::from([(0, 1)]);
+ verify_that!(value, not(has_entry(0, eq(0))))
+ }
+
+ #[test]
+ fn has_entry_does_not_match_hash_map_with_wrong_key() -> Result<()> {
+ let value: HashMap<i32, i32> = HashMap::from([(1, 0)]);
+ verify_that!(value, not(has_entry(0, eq(0))))
+ }
+
+ #[test]
+ fn has_entry_shows_correct_message_when_key_is_not_present() -> Result<()> {
+ let result = verify_that!(HashMap::from([(0, 0)]), has_entry(1, eq(0)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: HashMap::from([(0, 0)])
+ Expected: contains key 1, which value is equal to 0
+ Actual: {0: 0},
+ which doesn't contain key 1
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn has_entry_shows_correct_message_when_key_has_non_matching_value() -> Result<()> {
+ let result = verify_that!(HashMap::from([(0, 0)]), has_entry(0, eq(1)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: HashMap::from([(0, 0)])
+ Expected: contains key 0, which value is equal to 1
+ Actual: {0: 0},
+ which contains key 0, but is mapped to value 0, which isn't equal to 1
+ "
+ ))))
+ )
+ }
+}
diff --git a/src/matchers/is_matcher.rs b/src/matchers/is_matcher.rs
new file mode 100644
index 0000000..51fd3b2
--- /dev/null
+++ b/src/matchers/is_matcher.rs
@@ -0,0 +1,65 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![doc(hidden)]
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches precisely values matched by `inner`.
+///
+/// The returned matcher produces a description prefixed by the string
+/// `description`. This is useful in contexts where the test assertion failure
+/// output must include the additional description.
+pub fn is<'a, ActualT: Debug + 'a, InnerMatcherT: Matcher<ActualT = ActualT> + 'a>(
+ description: &'a str,
+ inner: InnerMatcherT,
+) -> impl Matcher<ActualT = ActualT> + 'a {
+ IsMatcher { description, inner, phantom: Default::default() }
+}
+
+struct IsMatcher<'a, ActualT, InnerMatcherT> {
+ description: &'a str,
+ inner: InnerMatcherT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<'a, ActualT: Debug, InnerMatcherT: Matcher<ActualT = ActualT>> Matcher
+ for IsMatcher<'a, ActualT, InnerMatcherT>
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ self.inner.matches(actual)
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!(
+ "is {} which {}",
+ self.description,
+ self.inner.describe(MatcherResult::Match)
+ ),
+ MatcherResult::NoMatch => format!(
+ "is not {} which {}",
+ self.description,
+ self.inner.describe(MatcherResult::Match)
+ ),
+ }
+ }
+
+ fn explain_match(&self, actual: &Self::ActualT) -> String {
+ self.inner.explain_match(actual)
+ }
+}
diff --git a/src/matchers/is_nan_matcher.rs b/src/matchers/is_nan_matcher.rs
new file mode 100644
index 0000000..3cbe694
--- /dev/null
+++ b/src/matchers/is_nan_matcher.rs
@@ -0,0 +1,62 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use num_traits::float::Float;
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a floating point value which is NaN.
+pub fn is_nan<T: Float + Debug>() -> impl Matcher<ActualT = T> {
+ IsNanMatcher::<T>(Default::default())
+}
+
+struct IsNanMatcher<T>(PhantomData<T>);
+
+impl<T: Float + Debug> Matcher for IsNanMatcher<T> {
+ type ActualT = T;
+
+ fn matches(&self, actual: &T) -> MatcherResult {
+ actual.is_nan().into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ if matcher_result.into() { "is NaN" } else { "isn't NaN" }.to_string()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::is_nan;
+ use crate::prelude::*;
+
+ #[test]
+ fn matches_f32_nan() -> Result<()> {
+ verify_that!(f32::NAN, is_nan())
+ }
+
+ #[test]
+ fn does_not_match_f32_number() -> Result<()> {
+ verify_that!(0.0f32, not(is_nan()))
+ }
+
+ #[test]
+ fn matches_f64_nan() -> Result<()> {
+ verify_that!(f64::NAN, is_nan())
+ }
+
+ #[test]
+ fn does_not_match_f64_number() -> Result<()> {
+ verify_that!(0.0f64, not(is_nan()))
+ }
+}
diff --git a/src/matchers/le_matcher.rs b/src/matchers/le_matcher.rs
new file mode 100644
index 0000000..6e7a16f
--- /dev/null
+++ b/src/matchers/le_matcher.rs
@@ -0,0 +1,219 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value less than or equal to (in the sense of `<=`) `expected`.
+///
+/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
+/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
+/// `PartialOrd<ExpectedT>`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(0, le(0))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(1, le(0))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// In most cases the params neeed to be the same type or they need to be cast
+/// explicitly. This can be surprising when comparing integer types or
+/// references:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(1u32, le(2u64))?; // Does not compile
+/// verify_that!(1u32 as u64, le(2u64))?; // Passes
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// let actual: &u32 = &1;
+/// let expected: u32 = 2;
+/// verify_that!(actual, le(expected))?; // Does not compile
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let actual: &u32 = &1;
+/// let expected: u32 = 2;
+/// verify_that!(actual, le(&expected))?; // Compiles and passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// You can find the standard library `PartialOrd` implementation in
+/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
+pub fn le<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug>(
+ expected: ExpectedT,
+) -> impl Matcher<ActualT = ActualT> {
+ LeMatcher::<ActualT, _> { expected, phantom: Default::default() }
+}
+
+struct LeMatcher<ActualT, ExpectedT> {
+ expected: ExpectedT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug> Matcher
+ for LeMatcher<ActualT, ExpectedT>
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ (*actual <= self.expected).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!("is less than or equal to {:?}", self.expected),
+ MatcherResult::NoMatch => format!("is greater than {:?}", self.expected),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::le;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::ffi::OsString;
+
+ #[test]
+ fn le_matches_i32_with_i32() -> Result<()> {
+ let actual: i32 = 0;
+ let expected: i32 = 0;
+ verify_that!(actual, le(expected))
+ }
+
+ #[test]
+ fn le_does_not_match_bigger_i32() -> Result<()> {
+ let matcher = le(0);
+ let result = matcher.matches(&1);
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn le_matches_smaller_str() -> Result<()> {
+ verify_that!("A", le("B"))
+ }
+
+ #[test]
+ fn le_does_not_match_bigger_str() -> Result<()> {
+ let matcher = le("a");
+ let result = matcher.matches(&"z");
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn le_mismatch_contains_actual_and_expected() -> Result<()> {
+ let result = verify_that!(489, le(294));
+ let formatted_message = format!("{}", result.unwrap_err());
+
+ verify_that!(
+ formatted_message.as_str(),
+ contains_substring(indoc!(
+ "
+ Value of: 489
+ Expected: is less than or equal to 294
+ Actual: 489,
+ which is greater than 294
+ "
+ ))
+ )
+ }
+
+ // Test `le` matcher where actual is `&OsString` and expected is `&str`.
+ // Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
+ // `OsString` and `str`, but only in one direction: it's only possible to
+ // compare `OsString` with `str` if `OsString` is on the left side of the
+ // "<=" operator (`impl PartialOrd<str> for OsString`).
+ //
+ // The comparison in the other direction is not defined.
+ //
+ // This means that the test case bellow effectively ensures that
+ // `verify_that(actual, le(expected))` works if `actual <= expected` works
+ // (regardless whether the `expected <= actual` works`).
+ #[test]
+ fn le_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
+ let expected = "B";
+ let actual: OsString = "A".into();
+ verify_that!(&actual, le(expected))
+ }
+
+ #[test]
+ fn le_matches_ipv6addr_with_ipaddr() -> Result<()> {
+ use std::net::IpAddr;
+ use std::net::Ipv6Addr;
+ let actual: IpAddr = "127.0.0.1".parse().unwrap();
+ let expected: Ipv6Addr = "2001:4860:4860::8844".parse().unwrap();
+ verify_that!(actual, le(expected))
+ }
+
+ #[test]
+ fn le_matches_with_custom_partial_ord() -> Result<()> {
+ /// A custom "number" that is lower than all other numbers. The only
+ /// things we define about this "special" number is `PartialOrd` and
+ /// `PartialEq` against `u32`.
+ #[derive(Debug)]
+ struct VeryLowNumber {}
+
+ impl std::cmp::PartialEq<u32> for VeryLowNumber {
+ fn eq(&self, _other: &u32) -> bool {
+ false
+ }
+ }
+
+ // PartialOrd (required for >) requires PartialEq.
+ impl std::cmp::PartialOrd<u32> for VeryLowNumber {
+ fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
+ Some(std::cmp::Ordering::Less)
+ }
+ }
+
+ impl std::cmp::PartialEq<VeryLowNumber> for u32 {
+ fn eq(&self, _other: &VeryLowNumber) -> bool {
+ false
+ }
+ }
+
+ impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
+ fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
+ Some(std::cmp::Ordering::Greater)
+ }
+ }
+
+ let actual = VeryLowNumber {};
+ let expected: u32 = 42;
+
+ verify_that!(actual, le(expected))
+ }
+}
diff --git a/src/matchers/len_matcher.rs b/src/matchers/len_matcher.rs
new file mode 100644
index 0000000..3a31873
--- /dev/null
+++ b/src/matchers/len_matcher.rs
@@ -0,0 +1,211 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use crate::matcher_support::count_elements::count_elements;
+use std::{fmt::Debug, marker::PhantomData};
+
+/// 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.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let array = [1,2,3];
+/// verify_that!(array, len(eq(3)))?;
+/// let vec = vec![1,2,3];
+/// verify_that!(vec, len(eq(3)))?;
+/// let slice = vec.as_slice();
+/// verify_that!(*slice, len(eq(3)))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// The parameter `expected` can be any integer numeric matcher.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let vec = vec![1,2,3];
+/// verify_that!(vec, len(gt(1)))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn len<T: Debug + ?Sized, E: Matcher<ActualT = usize>>(expected: E) -> impl Matcher<ActualT = T>
+where
+ for<'a> &'a T: IntoIterator,
+{
+ LenMatcher { expected, phantom: Default::default() }
+}
+
+struct LenMatcher<T: ?Sized, E> {
+ expected: E,
+ phantom: PhantomData<T>,
+}
+
+impl<T: Debug + ?Sized, E: Matcher<ActualT = usize>> Matcher for LenMatcher<T, E>
+where
+ for<'a> &'a T: IntoIterator,
+{
+ type ActualT = T;
+
+ fn matches(&self, actual: &T) -> MatcherResult {
+ self.expected.matches(&count_elements(actual))
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!("has length, which {}", self.expected.describe(MatcherResult::Match))
+ }
+ MatcherResult::NoMatch => {
+ format!("has length, which {}", self.expected.describe(MatcherResult::NoMatch))
+ }
+ }
+ }
+
+ fn explain_match(&self, actual: &T) -> String {
+ let actual_size = count_elements(actual);
+ format!("which has length {}, {}", actual_size, self.expected.explain_match(&actual_size))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::len;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::collections::{
+ BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque,
+ };
+ use std::fmt::Debug;
+ use std::marker::PhantomData;
+
+ #[test]
+ fn len_matcher_match_vec() -> Result<()> {
+ let value = vec![1, 2, 3];
+ verify_that!(value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_array_reference() -> Result<()> {
+ let value = &[1, 2, 3];
+ verify_that!(*value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_slice_of_array() -> Result<()> {
+ let value = &[1, 2, 3];
+ verify_that!(value[0..1], len(eq(1)))
+ }
+
+ #[test]
+ fn len_matcher_match_slice_of_vec() -> Result<()> {
+ let value = vec![1, 2, 3];
+ let slice = value.as_slice();
+ verify_that!(*slice, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_sized_slice() -> Result<()> {
+ let value = [1, 2, 3];
+ verify_that!(value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_btreemap() -> Result<()> {
+ let value = BTreeMap::from([(1, 2), (2, 3), (3, 4)]);
+ verify_that!(value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_btreeset() -> Result<()> {
+ let value = BTreeSet::from([1, 2, 3]);
+ verify_that!(value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_binaryheap() -> Result<()> {
+ let value = BinaryHeap::from([1, 2, 3]);
+ verify_that!(value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_hashmap() -> Result<()> {
+ let value = HashMap::from([(1, 2), (2, 3), (3, 4)]);
+ verify_that!(value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_hashset() -> Result<()> {
+ let value = HashSet::from([1, 2, 3]);
+ verify_that!(value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_linkedlist() -> Result<()> {
+ let value = LinkedList::from([1, 2, 3]);
+ verify_that!(value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_match_vecdeque() -> Result<()> {
+ let value = VecDeque::from([1, 2, 3]);
+ verify_that!(value, len(eq(3)))
+ }
+
+ #[test]
+ fn len_matcher_explain_match() -> Result<()> {
+ struct TestMatcher<T>(PhantomData<T>);
+ impl<T: Debug> Matcher for TestMatcher<T> {
+ type ActualT = T;
+
+ fn matches(&self, _: &T) -> MatcherResult {
+ false.into()
+ }
+
+ fn describe(&self, _: MatcherResult) -> String {
+ "called described".into()
+ }
+
+ fn explain_match(&self, _: &T) -> String {
+ "called explain_match".into()
+ }
+ }
+ verify_that!(
+ len(TestMatcher(Default::default())).explain_match(&[1, 2, 3]),
+ displays_as(eq("which has length 3, called explain_match"))
+ )
+ }
+
+ #[test]
+ fn len_matcher_error_message() -> Result<()> {
+ let result = verify_that!(vec![1, 2, 3, 4], len(eq(3)));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 2, 3, 4]
+ Expected: has length, which is equal to 3
+ Actual: [1, 2, 3, 4],
+ which has length 4, which isn't equal to 3"
+ ))))
+ )
+ }
+}
diff --git a/src/matchers/lt_matcher.rs b/src/matchers/lt_matcher.rs
new file mode 100644
index 0000000..96df00c
--- /dev/null
+++ b/src/matchers/lt_matcher.rs
@@ -0,0 +1,228 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value less (in the sense of `<`) than `expected`.
+///
+/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
+/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
+/// `PartialOrd<ExpectedT>`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(1, lt(2))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(2, lt(2))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// In most cases the params neeed to be the same type or they need to be cast
+/// explicitly. This can be surprising when comparing integer types or
+/// references:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(123u32, lt(0u64))?; // Does not compile
+/// verify_that!(123u32 as u64, lt(100000000u64))?; // Passes
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 70;
+/// verify_that!(actual, lt(expected))?; // Does not compile
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 70;
+/// verify_that!(actual, lt(&expected))?; // Compiles and passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// You can find the standard library `PartialOrd` implementation in
+/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
+pub fn lt<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug>(
+ expected: ExpectedT,
+) -> impl Matcher<ActualT = ActualT> {
+ LtMatcher::<ActualT, _> { expected, phantom: Default::default() }
+}
+
+struct LtMatcher<ActualT, ExpectedT> {
+ expected: ExpectedT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug> Matcher
+ for LtMatcher<ActualT, ExpectedT>
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ (*actual < self.expected).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!("is less than {:?}", self.expected),
+ MatcherResult::NoMatch => {
+ format!("is greater than or equal to {:?}", self.expected)
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::lt;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::ffi::OsString;
+
+ #[test]
+ fn lt_matches_i32_with_i32() -> Result<()> {
+ let actual: i32 = 10000;
+ let expected: i32 = 20000;
+ verify_that!(actual, lt(expected))
+ }
+
+ #[test]
+ fn lt_does_not_match_equal_i32() -> Result<()> {
+ let matcher = lt(10);
+ let result = matcher.matches(&10);
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn lt_does_not_match_lower_i32() -> Result<()> {
+ let matcher = lt(-50);
+ let result = matcher.matches(&50);
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn lt_matches_lesser_str() -> Result<()> {
+ verify_that!("A", lt("B"))
+ }
+
+ #[test]
+ fn lt_does_not_match_bigger_str() -> Result<()> {
+ let matcher = lt("ab");
+ let result = matcher.matches(&"az");
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn lt_mismatch_contains_actual_and_expected() -> Result<()> {
+ let result = verify_that!(481, lt(45));
+ let formatted_message = format!("{}", result.unwrap_err());
+
+ verify_that!(
+ formatted_message.as_str(),
+ contains_substring(indoc!(
+ "
+ Value of: 481
+ Expected: is less than 45
+ Actual: 481,
+ which is greater than or equal to 45
+ "
+ ))
+ )
+ }
+
+ // Test `lt` matcher where actual is `&OsString` and expected is `&str`.
+ // Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
+ // `OsString` and `str`, but only in one direction: it's only possible to
+ // compare `OsString` with `str` if `OsString` is on the left side of the
+ // "<" operator (`impl PartialOrd<str> for OsString`).
+ //
+ // The comparison in the other direction is not defined.
+ //
+ // This means that the test case bellow effectively ensures that
+ // `verify_that(actual, lt(expected))` works if `actual < expected` works
+ // (regardless whether the `expected < actual` works`).
+ #[test]
+ fn lt_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
+ let expected = "C";
+ let actual: OsString = "B".to_string().into();
+ verify_that!(&actual, lt(expected))
+ }
+
+ #[test]
+ fn lt_matches_ipv6addr_with_ipaddr() -> Result<()> {
+ use std::net::IpAddr;
+ use std::net::Ipv6Addr;
+ let actual: IpAddr = "127.0.0.1".parse().unwrap();
+ let expected: Ipv6Addr = "2001:4860:4860::8844".parse().unwrap();
+ verify_that!(actual, lt(expected))
+ }
+
+ #[test]
+ fn lt_matches_with_custom_partial_ord() -> Result<()> {
+ /// A custom "number" that is smaller than all other numbers. The only
+ /// things we define about this "special" number is `PartialOrd` and
+ /// `PartialEq` against `u32`.
+ #[derive(Debug)]
+ struct VeryLowNumber {}
+
+ impl std::cmp::PartialEq<u32> for VeryLowNumber {
+ fn eq(&self, _other: &u32) -> bool {
+ false
+ }
+ }
+
+ // PartialOrd (required for >) requires PartialEq.
+ impl std::cmp::PartialOrd<u32> for VeryLowNumber {
+ fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
+ Some(std::cmp::Ordering::Less)
+ }
+ }
+
+ impl std::cmp::PartialEq<VeryLowNumber> for u32 {
+ fn eq(&self, _other: &VeryLowNumber) -> bool {
+ false
+ }
+ }
+
+ impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
+ fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
+ Some(std::cmp::Ordering::Greater)
+ }
+ }
+
+ let actual = VeryLowNumber {};
+ let expected: u32 = 42;
+
+ verify_that!(actual, lt(expected))
+ }
+}
diff --git a/src/matchers/matches_pattern.rs b/src/matchers/matches_pattern.rs
new file mode 100644
index 0000000..9c252e5
--- /dev/null
+++ b/src/matchers/matches_pattern.rs
@@ -0,0 +1,599 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a value according to a pattern of matchers.
+///
+/// This takes as an argument a specification similar to a struct or enum
+/// initialiser, where each value is a [`Matcher`][crate::matcher::Matcher]
+/// which is applied to the corresponding field.
+///
+/// This can be used to match arbitrary combinations of fields on structures
+/// using arbitrary matchers:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct MyStruct {
+/// a_field: String,
+/// another_field: String,
+/// }
+///
+/// let my_struct = MyStruct {
+/// a_field: "Something to believe in".into(),
+/// another_field: "Something else".into()
+/// };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+/// a_field: starts_with("Something"),
+/// another_field: ends_with("else"),
+/// }))
+/// # .unwrap();
+/// ```
+///
+/// It is not required to include all named fields in the specification. Omitted
+/// fields have no effect on the output of the matcher.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # struct MyStruct {
+/// # a_field: String,
+/// # another_field: String,
+/// # }
+/// #
+/// # let my_struct = MyStruct {
+/// # a_field: "Something to believe in".into(),
+/// # another_field: "Something else".into()
+/// # };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+/// a_field: starts_with("Something"),
+/// // another_field is missing, so it may be anything.
+/// }))
+/// # .unwrap();
+/// ```
+///
+/// One can use it recursively to match nested structures:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct MyStruct {
+/// a_nested_struct: MyInnerStruct,
+/// }
+///
+/// #[derive(Debug)]
+/// struct MyInnerStruct {
+/// a_field: String,
+/// }
+///
+/// let my_struct = MyStruct {
+/// a_nested_struct: MyInnerStruct { a_field: "Something to believe in".into() },
+/// };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+/// a_nested_struct: matches_pattern!(MyInnerStruct {
+/// a_field: starts_with("Something"),
+/// }),
+/// }))
+/// # .unwrap();
+/// ```
+///
+/// One can use the alias [`pat`][crate::pat] to make this less verbose:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # struct MyStruct {
+/// # a_nested_struct: MyInnerStruct,
+/// # }
+/// #
+/// # #[derive(Debug)]
+/// # struct MyInnerStruct {
+/// # a_field: String,
+/// # }
+/// #
+/// # let my_struct = MyStruct {
+/// # a_nested_struct: MyInnerStruct { a_field: "Something to believe in".into() },
+/// # };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+/// a_nested_struct: pat!(MyInnerStruct {
+/// a_field: starts_with("Something"),
+/// }),
+/// }))
+/// # .unwrap();
+/// ```
+///
+/// In addition to fields, one can match on the outputs of methods
+/// ("properties"):
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct MyStruct {
+/// a_field: String,
+/// }
+///
+/// impl MyStruct {
+/// fn get_a_field(&self) -> String { self.a_field.clone() }
+/// }
+///
+/// let my_struct = MyStruct { a_field: "Something to believe in".into() };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+/// get_a_field(): starts_with("Something"),
+/// }))
+/// # .unwrap();
+/// ```
+///
+/// **Important**: The method should be pure function with a deterministic
+/// output and no side effects. In particular, in the event of an assertion
+/// failure, it will be invoked a second time, with the assertion failure output
+/// reflecting the *second* invocation.
+///
+/// These may also include extra parameters you pass in:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # struct MyStruct {
+/// # a_field: String,
+/// # }
+/// #
+/// impl MyStruct {
+/// fn append_to_a_field(&self, suffix: &str) -> String { self.a_field.clone() + suffix }
+/// }
+///
+/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+/// append_to_a_field("a suffix"): ends_with("a suffix"),
+/// }))
+/// # .unwrap();
+/// ```
+///
+/// If the method returns a reference, precede it with the keyword `ref`:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # struct MyStruct {
+/// # a_field: String,
+/// # }
+/// #
+/// impl MyStruct {
+/// fn get_a_field_ref(&self) -> &String { &self.a_field }
+/// }
+///
+/// # 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"),
+/// }))
+/// # .unwrap();
+/// ```
+///
+/// One can also match tuple structs with up to 10 fields. In this case, all
+/// fields must have matchers:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct MyTupleStruct(String, String);
+///
+/// let my_struct = MyTupleStruct("Something".into(), "Some other thing".into());
+/// verify_that!(
+/// my_struct,
+/// matches_pattern!(MyTupleStruct(eq("Something"), eq("Some other thing")))
+/// )
+/// # .unwrap();
+/// ```
+///
+/// One can also match enum values:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// enum MyEnum {
+/// A(u32),
+/// B,
+/// }
+///
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(MyEnum::A(123), matches_pattern!(MyEnum::A(eq(123))))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(MyEnum::B, matches_pattern!(MyEnum::A(eq(123))))?; // Fails - wrong enum variant
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// This macro does not support plain (non-struct) tuples. Use the macro
+/// [`tuple`] for that purpose.
+///
+/// Trailing commas are allowed (but not required) in both ordinary and tuple
+/// structs.
+///
+/// Unfortunately, this matcher does *not* work with methods returning string
+/// slices:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// pub struct MyStruct {
+/// a_string: String,
+/// }
+/// impl MyStruct {
+/// pub fn get_a_string(&self) -> &str { &self.a_string }
+/// }
+///
+/// let value = MyStruct { a_string: "A string".into() };
+/// verify_that!(value, matches_pattern!( MyStruct {
+/// get_a_string(): eq("A string"), // Does not compile
+/// }))
+/// # .unwrap();
+/// ```
+#[macro_export]
+macro_rules! matches_pattern {
+ ($($t:tt)*) => { $crate::matches_pattern_internal!($($t)*) }
+}
+
+// Internal-only macro created so that the macro definition does not appear in
+// generated documentation.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! matches_pattern_internal {
+ (
+ [$($struct_name:tt)*],
+ { $field_name:ident : $matcher:expr $(,)? }
+ ) => {
+ $crate::matchers::is_matcher::is(
+ stringify!($($struct_name)*),
+ all!(field!($($struct_name)*.$field_name, $matcher))
+ )
+ };
+
+ (
+ [$($struct_name:tt)*],
+ { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+ ) => {
+ $crate::matchers::is_matcher::is(
+ stringify!($($struct_name)*),
+ all!(property!($($struct_name)*.$property_name($($argument),*), $matcher))
+ )
+ };
+
+ (
+ [$($struct_name:tt)*],
+ { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+ ) => {
+ $crate::matchers::is_matcher::is(
+ stringify!($($struct_name)*),
+ all!(property!(ref $($struct_name)*.$property_name($($argument),*), $matcher))
+ )
+ };
+
+ (
+ [$($struct_name:tt)*],
+ { $field_name:ident : $matcher:expr, $($rest:tt)* }
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(field!($($struct_name)*.$field_name, $matcher)),
+ [$($struct_name)*],
+ { $($rest)* }
+ )
+ };
+
+ (
+ [$($struct_name:tt)*],
+ { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(property!($($struct_name)*.$property_name($($argument),*), $matcher)),
+ [$($struct_name)*],
+ { $($rest)* }
+ )
+ };
+
+ (
+ [$($struct_name:tt)*],
+ { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)),
+ [$($struct_name)*],
+ { $($rest)* }
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ { $field_name:ident : $matcher:expr $(,)? }
+ ) => {
+ $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+ $($processed)*,
+ field!($($struct_name)*.$field_name, $matcher)
+ ))
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+ ) => {
+ $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+ $($processed)*,
+ property!($($struct_name)*.$property_name($($argument),*), $matcher)
+ ))
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+ ) => {
+ $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+ $($processed)*,
+ property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)
+ ))
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ { $field_name:ident : $matcher:expr, $($rest:tt)* }
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ field!($($struct_name)*.$field_name, $matcher)
+ ),
+ [$($struct_name)*],
+ { $($rest)* }
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ property!($($struct_name)*.$property_name($($argument),*), $matcher)
+ ),
+ [$($struct_name)*],
+ { $($rest)* }
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)
+ ),
+ [$($struct_name)*],
+ { $($rest)* }
+ )
+ };
+
+ (
+ [$($struct_name:tt)*],
+ ) => {
+ $crate::matchers::predicate(|v| matches!(v, $($struct_name)*))
+ .with_description(
+ concat!("is ", stringify!($($struct_name)*)),
+ concat!("is not ", stringify!($($struct_name)*)),
+ )
+ };
+
+ (
+ [$($struct_name:tt)*],
+ ($matcher:expr $(,)?)
+ ) => {
+ $crate::matchers::is_matcher::is(
+ stringify!($($struct_name)*),
+ all!(field!($($struct_name)*.0, $matcher))
+ )
+ };
+
+ (
+ [$($struct_name:tt)*],
+ ($matcher:expr, $($rest:tt)*)
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ field!($($struct_name)*.0, $matcher)
+ ),
+ [$($struct_name)*],
+ 1,
+ ($($rest)*)
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ $field:tt,
+ ($matcher:expr $(,)?)
+ ) => {
+ $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+ $($processed)*,
+ field!($($struct_name)*.$field, $matcher)
+ ))
+ };
+
+ // We need to repeat this once for every supported field position, unfortunately. There appears
+ // to be no way in declarative macros to compute $field + 1 and have the result evaluated to a
+ // token which can be used as a tuple index.
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ 1,
+ ($matcher:expr, $($rest:tt)*)
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ field!($($struct_name)*.1, $matcher)
+ ),
+ [$($struct_name)*],
+ 2,
+ ($($rest)*)
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ 2,
+ ($matcher:expr, $($rest:tt)*)
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ field!($($struct_name)*.2, $matcher)
+ ),
+ [$($struct_name)*],
+ 3,
+ ($($rest)*)
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ 3,
+ ($matcher:expr, $($rest:tt)*)
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ field!($($struct_name)*.3, $matcher)
+ ),
+ [$($struct_name)*],
+ 4,
+ ($($rest)*)
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ 4,
+ ($matcher:expr, $($rest:tt)*)
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ field!($($struct_name)*.4, $matcher)
+ ),
+ [$($struct_name)*],
+ 5,
+ ($($rest)*)
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ 5,
+ ($matcher:expr, $($rest:tt)*)
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ field!($($struct_name)*.5, $matcher)
+ ),
+ [$($struct_name)*],
+ 6,
+ ($($rest)*)
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ 6,
+ ($matcher:expr, $($rest:tt)*)
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ field!($($struct_name)*.6, $matcher)
+ ),
+ [$($struct_name)*],
+ 7,
+ ($($rest)*)
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ 7,
+ ($matcher:expr, $($rest:tt)*)
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ field!($($struct_name)*.7, $matcher)
+ ),
+ [$($struct_name)*],
+ 8,
+ ($($rest)*)
+ )
+ };
+
+ (
+ all!($($processed:tt)*),
+ [$($struct_name:tt)*],
+ 8,
+ ($matcher:expr, $($rest:tt)*)
+ ) => {
+ $crate::matches_pattern_internal!(
+ all!(
+ $($processed)*,
+ field!($($struct_name)*.8, $matcher)
+ ),
+ [$($struct_name)*],
+ 9,
+ ($($rest)*)
+ )
+ };
+
+ ([$($struct_name:tt)*], $first:tt $($rest:tt)*) => {
+ $crate::matches_pattern_internal!([$($struct_name)* $first], $($rest)*)
+ };
+
+ ($first:tt $($rest:tt)*) => {{
+ #[allow(unused)]
+ use $crate::{all, field, property};
+ $crate::matches_pattern_internal!([$first], $($rest)*)
+ }};
+}
+
+/// An alias for [`matches_pattern`].
+#[macro_export]
+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
new file mode 100644
index 0000000..d0001e2
--- /dev/null
+++ b/src/matchers/matches_regex_matcher.rs
@@ -0,0 +1,210 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use regex::Regex;
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::ops::Deref;
+
+/// Matches a string the entirety of which which matches the given regular
+/// expression.
+///
+/// This is similar to [`contains_regex`][crate::matchers::contains_regex],
+/// except that the match must cover the whole string and not a substring.
+///
+/// Both the actual value and the expected regular expression may be either a
+/// `String` or a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", matches_regex("S.*e"))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!("Another value", matches_regex("Some"))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!("Some value", matches_regex("Some"))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), matches_regex(".*v.*e"))?; // Passes
+/// verify_that!("Some value", matches_regex(".*v.*e".to_string()))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// Panics if the given `pattern` is not a syntactically valid regular
+/// expression.
+// N.B. This returns the concrete type rather than an impl Matcher so that it
+// can act simultaneously as a Matcher<str> and a Matcher<String>. Otherwise the
+// compiler treats it as a Matcher<str> only and the code
+// verify_that!("Some value".to_string(), matches_regex(".*value"))?;
+// doesn't compile.
+pub fn matches_regex<ActualT: ?Sized, PatternT: Deref<Target = str>>(
+ pattern: PatternT,
+) -> MatchesRegexMatcher<ActualT, PatternT> {
+ let adjusted_pattern = format!("^{}$", pattern.deref());
+ let regex = Regex::new(adjusted_pattern.as_str()).unwrap();
+ MatchesRegexMatcher {
+ regex,
+ pattern,
+ _adjusted_pattern: adjusted_pattern,
+ phantom: Default::default(),
+ }
+}
+
+/// A matcher matching a string-like type matching a given regular expression.
+///
+/// Intended only to be used from the function [`matches_regex`] only.
+/// Should not be referenced by code outside this library.
+pub struct MatchesRegexMatcher<ActualT: ?Sized, PatternT: Deref<Target = str>> {
+ regex: Regex,
+ pattern: PatternT,
+ _adjusted_pattern: String,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<PatternT, ActualT> Matcher for MatchesRegexMatcher<ActualT, PatternT>
+where
+ PatternT: Deref<Target = str>,
+ ActualT: AsRef<str> + Debug + ?Sized,
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ self.regex.is_match(actual.as_ref()).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!("matches the regular expression {:#?}", self.pattern.deref())
+ }
+ MatcherResult::NoMatch => {
+ format!("doesn't match the regular expression {:#?}", self.pattern.deref())
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{matches_regex, MatchesRegexMatcher};
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+
+ #[test]
+ fn matches_regex_matches_string_reference_with_pattern() -> Result<()> {
+ let matcher = matches_regex("S.*e");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn matches_regex_does_not_match_string_without_pattern() -> Result<()> {
+ let matcher = matches_regex("Another");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn matches_regex_does_not_match_string_only_beginning_of_which_matches() -> Result<()> {
+ let matcher = matches_regex("Some");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn matches_regex_does_not_match_string_only_end_of_which_matches() -> Result<()> {
+ let matcher = matches_regex("value");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn matches_regex_matches_owned_string_with_pattern() -> Result<()> {
+ let matcher = matches_regex(".*value");
+
+ let result = matcher.matches(&"Some value".to_string());
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn matches_regex_matches_string_when_regex_has_beginning_of_string_marker() -> Result<()> {
+ let matcher = matches_regex("^Some value");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn matches_regex_matches_string_when_regex_has_end_of_string_marker() -> Result<()> {
+ let matcher = matches_regex("Some value$");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn matches_regex_matches_string_when_regex_has_both_end_markers() -> Result<()> {
+ let matcher = matches_regex("^Some value$");
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn matches_regex_matches_string_reference_with_owned_string() -> Result<()> {
+ let matcher = matches_regex(".*value".to_string());
+
+ let result = matcher.matches("Some value");
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn verify_that_works_with_owned_string() -> Result<()> {
+ verify_that!("Some value".to_string(), matches_regex(".*value"))
+ }
+
+ #[test]
+ fn matches_regex_displays_quoted_debug_of_pattern() -> Result<()> {
+ let matcher: MatchesRegexMatcher<&str, _> = matches_regex("\n");
+
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ eq("matches the regular expression \"\\n\"")
+ )
+ }
+}
diff --git a/src/matchers/mod.rs b/src/matchers/mod.rs
new file mode 100644
index 0000000..f8aef10
--- /dev/null
+++ b/src/matchers/mod.rs
@@ -0,0 +1,89 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! All built-in matchers of this crate are in submodules of this module.
+
+pub mod all_matcher;
+pub mod any_matcher;
+mod anything_matcher;
+mod char_count_matcher;
+pub mod conjunction_matcher;
+mod container_eq_matcher;
+mod contains_matcher;
+mod contains_regex_matcher;
+pub mod disjunction_matcher;
+mod display_matcher;
+mod each_matcher;
+pub mod elements_are_matcher;
+mod empty_matcher;
+mod eq_deref_of_matcher;
+mod eq_matcher;
+mod err_matcher;
+pub mod field_matcher;
+mod ge_matcher;
+mod gt_matcher;
+mod has_entry_matcher;
+pub mod is_matcher;
+mod is_nan_matcher;
+mod le_matcher;
+mod len_matcher;
+mod lt_matcher;
+mod matches_pattern;
+mod matches_regex_matcher;
+mod near_matcher;
+mod none_matcher;
+mod not_matcher;
+mod ok_matcher;
+mod points_to_matcher;
+pub mod pointwise_matcher;
+mod predicate_matcher;
+pub 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;
+
+pub use anything_matcher::anything;
+pub use char_count_matcher::char_count;
+pub use container_eq_matcher::container_eq;
+pub use contains_matcher::{contains, ContainsMatcher};
+pub use contains_regex_matcher::contains_regex;
+pub use display_matcher::displays_as;
+pub use each_matcher::each;
+pub use empty_matcher::empty;
+pub use eq_deref_of_matcher::eq_deref_of;
+pub use eq_matcher::{eq, EqMatcher};
+pub use err_matcher::err;
+pub use ge_matcher::ge;
+pub use gt_matcher::gt;
+pub use has_entry_matcher::has_entry;
+pub use is_nan_matcher::is_nan;
+pub use le_matcher::le;
+pub use len_matcher::len;
+pub use lt_matcher::lt;
+pub use matches_regex_matcher::matches_regex;
+pub use near_matcher::{approx_eq, near, NearMatcher};
+pub use none_matcher::none;
+pub use not_matcher::not;
+pub use ok_matcher::ok;
+pub use points_to_matcher::points_to;
+pub use predicate_matcher::{predicate, PredicateMatcher};
+pub use some_matcher::some;
+pub use str_matcher::{
+ contains_substring, ends_with, starts_with, StrMatcher, StrMatcherConfigurator,
+};
+pub use subset_of_matcher::subset_of;
+pub use superset_of_matcher::superset_of;
diff --git a/src/matchers/near_matcher.rs b/src/matchers/near_matcher.rs
new file mode 100644
index 0000000..484939c
--- /dev/null
+++ b/src/matchers/near_matcher.rs
@@ -0,0 +1,347 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use num_traits::{Float, FloatConst};
+use std::fmt::Debug;
+
+/// Matches a value equal within `max_abs_error` of `expected`.
+///
+/// The type `T` of the actual, `expected`, and `max_abs_error` values must
+/// implement [`Float`].
+///
+/// The values `expected` and `max_abs_error` may not be NaN. The value
+/// `max_abs_error` must be non-negative. The matcher panics on construction
+/// otherwise.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!(1.0, near(1.0, 0.1))?; // Passes
+/// verify_that!(1.01, near(1.0, 0.1))?; // Passes
+/// verify_that!(1.25, near(1.0, 0.25))?; // Passes
+/// verify_that!(0.75, near(1.0, 0.25))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(1.101, near(1.0, 0.1))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(0.899, near(1.0, 0.1))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!(100.25, near(100.0, 0.25))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// The default behaviour for special values is consistent with the IEEE
+/// floating point standard. Thus infinity is infinitely far away from any
+/// floating point value:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(f64::INFINITY, near(0.0, f64::MAX))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(0.0, near(f64::INFINITY, f64::MAX))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(f64::INFINITY, near(f64::INFINITY, f64::MAX))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// Similarly, by default, `NaN` is infinitely far away from any value:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(f64::NAN, near(0.0, f64::MAX))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(0.0, near(f64::NAN, f64::MAX))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(f64::NAN, near(f64::NAN, f64::MAX))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// To treat two `NaN` values as equal, use the method
+/// [`NearMatcher::nans_are_equal`].
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(f64::NAN, near(f64::NAN, f64::MAX).nans_are_equal())?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn near<T: Debug + Float + Copy>(expected: T, max_abs_error: T) -> NearMatcher<T> {
+ if max_abs_error.is_nan() {
+ panic!("max_abs_error must not be NaN");
+ }
+ if max_abs_error < T::zero() {
+ panic!("max_abs_error must be non-negative");
+ }
+ NearMatcher { expected, max_abs_error, nans_are_equal: false }
+}
+
+/// Matches a value approximately equal to `expected`.
+///
+/// This automatically computes a tolerance from the magnitude of `expected` and
+/// matches any actual value within this tolerance of the expected value. The
+/// tolerance is chosen to account for the inaccuracies in most ordinary
+/// floating point calculations.
+///
+/// Otherwise this works analogously to [`near`]; see its documentation for
+/// further notes.
+pub fn approx_eq<T: Debug + Float + FloatConst + Copy>(expected: T) -> NearMatcher<T> {
+ // The FloatConst trait doesn't offer 2 as a constant but does offer 1.
+ let five_bits_of_mantissa = (T::one() + T::one()).powi(5);
+ let abs_tolerance = five_bits_of_mantissa * T::epsilon();
+ let max_abs_error = T::max(expected.abs() * abs_tolerance, abs_tolerance);
+ NearMatcher { expected, max_abs_error, nans_are_equal: false }
+}
+
+/// A matcher which matches floating-point numbers approximately equal to its
+/// expected value.
+pub struct NearMatcher<T: Debug> {
+ expected: T,
+ max_abs_error: T,
+ nans_are_equal: bool,
+}
+
+impl<T: Debug> NearMatcher<T> {
+ /// Configures this instance to treat two NaNs as equal.
+ ///
+ /// This behaviour differs from the IEEE standad for floating point which
+ /// treats two NaNs as infinitely far apart.
+ pub fn nans_are_equal(mut self) -> Self {
+ self.nans_are_equal = true;
+ self
+ }
+
+ /// Configures this instance to treat two NaNs as not equal.
+ ///
+ /// This behaviour complies with the IEEE standad for floating point. It is
+ /// the default behaviour for this matcher, so invoking this method is
+ /// usually redunant.
+ pub fn nans_are_not_equal(mut self) -> Self {
+ self.nans_are_equal = false;
+ self
+ }
+}
+
+impl<T: Debug + Float> Matcher for NearMatcher<T> {
+ type ActualT = T;
+
+ fn matches(&self, actual: &T) -> MatcherResult {
+ if self.nans_are_equal && self.expected.is_nan() && actual.is_nan() {
+ return MatcherResult::Match;
+ }
+
+ let delta = *actual - self.expected;
+ if delta >= -self.max_abs_error && delta <= self.max_abs_error {
+ MatcherResult::Match
+ } else {
+ MatcherResult::NoMatch
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!("is within {:?} of {:?}", self.max_abs_error, self.expected)
+ }
+ MatcherResult::NoMatch => {
+ format!("isn't within {:?} of {:?}", self.max_abs_error, self.expected)
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{approx_eq, near};
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+
+ #[test]
+ fn matches_value_inside_range() -> Result<()> {
+ let matcher = near(1.0f64, 0.1f64);
+
+ let result = matcher.matches(&1.0f64);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn matches_value_at_low_end_of_range() -> Result<()> {
+ let matcher = near(1.0f64, 0.1f64);
+
+ let result = matcher.matches(&0.9f64);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn matches_value_at_high_end_of_range() -> Result<()> {
+ let matcher = near(1.0f64, 0.25f64);
+
+ let result = matcher.matches(&1.25f64);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn does_not_match_value_below_low_end_of_range() -> Result<()> {
+ let matcher = near(1.0f64, 0.1f64);
+
+ let result = matcher.matches(&0.899999f64);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn does_not_match_value_above_high_end_of_range() -> Result<()> {
+ let matcher = near(1.0f64, 0.1f64);
+
+ let result = matcher.matches(&1.100001f64);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn nan_is_not_near_a_number() -> Result<()> {
+ let matcher = near(0.0f64, f64::MAX);
+
+ let result = matcher.matches(&f64::NAN);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn nan_is_not_near_nan_by_default() -> Result<()> {
+ verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX)))
+ }
+
+ #[test]
+ fn nan_is_not_near_nan_when_explicitly_configured() -> Result<()> {
+ verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX).nans_are_not_equal()))
+ }
+
+ #[test]
+ fn nan_is_near_nan_if_nans_are_equal() -> Result<()> {
+ verify_that!(f64::NAN, near(f64::NAN, f64::MAX).nans_are_equal())
+ }
+
+ #[test]
+ fn nan_is_not_near_number_when_nans_are_equal() -> Result<()> {
+ verify_that!(f64::NAN, not(near(0.0, f64::MAX).nans_are_equal()))
+ }
+
+ #[test]
+ fn number_is_not_near_nan_when_nans_are_equal() -> Result<()> {
+ verify_that!(0.0, not(near(f64::NAN, f64::MAX).nans_are_equal()))
+ }
+
+ #[test]
+ fn inf_is_not_near_inf() -> Result<()> {
+ let matcher = near(f64::INFINITY, f64::MAX);
+
+ let result = matcher.matches(&f64::INFINITY);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn inf_is_not_near_a_number() -> Result<()> {
+ let matcher = near(f64::INFINITY, f64::MAX);
+
+ let result = matcher.matches(&f64::MIN);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn any_two_numbers_are_within_inf_of_each_other() -> Result<()> {
+ let matcher = near(f64::MIN, f64::INFINITY);
+
+ let result = matcher.matches(&f64::MAX);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[::core::prelude::v1::test]
+ #[should_panic]
+ fn panics_if_max_abs_error_is_nan() {
+ near(0.0, f64::NAN);
+ }
+
+ #[::core::prelude::v1::test]
+ #[should_panic]
+ fn panics_if_tolerance_is_negative() {
+ near(0.0, -1.0);
+ }
+
+ #[test]
+ fn approx_eq_matches_equal_number() -> Result<()> {
+ verify_that!(1.0f64, approx_eq(1.0f64))
+ }
+
+ #[test]
+ fn approx_eq_matches_really_close_f64_number() -> Result<()> {
+ verify_that!(1.0f64, approx_eq(1.0 + 16.0 * f64::EPSILON))
+ }
+
+ #[test]
+ fn approx_eq_matches_really_close_f64_number_to_large_number() -> Result<()> {
+ verify_that!(1000f64, approx_eq(1000.0 + 16000.0 * f64::EPSILON))
+ }
+
+ #[test]
+ fn approx_eq_matches_really_close_f64_number_to_zero() -> Result<()> {
+ verify_that!(16.0 * f64::EPSILON, approx_eq(0.0))
+ }
+
+ #[test]
+ fn approx_eq_matches_really_close_f32_number() -> Result<()> {
+ verify_that!(1.0f32, approx_eq(1.0 + 16.0 * f32::EPSILON))
+ }
+
+ #[test]
+ fn approx_eq_does_not_match_distant_number() -> Result<()> {
+ verify_that!(0.0f64, not(approx_eq(1.0f64)))
+ }
+}
diff --git a/src/matchers/none_matcher.rs b/src/matchers/none_matcher.rs
new file mode 100644
index 0000000..e48d549
--- /dev/null
+++ b/src/matchers/none_matcher.rs
@@ -0,0 +1,80 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+use std::marker::PhantomData;
+
+/// Matches an `Option` containing `None`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(None::<()>, none())?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(Some("Some value"), none())?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+pub fn none<T: Debug>() -> impl Matcher<ActualT = Option<T>> {
+ NoneMatcher::<T> { phantom: Default::default() }
+}
+
+struct NoneMatcher<T> {
+ phantom: PhantomData<T>,
+}
+
+impl<T: Debug> Matcher for NoneMatcher<T> {
+ type ActualT = Option<T>;
+
+ fn matches(&self, actual: &Option<T>) -> MatcherResult {
+ (actual.is_none()).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => "is none".to_string(),
+ MatcherResult::NoMatch => "is some(_)".to_string(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::none;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+
+ #[test]
+ fn none_matches_option_with_none() -> Result<()> {
+ let matcher = none::<i32>();
+
+ let result = matcher.matches(&None);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn none_does_not_match_option_with_value() -> Result<()> {
+ let matcher = none();
+
+ let result = matcher.matches(&Some(0));
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+}
diff --git a/src/matchers/not_matcher.rs b/src/matchers/not_matcher.rs
new file mode 100644
index 0000000..1dff791
--- /dev/null
+++ b/src/matchers/not_matcher.rs
@@ -0,0 +1,106 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches the actual value exactly when the inner matcher does _not_ match.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(0, not(eq(1)))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(0, not(eq(0)))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+pub fn not<T: Debug, InnerMatcherT: Matcher<ActualT = T>>(
+ inner: InnerMatcherT,
+) -> impl Matcher<ActualT = T> {
+ NotMatcher::<T, _> { inner, phantom: Default::default() }
+}
+
+struct NotMatcher<T, InnerMatcherT> {
+ inner: InnerMatcherT,
+ phantom: PhantomData<T>,
+}
+
+impl<T: Debug, InnerMatcherT: Matcher<ActualT = T>> Matcher for NotMatcher<T, InnerMatcherT> {
+ type ActualT = T;
+
+ fn matches(&self, actual: &T) -> MatcherResult {
+ match self.inner.matches(actual) {
+ MatcherResult::Match => MatcherResult::NoMatch,
+ MatcherResult::NoMatch => MatcherResult::Match,
+ }
+ }
+
+ fn explain_match(&self, actual: &T) -> String {
+ self.inner.explain_match(actual)
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ self.inner.describe(if matcher_result.into() {
+ MatcherResult::NoMatch
+ } else {
+ MatcherResult::Match
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::not;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn matches_when_inner_matcher_does_not_match() -> Result<()> {
+ let matcher = not(eq(1));
+
+ let result = matcher.matches(&0);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn does_not_match_when_inner_matcher_matches() -> Result<()> {
+ let matcher = not(eq(1));
+
+ let result = matcher.matches(&1);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn match_explanation_references_actual_value() -> Result<()> {
+ let result = verify_that!([1], not(container_eq([1])));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Actual: [1],
+ which contains all the elements
+ "
+ ))))
+ )
+ }
+}
diff --git a/src/matchers/ok_matcher.rs b/src/matchers/ok_matcher.rs
new file mode 100644
index 0000000..5c6fa51
--- /dev/null
+++ b/src/matchers/ok_matcher.rs
@@ -0,0 +1,150 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a `Result` containing `Ok` with a value matched by `inner`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> googletest::Result<()> {
+/// verify_that!(Ok::<_, ()>("Some value"), ok(eq("Some value")))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> googletest::Result<()> {
+/// verify_that!(Err::<&str, _>("An error"), ok(eq("An error")))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> googletest::Result<()> {
+/// verify_that!(Ok::<_, ()>("Some value"), ok(eq("Some other value")))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+pub fn ok<T: Debug, E: Debug>(
+ inner: impl Matcher<ActualT = T>,
+) -> impl Matcher<ActualT = std::result::Result<T, E>> {
+ OkMatcher::<T, E, _> { inner, phantom_t: Default::default(), phantom_e: Default::default() }
+}
+
+struct OkMatcher<T, E, InnerMatcherT> {
+ inner: InnerMatcherT,
+ phantom_t: PhantomData<T>,
+ phantom_e: PhantomData<E>,
+}
+
+impl<T: Debug, E: Debug, InnerMatcherT: Matcher<ActualT = T>> Matcher
+ for OkMatcher<T, E, InnerMatcherT>
+{
+ type ActualT = std::result::Result<T, E>;
+
+ fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
+ }
+
+ fn explain_match(&self, actual: &Self::ActualT) -> String {
+ match actual {
+ Ok(o) => format!("which is a success {}", self.inner.explain_match(o)),
+ Err(_) => "which is an error".to_string(),
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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)
+ )
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::ok;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn ok_matches_result_with_value() -> Result<()> {
+ let matcher = ok(eq(1));
+ let value: std::result::Result<i32, i32> = Ok(1);
+
+ let result = matcher.matches(&value);
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn ok_does_not_match_result_with_wrong_value() -> Result<()> {
+ let matcher = ok(eq(1));
+ let value: std::result::Result<i32, i32> = Ok(0);
+
+ let result = matcher.matches(&value);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn ok_does_not_match_result_with_err() -> Result<()> {
+ let matcher = ok(eq(1));
+ let value: std::result::Result<i32, i32> = Err(1);
+
+ let result = matcher.matches(&value);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn ok_full_error_message() -> Result<()> {
+ let result = verify_that!(Ok::<i32, i32>(1), ok(eq(2)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: Ok::<i32, i32>(1)
+ Expected: is a success containing a value, which is equal to 2
+ Actual: Ok(1),
+ which is a success which isn't equal to 2
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn ok_describe_matches() -> Result<()> {
+ let matcher = super::OkMatcher::<i32, i32, _> {
+ inner: eq(1),
+ phantom_t: Default::default(),
+ phantom_e: Default::default(),
+ };
+ verify_that!(
+ matcher.describe(MatcherResult::Match),
+ 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
new file mode 100644
index 0000000..08c7343
--- /dev/null
+++ b/src/matchers/points_to_matcher.rs
@@ -0,0 +1,107 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::ops::Deref;
+
+/// Matches a (smart) pointer pointing to a value matched by the [`Matcher`]
+/// `expected`.
+///
+/// This allows easily matching smart pointers such as `Box`, `Rc`, and `Arc`.
+/// For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(Box::new(123), points_to(eq(123)))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn points_to<ExpectedT, MatcherT, ActualT>(
+ expected: MatcherT,
+) -> impl Matcher<ActualT = ActualT>
+where
+ ExpectedT: Debug,
+ MatcherT: Matcher<ActualT = ExpectedT>,
+ ActualT: Deref<Target = ExpectedT> + Debug + ?Sized,
+{
+ PointsToMatcher { expected, phantom: Default::default() }
+}
+
+struct PointsToMatcher<ActualT: ?Sized, MatcherT> {
+ expected: MatcherT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ExpectedT, MatcherT, ActualT> Matcher for PointsToMatcher<ActualT, MatcherT>
+where
+ ExpectedT: Debug,
+ MatcherT: Matcher<ActualT = ExpectedT>,
+ ActualT: Deref<Target = ExpectedT> + Debug + ?Sized,
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ self.expected.matches(actual.deref())
+ }
+
+ fn explain_match(&self, actual: &ActualT) -> String {
+ self.expected.explain_match(actual.deref())
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ self.expected.describe(matcher_result)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::points_to;
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::rc::Rc;
+
+ #[test]
+ fn points_to_matches_box_of_int_with_int() -> Result<()> {
+ verify_that!(Box::new(123), points_to(eq(123)))
+ }
+
+ #[test]
+ fn points_to_matches_rc_of_int_with_int() -> Result<()> {
+ verify_that!(Rc::new(123), points_to(eq(123)))
+ }
+
+ #[test]
+ fn points_to_matches_box_of_owned_string_with_string_reference() -> Result<()> {
+ verify_that!(Rc::new("A string".to_string()), points_to(eq("A string")))
+ }
+
+ #[test]
+ fn match_explanation_references_actual_value() -> Result<()> {
+ let result = verify_that!(&vec![1], points_to(container_eq([])));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Actual: [1],
+ which contains the unexpected element 1
+ "
+ ))))
+ )
+ }
+}
diff --git a/src/matchers/pointwise_matcher.rs b/src/matchers/pointwise_matcher.rs
new file mode 100644
index 0000000..5ee0f22
--- /dev/null
+++ b/src/matchers/pointwise_matcher.rs
@@ -0,0 +1,236 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Generates a matcher which matches a container each of whose elements match
+/// the given matcher name applied respectively to each element of the given
+/// container.
+///
+/// For example, the following matches a container of integers each of which
+/// does not exceed the given upper bounds:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(value, pointwise!(le, [1, 3, 3]))?; // Passes
+/// verify_that!(value, pointwise!(le, vec![1, 3, 3]))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// # let value = vec![1, 2, 3];
+/// verify_that!(value, pointwise!(le, [1, 1, 3]))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// One can also use a closure which returns a matcher:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = vec![1.00001, 2.000001, 3.00001];
+/// verify_that!(value, pointwise!(|v| near(v, 0.001), [1.0, 2.0, 3.0]))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// One can pass up to three containers to supply arguments to the function
+/// creating the matcher:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = vec![1.00001, 2.000001, 3.00001];
+/// verify_that!(value, pointwise!(|v, t| near(v, t), [1.0, 2.0, 3.0], [0.001, 0.0001, 0.01]))?;
+/// verify_that!(value, pointwise!(near, [1.0, 2.0, 3.0], [0.001, 0.0001, 0.01]))?; // Same as above
+/// verify_that!(
+/// value,
+/// pointwise!(
+/// |v, t, u| near(v, t * u),
+/// [1.0, 2.0, 3.0],
+/// [0.001, 0.0001, 0.01],
+/// [0.5, 0.5, 1.0]
+/// )
+/// )?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// When using `pointwise!` with multiple containers, the caller must ensure
+/// that all of the containers have the same size. This matcher does not check
+/// whether the sizes match.
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(*value.as_slice(), pointwise!(le, [1, 3, 3]))?; // Passes
+/// verify_that!([1, 2, 3], pointwise!(le, [1, 3, 3]))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`]
+/// first.
+///
+/// The second argument can be any value implementing `IntoIterator`, such as a
+/// `Vec` or an array. The container does not have to have the same type as the
+/// actual value, but the value type must be the same.
+///
+/// **Note for users of the [`Pointwise`] matcher in C++ GoogleTest:**
+///
+/// This macro differs from `Pointwise` in that the first parameter is not a
+/// matcher which matches a pair but rather the name of a function of one
+/// argument whose output is a matcher. This means that one can use standard
+/// matchers like `eq`, `le`, and so on with `pointwise!` but certain C++ tests
+/// using `Pointwise` will require some extra work to port.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Pointwise`]: https://google.github.io/googletest/reference/matchers.html#container-matchers
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! pointwise {
+ ($matcher:expr, $container:expr) => {{
+ use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
+ PointwiseMatcher::new($container.into_iter().map($matcher).collect())
+ }};
+
+ ($matcher:expr, $left_container:expr, $right_container:expr) => {{
+ use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
+ PointwiseMatcher::new(
+ $left_container
+ .into_iter()
+ .zip($right_container.into_iter())
+ .map(|(l, r)| $matcher(l, r))
+ .collect(),
+ )
+ }};
+
+ ($matcher:expr, $left_container:expr, $middle_container:expr, $right_container:expr) => {{
+ use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
+ PointwiseMatcher::new(
+ $left_container
+ .into_iter()
+ .zip($right_container.into_iter().zip($middle_container.into_iter()))
+ .map(|(l, (m, r))| $matcher(l, m, r))
+ .collect(),
+ )
+ }};
+}
+
+/// Module for use only by the procedural macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::matcher_support::description::Description;
+ use crate::matcher_support::zipped_iterator::zip;
+ use std::{fmt::Debug, marker::PhantomData};
+
+ /// This struct is meant to be used only through the `pointwise` macro.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub struct PointwiseMatcher<ContainerT: ?Sized, MatcherT> {
+ matchers: Vec<MatcherT>,
+ phantom: PhantomData<ContainerT>,
+ }
+
+ impl<ContainerT: ?Sized, MatcherT> PointwiseMatcher<ContainerT, MatcherT> {
+ pub fn new(matchers: Vec<MatcherT>) -> Self {
+ Self { matchers, phantom: Default::default() }
+ }
+ }
+
+ impl<T: Debug, MatcherT: Matcher<ActualT = T>, ContainerT: ?Sized + Debug> Matcher
+ for PointwiseMatcher<ContainerT, MatcherT>
+ where
+ for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+ {
+ type ActualT = ContainerT;
+
+ fn matches(&self, actual: &ContainerT) -> MatcherResult {
+ let mut zipped_iterator = zip(actual.into_iter(), self.matchers.iter());
+ for (element, matcher) in zipped_iterator.by_ref() {
+ if matcher.matches(element).is_no_match() {
+ return MatcherResult::NoMatch;
+ }
+ }
+ if zipped_iterator.has_size_mismatch() {
+ MatcherResult::NoMatch
+ } else {
+ MatcherResult::Match
+ }
+ }
+
+ fn explain_match(&self, actual: &ContainerT) -> String {
+ // TODO(b/260819741) This code duplicates elements_are_matcher.rs. Consider
+ // extract as a separate library. (or implement pointwise! with
+ // elements_are)
+ let actual_iterator = actual.into_iter();
+ let mut zipped_iterator = zip(actual_iterator, self.matchers.iter());
+ let mut mismatches = Vec::new();
+ for (idx, (a, e)) in zipped_iterator.by_ref().enumerate() {
+ if e.matches(a).is_no_match() {
+ mismatches.push(format!("element #{idx} is {a:?}, {}", e.explain_match(a)));
+ }
+ }
+ if mismatches.is_empty() {
+ if !zipped_iterator.has_size_mismatch() {
+ "which matches all elements".to_string()
+ } else {
+ format!(
+ "which has size {} (expected {})",
+ zipped_iterator.left_size(),
+ self.matchers.len()
+ )
+ }
+ } else if mismatches.len() == 1 {
+ format!("where {}", mismatches[0])
+ } else {
+ let mismatches = mismatches.into_iter().collect::<Description>();
+ format!("where:\n{}", mismatches.bullet_list().indent())
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ format!(
+ "{} elements satisfying respectively:\n{}",
+ if matcher_result.into() { "has" } else { "doesn't have" },
+ self.matchers
+ .iter()
+ .map(|m| m.describe(MatcherResult::Match))
+ .collect::<Description>()
+ .enumerate()
+ .indent()
+ )
+ }
+ }
+}
diff --git a/src/matchers/predicate_matcher.rs b/src/matchers/predicate_matcher.rs
new file mode 100644
index 0000000..fabd6c3
--- /dev/null
+++ b/src/matchers/predicate_matcher.rs
@@ -0,0 +1,228 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Creates a matcher based on the predicate provided.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(3, predicate(|x: &i32| x % 2 == 1))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// The predicate should take the subject type by reference and return a
+/// boolean.
+///
+/// Note: even if the Rust compiler should be able to infer the type of
+/// the closure argument, it is likely that it won't.
+/// See <https://github.com/rust-lang/rust/issues/12679> for update on this issue.
+/// This is easily fixed by explicitly declaring the type of the argument
+pub fn predicate<T: Debug + ?Sized, P>(
+ predicate: P,
+) -> PredicateMatcher<T, P, NoDescription, NoDescription>
+where
+ for<'a> P: Fn(&'a T) -> bool,
+{
+ PredicateMatcher {
+ predicate,
+ positive_description: NoDescription,
+ negative_description: NoDescription,
+ phantom: Default::default(),
+ }
+}
+
+impl<T, P> PredicateMatcher<T, P, NoDescription, NoDescription> {
+ /// Configures this instance to provide a more meaningful description.
+ ///
+ /// For example, to make sure the error message is more useful
+ ///
+ /// ```
+ /// # use googletest::matchers::{predicate, PredicateMatcher};
+ /// # let _ =
+ /// predicate(|x: &i32| x % 2 == 1)
+ /// .with_description("is odd", "is even")
+ /// # ;
+ /// ```
+ ///
+ /// This is optional as it only provides value when the test fails.
+ ///
+ /// Description can be passed by `&str`, `String` or `Fn() -> Into<String>`.
+ pub fn with_description<D1: PredicateDescription, D2: PredicateDescription>(
+ self,
+ positive_description: D1,
+ negative_description: D2,
+ ) -> PredicateMatcher<T, P, D1, D2> {
+ PredicateMatcher {
+ predicate: self.predicate,
+ positive_description,
+ negative_description,
+ phantom: Default::default(),
+ }
+ }
+}
+
+/// A matcher which applies `predicate` on the value.
+///
+/// See [`predicate`].
+pub struct PredicateMatcher<T: ?Sized, P, D1, D2> {
+ predicate: P,
+ positive_description: D1,
+ negative_description: D2,
+ phantom: PhantomData<T>,
+}
+
+/// A trait to allow [`PredicateMatcher::with_description`] to accept multiple
+/// types.
+///
+/// See [`PredicateMatcher::with_description`]
+pub trait PredicateDescription {
+ fn to_description(&self) -> String;
+}
+
+impl PredicateDescription for &str {
+ fn to_description(&self) -> String {
+ self.to_string()
+ }
+}
+
+impl PredicateDescription for String {
+ fn to_description(&self) -> String {
+ self.clone()
+ }
+}
+
+impl<T, S> PredicateDescription for T
+where
+ T: Fn() -> S,
+ S: Into<String>,
+{
+ fn to_description(&self) -> String {
+ self().into()
+ }
+}
+
+// Sentinel type to tag a MatcherBuilder as without a description.
+#[doc(hidden)]
+pub struct NoDescription;
+
+impl<T: Debug, P> Matcher for PredicateMatcher<T, P, NoDescription, NoDescription>
+where
+ for<'a> P: Fn(&'a T) -> bool,
+{
+ type ActualT = T;
+
+ fn matches(&self, actual: &T) -> MatcherResult {
+ (self.predicate)(actual).into()
+ }
+
+ fn describe(&self, result: MatcherResult) -> String {
+ match result {
+ MatcherResult::Match => "matches".to_string(),
+ MatcherResult::NoMatch => "does not match".to_string(),
+ }
+ }
+}
+
+impl<T: Debug, P, D1: PredicateDescription, D2: PredicateDescription> Matcher
+ for PredicateMatcher<T, P, D1, D2>
+where
+ for<'a> P: Fn(&'a T) -> bool,
+{
+ type ActualT = T;
+
+ fn matches(&self, actual: &T) -> MatcherResult {
+ (self.predicate)(actual).into()
+ }
+
+ fn describe(&self, result: MatcherResult) -> String {
+ match result {
+ MatcherResult::Match => self.positive_description.to_description(),
+ MatcherResult::NoMatch => self.negative_description.to_description(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::predicate;
+ use crate::matcher::Matcher;
+ use crate::prelude::*;
+
+ // Simple matcher with a description
+ fn is_odd() -> impl Matcher<ActualT = i32> {
+ predicate(|x| x % 2 == 1).with_description("is odd", "is even")
+ }
+
+ #[test]
+ fn predicate_matcher_odd() -> Result<()> {
+ verify_that!(1, is_odd())
+ }
+
+ #[test]
+ fn predicate_matcher_odd_explain_match_matches() -> Result<()> {
+ verify_that!(is_odd().explain_match(&1), displays_as(eq("which is odd")))
+ }
+
+ #[test]
+ fn predicate_matcher_odd_explain_match_does_not_match() -> Result<()> {
+ verify_that!(is_odd().explain_match(&2), displays_as(eq("which is even")))
+ }
+
+ // Simple Matcher without description
+ fn is_even() -> impl Matcher<ActualT = i32> {
+ predicate(|x| x % 2 == 0)
+ }
+
+ #[test]
+ fn predicate_matcher_even() -> Result<()> {
+ verify_that!(2, is_even())
+ }
+
+ #[test]
+ fn predicate_matcher_even_explain_match_matches() -> Result<()> {
+ verify_that!(is_even().explain_match(&2), displays_as(eq("which matches")))
+ }
+
+ #[test]
+ fn predicate_matcher_even_explain_match_does_not_match() -> Result<()> {
+ verify_that!(is_even().explain_match(&1), displays_as(eq("which does not match")))
+ }
+
+ #[test]
+ fn predicate_matcher_generator_lambda() -> Result<()> {
+ let is_divisible_by = |quotient| {
+ predicate(move |x: &i32| x % quotient == 0).with_description(
+ move || format!("is divisible by {quotient}"),
+ move || format!("is not divisible by {quotient}"),
+ )
+ };
+ verify_that!(49, is_divisible_by(7))
+ }
+
+ #[test]
+ fn predicate_matcher_inline() -> Result<()> {
+ verify_that!(2048, predicate(|x: &i32| x.count_ones() == 1))
+ }
+
+ #[test]
+ fn predicate_matcher_function_pointer() -> Result<()> {
+ use std::time::Duration;
+ verify_that!(Duration::new(0, 0), predicate(Duration::is_zero))
+ }
+}
diff --git a/src/matchers/property_matcher.rs b/src/matchers/property_matcher.rs
new file mode 100644
index 0000000..d69ba1d
--- /dev/null
+++ b/src/matchers/property_matcher.rs
@@ -0,0 +1,237 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches an object which, upon calling the given method on it with the given
+/// arguments, produces a value matched by the given inner matcher.
+///
+/// This is particularly useful as a nested matcher when the desired
+/// property cannot be accessed through a field and must instead be
+/// extracted through a method call. For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// pub struct MyStruct {
+/// a_field: u32,
+/// }
+///
+/// impl MyStruct {
+/// pub fn get_a_field(&self) -> u32 { self.a_field }
+/// }
+///
+/// let value = vec![MyStruct { a_field: 100 }];
+/// verify_that!(value, contains(property!(MyStruct.get_a_field(), eq(100))))
+/// # .unwrap();
+/// ```
+///
+/// **Important**: The method should be pure function with a deterministic
+/// output and no side effects. In particular, in the event of an assertion
+/// failure, it will be invoked a second time, with the assertion failure output
+/// reflecting the *second* invocation.
+///
+/// If the method returns a *reference*, then it must be preceded by the keyword
+/// `ref`:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # pub struct MyStruct {
+/// # a_field: u32,
+/// # }
+/// impl MyStruct {
+/// pub fn get_a_field(&self) -> &u32 { &self.a_field }
+/// }
+///
+/// # let value = vec![MyStruct { a_field: 100 }];
+/// verify_that!(value, contains(property!(ref MyStruct.get_a_field(), eq(100))))
+/// # .unwrap();
+/// ```
+///
+/// The method may also take additional arguments:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # pub struct MyStruct {
+/// # a_field: u32,
+/// # }
+/// impl MyStruct {
+/// pub fn add_to_a_field(&self, a: u32) -> u32 { self.a_field + a }
+/// }
+///
+/// # let value = vec![MyStruct { a_field: 100 }];
+/// verify_that!(value, contains(property!(MyStruct.add_to_a_field(50), eq(150))))
+/// # .unwrap();
+/// ```
+///
+/// Unfortunately, this matcher does *not* work with methods returning string
+/// slices:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// pub struct MyStruct {
+/// a_string: String,
+/// }
+/// impl MyStruct {
+/// pub fn get_a_string(&self) -> &str { &self.a_string }
+/// }
+///
+/// let value = MyStruct { a_string: "A string".into() };
+/// verify_that!(value, property!(ref 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.
+///
+/// The list of arguments may optionally have a trailing comma.
+#[macro_export]
+macro_rules! property {
+ ($($t:tt)*) => { $crate::property_internal!($($t)*) }
+}
+
+// Internal-only macro created so that the macro definition does not appear in
+// generated documentation.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! property_internal {
+ ($($t:ident)::+.$method:tt($($argument:tt),* $(,)?), $m:expr) => {{
+ use $crate::matchers::property_matcher::internal::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;
+ property_ref_matcher(
+ |o: &$($t)::+| o.$method($($argument),*),
+ &stringify!($method($($argument),*)),
+ $m)
+ }};
+}
+
+/// Items for use only by the declarative macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+ use crate::matcher::{Matcher, MatcherResult};
+ use std::{fmt::Debug, marker::PhantomData};
+
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub fn property_matcher<OuterT: Debug, InnerT: Debug, MatcherT: Matcher<ActualT = InnerT>>(
+ extractor: impl Fn(&OuterT) -> InnerT,
+ property_desc: &'static str,
+ inner: MatcherT,
+ ) -> impl Matcher<ActualT = OuterT> {
+ PropertyMatcher { extractor, property_desc, inner, phantom: Default::default() }
+ }
+
+ struct PropertyMatcher<OuterT, ExtractorT, MatcherT> {
+ extractor: ExtractorT,
+ property_desc: &'static str,
+ inner: MatcherT,
+ phantom: PhantomData<OuterT>,
+ }
+
+ impl<InnerT, OuterT, ExtractorT, MatcherT> Matcher for PropertyMatcher<OuterT, ExtractorT, MatcherT>
+ where
+ InnerT: Debug,
+ OuterT: Debug,
+ ExtractorT: Fn(&OuterT) -> InnerT,
+ MatcherT: Matcher<ActualT = InnerT>,
+ {
+ type ActualT = OuterT;
+
+ fn matches(&self, actual: &OuterT) -> MatcherResult {
+ self.inner.matches(&(self.extractor)(actual))
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ format!(
+ "has property `{}`, which {}",
+ self.property_desc,
+ self.inner.describe(matcher_result)
+ )
+ }
+
+ fn explain_match(&self, actual: &OuterT) -> String {
+ let actual_inner = (self.extractor)(actual);
+ format!(
+ "whose property `{}` is `{:#?}`, {}",
+ self.property_desc,
+ actual_inner,
+ self.inner.explain_match(&actual_inner)
+ )
+ }
+ }
+
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub fn property_ref_matcher<OuterT, InnerT, MatcherT>(
+ extractor: fn(&OuterT) -> &InnerT,
+ property_desc: &'static str,
+ inner: MatcherT,
+ ) -> impl Matcher<ActualT = OuterT>
+ where
+ OuterT: Debug,
+ InnerT: Debug + ?Sized,
+ MatcherT: Matcher<ActualT = InnerT>,
+ {
+ PropertyRefMatcher { extractor, property_desc, inner }
+ }
+
+ struct PropertyRefMatcher<InnerT: ?Sized, OuterT, MatcherT> {
+ extractor: fn(&OuterT) -> &InnerT,
+ property_desc: &'static str,
+ inner: MatcherT,
+ }
+
+ impl<InnerT: Debug + ?Sized, OuterT: Debug, MatcherT: Matcher<ActualT = InnerT>> Matcher
+ for PropertyRefMatcher<InnerT, OuterT, MatcherT>
+ {
+ type ActualT = OuterT;
+
+ fn matches(&self, actual: &OuterT) -> MatcherResult {
+ self.inner.matches((self.extractor)(actual))
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ format!(
+ "has property `{}`, which {}",
+ self.property_desc,
+ self.inner.describe(matcher_result)
+ )
+ }
+
+ fn explain_match(&self, actual: &OuterT) -> String {
+ let actual_inner = (self.extractor)(actual);
+ format!(
+ "whose property `{}` is `{:#?}`, {}",
+ self.property_desc,
+ actual_inner,
+ self.inner.explain_match(actual_inner)
+ )
+ }
+ }
+}
diff --git a/src/matchers/some_matcher.rs b/src/matchers/some_matcher.rs
new file mode 100644
index 0000000..a6ce021
--- /dev/null
+++ b/src/matchers/some_matcher.rs
@@ -0,0 +1,162 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches an `Option` containing a value matched by `inner`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(Some("Some value"), some(eq("Some value")))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(None::<&str>, some(eq("Some value")))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(Some("Some value"), some(eq("Some other value")))?; // Fails
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+pub fn some<T: Debug>(inner: impl Matcher<ActualT = T>) -> impl Matcher<ActualT = Option<T>> {
+ SomeMatcher { inner, phantom: Default::default() }
+}
+
+struct SomeMatcher<T, InnerMatcherT> {
+ inner: InnerMatcherT,
+ phantom: PhantomData<T>,
+}
+
+impl<T: Debug, InnerMatcherT: Matcher<ActualT = T>> Matcher for SomeMatcher<T, InnerMatcherT> {
+ type ActualT = Option<T>;
+
+ fn matches(&self, actual: &Option<T>) -> MatcherResult {
+ actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
+ }
+
+ fn explain_match(&self, actual: &Option<T>) -> String {
+ match (self.matches(actual), actual) {
+ (_, Some(t)) => format!("which has a value {}", self.inner.explain_match(t)),
+ (_, None) => "which is None".to_string(),
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => {
+ format!("has a value which {}", self.inner.describe(MatcherResult::Match))
+ }
+ MatcherResult::NoMatch => {
+ format!(
+ "is None or has a value which {}",
+ self.inner.describe(MatcherResult::NoMatch)
+ )
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::some;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn some_matches_option_with_value() -> Result<()> {
+ let matcher = some(eq(1));
+
+ let result = matcher.matches(&Some(1));
+
+ verify_that!(result, eq(MatcherResult::Match))
+ }
+
+ #[test]
+ fn some_does_not_match_option_with_wrong_value() -> Result<()> {
+ let matcher = some(eq(1));
+
+ let result = matcher.matches(&Some(0));
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn some_does_not_match_option_with_none() -> Result<()> {
+ let matcher = some(eq(1));
+
+ let result = matcher.matches(&None);
+
+ verify_that!(result, eq(MatcherResult::NoMatch))
+ }
+
+ #[test]
+ fn some_full_error_message() -> Result<()> {
+ let result = verify_that!(Some(2), some(eq(1)));
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: Some(2)
+ Expected: has a value which is equal to 1
+ Actual: Some(2),
+ which has a value which isn't equal to 1
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn some_describe_matches() -> Result<()> {
+ verify_that!(
+ some(eq(1)).describe(MatcherResult::Match),
+ 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")
+ )
+ }
+
+ #[test]
+ fn some_explain_match_with_none() -> Result<()> {
+ verify_that!(some(eq(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"))
+ )
+ }
+
+ #[test]
+ fn some_explain_match_with_some_fail() -> Result<()> {
+ verify_that!(
+ some(eq(1)).explain_match(&Some(2)),
+ displays_as(eq("which has a value which isn't equal to 1"))
+ )
+ }
+}
diff --git a/src/matchers/str_matcher.rs b/src/matchers/str_matcher.rs
new file mode 100644
index 0000000..3a4e2e9
--- /dev/null
+++ b/src/matchers/str_matcher.rs
@@ -0,0 +1,1234 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{
+ matcher::{Matcher, MatcherResult},
+ matcher_support::{
+ edit_distance,
+ summarize_diff::{create_diff, create_diff_reversed},
+ },
+ matchers::{eq_deref_of_matcher::EqDerefOfMatcher, eq_matcher::EqMatcher},
+};
+use std::borrow::Cow;
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::ops::Deref;
+
+/// Matches a string containing a given substring.
+///
+/// Both the actual value and the expected substring may be either a `String` or
+/// a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", contains_substring("Some"))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!("Another value", contains_substring("Some"))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), contains_substring("value"))?; // Passes
+/// verify_that!("Some value", contains_substring("value".to_string()))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// See the [`StrMatcherConfigurator`] extension trait for more options on how
+/// the string is matched.
+///
+/// > Note on memory use: In most cases, this matcher does not allocate memory
+/// > when matching strings. However, it must allocate copies of both the actual
+/// > and expected values when matching strings while
+/// > [`ignoring_ascii_case`][StrMatcherConfigurator::ignoring_ascii_case] is
+/// > set.
+pub fn contains_substring<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> {
+ StrMatcher {
+ configuration: Configuration { mode: MatchMode::Contains, ..Default::default() },
+ expected,
+ phantom: Default::default(),
+ }
+}
+
+/// Matches a string which starts with the given prefix.
+///
+/// Both the actual value and the expected prefix may be either a `String` or
+/// a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", starts_with("Some"))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!("Another value", starts_with("Some"))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!("Some value", starts_with("value"))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), starts_with("Some"))?; // Passes
+/// verify_that!("Some value", starts_with("Some".to_string()))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// See the [`StrMatcherConfigurator`] extension trait for more options on how
+/// the string is matched.
+pub fn starts_with<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> {
+ StrMatcher {
+ configuration: Configuration { mode: MatchMode::StartsWith, ..Default::default() },
+ expected,
+ phantom: Default::default(),
+ }
+}
+
+/// Matches a string which ends with the given suffix.
+///
+/// Both the actual value and the expected suffix may be either a `String` or
+/// a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", ends_with("value"))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!("Some value", ends_with("other value"))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!("Some value", ends_with("Some"))?; // Fails
+/// # Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), ends_with("value"))?; // Passes
+/// verify_that!("Some value", ends_with("value".to_string()))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// See the [`StrMatcherConfigurator`] extension trait for more options on how
+/// the string is matched.
+pub fn ends_with<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> {
+ StrMatcher {
+ configuration: Configuration { mode: MatchMode::EndsWith, ..Default::default() },
+ expected,
+ phantom: Default::default(),
+ }
+}
+
+/// Extension trait to configure [`StrMatcher`].
+///
+/// Matchers which match against string values and, through configuration,
+/// specialise to [`StrMatcher`] implement this trait. That includes
+/// [`EqMatcher`] and [`StrMatcher`].
+pub trait StrMatcherConfigurator<ActualT: ?Sized, ExpectedT> {
+ /// Configures the matcher to ignore any leading whitespace in either the
+ /// actual or the expected value.
+ ///
+ /// Whitespace is defined as in [`str::trim_start`].
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_pass() -> Result<()> {
+ /// verify_that!("A string", eq(" A string").ignoring_leading_whitespace())?; // Passes
+ /// verify_that!(" A string", eq("A string").ignoring_leading_whitespace())?; // Passes
+ /// # Ok(())
+ /// # }
+ /// # should_pass().unwrap();
+ /// ```
+ ///
+ /// When all other configuration options are left as the defaults, this is
+ /// equivalent to invoking [`str::trim_start`] on both the expected and
+ /// actual value.
+ fn ignoring_leading_whitespace(self) -> StrMatcher<ActualT, ExpectedT>;
+
+ /// Configures the matcher to ignore any trailing whitespace in either the
+ /// actual or the expected value.
+ ///
+ /// Whitespace is defined as in [`str::trim_end`].
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_pass() -> Result<()> {
+ /// verify_that!("A string", eq("A string ").ignoring_trailing_whitespace())?; // Passes
+ /// verify_that!("A string ", eq("A string").ignoring_trailing_whitespace())?; // Passes
+ /// # Ok(())
+ /// # }
+ /// # should_pass().unwrap();
+ /// ```
+ ///
+ /// When all other configuration options are left as the defaults, this is
+ /// equivalent to invoking [`str::trim_end`] on both the expected and
+ /// actual value.
+ fn ignoring_trailing_whitespace(self) -> StrMatcher<ActualT, ExpectedT>;
+
+ /// Configures the matcher to ignore both leading and trailing whitespace in
+ /// either the actual or the expected value.
+ ///
+ /// Whitespace is defined as in [`str::trim`].
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_pass() -> Result<()> {
+ /// verify_that!("A string", eq(" A string ").ignoring_outer_whitespace())?; // Passes
+ /// verify_that!(" A string ", eq("A string").ignoring_outer_whitespace())?; // Passes
+ /// # Ok(())
+ /// # }
+ /// # should_pass().unwrap();
+ /// ```
+ ///
+ /// This is equivalent to invoking both
+ /// [`ignoring_leading_whitespace`][StrMatcherConfigurator::ignoring_leading_whitespace] and
+ /// [`ignoring_trailing_whitespace`][StrMatcherConfigurator::ignoring_trailing_whitespace].
+ ///
+ /// When all other configuration options are left as the defaults, this is
+ /// equivalent to invoking [`str::trim`] on both the expected and actual
+ /// value.
+ fn ignoring_outer_whitespace(self) -> StrMatcher<ActualT, ExpectedT>;
+
+ /// Configures the matcher to ignore ASCII case when comparing values.
+ ///
+ /// This uses the same rules for case as [`str::eq_ignore_ascii_case`].
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_pass() -> Result<()> {
+ /// verify_that!("Some value", eq("SOME VALUE").ignoring_ascii_case())?; // Passes
+ /// # Ok(())
+ /// # }
+ /// # fn should_fail() -> Result<()> {
+ /// verify_that!("Another value", eq("Some value").ignoring_ascii_case())?; // Fails
+ /// # Ok(())
+ /// # }
+ /// # should_pass().unwrap();
+ /// # should_fail().unwrap_err();
+ /// ```
+ ///
+ /// This is **not guaranteed** to match strings with differing upper/lower
+ /// case characters outside of the codepoints 0-127 covered by ASCII.
+ fn ignoring_ascii_case(self) -> StrMatcher<ActualT, ExpectedT>;
+
+ /// Configures the matcher to match only strings which otherwise satisfy the
+ /// conditions a number times matched by the matcher `times`.
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_pass() -> Result<()> {
+ /// verify_that!("Some value\nSome value", contains_substring("value").times(eq(2)))?; // Passes
+ /// # Ok(())
+ /// # }
+ /// # fn should_fail() -> Result<()> {
+ /// verify_that!("Some value", contains_substring("value").times(eq(2)))?; // Fails
+ /// # Ok(())
+ /// # }
+ /// # should_pass().unwrap();
+ /// # should_fail().unwrap_err();
+ /// ```
+ ///
+ /// The matched substrings must be disjoint from one another to be counted.
+ /// For example:
+ ///
+ /// ```
+ /// # use googletest::prelude::*;
+ /// # fn should_fail() -> Result<()> {
+ /// // Fails: substrings distinct but not disjoint!
+ /// verify_that!("ababab", contains_substring("abab").times(eq(2)))?;
+ /// # Ok(())
+ /// # }
+ /// # should_fail().unwrap_err();
+ /// ```
+ ///
+ /// This is only meaningful when the matcher was constructed with
+ /// [`contains_substring`]. This method will panic when it is used with any
+ /// other matcher construction.
+ fn times(
+ self,
+ times: impl Matcher<ActualT = usize> + 'static,
+ ) -> StrMatcher<ActualT, ExpectedT>;
+}
+
+/// A matcher which matches equality or containment of a string-like value in a
+/// configurable way.
+///
+/// The following matcher methods instantiate this:
+///
+/// * [`eq`][crate::matchers::eq_matcher::eq],
+/// * [`contains_substring`],
+/// * [`starts_with`],
+/// * [`ends_with`].
+pub struct StrMatcher<ActualT: ?Sized, ExpectedT> {
+ expected: ExpectedT,
+ configuration: Configuration,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ExpectedT, ActualT> Matcher for StrMatcher<ActualT, ExpectedT>
+where
+ ExpectedT: Deref<Target = str> + Debug,
+ ActualT: AsRef<str> + Debug + ?Sized,
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ self.configuration.do_strings_match(self.expected.deref(), actual.as_ref()).into()
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ self.configuration.describe(matcher_result, self.expected.deref())
+ }
+
+ fn explain_match(&self, actual: &ActualT) -> String {
+ self.configuration.explain_match(self.expected.deref(), actual.as_ref())
+ }
+}
+
+impl<ActualT: ?Sized, ExpectedT, MatcherT: Into<StrMatcher<ActualT, ExpectedT>>>
+ StrMatcherConfigurator<ActualT, ExpectedT> for MatcherT
+{
+ fn ignoring_leading_whitespace(self) -> StrMatcher<ActualT, ExpectedT> {
+ let existing = self.into();
+ StrMatcher {
+ configuration: existing.configuration.ignoring_leading_whitespace(),
+ ..existing
+ }
+ }
+
+ fn ignoring_trailing_whitespace(self) -> StrMatcher<ActualT, ExpectedT> {
+ let existing = self.into();
+ StrMatcher {
+ configuration: existing.configuration.ignoring_trailing_whitespace(),
+ ..existing
+ }
+ }
+
+ fn ignoring_outer_whitespace(self) -> StrMatcher<ActualT, ExpectedT> {
+ let existing = self.into();
+ StrMatcher { configuration: existing.configuration.ignoring_outer_whitespace(), ..existing }
+ }
+
+ fn ignoring_ascii_case(self) -> StrMatcher<ActualT, ExpectedT> {
+ let existing = self.into();
+ StrMatcher { configuration: existing.configuration.ignoring_ascii_case(), ..existing }
+ }
+
+ fn times(
+ self,
+ times: impl Matcher<ActualT = usize> + 'static,
+ ) -> StrMatcher<ActualT, ExpectedT> {
+ let existing = self.into();
+ if !matches!(existing.configuration.mode, MatchMode::Contains) {
+ panic!("The times() configurator is only meaningful with contains_substring().");
+ }
+ StrMatcher { configuration: existing.configuration.times(times), ..existing }
+ }
+}
+
+impl<A: ?Sized, T: Deref<Target = str>> From<EqMatcher<A, T>> for StrMatcher<A, T> {
+ fn from(value: EqMatcher<A, T>) -> Self {
+ Self::with_default_config(value.expected)
+ }
+}
+
+impl<A: ?Sized, T: Deref<Target = str>> From<EqDerefOfMatcher<A, T>> for StrMatcher<A, T> {
+ fn from(value: EqDerefOfMatcher<A, T>) -> Self {
+ Self::with_default_config(value.expected)
+ }
+}
+
+impl<A: ?Sized, T> StrMatcher<A, T> {
+ /// Returns a [`StrMatcher`] with a default configuration to match against
+ /// the given expected value.
+ ///
+ /// This default configuration is sensitive to whitespace and case.
+ fn with_default_config(expected: T) -> Self {
+ Self { expected, configuration: Default::default(), phantom: Default::default() }
+ }
+}
+
+// Holds all the information on how the expected and actual strings are to be
+// compared. Its associated functions perform the actual matching operations
+// on string references. The struct and comparison methods therefore need not be
+// parameterised, saving compilation time and binary size on monomorphisation.
+//
+// The default value represents exact equality of the strings.
+struct Configuration {
+ mode: MatchMode,
+ ignore_leading_whitespace: bool,
+ ignore_trailing_whitespace: bool,
+ case_policy: CasePolicy,
+ times: Option<Box<dyn Matcher<ActualT = usize>>>,
+}
+
+#[derive(Clone)]
+enum MatchMode {
+ Equals,
+ Contains,
+ StartsWith,
+ EndsWith,
+}
+
+impl MatchMode {
+ fn to_diff_mode(&self) -> edit_distance::Mode {
+ match self {
+ MatchMode::StartsWith | MatchMode::EndsWith => edit_distance::Mode::Prefix,
+ MatchMode::Contains => edit_distance::Mode::Contains,
+ MatchMode::Equals => edit_distance::Mode::Exact,
+ }
+ }
+}
+
+#[derive(Clone)]
+enum CasePolicy {
+ Respect,
+ IgnoreAscii,
+}
+
+impl Configuration {
+ // The entry point for all string matching. StrMatcher::matches redirects
+ // immediately to this function.
+ fn do_strings_match(&self, expected: &str, actual: &str) -> bool {
+ let (expected, actual) =
+ match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
+ (true, true) => (expected.trim(), actual.trim()),
+ (true, false) => (expected.trim_start(), actual.trim_start()),
+ (false, true) => (expected.trim_end(), actual.trim_end()),
+ (false, false) => (expected, actual),
+ };
+ match self.mode {
+ MatchMode::Equals => match self.case_policy {
+ CasePolicy::Respect => expected == actual,
+ CasePolicy::IgnoreAscii => expected.eq_ignore_ascii_case(actual),
+ },
+ MatchMode::Contains => match self.case_policy {
+ CasePolicy::Respect => self.does_containment_match(actual, expected),
+ CasePolicy::IgnoreAscii => self.does_containment_match(
+ actual.to_ascii_lowercase().as_str(),
+ expected.to_ascii_lowercase().as_str(),
+ ),
+ },
+ MatchMode::StartsWith => match self.case_policy {
+ CasePolicy::Respect => actual.starts_with(expected),
+ CasePolicy::IgnoreAscii => {
+ actual.len() >= expected.len()
+ && actual[..expected.len()].eq_ignore_ascii_case(expected)
+ }
+ },
+ MatchMode::EndsWith => match self.case_policy {
+ CasePolicy::Respect => actual.ends_with(expected),
+ CasePolicy::IgnoreAscii => {
+ actual.len() >= expected.len()
+ && actual[actual.len() - expected.len()..].eq_ignore_ascii_case(expected)
+ }
+ },
+ }
+ }
+
+ // Returns whether actual contains expected a number of times matched by the
+ // matcher self.times. Does not take other configuration into account.
+ fn does_containment_match(&self, actual: &str, expected: &str) -> bool {
+ if let Some(times) = self.times.as_ref() {
+ // Split returns an iterator over the "boundaries" left and right of the
+ // substring to be matched, of which there is one more than the number of
+ // substrings.
+ matches!(times.matches(&(actual.split(expected).count() - 1)), MatcherResult::Match)
+ } else {
+ actual.contains(expected)
+ }
+ }
+
+ // StrMatcher::describe redirects immediately to this function.
+ fn describe(&self, matcher_result: MatcherResult, expected: &str) -> String {
+ 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()),
+ (true, false) => addenda.push("ignoring leading whitespace".into()),
+ (false, true) => addenda.push("ignoring trailing whitespace".into()),
+ (false, false) => {}
+ }
+ match self.case_policy {
+ CasePolicy::Respect => {}
+ CasePolicy::IgnoreAscii => addenda.push("ignoring ASCII case".into()),
+ }
+ if let Some(times) = self.times.as_ref() {
+ addenda.push(format!("count {}", times.describe(matcher_result)).into());
+ }
+ let extra =
+ if !addenda.is_empty() { format!(" ({})", addenda.join(", ")) } else { "".into() };
+ let match_mode_description = match self.mode {
+ MatchMode::Equals => match matcher_result {
+ MatcherResult::Match => "is equal to",
+ MatcherResult::NoMatch => "isn't equal to",
+ },
+ MatchMode::Contains => match matcher_result {
+ MatcherResult::Match => "contains a substring",
+ MatcherResult::NoMatch => "does not contain a substring",
+ },
+ MatchMode::StartsWith => match matcher_result {
+ MatcherResult::Match => "starts with prefix",
+ MatcherResult::NoMatch => "does not start with",
+ },
+ MatchMode::EndsWith => match matcher_result {
+ MatcherResult::Match => "ends with suffix",
+ MatcherResult::NoMatch => "does not end with",
+ },
+ };
+ format!("{match_mode_description} {expected:?}{extra}")
+ }
+
+ fn explain_match(&self, expected: &str, actual: &str) -> String {
+ let default_explanation = format!(
+ "which {}",
+ self.describe(self.do_strings_match(expected, actual).into(), expected)
+ );
+ if !expected.contains('\n') || !actual.contains('\n') {
+ return default_explanation;
+ }
+
+ if self.ignore_leading_whitespace {
+ // TODO - b/283448414 : Support StrMatcher with ignore_leading_whitespace.
+ return default_explanation;
+ }
+
+ if self.ignore_trailing_whitespace {
+ // TODO - b/283448414 : Support StrMatcher with ignore_trailing_whitespace.
+ return default_explanation;
+ }
+
+ if self.times.is_some() {
+ // TODO - b/283448414 : Support StrMatcher with times.
+ return default_explanation;
+ }
+ if matches!(self.case_policy, CasePolicy::IgnoreAscii) {
+ // TODO - b/283448414 : Support StrMatcher with ignore ascii case policy.
+ return default_explanation;
+ }
+ if self.do_strings_match(expected, actual) {
+ // TODO - b/283448414 : Consider supporting debug difference if the
+ // strings match. This can be useful when a small contains is found
+ // in a long string.
+ return default_explanation;
+ }
+
+ let diff = match self.mode {
+ MatchMode::Equals | MatchMode::StartsWith | MatchMode::Contains => {
+ // TODO(b/287632452): Also consider improving the output in MatchMode::Contains
+ // when the substring begins or ends in the middle of a line of the actual
+ // value.
+ create_diff(actual, expected, self.mode.to_diff_mode())
+ }
+ MatchMode::EndsWith => create_diff_reversed(actual, expected, self.mode.to_diff_mode()),
+ };
+
+ format!("{default_explanation}\n{diff}",)
+ }
+
+ fn ignoring_leading_whitespace(self) -> Self {
+ Self { ignore_leading_whitespace: true, ..self }
+ }
+
+ fn ignoring_trailing_whitespace(self) -> Self {
+ Self { ignore_trailing_whitespace: true, ..self }
+ }
+
+ fn ignoring_outer_whitespace(self) -> Self {
+ Self { ignore_leading_whitespace: true, ignore_trailing_whitespace: true, ..self }
+ }
+
+ fn ignoring_ascii_case(self) -> Self {
+ Self { case_policy: CasePolicy::IgnoreAscii, ..self }
+ }
+
+ fn times(self, times: impl Matcher<ActualT = usize> + 'static) -> Self {
+ Self { times: Some(Box::new(times)), ..self }
+ }
+}
+
+impl Default for Configuration {
+ fn default() -> Self {
+ Self {
+ mode: MatchMode::Equals,
+ ignore_leading_whitespace: false,
+ ignore_trailing_whitespace: false,
+ case_policy: CasePolicy::Respect,
+ times: None,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{contains_substring, ends_with, starts_with, StrMatcher, StrMatcherConfigurator};
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+
+ #[test]
+ fn matches_string_reference_with_equal_string_reference() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string");
+ verify_that!("A string", matcher)
+ }
+
+ #[test]
+ fn does_not_match_string_reference_with_non_equal_string_reference() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("Another string");
+ verify_that!("A string", not(matcher))
+ }
+
+ #[test]
+ fn matches_owned_string_with_string_reference() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string");
+ let value = "A string".to_string();
+ verify_that!(value, matcher)
+ }
+
+ #[test]
+ fn matches_owned_string_reference_with_string_reference() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string");
+ let value = "A string".to_string();
+ verify_that!(&value, matcher)
+ }
+
+ #[test]
+ fn ignores_leading_whitespace_in_expected_when_requested() -> Result<()> {
+ let matcher = StrMatcher::with_default_config(" \n\tA string");
+ verify_that!("A string", matcher.ignoring_leading_whitespace())
+ }
+
+ #[test]
+ fn ignores_leading_whitespace_in_actual_when_requested() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string");
+ verify_that!(" \n\tA string", matcher.ignoring_leading_whitespace())
+ }
+
+ #[test]
+ fn does_not_match_unequal_remaining_string_when_ignoring_leading_whitespace() -> Result<()> {
+ let matcher = StrMatcher::with_default_config(" \n\tAnother string");
+ verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
+ }
+
+ #[test]
+ fn remains_sensitive_to_trailing_whitespace_when_ignoring_leading_whitespace() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string \n\t");
+ verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
+ }
+
+ #[test]
+ fn ignores_trailing_whitespace_in_expected_when_requested() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string \n\t");
+ verify_that!("A string", matcher.ignoring_trailing_whitespace())
+ }
+
+ #[test]
+ fn ignores_trailing_whitespace_in_actual_when_requested() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string");
+ verify_that!("A string \n\t", matcher.ignoring_trailing_whitespace())
+ }
+
+ #[test]
+ fn does_not_match_unequal_remaining_string_when_ignoring_trailing_whitespace() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("Another string \n\t");
+ verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
+ }
+
+ #[test]
+ fn remains_sensitive_to_leading_whitespace_when_ignoring_trailing_whitespace() -> Result<()> {
+ let matcher = StrMatcher::with_default_config(" \n\tA string");
+ verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
+ }
+
+ #[test]
+ fn ignores_leading_and_trailing_whitespace_in_expected_when_requested() -> Result<()> {
+ let matcher = StrMatcher::with_default_config(" \n\tA string \n\t");
+ verify_that!("A string", matcher.ignoring_outer_whitespace())
+ }
+
+ #[test]
+ fn ignores_leading_and_trailing_whitespace_in_actual_when_requested() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string");
+ verify_that!(" \n\tA string \n\t", matcher.ignoring_outer_whitespace())
+ }
+
+ #[test]
+ fn respects_ascii_case_by_default() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string");
+ verify_that!("A STRING", not(matcher))
+ }
+
+ #[test]
+ fn ignores_ascii_case_when_requested() -> Result<()> {
+ let matcher = StrMatcher::with_default_config("A string");
+ verify_that!("A STRING", matcher.ignoring_ascii_case())
+ }
+
+ #[test]
+ fn allows_ignoring_leading_whitespace_from_eq() -> Result<()> {
+ verify_that!("A string", eq(" \n\tA string").ignoring_leading_whitespace())
+ }
+
+ #[test]
+ fn allows_ignoring_trailing_whitespace_from_eq() -> Result<()> {
+ verify_that!("A string", eq("A string \n\t").ignoring_trailing_whitespace())
+ }
+
+ #[test]
+ fn allows_ignoring_outer_whitespace_from_eq() -> Result<()> {
+ verify_that!("A string", eq(" \n\tA string \n\t").ignoring_outer_whitespace())
+ }
+
+ #[test]
+ fn allows_ignoring_ascii_case_from_eq() -> Result<()> {
+ verify_that!("A string", eq("A STRING").ignoring_ascii_case())
+ }
+
+ #[test]
+ fn allows_ignoring_ascii_case_from_eq_deref_of_str_slice() -> Result<()> {
+ verify_that!("A string", eq_deref_of("A STRING").ignoring_ascii_case())
+ }
+
+ #[test]
+ fn allows_ignoring_ascii_case_from_eq_deref_of_owned_string() -> Result<()> {
+ verify_that!("A string", eq_deref_of("A STRING".to_string()).ignoring_ascii_case())
+ }
+
+ #[test]
+ fn matches_string_containing_expected_value_in_contains_mode() -> Result<()> {
+ verify_that!("Some string", contains_substring("str"))
+ }
+
+ #[test]
+ fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case()
+ -> Result<()> {
+ verify_that!("Some string", contains_substring("STR").ignoring_ascii_case())
+ }
+
+ #[test]
+ fn contains_substring_matches_correct_number_of_substrings() -> Result<()> {
+ verify_that!("Some string", contains_substring("str").times(eq(1)))
+ }
+
+ #[test]
+ fn contains_substring_does_not_match_incorrect_number_of_substrings() -> Result<()> {
+ verify_that!("Some string\nSome string", not(contains_substring("string").times(eq(1))))
+ }
+
+ #[test]
+ fn contains_substring_does_not_match_when_substrings_overlap() -> Result<()> {
+ verify_that!("ababab", not(contains_substring("abab").times(eq(2))))
+ }
+
+ #[test]
+ fn starts_with_matches_string_reference_with_prefix() -> Result<()> {
+ verify_that!("Some value", starts_with("Some"))
+ }
+
+ #[test]
+ fn starts_with_matches_string_reference_with_prefix_ignoring_ascii_case() -> Result<()> {
+ verify_that!("Some value", starts_with("SOME").ignoring_ascii_case())
+ }
+
+ #[test]
+ fn starts_with_does_not_match_wrong_prefix_ignoring_ascii_case() -> Result<()> {
+ verify_that!("Some value", not(starts_with("OTHER").ignoring_ascii_case()))
+ }
+
+ #[test]
+ fn ends_with_does_not_match_short_string_ignoring_ascii_case() -> Result<()> {
+ verify_that!("Some", not(starts_with("OTHER").ignoring_ascii_case()))
+ }
+
+ #[test]
+ fn starts_with_does_not_match_string_without_prefix() -> Result<()> {
+ verify_that!("Some value", not(starts_with("Another")))
+ }
+
+ #[test]
+ fn starts_with_does_not_match_string_with_substring_not_at_beginning() -> Result<()> {
+ verify_that!("Some value", not(starts_with("value")))
+ }
+
+ #[test]
+ fn ends_with_matches_string_reference_with_suffix() -> Result<()> {
+ verify_that!("Some value", ends_with("value"))
+ }
+
+ #[test]
+ fn ends_with_matches_string_reference_with_suffix_ignoring_ascii_case() -> Result<()> {
+ verify_that!("Some value", ends_with("VALUE").ignoring_ascii_case())
+ }
+
+ #[test]
+ fn ends_with_does_not_match_wrong_suffix_ignoring_ascii_case() -> Result<()> {
+ verify_that!("Some value", not(ends_with("OTHER").ignoring_ascii_case()))
+ }
+
+ #[test]
+ fn ends_with_does_not_match_too_short_string_ignoring_ascii_case() -> Result<()> {
+ verify_that!("Some", not(ends_with("OTHER").ignoring_ascii_case()))
+ }
+
+ #[test]
+ fn ends_with_does_not_match_string_without_suffix() -> Result<()> {
+ verify_that!("Some value", not(ends_with("other value")))
+ }
+
+ #[test]
+ fn ends_with_does_not_match_string_with_substring_not_at_end() -> Result<()> {
+ verify_that!("Some value", not(ends_with("Some")))
+ }
+
+ #[test]
+ fn describes_itself_for_matching_result() -> Result<()> {
+ let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string");
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ eq("is equal to \"A string\"")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_non_matching_result() -> Result<()> {
+ let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string");
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::NoMatch),
+ eq("isn't equal to \"A string\"")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_matching_result_ignoring_leading_whitespace() -> Result<()> {
+ let matcher: StrMatcher<&str, _> =
+ 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)")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_non_matching_result_ignoring_leading_whitespace() -> Result<()> {
+ let matcher: StrMatcher<&str, _> =
+ 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)")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_matching_result_ignoring_trailing_whitespace() -> Result<()> {
+ let matcher: StrMatcher<&str, _> =
+ 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)")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_matching_result_ignoring_leading_and_trailing_whitespace() -> Result<()>
+ {
+ let matcher: StrMatcher<&str, _> =
+ 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)")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_matching_result_ignoring_ascii_case() -> Result<()> {
+ let matcher: StrMatcher<&str, _> =
+ 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)")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace()
+ -> Result<()> {
+ let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string")
+ .ignoring_leading_whitespace()
+ .ignoring_ascii_case();
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ eq("is equal to \"A string\" (ignoring leading whitespace, ignoring ASCII case)")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_matching_result_in_contains_mode() -> Result<()> {
+ let matcher: StrMatcher<&str, _> = contains_substring("A string");
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ eq("contains a substring \"A string\"")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_non_matching_result_in_contains_mode() -> Result<()> {
+ let matcher: StrMatcher<&str, _> = contains_substring("A string");
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::NoMatch),
+ eq("does not contain a substring \"A string\"")
+ )
+ }
+
+ #[test]
+ fn describes_itself_with_count_number() -> Result<()> {
+ 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)")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_matching_result_in_starts_with_mode() -> Result<()> {
+ let matcher: StrMatcher<&str, _> = starts_with("A string");
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ eq("starts with prefix \"A string\"")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_non_matching_result_in_starts_with_mode() -> Result<()> {
+ let matcher: StrMatcher<&str, _> = starts_with("A string");
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::NoMatch),
+ eq("does not start with \"A string\"")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_matching_result_in_ends_with_mode() -> Result<()> {
+ let matcher: StrMatcher<&str, _> = ends_with("A string");
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ eq("ends with suffix \"A string\"")
+ )
+ }
+
+ #[test]
+ fn describes_itself_for_non_matching_result_in_ends_with_mode() -> Result<()> {
+ let matcher: StrMatcher<&str, _> = ends_with("A string");
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::NoMatch),
+ eq("does not end with \"A string\"")
+ )
+ }
+
+ #[test]
+ fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()> {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ "
+ ),
+ starts_with(indoc!(
+ "
+ First line
+ Second lines
+ Third line
+ "
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ First line
+ -Second line
+ +Second lines
+ Third line
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_for_starts_with_ignores_trailing_lines_in_actual_string() -> Result<()> {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ Fourth line
+ "
+ ),
+ starts_with(indoc!(
+ "
+ First line
+ Second lines
+ Third line
+ "
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ First line
+ -Second line
+ +Second lines
+ Third line
+ <---- remaining lines omitted ---->
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_for_starts_with_includes_both_versions_of_differing_last_line()
+ -> Result<()> {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ "
+ ),
+ starts_with(indoc!(
+ "
+ First line
+ Second lines
+ "
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ First line
+ -Second line
+ +Second lines
+ <---- remaining lines omitted ---->
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_for_ends_with_ignores_leading_lines_in_actual_string() -> Result<()> {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ Fourth line
+ "
+ ),
+ ends_with(indoc!(
+ "
+ Second line
+ Third lines
+ Fourth line
+ "
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Difference(-actual / +expected):
+ <---- remaining lines omitted ---->
+ Second line
+ +Third lines
+ -Third line
+ Fourth line
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_for_contains_substring_ignores_outer_lines_in_actual_string() -> Result<()>
+ {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ Fourth line
+ Fifth line
+ "
+ ),
+ contains_substring(indoc!(
+ "
+ Second line
+ Third lines
+ Fourth line
+ "
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Difference(-actual / +expected):
+ <---- remaining lines omitted ---->
+ Second line
+ +Third lines
+ -Third line
+ Fourth line
+ <---- remaining lines omitted ---->"
+ ))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_for_contains_substring_shows_diff_when_first_and_last_line_are_incomplete()
+ -> Result<()> {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ Fourth line
+ Fifth line
+ "
+ ),
+ contains_substring(indoc!(
+ "
+ line
+ Third line
+ Foorth line
+ Fifth"
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Difference(-actual / +expected):
+ <---- remaining lines omitted ---->
+ +line
+ -Second line
+ Third line
+ +Foorth line
+ -Fourth line
+ +Fifth
+ -Fifth line
+ <---- remaining lines omitted ---->
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_for_eq_does_not_ignore_trailing_lines_in_actual_string() -> Result<()> {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ Fourth line
+ "
+ ),
+ eq(indoc!(
+ "
+ First line
+ Second lines
+ Third line
+ "
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ First line
+ -Second line
+ +Second lines
+ Third line
+ -Fourth line
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()> {
+ let result = verify_that!(
+ "First line",
+ starts_with(indoc!(
+ "
+ Second line
+ Third line
+ "
+ ))
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
+ )
+ }
+
+ #[test]
+ fn match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()> {
+ let result = verify_that!(
+ indoc!(
+ "
+ First line
+ Second line
+ Third line
+ "
+ ),
+ starts_with("Second line")
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
+ )
+ }
+}
diff --git a/src/matchers/subset_of_matcher.rs b/src/matchers/subset_of_matcher.rs
new file mode 100644
index 0000000..facee4f
--- /dev/null
+++ b/src/matchers/subset_of_matcher.rs
@@ -0,0 +1,267 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a container all of whose items are in the given container
+/// `superset`.
+///
+/// The element type `ElementT` must implement `PartialEq` to allow element
+/// comparison.
+///
+/// `ActualT` and `ExpectedT` can each be any container a reference to which
+/// implements `IntoIterator`. They need not be the same container type.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass_1() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(value, subset_of([1, 2, 3, 4]))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// # let value = vec![1, 2, 3];
+/// verify_that!(value, subset_of([1, 2]))?; // Fails: 3 is not in the superset
+/// # Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+///
+/// # fn should_pass_2() -> Result<()> {
+/// let value: HashSet<i32> = [1, 2, 3].into();
+/// verify_that!(value, subset_of([1, 2, 3]))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// Item multiplicity in both the actual and expected containers is ignored:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value: Vec<i32> = vec![0, 0, 1];
+/// verify_that!(value, subset_of([0, 1]))?; // Passes
+/// verify_that!(value, subset_of([0, 1, 1]))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// One can also verify the contents of a slice by dereferencing it:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = &[1, 2, 3];
+/// verify_that!(*value, subset_of([1, 2, 3]))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// A note on performance: This matcher uses a naive algorithm with a worst-case
+/// runtime proportional to the *product* of the sizes of the actual and
+/// expected containers as well as the time to check equality of each pair of
+/// items. It should not be used on especially large containers.
+pub fn subset_of<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug>(
+ superset: ExpectedT,
+) -> impl Matcher<ActualT = ActualT>
+where
+ for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+ for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+ SubsetOfMatcher::<ActualT, _> { superset, phantom: Default::default() }
+}
+
+struct SubsetOfMatcher<ActualT: ?Sized, ExpectedT> {
+ superset: ExpectedT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug> Matcher
+ for SubsetOfMatcher<ActualT, ExpectedT>
+where
+ for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+ for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ for actual_item in actual {
+ if self.expected_is_missing(actual_item) {
+ return MatcherResult::NoMatch;
+ }
+ }
+ MatcherResult::Match
+ }
+
+ fn explain_match(&self, actual: &ActualT) -> String {
+ let unexpected_elements = actual
+ .into_iter()
+ .enumerate()
+ .filter(|&(_, actual_item)| self.expected_is_missing(actual_item))
+ .map(|(idx, actual_item)| format!("{actual_item:#?} at #{idx}"))
+ .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(", ")),
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!("is a subset of {:#?}", self.superset),
+ MatcherResult::NoMatch => format!("isn't a subset of {:#?}", self.superset),
+ }
+ }
+}
+
+impl<ActualT: ?Sized, ElementT: PartialEq, ExpectedT> SubsetOfMatcher<ActualT, ExpectedT>
+where
+ for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+ fn expected_is_missing(&self, needle: &ElementT) -> bool {
+ !self.superset.into_iter().any(|item| *item == *needle)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::subset_of;
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::collections::HashSet;
+
+ #[test]
+ fn subset_of_matches_empty_vec() -> Result<()> {
+ let value: Vec<i32> = vec![];
+ verify_that!(value, subset_of([]))
+ }
+
+ #[test]
+ fn subset_of_matches_vec_with_one_element() -> Result<()> {
+ let value = vec![1];
+ verify_that!(value, subset_of([1]))
+ }
+
+ #[test]
+ fn subset_of_matches_vec_with_two_elements() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, subset_of([1, 2]))
+ }
+
+ #[test]
+ fn subset_of_matches_vec_when_expected_has_excess_element() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, subset_of([1, 2, 3]))
+ }
+
+ #[test]
+ fn subset_of_matches_vec_when_expected_has_excess_element_first() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, subset_of([3, 1, 2]))
+ }
+
+ #[test]
+ fn subset_of_matches_slice_with_one_element() -> Result<()> {
+ let value = &[1];
+ verify_that!(*value, subset_of([1]))
+ }
+
+ #[test]
+ fn subset_of_matches_hash_set_with_one_element() -> Result<()> {
+ let value: HashSet<i32> = [1].into();
+ verify_that!(value, subset_of([1]))
+ }
+
+ #[test]
+ fn subset_of_does_not_match_when_first_element_does_not_match() -> Result<()> {
+ let value = vec![0];
+ verify_that!(value, not(subset_of([1])))
+ }
+
+ #[test]
+ fn subset_of_does_not_match_when_second_element_does_not_match() -> Result<()> {
+ let value = vec![2, 0];
+ verify_that!(value, not(subset_of([2])))
+ }
+
+ #[test]
+ fn subset_of_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
+ let result = verify_that!(vec![0, 2, 3], subset_of([1, 2, 3]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![0, 2, 3]
+ Expected: is a subset of [
+ 1,
+ 2,
+ 3,
+ ]
+ Actual: [0, 2, 3],
+ whose element 0 at #0 is unexpected
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn subset_of_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
+ let result = verify_that!(vec![1, 0, 3], subset_of([1, 2, 3]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 0, 3]
+ Expected: is a subset of [
+ 1,
+ 2,
+ 3,
+ ]
+ Actual: [1, 0, 3],
+ whose element 0 at #1 is unexpected
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn subset_of_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
+ let result = verify_that!(vec![0, 0, 3], subset_of([1, 2, 3]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![0, 0, 3]
+ Expected: is a subset of [
+ 1,
+ 2,
+ 3,
+ ]
+ Actual: [0, 0, 3],
+ whose elements 0 at #0, 0 at #1 are unexpected
+ "
+ ))))
+ )
+ }
+}
diff --git a/src/matchers/superset_of_matcher.rs b/src/matchers/superset_of_matcher.rs
new file mode 100644
index 0000000..8e98015
--- /dev/null
+++ b/src/matchers/superset_of_matcher.rs
@@ -0,0 +1,267 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a container containing all of the items in the given container
+/// `subset`.
+///
+/// The element type `ElementT` must implement `PartialEq` to allow element
+/// comparison.
+///
+/// `ActualT` and `ExpectedT` can each be any container a reference to which
+/// implements `IntoIterator`. They need not be the same container type.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass_1() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(value, superset_of([1, 2]))?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// # let value = vec![1, 2, 3];
+/// verify_that!(value, superset_of([1, 2, 4]))?; // Fails: 4 is not in the subset
+/// # Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+///
+/// # fn should_pass_2() -> Result<()> {
+/// let value: HashSet<i32> = [1, 2, 3].into();
+/// verify_that!(value, superset_of([1, 2, 3]))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// Item multiplicity in both the actual and expected containers is ignored:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value: Vec<i32> = vec![0, 0, 1];
+/// verify_that!(value, superset_of([0, 1]))?; // Passes
+/// verify_that!(value, superset_of([0, 1, 1]))?; // Passes
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// One can also verify the contents of a slice by dereferencing it:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = &[1, 2, 3];
+/// verify_that!(*value, superset_of([1, 2, 3]))?;
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// A note on performance: This matcher uses a naive algorithm with a worst-case
+/// runtime proportional to the *product* of the sizes of the actual and
+/// expected containers as well as the time to check equality of each pair of
+/// items. It should not be used on especially large containers.
+pub fn superset_of<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug>(
+ subset: ExpectedT,
+) -> impl Matcher<ActualT = ActualT>
+where
+ for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+ for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+ SupersetOfMatcher::<ActualT, _> { subset, phantom: Default::default() }
+}
+
+struct SupersetOfMatcher<ActualT: ?Sized, ExpectedT> {
+ subset: ExpectedT,
+ phantom: PhantomData<ActualT>,
+}
+
+impl<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug> Matcher
+ for SupersetOfMatcher<ActualT, ExpectedT>
+where
+ for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+ for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+ type ActualT = ActualT;
+
+ fn matches(&self, actual: &ActualT) -> MatcherResult {
+ for expected_item in &self.subset {
+ if actual_is_missing(actual, expected_item) {
+ return MatcherResult::NoMatch;
+ }
+ }
+ MatcherResult::Match
+ }
+
+ fn explain_match(&self, actual: &ActualT) -> String {
+ let missing_items: Vec<_> = self
+ .subset
+ .into_iter()
+ .filter(|expected_item| actual_is_missing(actual, expected_item))
+ .map(|expected_item| format!("{expected_item:#?}"))
+ .collect();
+ match missing_items.len() {
+ 0 => "whose no element is missing".to_string(),
+ 1 => format!("whose element {} is missing", &missing_items[0]),
+ _ => format!("whose elements {} are missing", missing_items.join(", ")),
+ }
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => format!("is a superset of {:#?}", self.subset),
+ MatcherResult::NoMatch => format!("isn't a superset of {:#?}", self.subset),
+ }
+ }
+}
+
+fn actual_is_missing<ElementT: PartialEq, ActualT: ?Sized>(
+ actual: &ActualT,
+ needle: &ElementT,
+) -> bool
+where
+ for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+{
+ !actual.into_iter().any(|item| *item == *needle)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::superset_of;
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::collections::HashSet;
+
+ #[test]
+ fn superset_of_matches_empty_vec() -> Result<()> {
+ let value: Vec<i32> = vec![];
+ verify_that!(value, superset_of([]))
+ }
+
+ #[test]
+ fn superset_of_matches_vec_with_one_element() -> Result<()> {
+ let value = vec![1];
+ verify_that!(value, superset_of([1]))
+ }
+
+ #[test]
+ fn superset_of_matches_vec_with_two_items() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, superset_of([1, 2]))
+ }
+
+ #[test]
+ fn superset_of_matches_vec_when_actual_has_excess_element() -> Result<()> {
+ let value = vec![1, 2, 3];
+ verify_that!(value, superset_of([1, 2]))
+ }
+
+ #[test]
+ fn superset_of_matches_vec_when_actual_has_excess_element_first() -> Result<()> {
+ let value = vec![3, 1, 2];
+ verify_that!(value, superset_of([1, 2]))
+ }
+
+ #[test]
+ fn superset_of_matches_slice_with_one_element() -> Result<()> {
+ let value = &[1];
+ verify_that!(*value, superset_of([1]))
+ }
+
+ #[test]
+ fn superset_of_matches_hash_set_with_one_element() -> Result<()> {
+ let value: HashSet<i32> = [1].into();
+ verify_that!(value, superset_of([1]))
+ }
+
+ #[test]
+ fn superset_of_does_not_match_when_first_element_does_not_match() -> Result<()> {
+ let value = vec![0];
+ verify_that!(value, not(superset_of([1])))
+ }
+
+ #[test]
+ fn superset_of_does_not_match_when_second_element_does_not_match() -> Result<()> {
+ let value = vec![2];
+ verify_that!(value, not(superset_of([2, 0])))
+ }
+
+ #[test]
+ fn superset_of_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
+ let result = verify_that!(vec![0, 2, 3], superset_of([1, 2, 3]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![0, 2, 3]
+ Expected: is a superset of [
+ 1,
+ 2,
+ 3,
+ ]
+ Actual: [0, 2, 3],
+ whose element 1 is missing
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn superset_of_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
+ let result = verify_that!(vec![1, 0, 3], superset_of([1, 2, 3]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 0, 3]
+ Expected: is a superset of [
+ 1,
+ 2,
+ 3,
+ ]
+ Actual: [1, 0, 3],
+ whose element 2 is missing
+ "
+ ))))
+ )
+ }
+
+ #[test]
+ fn superset_of_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
+ let result = verify_that!(vec![0, 0, 3], superset_of([1, 2, 3]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![0, 0, 3]
+ Expected: is a superset of [
+ 1,
+ 2,
+ 3,
+ ]
+ Actual: [0, 0, 3],
+ whose elements 1, 2 are missing
+ "
+ ))))
+ )
+ }
+}
diff --git a/src/matchers/tuple_matcher.rs b/src/matchers/tuple_matcher.rs
new file mode 100644
index 0000000..a2e325b
--- /dev/null
+++ b/src/matchers/tuple_matcher.rs
@@ -0,0 +1,207 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Functions for use only by the declarative macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+ use crate::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
+ };
+ }
+
+ // This implementation is provided for completeness, but is completely trivial.
+ // The only actual value which can be supplied is (), which must match.
+ impl Matcher for () {
+ type ActualT = ();
+
+ fn matches(&self, _: &Self::ActualT) -> MatcherResult {
+ MatcherResult::Match
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ match matcher_result {
+ MatcherResult::Match => "is the empty tuple".into(),
+ MatcherResult::NoMatch => "is not the empty tuple".into(),
+ }
+ }
+ }
+
+ /// Generates a tuple matcher for tuples of a specific length.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ macro_rules! tuple_matcher_n {
+ ($([$field_number:tt, $matcher_type:ident, $field_type:ident]),*) => {
+ impl<$($field_type: Debug, $matcher_type: Matcher<ActualT = $field_type>),*>
+ Matcher for ($($matcher_type,)*)
+ {
+ type ActualT = ($($field_type,)*);
+
+ fn matches(&self, actual: &($($field_type,)*)) -> MatcherResult {
+ $(match self.$field_number.matches(&actual.$field_number) {
+ MatcherResult::Match => {},
+ MatcherResult::NoMatch => {
+ return MatcherResult::NoMatch;
+ }
+ })*
+ MatcherResult::Match
+ }
+
+ fn explain_match(&self, actual: &($($field_type,)*)) -> String {
+ let mut explanation = format!("which {}", 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)
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ 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)),*
+ )
+ }
+ 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)),*
+ )
+ }
+ }
+ }
+ }
+ };
+ }
+
+ tuple_matcher_n!([0, I0, T0]);
+
+ tuple_matcher_n!([0, I0, T0], [1, I1, T1]);
+
+ tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2]);
+
+ tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2], [3, I3, T3]);
+
+ tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2], [3, I3, T3], [4, I4, T4]);
+
+ tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2], [3, I3, T3], [4, I4, T4], [5, I5, T5]);
+
+ tuple_matcher_n!(
+ [0, I0, T0],
+ [1, I1, T1],
+ [2, I2, T2],
+ [3, I3, T3],
+ [4, I4, T4],
+ [5, I5, T5],
+ [6, I6, T6]
+ );
+
+ tuple_matcher_n!(
+ [0, I0, T0],
+ [1, I1, T1],
+ [2, I2, T2],
+ [3, I3, T3],
+ [4, I4, T4],
+ [5, I5, T5],
+ [6, I6, T6],
+ [7, I7, T7]
+ );
+
+ tuple_matcher_n!(
+ [0, I0, T0],
+ [1, I1, T1],
+ [2, I2, T2],
+ [3, I3, T3],
+ [4, I4, T4],
+ [5, I5, T5],
+ [6, I6, T6],
+ [7, I7, T7],
+ [8, I8, T8]
+ );
+
+ tuple_matcher_n!(
+ [0, I0, T0],
+ [1, I1, T1],
+ [2, I2, T2],
+ [3, I3, T3],
+ [4, I4, T4],
+ [5, I5, T5],
+ [6, I6, T6],
+ [7, I7, T7],
+ [8, I8, T8],
+ [9, I9, T9]
+ );
+
+ tuple_matcher_n!(
+ [0, I0, T0],
+ [1, I1, T1],
+ [2, I2, T2],
+ [3, I3, T3],
+ [4, I4, T4],
+ [5, I5, T5],
+ [6, I6, T6],
+ [7, I7, T7],
+ [8, I8, T8],
+ [9, I9, T9],
+ [10, I10, T10]
+ );
+
+ tuple_matcher_n!(
+ [0, I0, T0],
+ [1, I1, T1],
+ [2, I2, T2],
+ [3, I3, T3],
+ [4, I4, T4],
+ [5, I5, T5],
+ [6, I6, T6],
+ [7, I7, T7],
+ [8, I8, T8],
+ [9, I9, T9],
+ [10, I10, T10],
+ [11, I11, T11]
+ );
+}
diff --git a/src/matchers/unordered_elements_are_matcher.rs b/src/matchers/unordered_elements_are_matcher.rs
new file mode 100644
index 0000000..1930697
--- /dev/null
+++ b/src/matchers/unordered_elements_are_matcher.rs
@@ -0,0 +1,1116 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a container whose elements in any order have a 1:1 correspondence
+/// with the provided element matchers.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], unordered_elements_are![eq(1), ge(2), anything()])?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(vec![1], unordered_elements_are![eq(1), ge(2)])?; // Fails: container has wrong size
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], unordered_elements_are![eq(1), ge(4), eq(2)])?; // Fails: second matcher not matched
+/// # Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], unordered_elements_are![ge(3), ge(3), ge(3)])?; // Fails: no 1:1 correspondence
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// This can also match against [`HashMap`][std::collections::HashMap] and
+/// similar collections. The arguments are a sequence of pairs of matchers
+/// corresponding to the keys and their respective values.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// let value: HashMap<u32, &'static str> =
+/// HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
+/// verify_that!(
+/// value,
+/// unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
+/// )
+/// # .unwrap();
+/// ```
+///
+/// This can also be omitted in [`verify_that!`] macros and replaced with curly
+/// brackets.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!(vec![1, 2], {eq(2), eq(1)})
+/// # .unwrap();
+/// ```
+///
+/// Note: This behavior is only possible in [`verify_that!`] macros. In any
+/// other cases, it is still necessary to use the
+/// [`unordered_elements_are!`][crate::unordered_elements_are] macro.
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// verify_that!(vec![vec![1,2], vec![3]], {{eq(2), eq(1)}, {eq(3)}})
+/// # .unwrap();
+/// ```
+///
+/// Use this instead:
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!(vec![vec![1,2], vec![3]],
+/// {unordered_elements_are![eq(2), eq(1)], unordered_elements_are![eq(3)]})
+/// # .unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`].
+///
+/// The matcher proceeds in three stages:
+///
+/// 1. It first checks whether the actual value is of the right size to possibly
+/// be matched by each of the given matchers. If not, then it immediately
+/// fails explaining that the size is incorrect.
+///
+/// 2. It then checks whether each matcher matches at least one corresponding
+/// element in the actual container and each element in the actual container
+/// is matched by at least one matcher. If not, it fails with a message
+/// indicating which matcher respectively container elements had no
+/// counterparts.
+///
+/// 3. Finally, it checks whether the mapping of matchers to corresponding
+/// actual elements is a 1-1 correspondence and fails if that is not the
+/// case. The failure message then shows the best matching it could find,
+/// including which matchers did not have corresponding unique elements in
+/// the container and which container elements had no corresponding matchers.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! unordered_elements_are {
+ ($(,)?) => {{
+ use $crate::matchers::unordered_elements_are_matcher::internal::{
+ UnorderedElementsAreMatcher, Requirements
+ };
+ UnorderedElementsAreMatcher::new([], Requirements::PerfectMatch)
+ }};
+
+ // 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::{
+ UnorderedElementsOfMapAreMatcher, Requirements
+ };
+ UnorderedElementsOfMapAreMatcher::new(
+ [$((Box::new($key_matcher), Box::new($value_matcher))),*],
+ Requirements::PerfectMatch
+ )
+ }};
+
+ ($($matcher:expr),* $(,)?) => {{
+ use $crate::matchers::unordered_elements_are_matcher::internal::{
+ UnorderedElementsAreMatcher, Requirements
+ };
+ UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::PerfectMatch)
+ }};
+}
+
+/// Matches a container containing elements matched by the given matchers.
+///
+/// To match, each given matcher must have a corresponding element in the
+/// container which it matches. There must be a mapping uniquely matching each
+/// matcher to a container element. The container can, however, contain
+/// additional elements that don't correspond to any matcher.
+///
+/// Put another way, `contains_each!` matches if there is a subset of the actual
+/// container which [`unordered_elements_are`] would match.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], contains_each![eq(2), ge(3)])?; // Passes
+/// verify_that!(vec![3, 2, 1], contains_each![ge(2), ge(2)])?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(vec![1], contains_each![eq(1), ge(2)])?; // Fails: container too small
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], contains_each![eq(1), ge(4)])?; // Fails: second matcher unmatched
+/// # Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], contains_each![ge(3), ge(3), ge(3)])?; // Fails: no matching
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// This can also match against [`HashMap`][std::collections::HashMap] and
+/// similar collections. The arguments are a sequence of pairs of matchers
+/// corresponding to the keys and their respective values.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// let value: HashMap<u32, &'static str> =
+/// HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
+/// verify_that!(value, contains_each![(eq(2), eq("Two")), (eq(1), eq("One"))])
+/// # .unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`].
+///
+/// The matcher proceeds in three stages:
+///
+/// 1. It first checks whether the actual value is large enough to possibly be
+/// matched by each of the given matchers. If not, then it immediately fails
+/// explaining that the size is too small.
+///
+/// 2. It then checks whether each matcher matches at least one corresponding
+/// element in the actual container and fails if that is not the case. The
+/// failure message indicates which matcher had no corresponding element.
+///
+/// 3. Finally, it checks whether the mapping of matchers to corresponding
+/// actual elements is 1-1 and fails if that is not the case. The failure
+/// message then shows the best matching it could find, including which
+/// matchers did not have corresponding unique elements in the container.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! contains_each {
+ ($(,)?) => {{
+ use $crate::matchers::unordered_elements_are_matcher::internal::{
+ UnorderedElementsAreMatcher, Requirements
+ };
+ UnorderedElementsAreMatcher::new([], Requirements::Superset)
+ }};
+
+ // 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::{
+ UnorderedElementsOfMapAreMatcher, Requirements
+ };
+ UnorderedElementsOfMapAreMatcher::new(
+ [$((Box::new($key_matcher), Box::new($value_matcher))),*],
+ Requirements::Superset
+ )
+ }};
+
+ ($($matcher:expr),* $(,)?) => {{
+ use $crate::matchers::unordered_elements_are_matcher::internal::{
+ UnorderedElementsAreMatcher, Requirements
+ };
+ UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::Superset)
+ }}
+}
+
+/// Matches a container all of whose elements are matched by the given matchers.
+///
+/// To match, each element in the container must have a corresponding matcher
+/// which matches it. There must be a 1-1 mapping from container elements to
+/// matchers, so that no matcher has more than one corresponding element.
+///
+/// There may, however, be matchers not corresponding to any elements in the
+/// container.
+///
+/// Put another way, `is_contained_in!` matches if there is a subset of the
+/// matchers which would match with [`unordered_elements_are`].
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(vec![2, 1], is_contained_in![eq(1), ge(2)])?; // Passes
+/// verify_that!(vec![2, 1], is_contained_in![ge(1), ge(1)])?; // Passes
+/// # Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(vec![1, 2, 3], is_contained_in![eq(1), ge(2)])?; // Fails: container too large
+/// # Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(vec![2, 1], is_contained_in![eq(1), ge(4)])?; // Fails: second matcher unmatched
+/// # Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(vec![3, 1], is_contained_in![ge(3), ge(3), ge(3)])?; // Fails: no matching
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// This can also match against [`HashMap`][std::collections::HashMap] and
+/// similar collections. The arguments are a sequence of pairs of matchers
+/// corresponding to the keys and their respective values.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// let value: HashMap<u32, &'static str> = HashMap::from_iter([(1, "One"), (2, "Two")]);
+/// verify_that!(
+/// value,
+/// is_contained_in![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
+/// )
+/// # .unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`].
+///
+/// The matcher proceeds in three stages:
+///
+/// 1. It first checks whether the actual value is too large to possibly be
+/// matched by each of the given matchers. If so, it immediately fails
+/// explaining that the size is too large.
+///
+/// 2. It then checks whether each actual container element is matched by at
+/// least one matcher and fails if that is not the case. The failure message
+/// indicates which element had no corresponding matcher.
+///
+/// 3. Finally, it checks whether the mapping of elements to corresponding
+/// matchers is 1-1 and fails if that is not the case. The failure message
+/// then shows the best matching it could find, including which container
+/// elements did not have corresponding matchers.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! is_contained_in {
+ ($(,)?) => {{
+ use $crate::matchers::unordered_elements_are_matcher::internal::{
+ UnorderedElementsAreMatcher, Requirements
+ };
+ UnorderedElementsAreMatcher::new([], Requirements::Subset)
+ }};
+
+ // 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::{
+ UnorderedElementsOfMapAreMatcher, Requirements
+ };
+ UnorderedElementsOfMapAreMatcher::new(
+ [$((Box::new($key_matcher), Box::new($value_matcher))),*],
+ Requirements::Subset
+ )
+ }};
+
+ ($($matcher:expr),* $(,)?) => {{
+ use $crate::matchers::unordered_elements_are_matcher::internal::{
+ UnorderedElementsAreMatcher, Requirements
+ };
+ UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::Subset)
+ }}
+}
+
+/// Module for use only by the macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+ use crate::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;
+
+ /// This struct is meant to be used only through the
+ /// `unordered_elements_are![...]` macro.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub struct UnorderedElementsAreMatcher<'a, ContainerT: ?Sized, T: Debug, const N: usize> {
+ elements: [Box<dyn Matcher<ActualT = T> + 'a>; N],
+ requirements: Requirements,
+ phantom: PhantomData<ContainerT>,
+ }
+
+ impl<'a, ContainerT: ?Sized, T: Debug, const N: usize>
+ UnorderedElementsAreMatcher<'a, ContainerT, T, N>
+ {
+ pub fn new(
+ elements: [Box<dyn Matcher<ActualT = T> + 'a>; N],
+ requirements: Requirements,
+ ) -> Self {
+ Self { elements, requirements, phantom: Default::default() }
+ }
+ }
+
+ // This matcher performs the checks in three different steps in both `matches`
+ // and `explain_match`. This is useful for performance but also to produce
+ // an actionable error message.
+ // 1. `UnorderedElementsAreMatcher` verifies that both collections have the same
+ // size
+ // 2. `UnorderedElementsAreMatcher` verifies that each actual element matches at
+ // least one expected element and vice versa.
+ // 3. `UnorderedElementsAreMatcher` verifies that a perfect matching exists
+ // using Ford-Fulkerson.
+ impl<'a, T: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher
+ for UnorderedElementsAreMatcher<'a, ContainerT, T, N>
+ where
+ for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+ {
+ type ActualT = ContainerT;
+
+ fn matches(&self, actual: &ContainerT) -> MatcherResult {
+ let match_matrix = MatchMatrix::generate(actual, &self.elements);
+ match_matrix.is_match_for(self.requirements).into()
+ }
+
+ fn explain_match(&self, actual: &ContainerT) -> String {
+ if let Some(size_mismatch_explanation) =
+ self.requirements.explain_size_mismatch(actual, N)
+ {
+ return size_mismatch_explanation;
+ }
+
+ let match_matrix = MatchMatrix::generate(actual, &self.elements);
+ if let Some(unmatchable_explanation) =
+ match_matrix.explain_unmatchable(self.requirements)
+ {
+ return unmatchable_explanation;
+ }
+
+ let best_match = match_matrix.find_best_match();
+ best_match
+ .get_explanation(actual, &self.elements, self.requirements)
+ .unwrap_or("whose elements all match".to_string())
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ format!(
+ "{} elements matching in any order:\n{}",
+ if matcher_result.into() { "contains" } else { "doesn't contain" },
+ self.elements
+ .iter()
+ .map(|matcher| matcher.describe(MatcherResult::Match))
+ .collect::<Description>()
+ .enumerate()
+ .indent()
+ )
+ }
+ }
+
+ type KeyValueMatcher<'a, KeyT, ValueT> =
+ (Box<dyn Matcher<ActualT = KeyT> + 'a>, Box<dyn Matcher<ActualT = ValueT> + 'a>);
+
+ /// This is the analogue to [UnorderedElementsAreMatcher] for maps and
+ /// map-like collections.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ pub struct UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, const N: usize>
+ where
+ ContainerT: ?Sized,
+ KeyT: Debug,
+ ValueT: Debug,
+ {
+ elements: [KeyValueMatcher<'a, KeyT, ValueT>; N],
+ requirements: Requirements,
+ phantom: PhantomData<ContainerT>,
+ }
+
+ impl<'a, ContainerT, KeyT: Debug, ValueT: Debug, const N: usize>
+ UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N>
+ {
+ pub fn new(
+ elements: [KeyValueMatcher<'a, KeyT, ValueT>; N],
+ requirements: Requirements,
+ ) -> Self {
+ Self { elements, requirements, phantom: Default::default() }
+ }
+ }
+
+ impl<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher
+ for UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N>
+ where
+ for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
+ {
+ type ActualT = ContainerT;
+
+ fn matches(&self, actual: &ContainerT) -> MatcherResult {
+ let match_matrix = MatchMatrix::generate_for_map(actual, &self.elements);
+ match_matrix.is_match_for(self.requirements).into()
+ }
+
+ fn explain_match(&self, actual: &ContainerT) -> String {
+ if let Some(size_mismatch_explanation) =
+ self.requirements.explain_size_mismatch(actual, N)
+ {
+ return size_mismatch_explanation;
+ }
+
+ let match_matrix = MatchMatrix::generate_for_map(actual, &self.elements);
+ if let Some(unmatchable_explanation) =
+ match_matrix.explain_unmatchable(self.requirements)
+ {
+ return unmatchable_explanation;
+ }
+
+ let best_match = match_matrix.find_best_match();
+
+ best_match
+ .get_explanation_for_map(actual, &self.elements, self.requirements)
+ .unwrap_or("whose elements all match".to_string())
+ }
+
+ fn describe(&self, matcher_result: MatcherResult) -> String {
+ format!(
+ "{} elements matching in any order:\n{}",
+ if matcher_result.into() { "contains" } else { "doesn't contain" },
+ self.elements
+ .iter()
+ .map(|(key_matcher, value_matcher)| format!(
+ "{} => {}",
+ key_matcher.describe(MatcherResult::Match),
+ value_matcher.describe(MatcherResult::Match)
+ ))
+ .collect::<Description>()
+ .indent()
+ )
+ }
+ }
+
+ /// The requirements of the mapping between matchers and actual values by
+ /// which [`UnorderedElemetnsAre`] is deemed to match its input.
+ ///
+ /// **For internal use only. API stablility is not guaranteed!**
+ #[doc(hidden)]
+ #[derive(Clone, Copy)]
+ pub enum Requirements {
+ /// There must be a 1:1 correspondence between the actual values and the
+ /// matchers.
+ PerfectMatch,
+
+ /// The mapping from matched actual values to their corresponding
+ /// matchers must be surjective.
+ Superset,
+
+ /// The mapping from matchers to matched actual values must be
+ /// surjective.
+ Subset,
+ }
+
+ impl Requirements {
+ fn explain_size_mismatch<ContainerT: ?Sized>(
+ &self,
+ actual: &ContainerT,
+ expected_size: usize,
+ ) -> Option<String>
+ 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::Superset if actual_size < expected_size => Some(format!(
+ "which has size {} (expected at least {})",
+ actual_size, expected_size
+ )),
+
+ Requirements::Subset if actual_size > expected_size => Some(format!(
+ "which has size {} (expected at most {})",
+ actual_size, expected_size
+ )),
+
+ _ => None,
+ }
+ }
+ }
+
+ impl Display for Requirements {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Requirements::PerfectMatch => {
+ write!(f, "perfect")
+ }
+ Requirements::Superset => {
+ write!(f, "superset")
+ }
+ Requirements::Subset => {
+ write!(f, "subset")
+ }
+ }
+ }
+ }
+
+ /// The bipartite matching graph between actual and expected elements.
+ struct MatchMatrix<const N: usize>(Vec<[MatcherResult; N]>);
+
+ impl<const N: usize> MatchMatrix<N> {
+ fn generate<'a, T: Debug + 'a, ContainerT: Debug + ?Sized>(
+ actual: &ContainerT,
+ expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N],
+ ) -> Self
+ where
+ for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+ {
+ let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]);
+ for (actual_idx, actual) in actual.into_iter().enumerate() {
+ for (expected_idx, expected) in expected.iter().enumerate() {
+ matrix.0[actual_idx][expected_idx] = expected.matches(actual);
+ }
+ }
+ matrix
+ }
+
+ fn generate_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>(
+ actual: &ContainerT,
+ expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N],
+ ) -> Self
+ where
+ for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
+ {
+ let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]);
+ for (actual_idx, (actual_key, actual_value)) in actual.into_iter().enumerate() {
+ for (expected_idx, (expected_key, expected_value)) in expected.iter().enumerate() {
+ matrix.0[actual_idx][expected_idx] = (expected_key.matches(actual_key).into()
+ && expected_value.matches(actual_value).into())
+ .into();
+ }
+ }
+ matrix
+ }
+
+ fn is_match_for(&self, requirements: Requirements) -> bool {
+ match requirements {
+ Requirements::PerfectMatch => {
+ !self.find_unmatchable_elements().has_unmatchable_elements()
+ && self.find_best_match().is_full_match()
+ }
+ Requirements::Superset => {
+ !self.find_unmatched_expected().has_unmatchable_elements()
+ && self.find_best_match().is_superset_match()
+ }
+ Requirements::Subset => {
+ !self.find_unmatched_actual().has_unmatchable_elements()
+ && self.find_best_match().is_subset_match()
+ }
+ }
+ }
+
+ fn explain_unmatchable(&self, requirements: Requirements) -> Option<String> {
+ let unmatchable_elements = match requirements {
+ Requirements::PerfectMatch => self.find_unmatchable_elements(),
+ Requirements::Superset => self.find_unmatched_expected(),
+ Requirements::Subset => self.find_unmatched_actual(),
+ };
+ unmatchable_elements.get_explanation()
+ }
+
+ // Verifies that each actual matches at least one expected and that
+ // each expected matches at least one actual.
+ // This is a necessary condition but not sufficient. But it is faster
+ // than `find_best_match()`.
+ fn find_unmatchable_elements(&self) -> UnmatchableElements<N> {
+ let unmatchable_actual =
+ self.0.iter().map(|row| row.iter().all(|&e| e.is_no_match())).collect();
+ let mut unmatchable_expected = [false; N];
+ for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
+ *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
+ }
+ UnmatchableElements { unmatchable_actual, unmatchable_expected }
+ }
+
+ fn find_unmatched_expected(&self) -> UnmatchableElements<N> {
+ let mut unmatchable_expected = [false; N];
+ for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
+ *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
+ }
+ UnmatchableElements { unmatchable_actual: vec![false; N], unmatchable_expected }
+ }
+
+ fn find_unmatched_actual(&self) -> UnmatchableElements<N> {
+ let unmatchable_actual =
+ self.0.iter().map(|row| row.iter().all(|e| e.is_no_match())).collect();
+ UnmatchableElements { unmatchable_actual, unmatchable_expected: [false; N] }
+ }
+
+ // Verifies that a full match exists.
+ //
+ // Uses the well-known Ford-Fulkerson max flow method to find a maximum
+ // bipartite matching. Flow is considered to be from actual to expected.
+ // There is an implicit source node that is connected to all of the actual
+ // nodes, and an implicit sink node that is connected to all of the
+ // expected nodes. All edges have unit capacity.
+ //
+ // Neither the flow graph nor the residual flow graph are represented
+ // explicitly. Instead, they are implied by the information in `self.0` and
+ // the local `actual_match : [Option<usize>; N]` whose elements are initialized
+ // to `None`. This represents the initial state of the algorithm,
+ // where the flow graph is empty, and the residual flow graph has the
+ // following edges:
+ // - An edge from source to each actual element node
+ // - An edge from each expected element node to sink
+ // - An edge from each actual element node to each expected element node, if
+ // the actual element matches the expected element, i.e.
+ // `matches!(self.0[actual_id][expected_id], Matches)`
+ //
+ // When the `try_augment(...)` method adds a flow, it sets `actual_match[l] =
+ // Some(r)` for some nodes l and r. This induces the following changes:
+ // - The edges (source, l), (l, r), and (r, sink) are added to the flow graph.
+ // - The same three edges are removed from the residual flow graph.
+ // - The reverse edges (l, source), (r, l), and (sink, r) are added to the
+ // residual flow graph, which is a directional graph representing unused
+ // flow capacity.
+ //
+ // When the method augments a flow (changing `actual_match[l]` from `Some(r1)`
+ // to `Some(r2)`), this can be thought of as "undoing" the above steps
+ // with respect to r1 and "redoing" them with respect to r2.
+ //
+ // It bears repeating that the flow graph and residual flow graph are
+ // never represented explicitly, but can be derived by looking at the
+ // information in 'self.0' and in `actual_match`.
+ //
+ // As an optimization, there is a second local `expected_match: [Option<usize>;
+ // N]` which does not provide any new information. Instead, it enables
+ // more efficient queries about edges entering or leaving the expected elements
+ // nodes of the flow or residual flow graphs. The following invariants
+ // are maintained:
+ //
+ // actual_match[a] == None or expected_match[actual_match[a].unwrap()] ==
+ // Some(a)
+ // expected_match[r] == None or actual_match[expected_match[e].unwrap()] ==
+ // Some(e)
+ //
+ // | [ source ] |
+ // | ||| |
+ // | ||| |
+ // | ||\-> actual_match[0]=Some(1) -\ expected_match[0]=None ---\ |
+ // | || | | |
+ // | |\--> actual_match[1]=None \-> expected_match[1]=Some(0) --\| |
+ // | | || |
+ // | \---> actual_match[2]=Some(2) --> expected_match[2]=Some(2) -\|| |
+ // | ||| |
+ // | elements matchers vvv |
+ // | [ sink ] |
+ //
+ // See Also:
+ // [1] Cormen, et al (2001). "Section 26.2: The Ford-Fulkerson method".
+ // "Introduction to Algorithms (Second ed.)", pp. 651-664.
+ // [2] "Ford-Fulkerson algorithm", Wikipedia,
+ // 'http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm'
+ fn find_best_match(&self) -> BestMatch<N> {
+ let mut actual_match = vec![None; self.0.len()];
+ let mut expected_match: [Option<usize>; N] = [None; N];
+ // Searches the residual flow graph for a path from each actual node to
+ // the sink in the residual flow graph, and if one is found, add this path
+ // to the graph.
+ // It's okay to search through the actual nodes once. The
+ // edge from the implicit source node to each previously-visited actual
+ // node will have flow if that actual node has any path to the sink
+ // whatsoever. Subsequent augmentations can only add flow to the
+ // network, and cannot take away that previous flow unit from the source.
+ // Since the source-to-actual edge can only carry one flow unit (or,
+ // each actual element can be matched to only one expected element), there is no
+ // need to visit the actual nodes more than once looking for
+ // augmented paths. The flow is known to be possible or impossible
+ // by looking at the node once.
+ for actual_idx in 0..self.0.len() {
+ assert!(actual_match[actual_idx].is_none());
+ let mut seen = [false; N];
+ self.try_augment(actual_idx, &mut seen, &mut actual_match, &mut expected_match);
+ }
+ BestMatch(actual_match)
+ }
+
+ // Perform a depth-first search from actual node `actual_idx` to the sink by
+ // searching for an unassigned expected node. If a path is found, flow
+ // is added to the network by linking the actual and expected vector elements
+ // corresponding each segment of the path. Returns true if a path to
+ // sink was found, which means that a unit of flow was added to the
+ // network. The 'seen' array elements correspond to expected nodes and are
+ // marked to eliminate cycles from the search.
+ //
+ // Actual nodes will only be explored at most once because they
+ // are accessible from at most one expected node in the residual flow
+ // graph.
+ //
+ // Note that `actual_match[actual_idx]` is the only element of `actual_match`
+ // that `try_augment(...)` will potentially transition from `None` to
+ // `Some(...)`. Any other `actual_match` element holding `None` before
+ // `try_augment(...)` will be holding it when `try_augment(...)`
+ // returns.
+ //
+ fn try_augment(
+ &self,
+ actual_idx: usize,
+ seen: &mut [bool; N],
+ actual_match: &mut [Option<usize>],
+ expected_match: &mut [Option<usize>; N],
+ ) -> bool {
+ for expected_idx in 0..N {
+ if seen[expected_idx] {
+ continue;
+ }
+ if self.0[actual_idx][expected_idx].is_no_match() {
+ continue;
+ }
+ // There is an edge between `actual_idx` and `expected_idx`.
+ seen[expected_idx] = true;
+ // Next a search is performed to determine whether
+ // this edge is a dead end or leads to the sink.
+ //
+ // `expected_match[expected_idx].is_none()` means that there is residual flow
+ // from expected node at index expected_idx to the sink, so we
+ // can use that to finish this flow path and return success.
+ //
+ // Otherwise, we look for a residual flow starting from
+ // `expected_match[expected_idx].unwrap()` by calling
+ // ourselves recursively to see if this ultimately leads to
+ // sink.
+ if expected_match[expected_idx].is_none()
+ || self.try_augment(
+ expected_match[expected_idx].unwrap(),
+ seen,
+ actual_match,
+ expected_match,
+ )
+ {
+ // We found a residual flow from source to sink. We thus need to add the new
+ // edge to the current flow.
+ // Note: this also remove the potential flow that existed by overwriting the
+ // value in the `expected_match` and `actual_match`.
+ expected_match[expected_idx] = Some(actual_idx);
+ actual_match[actual_idx] = Some(expected_idx);
+ return true;
+ }
+ }
+ false
+ }
+ }
+
+ /// The list of elements that do not match any element in the corresponding
+ /// set.
+ /// These lists are represented as fixed sized bit set to avoid
+ /// allocation.
+ /// TODO(bjacotg) Use BitArr!(for N) once generic_const_exprs is stable.
+ struct UnmatchableElements<const N: usize> {
+ unmatchable_actual: Vec<bool>,
+ unmatchable_expected: [bool; N],
+ }
+
+ impl<const N: usize> UnmatchableElements<N> {
+ fn has_unmatchable_elements(&self) -> bool {
+ self.unmatchable_actual.iter().any(|b| *b)
+ || self.unmatchable_expected.iter().any(|b| *b)
+ }
+
+ fn get_explanation(&self) -> Option<String> {
+ let unmatchable_actual = self.unmatchable_actual();
+ let actual_idx = unmatchable_actual
+ .iter()
+ .map(|idx| format!("#{}", idx))
+ .collect::<Vec<_>>()
+ .join(", ");
+ let unmatchable_expected = self.unmatchable_expected();
+ let expected_idx = unmatchable_expected
+ .iter()
+ .map(|idx| format!("#{}", idx))
+ .collect::<Vec<_>>()
+ .join(", ");
+ match (unmatchable_actual.len(), unmatchable_expected.len()) {
+ (0, 0) => None,
+ (1, 0) => {
+ Some(format!("whose element {actual_idx} does not match any expected elements"))
+ }
+ (_, 0) => {
+ Some(format!("whose elements {actual_idx} do not match any expected elements",))
+ }
+ (0, 1) => Some(format!(
+ "which has no element matching the expected element {expected_idx}"
+ )),
+ (0, _) => Some(format!(
+ "which has no elements matching the expected elements {expected_idx}"
+ )),
+ (1, 1) => Some(format!(
+ "whose element {actual_idx} does not match any expected elements and no elements match the expected element {expected_idx}"
+ )),
+ (_, 1) => Some(format!(
+ "whose elements {actual_idx} do not match any expected elements and no elements match the expected element {expected_idx}"
+ )),
+ (1, _) => Some(format!(
+ "whose element {actual_idx} does not match any expected elements and no elements match the expected elements {expected_idx}"
+ )),
+ (_, _) => Some(format!(
+ "whose elements {actual_idx} do not match any expected elements and no elements match the expected elements {expected_idx}"
+ )),
+ }
+ }
+
+ fn unmatchable_actual(&self) -> Vec<usize> {
+ self.unmatchable_actual
+ .iter()
+ .enumerate()
+ .filter_map(|(idx, b)| if *b { Some(idx) } else { None })
+ .collect()
+ }
+
+ fn unmatchable_expected(&self) -> Vec<usize> {
+ self.unmatchable_expected
+ .iter()
+ .enumerate()
+ .filter_map(|(idx, b)| if *b { Some(idx) } else { None })
+ .collect()
+ }
+ }
+
+ /// The representation of a match between actual and expected.
+ /// The value at idx represents to which expected the actual at idx is
+ /// matched with. For example, `BestMatch([Some(0), None, Some(1)])`
+ /// means:
+ /// * The 0th element in actual matches the 0th element in expected.
+ /// * The 1st element in actual does not match.
+ /// * The 2nd element in actual matches the 1st element in expected.
+ struct BestMatch<const N: usize>(Vec<Option<usize>>);
+
+ impl<const N: usize> BestMatch<N> {
+ fn is_full_match(&self) -> bool {
+ self.0.iter().all(|o| o.is_some())
+ }
+
+ fn is_subset_match(&self) -> bool {
+ self.is_full_match()
+ }
+
+ fn is_superset_match(&self) -> bool {
+ self.get_unmatched_expected().is_empty()
+ }
+
+ fn get_matches(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
+ self.0.iter().enumerate().filter_map(|(actual_idx, maybe_expected_idx)| {
+ maybe_expected_idx.map(|expected_idx| (actual_idx, expected_idx))
+ })
+ }
+
+ fn get_unmatched_actual(&self) -> impl Iterator<Item = usize> + '_ {
+ self.0
+ .iter()
+ .enumerate()
+ .filter(|&(_, o)| o.is_none())
+ .map(|(actual_idx, _)| actual_idx)
+ }
+
+ fn get_unmatched_expected(&self) -> Vec<usize> {
+ let matched_expected: HashSet<_> = self.0.iter().flatten().collect();
+ (0..N).filter(|expected_idx| !matched_expected.contains(expected_idx)).collect()
+ }
+
+ fn get_explanation<'a, T: Debug, ContainerT: Debug + ?Sized>(
+ &self,
+ actual: &ContainerT,
+ expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N],
+ requirements: Requirements,
+ ) -> Option<String>
+ where
+ for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+ {
+ let actual: Vec<_> = actual.into_iter().collect();
+ if self.is_full_match() {
+ return None;
+ }
+ let mut error_message =
+ format!("which does not have a {requirements} match with the expected elements.");
+
+ error_message.push_str("\n The best match found was: ");
+
+ let matches = self.get_matches().map(|(actual_idx, expected_idx)|{
+ format!(
+ "Actual element {:?} at index {actual_idx} matched expected element `{}` at index {expected_idx}.",
+ actual[actual_idx],
+ expected[expected_idx].describe(MatcherResult::Match),
+ )});
+
+ let unmatched_actual = self.get_unmatched_actual().map(|actual_idx| {
+ format!(
+ "Actual element {:#?} at index {actual_idx} did not match any remaining expected element.",
+ actual[actual_idx]
+ )
+ });
+
+ let unmatched_expected = self.get_unmatched_expected().into_iter().map(|expected_idx|{format!(
+ "Expected element `{}` at index {expected_idx} did not match any remaining actual element.",
+ expected[expected_idx].describe(MatcherResult::Match)
+ )});
+
+ let best_match = matches
+ .chain(unmatched_actual)
+ .chain(unmatched_expected)
+ .collect::<Description>()
+ .indent();
+ Some(format!(
+ "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
+ ))
+ }
+
+ fn get_explanation_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>(
+ &self,
+ actual: &ContainerT,
+ expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N],
+ requirements: Requirements,
+ ) -> Option<String>
+ where
+ for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
+ {
+ let actual: Vec<_> = actual.into_iter().collect();
+ if self.is_full_match() {
+ return None;
+ }
+ let mut error_message =
+ format!("which does not have a {requirements} match with the expected elements.");
+
+ error_message.push_str("\n The best match found was: ");
+
+ let matches = self.get_matches()
+ .map(|(actual_idx, expected_idx)| {
+ format!(
+ "Actual element {:?} => {:?} at index {actual_idx} matched expected element `{}` => `{}` at index {expected_idx}.",
+ actual[actual_idx].0,
+ actual[actual_idx].1,
+ expected[expected_idx].0.describe(MatcherResult::Match),
+ expected[expected_idx].1.describe(MatcherResult::Match),
+ )
+ });
+
+ let unmatched_actual = self.get_unmatched_actual()
+ .map(|actual_idx| {
+ format!(
+ "Actual element {:#?} => {:#?} at index {actual_idx} did not match any remaining expected element.",
+ actual[actual_idx].0,
+ actual[actual_idx].1,
+ )
+ });
+
+ let unmatched_expected = self.get_unmatched_expected()
+ .into_iter()
+ .map(|expected_idx| {
+ format!(
+ "Expected element `{}` => `{}` at index {expected_idx} did not match any remaining actual element.",
+ expected[expected_idx].0.describe(MatcherResult::Match),
+ expected[expected_idx].1.describe(MatcherResult::Match),
+ )
+ });
+
+ let best_match = matches
+ .chain(unmatched_actual)
+ .chain(unmatched_expected)
+ .collect::<Description>()
+ .indent();
+ Some(format!(
+ "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
+ ))
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::internal::UnorderedElementsOfMapAreMatcher;
+ use crate::matcher::{Matcher, MatcherResult};
+ use crate::prelude::*;
+ use indoc::indoc;
+ use std::collections::HashMap;
+
+ #[test]
+ fn has_correct_description_for_map() -> Result<()> {
+ // UnorderedElementsAreMatcher maintains references to the matchers, so the
+ // constituent matchers must live longer. Inside a verify_that! macro, the
+ // compiler takes care of that, but when the matcher is created separately,
+ // we must create the constitute matchers separately so that they
+ // aren't dropped too early.
+ let matchers = ((eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")));
+ let matcher: UnorderedElementsOfMapAreMatcher<HashMap<i32, &str>, _, _, 3> = unordered_elements_are![
+ (matchers.0.0, matchers.0.1),
+ (matchers.1.0, matchers.1.1),
+ (matchers.2.0, matchers.2.1)
+ ];
+ verify_that!(
+ Matcher::describe(&matcher, MatcherResult::Match),
+ 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\""
+ ))
+ )
+ }
+
+ #[test]
+ fn unordered_elements_are_description_no_full_match_with_map() -> Result<()> {
+ // UnorderedElementsAreMatcher maintains references to the matchers, so the
+ // constituent matchers must live longer. Inside a verify_that! macro, the
+ // compiler takes care of that, but when the matcher is created separately,
+ // we must create the constitute matchers separately so that they
+ // aren't dropped too early.
+ let matchers = ((anything(), eq(1)), (anything(), eq(2)), (anything(), eq(2)));
+ let matcher: UnorderedElementsOfMapAreMatcher<HashMap<u32, u32>, _, _, 3> = unordered_elements_are![
+ (matchers.0.0, matchers.0.1),
+ (matchers.1.0, matchers.1.1),
+ (matchers.2.0, matchers.2.1),
+ ];
+ let value: HashMap<u32, u32> = HashMap::from_iter([(0, 1), (1, 1), (2, 2)]);
+ verify_that!(
+ matcher.explain_match(&value),
+ displays_as(contains_regex(
+ "Actual element 2 => 2 at index [0-2] matched expected element `is anything` => `is equal to 2` at index [0-2]."
+ )).and(displays_as(contains_regex(
+ "Actual element [0-1] => [0-1] at index [0-2] did not match any remaining expected element."
+ ))).and(displays_as(contains_substring(
+ "Expected element `is anything` => `is equal to 2` at index 2 did not match any remaining actual element."
+ )))
+ )
+ }
+}
diff --git a/tests/all_matcher_test.rs b/tests/all_matcher_test.rs
new file mode 100644
index 0000000..6d7e3f8
--- /dev/null
+++ b/tests/all_matcher_test.rs
@@ -0,0 +1,93 @@
+// 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 googletest::matcher::Matcher;
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn matches_any_value_when_list_is_empty() -> Result<()> {
+ verify_that!((), all!())
+}
+
+#[test]
+fn matches_value_with_single_matching_component() -> Result<()> {
+ verify_that!(123, all!(eq(123)))
+}
+
+#[test]
+fn does_not_match_value_with_single_non_matching_component() -> Result<()> {
+ verify_that!(123, not(all!(eq(456))))
+}
+
+#[test]
+fn matches_value_with_two_matching_components() -> Result<()> {
+ verify_that!("A string", all!(starts_with("A"), ends_with("string")))
+}
+
+#[test]
+fn does_not_match_value_with_one_non_matching_component_among_two_components() -> Result<()> {
+ verify_that!(123, not(all!(eq(123), eq(456))))
+}
+
+#[test]
+fn supports_trailing_comma() -> Result<()> {
+ verify_that!(
+ "An important string",
+ all!(starts_with("An"), contains_substring("important"), ends_with("string"),)
+ )
+}
+
+#[test]
+fn admits_matchers_without_static_lifetime() -> Result<()> {
+ #[derive(Debug, PartialEq)]
+ struct AStruct(i32);
+ let expected_value = AStruct(123);
+ verify_that!(AStruct(123), all![eq_deref_of(&expected_value)])
+}
+
+#[test]
+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\""
+ ))
+ )
+}
+
+#[test]
+fn mismatch_description_empty_matcher() -> Result<()> {
+ verify_that!(all!().explain_match("Three"), displays_as(eq("which is anything")))
+}
+
+#[test]
+fn all_multiple_failed_assertions() -> Result<()> {
+ let result = verify_that!(4, all![eq(1), eq(2), eq(3)]);
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: 4
+ Expected: has all the following properties:
+ * is equal to 1
+ * is equal to 2
+ * is equal to 3
+ Actual: 4,
+ * which isn't equal to 1
+ * which isn't equal to 2
+ * which isn't equal to 3"
+ ))))
+ )
+}
diff --git a/tests/any_matcher_test.rs b/tests/any_matcher_test.rs
new file mode 100644
index 0000000..1bdd794
--- /dev/null
+++ b/tests/any_matcher_test.rs
@@ -0,0 +1,93 @@
+// 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 googletest::matcher::Matcher;
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn does_not_match_value_when_list_is_empty() -> Result<()> {
+ verify_that!((), not(any!()))
+}
+
+#[test]
+fn matches_value_with_single_matching_component() -> Result<()> {
+ verify_that!(123, any!(eq(123)))
+}
+
+#[test]
+fn does_not_match_value_with_single_non_matching_component() -> Result<()> {
+ verify_that!(123, not(any!(eq(456))))
+}
+
+#[test]
+fn matches_value_with_first_of_two_matching_components() -> Result<()> {
+ verify_that!("A string", any!(starts_with("A"), starts_with("string")))
+}
+
+#[test]
+fn matches_value_with_second_of_two_matching_components() -> Result<()> {
+ verify_that!("A string", any!(starts_with("string"), starts_with("A")))
+}
+
+#[test]
+fn supports_trailing_comma() -> Result<()> {
+ verify_that!(
+ "An important string",
+ any!(starts_with("An"), contains_substring("important"), ends_with("string"),)
+ )
+}
+
+#[test]
+fn admits_matchers_without_static_lifetime() -> Result<()> {
+ #[derive(Debug, PartialEq)]
+ struct AStruct(i32);
+ let expected_value = AStruct(123);
+ verify_that!(AStruct(123), any![eq_deref_of(&expected_value)])
+}
+
+#[test]
+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\""
+ ))
+ )
+}
+
+#[test]
+fn mismatch_description_empty_matcher() -> Result<()> {
+ verify_that!(any!().explain_match("Three"), displays_as(eq("which never matches")))
+}
+
+#[test]
+fn all_multiple_failed_assertions() -> Result<()> {
+ let result = verify_that!(4, any![eq(1), eq(2), eq(3)]);
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: 4
+ Expected: has at least one of the following properties:
+ * is equal to 1
+ * is equal to 2
+ * is equal to 3
+ Actual: 4,
+ * which isn't equal to 1
+ * which isn't equal to 2
+ * which isn't equal to 3"
+ ))))
+ )
+}
diff --git a/tests/colorized_diff_test.rs b/tests/colorized_diff_test.rs
new file mode 100644
index 0000000..d1b4ecb
--- /dev/null
+++ b/tests/colorized_diff_test.rs
@@ -0,0 +1,52 @@
+// 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 googletest::prelude::*;
+use indoc::indoc;
+use std::fmt::{Display, Write};
+
+// Make a long text with each element of the iterator on one line.
+// `collection` must contains at least one element.
+fn build_text<T: Display>(mut collection: impl Iterator<Item = T>) -> String {
+ let mut text = String::new();
+ write!(&mut text, "{}", collection.next().expect("Provided collection without elements"))
+ .unwrap();
+ for item in collection {
+ write!(&mut text, "\n{}", item).unwrap();
+ }
+ text
+}
+
+#[test]
+fn colors_appear_when_no_color_is_no_set_and_force_color_is_set() -> Result<()> {
+ std::env::remove_var("NO_COLOR");
+ std::env::set_var("FORCE_COLOR", "1");
+
+ let result = verify_that!(build_text(1..50), eq(build_text(1..51)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(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"
+ })))
+ )
+}
diff --git a/tests/composition_test.rs b/tests/composition_test.rs
new file mode 100644
index 0000000..7ae146f
--- /dev/null
+++ b/tests/composition_test.rs
@@ -0,0 +1,71 @@
+// 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 googletest::prelude::*;
+
+#[test]
+fn all_matcher_works_as_inner_matcher() -> Result<()> {
+ let value = vec![1];
+ verify_that!(value, contains_each![all!(gt(0), lt(2))])
+}
+
+#[test]
+fn matches_pattern_works_as_inner_matcher() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(i32);
+ verify_that!(vec![AStruct(123)], contains_each![matches_pattern!(AStruct(eq(123)))])
+}
+
+#[test]
+fn matches_pattern_works_with_property_as_inner_matcher() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(i32);
+ impl AStruct {
+ fn get_value(&self) -> i32 {
+ self.0
+ }
+ }
+ verify_that!(
+ vec![AStruct(123)],
+ contains_each![matches_pattern!(AStruct {
+ get_value(): eq(123)
+ })]
+ )
+}
+
+#[test]
+fn contains_each_works_as_inner_matcher() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(Vec<i32>);
+ verify_that!(AStruct(vec![123]), matches_pattern!(AStruct(contains_each![eq(123)])))
+}
+
+#[test]
+fn pointwise_works_as_inner_matcher() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(Vec<i32>);
+ verify_that!(AStruct(vec![123]), matches_pattern!(AStruct(pointwise!(eq, [123]))))
+}
+
+#[test]
+fn elements_are_works_as_inner_matcher() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(Vec<i32>);
+ verify_that!(AStruct(vec![123]), matches_pattern!(AStruct(elements_are![eq(123)])))
+}
+
+#[test]
+fn tuple_works_as_inner_matcher() -> Result<()> {
+ verify_that!(vec![(123,)], elements_are![(eq(123),)])
+}
diff --git a/tests/elements_are_matcher_test.rs b/tests/elements_are_matcher_test.rs
new file mode 100644
index 0000000..99e9fe7
--- /dev/null
+++ b/tests/elements_are_matcher_test.rs
@@ -0,0 +1,125 @@
+// 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 googletest::matcher::Matcher;
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn elements_are_matches_vector() -> Result<()> {
+ let value = vec![1, 2, 3];
+ verify_that!(value, elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn elements_are_matches_slice() -> Result<()> {
+ let value = vec![1, 2, 3];
+ let slice = value.as_slice();
+ verify_that!(*slice, elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn elements_are_matches_array() -> Result<()> {
+ verify_that!([1, 2, 3], elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn elements_are_supports_trailing_comma() -> Result<()> {
+ let value = vec![1, 2, 3];
+ verify_that!(value, elements_are![eq(1), eq(2), eq(3),])
+}
+
+#[test]
+fn elements_are_returns_no_match_when_expected_and_actual_sizes_differ() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, not(elements_are![eq(1), eq(2), eq(3)]))
+}
+
+#[test]
+fn elements_are_admits_matchers_without_static_lifetime() -> Result<()> {
+ #[derive(Debug, PartialEq)]
+ struct AStruct(i32);
+ let expected_value = AStruct(123);
+ verify_that!(vec![AStruct(123)], elements_are![eq_deref_of(&expected_value)])
+}
+
+#[test]
+fn elements_are_produces_correct_failure_message() -> Result<()> {
+ let result = verify_that!(vec![1, 4, 3], elements_are![eq(1), eq(2), eq(3)]);
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 4, 3]
+ Expected: has elements:
+ 0. is equal to 1
+ 1. is equal to 2
+ 2. is equal to 3
+ Actual: [1, 4, 3],
+ where element #1 is 4, which isn't equal to 2"
+ ))))
+ )
+}
+
+#[test]
+fn elements_are_produces_correct_failure_message_nested() -> Result<()> {
+ let result = verify_that!(
+ vec![vec![0, 1], vec![1, 2]],
+ elements_are![elements_are![eq(1), eq(2)], elements_are![eq(2), eq(3)]]
+ );
+ verify_that!(
+ 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
+ 1. is equal to 2
+ 1. has elements:
+ 0. is equal to 2
+ 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"
+ ))))
+ )
+}
+
+#[test]
+fn elements_are_explain_match_wrong_size() -> Result<()> {
+ verify_that!(
+ elements_are![eq(1)].explain_match(&vec![1, 2]),
+ displays_as(eq("whose size is 2"))
+ )
+}
+
+fn create_matcher() -> impl Matcher<ActualT = Vec<i32>> {
+ elements_are![eq(1)]
+}
+
+#[test]
+fn elements_are_works_when_matcher_is_created_in_subroutine() -> Result<()> {
+ verify_that!(vec![1], create_matcher())
+}
+
+#[test]
+fn elements_are_implicitly_called() -> Result<()> {
+ verify_that!(vec![1, 2, 3], [eq(1), eq(2), eq(3)])
+}
diff --git a/tests/field_matcher_test.rs b/tests/field_matcher_test.rs
new file mode 100644
index 0000000..f5d85c5
--- /dev/null
+++ b/tests/field_matcher_test.rs
@@ -0,0 +1,178 @@
+// 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 googletest::matcher::{Matcher, MatcherResult};
+use googletest::prelude::*;
+
+#[derive(Debug)]
+struct IntField {
+ int: i32,
+}
+
+#[test]
+fn field_matches_integer_field() -> Result<()> {
+ verify_that!(IntField { int: 32 }, field!(IntField.int, eq(32)))
+}
+
+#[derive(Debug)]
+struct StringField {
+ strink: String,
+}
+
+#[test]
+fn field_matches_string_field() -> Result<()> {
+ verify_that!(StringField { strink: "yes".to_string() }, field!(StringField.strink, eq("yes")))
+}
+
+#[test]
+fn field_error_message_shows_field_name_and_inner_matcher() -> Result<()> {
+ let matcher = field!(IntField.int, eq(31));
+
+ verify_that!(
+ matcher.describe(MatcherResult::Match),
+ eq("has field `int`, which is equal to 31")
+ )
+}
+
+mod sub {
+ #[derive(Debug)]
+ pub struct SubStruct {
+ pub field: i32,
+ }
+}
+
+#[test]
+fn struct_in_other_module_matches() -> Result<()> {
+ verify_that!(sub::SubStruct { field: 32 }, field!(sub::SubStruct.field, eq(32)))
+}
+
+#[derive(Debug)]
+struct Tuple(i32, String);
+
+#[test]
+fn tuple_matches_with_index() -> Result<()> {
+ verify_that!(Tuple(32, "yes".to_string()), field!(Tuple.0, eq(32)))
+}
+
+#[test]
+fn matches_enum_value() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ AValue(u32),
+ }
+ let value = AnEnum::AValue(123);
+
+ verify_that!(value, field!(AnEnum::AValue.0, eq(123)))
+}
+
+#[test]
+fn shows_correct_failure_message_for_wrong_struct_entry() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a: Vec<u32>,
+ }
+ let value = AStruct { a: vec![1] };
+
+ let result = verify_that!(value, field!(AStruct.a, container_eq([])));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "which has field `a`, which contains the unexpected element 1"
+ )))
+ )
+}
+
+#[test]
+fn does_not_match_enum_value_with_wrong_enum_variant() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ #[allow(dead_code)] // This variant is intentionally unused.
+ AValue(u32),
+ AnotherValue,
+ }
+ let value = AnEnum::AnotherValue;
+
+ verify_that!(value, not(field!(AnEnum::AValue.0, eq(123))))
+}
+
+#[test]
+fn shows_correct_failure_message_for_wrong_enum_value() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ #[allow(dead_code)] // This variant is intentionally unused.
+ AValue {
+ a: u32,
+ },
+ AnotherValue,
+ }
+ let value = AnEnum::AnotherValue;
+
+ let result = verify_that!(value, field!(AnEnum::AValue.a, eq(123)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring("which has the wrong enum variant `AnotherValue`")))
+ )
+}
+
+#[test]
+fn shows_correct_failure_message_for_wrong_enum_value_with_tuple_field() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ #[allow(dead_code)] // This variant is intentionally unused.
+ AValue(u32),
+ AnotherValue(u32),
+ }
+ let value = AnEnum::AnotherValue(123);
+
+ let result = verify_that!(value, field!(AnEnum::AValue.0, eq(123)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring("which has the wrong enum variant `AnotherValue`")))
+ )
+}
+
+#[test]
+fn shows_correct_failure_message_for_wrong_enum_value_with_named_field() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ #[allow(dead_code)] // This variant is intentionally unused.
+ AValue(u32),
+ AnotherValue {
+ #[allow(unused)]
+ a: u32,
+ },
+ }
+ let value = AnEnum::AnotherValue { a: 123 };
+
+ let result = verify_that!(value, field!(AnEnum::AValue.0, eq(123)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring("which has the wrong enum variant `AnotherValue`")))
+ )
+}
+
+#[test]
+fn matches_struct_like_enum_value() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ AValue { a_field: u32 },
+ }
+ let value = AnEnum::AValue { a_field: 123 };
+
+ verify_that!(value, field!(AnEnum::AValue.a_field, eq(123)))
+}
diff --git a/tests/lib.rs b/tests/lib.rs
new file mode 100644
index 0000000..fb243ca
--- /dev/null
+++ b/tests/lib.rs
@@ -0,0 +1,27 @@
+// 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.
+
+mod all_matcher_test;
+mod any_matcher_test;
+mod colorized_diff_test;
+mod composition_test;
+mod elements_are_matcher_test;
+mod field_matcher_test;
+mod matches_pattern_test;
+mod pointwise_matcher_test;
+mod property_matcher_test;
+#[cfg(feature = "proptest")]
+mod proptest_integration_test;
+mod tuple_matcher_test;
+mod unordered_elements_are_matcher_test;
diff --git a/tests/matches_pattern_test.rs b/tests/matches_pattern_test.rs
new file mode 100644
index 0000000..4904cf5
--- /dev/null
+++ b/tests/matches_pattern_test.rs
@@ -0,0 +1,1602 @@
+// 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 googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn matches_struct_containing_single_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+ let actual = AStruct { a_field: 123 };
+
+ verify_that!(actual, matches_pattern!(AStruct { a_field: eq(123) }))
+}
+
+#[test]
+fn matches_struct_containing_two_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(actual, matches_pattern!(AStruct { a_field: eq(123), another_field: eq(234) }))
+}
+
+#[test]
+#[rustfmt::skip]// Otherwise fmt strips the trailing comma
+fn supports_trailing_comma_with_one_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+ let actual = AStruct { a_field: 123 };
+
+ verify_that!(actual, matches_pattern!(AStruct {
+ a_field: eq(123), // Block reformatting
+ }))
+}
+
+#[test]
+fn supports_trailing_comma_with_two_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ a_field: eq(123),
+ another_field: eq(234), // Block reformatting
+ })
+ )
+}
+
+#[test]
+fn supports_trailing_comma_with_three_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+ let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ a_field: eq(123),
+ another_field: eq(234),
+ a_third_field: eq(345),
+ })
+ )
+}
+
+#[test]
+fn matches_struct_containing_nested_struct_with_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_nested_struct: ANestedStruct,
+ }
+ #[derive(Debug)]
+ struct ANestedStruct {
+ a_field: u32,
+ }
+ let actual = AStruct { a_nested_struct: ANestedStruct { a_field: 123 } };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { a_nested_struct: pat!(ANestedStruct { a_field: eq(123) }) })
+ )
+}
+
+#[test]
+fn has_correct_assertion_failure_message_for_single_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+ let actual = AStruct { a_field: 123 };
+ let result = verify_that!(actual, matches_pattern!(AStruct { a_field: eq(234) }));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {"
+ Value of: actual
+ Expected: is AStruct which has field `a_field`, which is equal to 234
+ Actual: AStruct { a_field: 123 },
+ which has field `a_field`, which isn't equal to 234
+ "
+ })))
+ )
+}
+
+#[test]
+fn has_correct_assertion_failure_message_for_two_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+ let actual = AStruct { a_field: 123, another_field: 234 };
+ let result = verify_that!(
+ actual,
+ matches_pattern!(AStruct { a_field: eq(234), another_field: eq(123) })
+ );
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: actual
+ Expected: is AStruct which has all the following properties:
+ * has field `a_field`, which is equal to 234
+ * has field `another_field`, which is equal to 123
+ Actual: AStruct { a_field: 123, another_field: 234 },
+ * which has field `a_field`, which isn't equal to 234
+ * which has field `another_field`, which isn't equal to 123"
+ ))))
+ )
+}
+
+#[test]
+fn has_correct_assertion_failure_message_for_field_and_property() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.a_field
+ }
+ }
+ let actual = AStruct { a_field: 123, another_field: 234 };
+ let result = verify_that!(
+ actual,
+ matches_pattern!(AStruct { get_field(): eq(234), another_field: eq(123) })
+ );
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: actual
+ Expected: is AStruct which has all the following properties:
+ * has property `get_field ()`, which is equal to 234
+ * has field `another_field`, which is equal to 123
+ Actual: AStruct { a_field: 123, another_field: 234 },
+ * whose property `get_field ()` is `123`, which isn't equal to 234
+ * which has field `another_field`, which isn't equal to 123"
+ ))))
+ )
+}
+
+#[test]
+fn has_meaningful_assertion_failure_message_when_wrong_enum_variant_is_used() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A(u32),
+ #[allow(unused)]
+ B(u32),
+ }
+ let actual = AnEnum::A(123);
+ let result = verify_that!(actual, matches_pattern!(AnEnum::B(eq(123))));
+
+ 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`
+ "
+ })))
+ )
+}
+
+#[test]
+fn supports_qualified_struct_names() -> Result<()> {
+ mod a_module {
+ #[derive(Debug)]
+ pub(super) struct AStruct {
+ pub(super) a_field: u32,
+ }
+ }
+ let actual = a_module::AStruct { a_field: 123 };
+
+ verify_that!(actual, matches_pattern!(a_module::AStruct { a_field: eq(123) }))
+}
+
+#[test]
+fn matches_tuple_struct_containing_single_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32);
+ let actual = AStruct(123);
+
+ verify_that!(actual, matches_pattern!(AStruct(eq(123))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_two_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32);
+ let actual = AStruct(123, 234);
+
+ verify_that!(actual, matches_pattern!(AStruct(eq(123), eq(234))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_three_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32, u32);
+ let actual = AStruct(123, 234, 345);
+
+ verify_that!(actual, matches_pattern!(AStruct(eq(123), eq(234), eq(345))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_four_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32, u32, u32);
+ let actual = AStruct(123, 234, 345, 456);
+
+ verify_that!(actual, matches_pattern!(AStruct(eq(123), eq(234), eq(345), eq(456))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_five_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32, u32, u32, u32);
+ let actual = AStruct(123, 234, 345, 456, 567);
+
+ verify_that!(actual, matches_pattern!(AStruct(eq(123), eq(234), eq(345), eq(456), eq(567))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_six_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32, u32, u32, u32, u32);
+ let actual = AStruct(123, 234, 345, 456, 567, 678);
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct(eq(123), eq(234), eq(345), eq(456), eq(567), eq(678)))
+ )
+}
+
+#[test]
+fn matches_tuple_struct_containing_seven_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32, u32, u32, u32, u32, u32);
+ let actual = AStruct(123, 234, 345, 456, 567, 678, 789);
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct(eq(123), eq(234), eq(345), eq(456), eq(567), eq(678), eq(789)))
+ )
+}
+
+#[test]
+fn matches_tuple_struct_containing_eight_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32, u32, u32, u32, u32, u32, u32);
+ let actual = AStruct(123, 234, 345, 456, 567, 678, 789, 890);
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct(
+ eq(123),
+ eq(234),
+ eq(345),
+ eq(456),
+ eq(567),
+ eq(678),
+ eq(789),
+ eq(890)
+ ))
+ )
+}
+
+#[test]
+fn matches_tuple_struct_containing_nine_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32, u32, u32, u32, u32, u32, u32, u32);
+ let actual = AStruct(123, 234, 345, 456, 567, 678, 789, 890, 901);
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct(
+ eq(123),
+ eq(234),
+ eq(345),
+ eq(456),
+ eq(567),
+ eq(678),
+ eq(789),
+ eq(890),
+ eq(901)
+ ))
+ )
+}
+
+#[test]
+fn matches_tuple_struct_containing_ten_fields() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32, u32, u32, u32, u32, u32, u32, u32, u32);
+ let actual = AStruct(123, 234, 345, 456, 567, 678, 789, 890, 901, 12);
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct(
+ eq(123),
+ eq(234),
+ eq(345),
+ eq(456),
+ eq(567),
+ eq(678),
+ eq(789),
+ eq(890),
+ eq(901),
+ eq(12)
+ ))
+ )
+}
+
+#[test]
+fn matches_tuple_struct_with_trailing_comma() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32);
+ let actual = AStruct(123);
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct(
+ eq(123), // Keep the trailing comma, block reformatting
+ ))
+ )
+}
+
+#[test]
+fn matches_tuple_struct_with_two_fields_and_trailing_comma() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct(u32, u32);
+ let actual = AStruct(123, 234);
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct(
+ eq(123),
+ eq(234), // Keep the trailing comma, block reformatting
+ ))
+ )
+}
+
+#[test]
+fn matches_enum_without_field() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A,
+ }
+ let actual = AnEnum::A;
+
+ verify_that!(actual, matches_pattern!(AnEnum::A))
+}
+
+#[test]
+fn generates_correct_failure_output_when_enum_variant_without_field_is_not_matched() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ #[allow(unused)]
+ A,
+ B,
+ }
+ let actual = AnEnum::B;
+
+ let result = verify_that!(actual, matches_pattern!(AnEnum::A));
+
+ verify_that!(result, err(displays_as(contains_substring("is not AnEnum :: A"))))
+}
+
+#[test]
+fn generates_correct_failure_output_when_enum_variant_without_field_is_matched() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A,
+ }
+ let actual = AnEnum::A;
+
+ let result = verify_that!(actual, not(matches_pattern!(AnEnum::A)));
+
+ verify_that!(result, err(displays_as(contains_substring("is AnEnum :: A"))))
+}
+
+#[test]
+fn matches_enum_with_field() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A(u32),
+ }
+ let actual = AnEnum::A(123);
+
+ verify_that!(actual, matches_pattern!(AnEnum::A(eq(123))))
+}
+
+#[test]
+fn does_not_match_wrong_enum_value() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ #[allow(unused)]
+ A(u32),
+ B,
+ }
+ let actual = AnEnum::B;
+
+ verify_that!(actual, not(matches_pattern!(AnEnum::A(eq(123)))))
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_field() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A(u32),
+ }
+ let actual = AnEnum::A(123);
+
+ let result = verify_that!(actual, matches_pattern!(AnEnum::A(eq(234))));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring("Expected: is AnEnum :: A which has field `0`")))
+ )
+}
+
+#[test]
+fn includes_enum_variant_in_negative_description_with_field() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A(u32),
+ }
+ let actual = AnEnum::A(123);
+
+ let result = verify_that!(actual, not(matches_pattern!(AnEnum::A(eq(123)))));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "Expected: is not AnEnum :: A which has field `0`, which is equal to"
+ )))
+ )
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_two_fields() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A(u32, u32),
+ }
+ let actual = AnEnum::A(123, 234);
+
+ let result = verify_that!(actual, matches_pattern!(AnEnum::A(eq(234), eq(234))));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "Expected: is AnEnum :: A which has all the following properties"
+ )))
+ )
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_three_fields() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A(u32, u32, u32),
+ }
+ let actual = AnEnum::A(123, 234, 345);
+
+ let result = verify_that!(actual, matches_pattern!(AnEnum::A(eq(234), eq(234), eq(345))));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "Expected: is AnEnum :: A which has all the following properties"
+ )))
+ )
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_named_field() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A { field: u32 },
+ }
+ let actual = AnEnum::A { field: 123 };
+
+ let result = verify_that!(actual, matches_pattern!(AnEnum::A { field: eq(234) }));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring("Expected: is AnEnum :: A which has field `field`")))
+ )
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_two_named_fields() -> Result<()> {
+ #[derive(Debug)]
+ enum AnEnum {
+ A { field: u32, another_field: u32 },
+ }
+ let actual = AnEnum::A { field: 123, another_field: 234 };
+
+ let result = verify_that!(
+ actual,
+ matches_pattern!(AnEnum::A { field: eq(234), another_field: eq(234) })
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "Expected: is AnEnum :: A which has all the following properties"
+ )))
+ )
+}
+
+#[test]
+fn includes_struct_name_in_description_with_property() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ field: u32,
+ }
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.field
+ }
+ }
+ let actual = AStruct { field: 123 };
+
+ let result = verify_that!(actual, matches_pattern!(AStruct { get_field(): eq(234) }));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "Expected: is AStruct which has property `get_field ()`"
+ )))
+ )
+}
+
+#[test]
+fn includes_struct_name_in_description_with_ref_property() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ field: u32,
+ }
+ impl AStruct {
+ fn get_field(&self) -> &u32 {
+ &self.field
+ }
+ }
+ let actual = AStruct { field: 123 };
+
+ let result = verify_that!(actual, matches_pattern!(AStruct { ref get_field(): eq(234) }));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "Expected: is AStruct which has property `get_field ()`"
+ )))
+ )
+}
+
+#[test]
+fn includes_struct_name_in_description_with_property_after_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ field: u32,
+ }
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.field
+ }
+ }
+ let actual = AStruct { field: 123 };
+
+ let result =
+ verify_that!(actual, matches_pattern!(AStruct { field: eq(123), get_field(): eq(234) }));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "Expected: is AStruct which has all the following properties"
+ )))
+ )
+}
+
+#[test]
+fn includes_struct_name_in_description_with_ref_property_after_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ field: u32,
+ }
+ impl AStruct {
+ fn get_field(&self) -> &u32 {
+ &self.field
+ }
+ }
+ let actual = AStruct { field: 123 };
+
+ let result = verify_that!(
+ actual,
+ matches_pattern!(AStruct { field: eq(123), ref get_field(): eq(234) })
+ );
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "Expected: is AStruct which has all the following properties"
+ )))
+ )
+}
+
+#[test]
+fn matches_struct_with_a_method() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123 };
+
+ verify_that!(actual, matches_pattern!(AStruct { get_field(): eq(123) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_and_trailing_comma() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123 };
+
+ verify_that!(actual, matches_pattern!(AStruct { get_field(): eq(123), }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_parameter() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn add_to_field(&self, a: u32) -> u32 {
+ self.a_field + a
+ }
+ }
+
+ let actual = AStruct { a_field: 1 };
+
+ verify_that!(actual, matches_pattern!(AStruct { add_to_field(2): eq(3) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+ self.a_field + a * b
+ }
+ }
+
+ let actual = AStruct { a_field: 1 };
+
+ verify_that!(actual, matches_pattern!(AStruct { add_product_to_field(2, 3): eq(7) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_enum_value_parameter() -> Result<()> {
+ enum AnEnum {
+ AVariant,
+ }
+
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn get_a_field(&self, _value: AnEnum) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1 };
+
+ verify_that!(actual, matches_pattern!(AStruct { get_a_field(AnEnum::AVariant): eq(1) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_with_trailing_comma() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+ self.a_field + a * b
+ }
+ }
+
+ let actual = AStruct { a_field: 1 };
+
+ verify_that!(actual, matches_pattern!(AStruct { add_product_to_field(2, 3,): eq(7) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_a_reference() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123 };
+
+ verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(): eq(123) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_a_reference_with_trailing_comma() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123 };
+
+ verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(): eq(123), }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_ret_ref() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1 };
+
+ verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(2, 3): eq(1) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_reference_taking_enum_value_parameter() -> Result<()> {
+ enum AnEnum {
+ AVariant,
+ }
+
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _value: AnEnum) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1 };
+
+ verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(AnEnum::AVariant): eq(1) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_with_trailing_comma_ret_ref() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1 };
+
+ verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(2, 3,): eq(1) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_followed_by_a_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(actual, matches_pattern!(AStruct { get_field(): eq(123), another_field: eq(234) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_followed_by_a_field_with_trailing_comma() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { get_field(): eq(123), another_field: eq(234), })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_and_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+ self.a_field + a * b
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 123 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { add_product_to_field(2, 3): eq(7), another_field: eq(123) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_enum_value_parameter_followed_by_field() -> Result<()> {
+ enum AnEnum {
+ AVariant,
+ }
+
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self, _value: AnEnum) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 2 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { get_field(AnEnum::AVariant): eq(1), another_field: eq(2) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_with_trailing_comma_and_field() -> Result<()>
+{
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+ self.a_field + a * b
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 123 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { add_product_to_field(2, 3,): eq(7), another_field: eq(123) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_reference_followed_by_a_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { ref get_field_ref(): eq(123), another_field: eq(234) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_reference_followed_by_a_field_with_trailing_comma()
+-> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { ref get_field_ref(): eq(123), another_field: eq(234), })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_ret_ref_and_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 123 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { ref get_field_ref(2, 3): eq(1), another_field: eq(123) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_enum_value_param_ret_ref_followed_by_field() -> Result<()> {
+ enum AnEnum {
+ AVariant,
+ }
+
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _value: AnEnum) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 2 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { ref get_field_ref(AnEnum::AVariant): eq(1), another_field: eq(2) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_with_trailing_comma_ret_ref_and_field()
+-> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 123 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { ref get_field_ref(2, 3,): eq(1), another_field: eq(123) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(actual, matches_pattern!(AStruct { another_field: eq(234), get_field(): eq(123) }))
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_trailing_comma() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { another_field: eq(234), get_field(): eq(123), })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+ self.a_field + a * b
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { another_field: eq(234), add_product_to_field(2, 3): eq(7) })
+ )
+}
+
+#[test]
+fn matches_struct_with_field_followed_by_method_taking_enum_value_param() -> Result<()> {
+ enum AnEnum {
+ AVariant,
+ }
+
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self, _value: AnEnum) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 2 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { another_field: eq(2), get_field(AnEnum::AVariant): eq(1) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_and_trailing_comma() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+ self.a_field + a * b
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { another_field: eq(234), add_product_to_field(2, 3,): eq(7) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_returning_reference() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(): eq(123) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_returning_ref_and_trailing_comma() -> Result<()>
+{
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(): eq(123), })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_ret_ref() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(2, 3): eq(123) })
+ )
+}
+
+#[test]
+fn matches_struct_with_field_followed_by_method_taking_enum_value_param_ret_ref() -> Result<()> {
+ enum AnEnum {
+ AVariant,
+ }
+
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _value: AnEnum) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 2 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { another_field: eq(2), ref get_field_ref(AnEnum::AVariant): eq(1) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_and_trailing_comma_ret_ref()
+-> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(2, 3,): eq(123) })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_followed_by_a_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(234),
+ get_field(): eq(123),
+ a_third_field: eq(345)
+ })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_followed_by_a_field_with_trailing_comma()
+-> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(234),
+ get_field(): eq(123),
+ a_third_field: eq(345),
+ })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_followed_by_a_field() -> Result<()>
+{
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+ self.a_field + a * b
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 234, a_third_field: 345 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(234),
+ add_product_to_field(2, 3): eq(7),
+ a_third_field: eq(345),
+ })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_and_trailing_comma_followed_by_a_field()
+-> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+ self.a_field + a * b
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 234, a_third_field: 345 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(234),
+ add_product_to_field(2, 3,): eq(7),
+ a_third_field: eq(345),
+ })
+ )
+}
+
+#[test]
+fn matches_struct_with_field_followed_by_method_taking_enum_value_param_followed_by_field()
+-> Result<()> {
+ enum AnEnum {
+ AVariant,
+ }
+
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field(&self, _value: AnEnum) -> u32 {
+ self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 2, a_third_field: 3 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(2),
+ get_field(AnEnum::AVariant): eq(1),
+ a_third_field: eq(3),
+ })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_ret_ref_followed_by_a_field() -> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(234),
+ ref get_field_ref(): eq(123),
+ a_third_field: eq(345)
+ })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_ret_ref_followed_by_a_field_with_trailing_comma()
+-> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(234),
+ ref get_field_ref(): eq(123),
+ a_third_field: eq(345),
+ })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_ret_ref_followed_by_a_field()
+-> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(234),
+ ref get_field_ref(2, 3): eq(123),
+ a_third_field: eq(345),
+ })
+ )
+}
+
+#[test]
+fn matches_struct_with_field_followed_by_method_taking_enum_value_param_ret_ref_followed_by_field()
+-> Result<()> {
+ enum AnEnum {
+ AVariant,
+ }
+
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _value: AnEnum) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 1, another_field: 2, a_third_field: 3 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(2),
+ ref get_field_ref(AnEnum::AVariant): eq(1),
+ a_third_field: eq(3),
+ })
+ )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_trailing_comma_ret_ref_followed_by_a_field()
+-> Result<()> {
+ #[derive(Debug)]
+ struct AStruct {
+ a_field: u32,
+ another_field: u32,
+ a_third_field: u32,
+ }
+
+ impl AStruct {
+ fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+ &self.a_field
+ }
+ }
+
+ let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+ verify_that!(
+ actual,
+ matches_pattern!(AStruct {
+ another_field: eq(234),
+ ref get_field_ref(2, 3,): eq(123),
+ a_third_field: eq(345),
+ })
+ )
+}
diff --git a/tests/no_color_test.rs b/tests/no_color_test.rs
new file mode 100644
index 0000000..f307890
--- /dev/null
+++ b/tests/no_color_test.rs
@@ -0,0 +1,54 @@
+// 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.
+
+#![cfg(feature = "supports-color")]
+
+use googletest::prelude::*;
+use indoc::indoc;
+use std::fmt::{Display, Write};
+
+// Make a long text with each element of the iterator on one line.
+// `collection` must contains at least one element.
+fn build_text<T: Display>(mut collection: impl Iterator<Item = T>) -> String {
+ let mut text = String::new();
+ write!(&mut text, "{}", collection.next().expect("Provided collection without elements"))
+ .unwrap();
+ for item in collection {
+ write!(&mut text, "\n{}", item).unwrap();
+ }
+ text
+}
+
+#[test]
+fn colors_suppressed_when_both_no_color_and_force_color_are_set() -> Result<()> {
+ std::env::set_var("NO_COLOR", "1");
+ std::env::set_var("FORCE_COLOR", "1");
+
+ let result = verify_that!(build_text(1..50), eq(build_text(1..51)));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc! {
+ "
+
+ Difference(-actual / +expected):
+ 1
+ 2
+ <---- 45 common lines omitted ---->
+ 48
+ 49
+ +50"
+ })))
+ )
+}
diff --git a/tests/pointwise_matcher_test.rs b/tests/pointwise_matcher_test.rs
new file mode 100644
index 0000000..85d16ff
--- /dev/null
+++ b/tests/pointwise_matcher_test.rs
@@ -0,0 +1,170 @@
+// 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 googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn pointwise_matches_single_element() -> Result<()> {
+ let value = vec![1];
+ verify_that!(value, pointwise!(lt, vec![2]))
+}
+
+#[test]
+fn pointwise_matches_two_elements() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, pointwise!(lt, vec![2, 3]))
+}
+
+#[test]
+fn pointwise_matches_two_elements_with_array() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, pointwise!(lt, [2, 3]))
+}
+
+#[test]
+fn pointwise_matches_two_element_slice() -> Result<()> {
+ let value = vec![1, 2];
+ let slice = value.as_slice();
+ verify_that!(*slice, pointwise!(lt, [2, 3]))
+}
+
+#[test]
+fn pointwise_does_not_match_value_of_wrong_length() -> Result<()> {
+ let value = vec![1];
+ verify_that!(value, not(pointwise!(lt, vec![2, 3])))
+}
+
+#[test]
+fn pointwise_does_not_match_value_not_matching_in_first_position() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, not(pointwise!(lt, vec![1, 3])))
+}
+
+#[test]
+fn pointwise_does_not_match_value_not_matching_in_second_position() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, not(pointwise!(lt, vec![2, 2])))
+}
+
+#[test]
+fn pointwise_allows_qualified_matcher_name() -> Result<()> {
+ mod submodule {
+ pub(super) use super::lt;
+ }
+ let value = vec![1];
+ verify_that!(value, pointwise!(submodule::lt, vec![2]))
+}
+
+#[test]
+fn pointwise_returns_mismatch_when_actual_value_has_wrong_length() -> Result<()> {
+ let result = verify_that!(vec![1, 2, 3], pointwise!(eq, vec![1, 2]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 2, 3]
+ Expected: has elements satisfying respectively:
+ 0. is equal to 1
+ 1. is equal to 2
+ Actual: [1, 2, 3],
+ which has size 3 (expected 2)
+ "
+ ))))
+ )
+}
+
+#[test]
+fn pointwise_returns_mismatch_when_actual_value_does_not_match_on_first_item() -> Result<()> {
+ let result = verify_that!(vec![1, 2, 3], pointwise!(eq, vec![2, 2, 3]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 2, 3]
+ Expected: has elements satisfying respectively:
+ 0. is equal to 2
+ 1. is equal to 2
+ 2. is equal to 3
+ Actual: [1, 2, 3],
+ where element #0 is 1, which isn't equal to 2
+ "
+ ))))
+ )
+}
+
+#[test]
+fn pointwise_returns_mismatch_when_actual_value_does_not_match_on_second_item() -> Result<()> {
+ let result = verify_that!(vec![1, 2, 3], pointwise!(eq, vec![1, 3, 3]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 2, 3]
+ Expected: has elements satisfying respectively:
+ 0. is equal to 1
+ 1. is equal to 3
+ 2. is equal to 3
+ Actual: [1, 2, 3],
+ where element #1 is 2, which isn't equal to 3
+ "
+ ))))
+ )
+}
+
+#[test]
+fn pointwise_returns_mismatch_when_actual_value_does_not_match_on_first_and_second_items()
+-> Result<()> {
+ let result = verify_that!(vec![1, 2, 3], pointwise!(eq, vec![2, 3, 3]));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 2, 3]
+ Expected: has elements satisfying respectively:
+ 0. is equal to 2
+ 1. is equal to 3
+ 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"
+ ))))
+ )
+}
+
+#[test]
+fn pointwise_matches_single_element_with_lambda_expression_with_extra_value() -> Result<()> {
+ let value = vec![1.00001f32];
+ verify_that!(value, pointwise!(|v| near(v, 0.0001), vec![1.0]))
+}
+
+#[test]
+fn pointwise_matches_single_element_with_two_containers() -> Result<()> {
+ let value = vec![1.00001f32];
+ verify_that!(value, pointwise!(near, vec![1.0], vec![0.0001]))
+}
+
+#[test]
+fn pointwise_matches_single_element_with_three_containers() -> Result<()> {
+ let value = vec![1.00001f32];
+ verify_that!(
+ value,
+ pointwise!(|v, t, u| near(v, t * u), vec![1.0f32], vec![0.0001f32], vec![0.5f32])
+ )
+}
diff --git a/tests/property_matcher_test.rs b/tests/property_matcher_test.rs
new file mode 100644
index 0000000..f9a88be
--- /dev/null
+++ b/tests/property_matcher_test.rs
@@ -0,0 +1,189 @@
+// 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 googletest::matcher::{Matcher, MatcherResult};
+use googletest::prelude::*;
+
+#[derive(Debug)]
+struct SomeStruct {
+ a_property: u32,
+}
+
+impl SomeStruct {
+ fn get_property(&self) -> u32 {
+ self.a_property
+ }
+
+ fn get_property_ref(&self) -> &u32 {
+ &self.a_property
+ }
+
+ fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+ self.a_property + a * b
+ }
+
+ fn get_property_ref_with_params(&self, _a: u32, _b: u32) -> &u32 {
+ &self.a_property
+ }
+}
+
+#[test]
+fn matches_struct_with_matching_property() -> Result<()> {
+ let value = SomeStruct { a_property: 10 };
+ verify_that!(value, property!(SomeStruct.get_property(), eq(10)))
+}
+
+#[test]
+fn matches_struct_with_matching_property_with_parameters() -> Result<()> {
+ let value = SomeStruct { a_property: 10 };
+ verify_that!(value, property!(SomeStruct.add_product_to_field(2, 3), eq(16)))
+}
+
+#[test]
+fn matches_struct_with_matching_property_with_captured_arguments() -> Result<()> {
+ let value = SomeStruct { a_property: 10 };
+ let arg1 = 2;
+ let arg2 = 3;
+ verify_that!(value, property!(SomeStruct.add_product_to_field(arg1, arg2), eq(16)))
+}
+
+#[test]
+fn matches_struct_with_matching_property_with_parameters_with_trailing_comma() -> Result<()> {
+ let value = SomeStruct { a_property: 10 };
+ verify_that!(value, property!(SomeStruct.add_product_to_field(2, 3,), eq(16)))
+}
+
+#[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)))
+}
+
+#[test]
+fn matches_struct_with_matching_string_reference_property() -> Result<()> {
+ #[derive(Debug)]
+ struct StructWithString {
+ property: String,
+ }
+ impl StructWithString {
+ fn get_property_ref(&self) -> &String {
+ &self.property
+ }
+ }
+ let value = StructWithString { property: "Something".into() };
+ verify_that!(value, property!(ref StructWithString.get_property_ref(), eq("Something")))
+}
+
+#[test]
+fn matches_struct_with_matching_slice_property() -> Result<()> {
+ #[derive(Debug)]
+ struct StructWithVec {
+ property: Vec<u32>,
+ }
+ impl StructWithVec {
+ fn get_property_ref(&self) -> &[u32] {
+ &self.property
+ }
+ }
+ let value = StructWithVec { property: vec![1, 2, 3] };
+ verify_that!(value, property!(ref 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)))
+}
+
+#[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)))
+}
+
+#[test]
+fn does_not_match_struct_with_non_matching_property() -> Result<()> {
+ let value = SomeStruct { a_property: 2 };
+ verify_that!(value, not(property!(SomeStruct.get_property(), eq(1))))
+}
+
+#[test]
+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")
+ )
+}
+
+#[test]
+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")
+ )
+}
+
+#[test]
+fn explains_mismatch_referencing_explanation_of_inner_matcher() -> Result<()> {
+ impl SomeStruct {
+ fn get_a_collection(&self) -> Vec<u32> {
+ vec![]
+ }
+ }
+ let value = SomeStruct { a_property: 2 };
+ let result = verify_that!(value, property!(SomeStruct.get_a_collection(), container_eq([1])));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "whose property `get_a_collection()` is `[]`, which is missing the element 1"
+ )))
+ )
+}
+
+#[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")
+ )
+}
+
+#[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")
+ )
+}
+
+#[test]
+fn explains_mismatch_referencing_explanation_of_inner_matcher_for_ref() -> Result<()> {
+ static EMPTY_COLLECTION: Vec<u32> = vec![];
+ impl SomeStruct {
+ fn get_a_collection_ref(&self) -> &[u32] {
+ &EMPTY_COLLECTION
+ }
+ }
+ let value = SomeStruct { a_property: 2 };
+ let result =
+ verify_that!(value, property!(ref SomeStruct.get_a_collection_ref(), container_eq([1])));
+
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(
+ "whose property `get_a_collection_ref()` is `[]`, which is missing the element 1"
+ )))
+ )
+}
diff --git a/tests/proptest_integration_test.rs b/tests/proptest_integration_test.rs
new file mode 100644
index 0000000..b33c9dd
--- /dev/null
+++ b/tests/proptest_integration_test.rs
@@ -0,0 +1,30 @@
+// 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.
+
+#![cfg(feature = "proptest")]
+
+use googletest::prelude::*;
+use proptest::test_runner::{Config, TestRunner};
+
+#[test]
+fn numbers_are_greater_than_zero() -> Result<()> {
+ let mut runner = TestRunner::new(Config::default());
+ runner.run(&(1..100i32), |v| Ok(verify_that!(v, gt(0))?)).into_test_result()
+}
+
+#[test]
+fn strings_are_nonempty() -> Result<()> {
+ let mut runner = TestRunner::new(Config::default());
+ runner.run(&"[a-zA-Z0-9]+", |v| Ok(verify_that!(v, not(eq("")))?)).into_test_result()
+}
diff --git a/tests/tuple_matcher_test.rs b/tests/tuple_matcher_test.rs
new file mode 100644
index 0000000..0c1eb01
--- /dev/null
+++ b/tests/tuple_matcher_test.rs
@@ -0,0 +1,259 @@
+// 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 googletest::matcher::{Matcher, MatcherResult};
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn empty_matcher_matches_empty_tuple() -> Result<()> {
+ verify_that!((), ())
+}
+
+#[test]
+fn singleton_matcher_matches_matching_singleton_tuple() -> Result<()> {
+ verify_that!((123,), (eq(123),))
+}
+
+#[test]
+fn singleton_matcher_does_not_match_non_matching_singleton_tuple() -> Result<()> {
+ verify_that!((123,), not((eq(456),)))
+}
+
+#[test]
+fn pair_matcher_matches_matching_pair_tuple() -> Result<()> {
+ verify_that!((123, 456), (eq(123), eq(456)))
+}
+
+#[test]
+fn pair_matcher_matches_matching_pair_tuple_with_different_types() -> Result<()> {
+ verify_that!((123, "A string"), (eq(123), eq("A string")))
+}
+
+#[test]
+fn pair_matcher_with_trailing_comma_matches_matching_pair_tuple() -> Result<()> {
+ verify_that!((123, 456), (eq(123), eq(456),))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_3_tuple() -> Result<()> {
+ verify_that!((1, 2, 3), (eq(1), eq(2), eq(3)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_4_tuple() -> Result<()> {
+ verify_that!((1, 2, 3, 4), (eq(1), eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_5_tuple() -> Result<()> {
+ verify_that!((1, 2, 3, 4, 5), (eq(1), eq(2), eq(3), eq(4), eq(5)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_6_tuple() -> Result<()> {
+ verify_that!((1, 2, 3, 4, 5, 6), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_7_tuple() -> Result<()> {
+ verify_that!((1, 2, 3, 4, 5, 6, 7), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_8_tuple() -> Result<()> {
+ verify_that!((1, 2, 3, 4, 5, 6, 7, 8), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_9_tuple() -> Result<()> {
+ verify_that!(
+ (1, 2, 3, 4, 5, 6, 7, 8, 9),
+ (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9))
+ )
+}
+
+#[test]
+fn tuple_matcher_matches_matching_10_tuple() -> Result<()> {
+ verify_that!(
+ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
+ (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10))
+ )
+}
+
+#[test]
+fn tuple_matcher_matches_matching_11_tuple() -> Result<()> {
+ verify_that!(
+ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
+ (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10), eq(11))
+ )
+}
+
+#[test]
+fn tuple_matcher_matches_matching_12_tuple() -> Result<()> {
+ verify_that!(
+ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
+ (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10), eq(11), eq(12))
+ )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_3_tuple() -> Result<()> {
+ verify_that!((1, 2, 3), (eq(1), eq(2), eq(3),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_4_tuple() -> Result<()> {
+ verify_that!((1, 2, 3, 4), (eq(1), eq(2), eq(3), eq(4),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_5_tuple() -> Result<()> {
+ verify_that!((1, 2, 3, 4, 5), (eq(1), eq(2), eq(3), eq(4), eq(5),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_6_tuple() -> Result<()> {
+ verify_that!((1, 2, 3, 4, 5, 6), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_7_tuple() -> Result<()> {
+ verify_that!((1, 2, 3, 4, 5, 6, 7), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_8_tuple() -> Result<()> {
+ verify_that!(
+ (1, 2, 3, 4, 5, 6, 7, 8),
+ (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8),)
+ )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_9_tuple() -> Result<()> {
+ verify_that!(
+ (1, 2, 3, 4, 5, 6, 7, 8, 9),
+ (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9),)
+ )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_10_tuple() -> Result<()> {
+ verify_that!(
+ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
+ (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10),)
+ )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_11_tuple() -> Result<()> {
+ verify_that!(
+ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
+ (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10), eq(11),)
+ )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_12_tuple() -> Result<()> {
+ verify_that!(
+ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
+ (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10), eq(11), eq(12),)
+ )
+}
+
+#[test]
+fn tuple_matcher_1_has_correct_description_for_match() -> Result<()> {
+ verify_that!(
+ (eq(1),).describe(MatcherResult::Match),
+ eq(indoc!(
+ "
+ is a tuple whose values respectively match:
+ is equal to 1,
+ "
+ ))
+ )
+}
+
+#[test]
+fn tuple_matcher_1_has_correct_description_for_mismatch() -> Result<()> {
+ verify_that!(
+ (eq(1),).describe(MatcherResult::NoMatch),
+ eq(indoc!(
+ "
+ is a tuple whose values do not respectively match:
+ 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!(
+ "
+ is a tuple whose values respectively match:
+ 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!(
+ "
+ is a tuple whose values do not respectively match:
+ is equal to 1,
+ is equal to 2,
+ "
+ ))
+ )
+}
+
+#[test]
+fn describe_match_shows_which_tuple_element_did_not_match() -> Result<()> {
+ verify_that!(
+ (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
+ "
+ )))
+ )
+}
+
+#[test]
+fn describe_match_shows_which_two_tuple_elements_did_not_match() -> Result<()> {
+ verify_that!(
+ (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
+ "
+ )))
+ )
+}
diff --git a/tests/unordered_elements_are_matcher_test.rs b/tests/unordered_elements_are_matcher_test.rs
new file mode 100644
index 0000000..a105b70
--- /dev/null
+++ b/tests/unordered_elements_are_matcher_test.rs
@@ -0,0 +1,403 @@
+// 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 googletest::matcher::Matcher;
+use googletest::prelude::*;
+use indoc::indoc;
+use std::collections::HashMap;
+
+#[test]
+fn unordered_elements_are_matches_empty_vector() -> Result<()> {
+ let value: Vec<u32> = vec![];
+ verify_that!(value, unordered_elements_are![])
+}
+
+#[test]
+fn unordered_elements_are_matches_empty_vector_with_trailing_comma() -> Result<()> {
+ let value: Vec<u32> = vec![];
+ verify_that!(value, unordered_elements_are![,])
+}
+
+#[test]
+fn unordered_elements_are_matches_vector() -> Result<()> {
+ let value = vec![1, 2, 3];
+ verify_that!(value, unordered_elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn unordered_elements_are_omitted() -> Result<()> {
+ let value = vec![1, 2, 3];
+ verify_that!(value, {eq(3), eq(2), eq(1)})
+}
+
+#[test]
+fn unordered_elements_are_matches_slice() -> Result<()> {
+ let value = vec![1, 2, 3];
+ let slice = value.as_slice();
+ verify_that!(*slice, unordered_elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn unordered_elements_are_matches_hash_map() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+ verify_that!(
+ value,
+ unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
+ )
+}
+
+#[test]
+fn unordered_elements_are_matches_hash_map_with_trailing_comma() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+ verify_that!(
+ value,
+ unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")),]
+ )
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_with_wrong_key() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (4, "Three")]);
+ verify_that!(
+ value,
+ not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
+ )
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_with_wrong_value() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Four")]);
+ verify_that!(
+ value,
+ not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
+ )
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_missing_element() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two")]);
+ verify_that!(
+ value,
+ not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
+ )
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_with_extra_element() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+ verify_that!(value, not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One"))]))
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_with_mismatched_key_and_value() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Three"), (3, "Two")]);
+ verify_that!(
+ value,
+ not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
+ )
+}
+
+#[test]
+fn unordered_elements_are_matches_vector_with_trailing_comma() -> Result<()> {
+ let value = vec![1, 2, 3];
+ verify_that!(value, unordered_elements_are![eq(1), eq(2), eq(3),])
+}
+
+#[test]
+fn unordered_elements_are_matches_size() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, not(unordered_elements_are![eq(1), eq(2), eq(3)]))
+}
+
+#[test]
+fn unordered_elements_are_admits_matchers_without_static_lifetime() -> Result<()> {
+ #[derive(Debug, PartialEq)]
+ struct AStruct(i32);
+ let expected_value = AStruct(123);
+ verify_that!(vec![AStruct(123)], unordered_elements_are![eq_deref_of(&expected_value)])
+}
+
+#[test]
+fn unordered_elements_are_with_map_admits_matchers_without_static_lifetime() -> Result<()> {
+ #[derive(Debug, PartialEq)]
+ struct AStruct(i32);
+ let expected_value = AStruct(123);
+ verify_that!(
+ HashMap::from([(1, AStruct(123))]),
+ unordered_elements_are![(eq(1), eq_deref_of(&expected_value))]
+ )
+}
+
+#[test]
+fn unordered_elements_are_description_mismatch() -> Result<()> {
+ let result = verify_that!(vec![1, 4, 3], unordered_elements_are![eq(1), eq(2), eq(3)]);
+ verify_that!(
+ result,
+ err(displays_as(contains_substring(indoc!(
+ "
+ Value of: vec![1, 4, 3]
+ Expected: contains elements matching in any order:
+ 0. is equal to 1
+ 1. is equal to 2
+ 2. is equal to 3
+ Actual: [1, 4, 3],
+ whose element #1 does not match any expected elements and no elements match the expected element #1"
+ ))))
+ )
+}
+
+#[test]
+fn unordered_elements_are_matches_unordered() -> Result<()> {
+ let value = vec![1, 2];
+ verify_that!(value, unordered_elements_are![eq(2), eq(1)])
+}
+
+#[test]
+fn unordered_elements_are_matches_unordered_with_repetition() -> Result<()> {
+ let value = vec![1, 2, 1, 2, 1];
+ verify_that!(value, unordered_elements_are![eq(1), eq(1), eq(1), eq(2), eq(2)])
+}
+
+#[test]
+fn unordered_elements_are_explains_mismatch_due_to_wrong_size() -> Result<()> {
+ verify_that!(
+ unordered_elements_are![eq(2), eq(3), eq(4)].explain_match(&vec![2, 3]),
+ displays_as(eq("which has size 2 (expected 3)"))
+ )
+}
+
+#[test]
+fn unordered_elements_are_description_no_full_match() -> Result<()> {
+ verify_that!(
+ unordered_elements_are![eq(1), eq(2), eq(2)].explain_match(&vec![1, 1, 2]),
+ displays_as(eq(indoc!(
+ "
+ which does not have a perfect match with the expected elements. The best match found was:
+ Actual element 1 at index 0 matched expected element `is equal to 1` at index 0.
+ Actual element 2 at index 2 matched expected element `is equal to 2` at index 1.
+ Actual element 1 at index 1 did not match any remaining expected element.
+ Expected element `is equal to 2` at index 2 did not match any remaining actual element."
+ )))
+ )
+}
+
+#[test]
+fn unordered_elements_are_unmatchable_expected_description_mismatch() -> Result<()> {
+ verify_that!(
+ unordered_elements_are![eq(1), eq(2), eq(3)].explain_match(&vec![1, 1, 3]),
+ displays_as(eq("which has no element matching the expected element #1"))
+ )
+}
+
+#[test]
+fn unordered_elements_are_unmatchable_actual_description_mismatch() -> Result<()> {
+ verify_that!(
+ unordered_elements_are![eq(1), eq(1), eq(3)].explain_match(&vec![1, 2, 3]),
+ displays_as(eq("whose element #1 does not match any expected elements"))
+ )
+}
+
+fn create_matcher() -> impl Matcher<ActualT = Vec<i32>> {
+ unordered_elements_are![eq(1)]
+}
+
+#[test]
+fn unordered_elements_are_works_when_matcher_is_created_in_subroutine() -> Result<()> {
+ verify_that!(vec![1], create_matcher())
+}
+
+fn create_matcher_for_map() -> impl Matcher<ActualT = HashMap<i32, i32>> {
+ unordered_elements_are![(eq(1), eq(1))]
+}
+
+#[test]
+fn unordered_elements_are_works_when_matcher_for_maps_is_created_in_subroutine() -> Result<()> {
+ verify_that!(HashMap::from([(1, 1)]), create_matcher_for_map())
+}
+
+#[test]
+fn contains_each_matches_when_one_to_one_correspondence_present() -> Result<()> {
+ verify_that!(vec![2, 3, 4], contains_each!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn contains_each_supports_trailing_comma() -> Result<()> {
+ verify_that!(vec![2, 3, 4], contains_each!(eq(2), eq(3), eq(4),))
+}
+
+#[test]
+fn contains_each_matches_hash_map() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+ verify_that!(value, contains_each![(eq(2), eq("Two")), (eq(1), eq("One"))])
+}
+
+#[test]
+fn contains_each_matches_hash_map_with_trailing_comma() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+ verify_that!(value, contains_each![(eq(2), eq("Two")), (eq(1), eq("One")),])
+}
+
+#[test]
+fn contains_each_matches_when_no_matchers_present() -> Result<()> {
+ verify_that!(vec![2, 3, 4], contains_each!())
+}
+
+#[test]
+fn contains_each_matches_when_no_matchers_present_and_trailing_comma() -> Result<()> {
+ verify_that!(vec![2, 3, 4], contains_each!(,))
+}
+
+#[test]
+fn contains_each_matches_when_list_is_empty_and_no_matchers_present() -> Result<()> {
+ verify_that!(Vec::<u32>::new(), contains_each!())
+}
+
+#[test]
+fn contains_each_matches_when_excess_elements_present() -> Result<()> {
+ verify_that!(vec![1, 2, 3, 4], contains_each!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn contains_each_does_not_match_when_matchers_are_unmatched() -> Result<()> {
+ verify_that!(vec![1, 2, 3], not(contains_each!(eq(2), eq(3), eq(4))))
+}
+
+#[test]
+fn contains_each_explains_mismatch_due_to_wrong_size() -> Result<()> {
+ verify_that!(
+ contains_each![eq(2), eq(3), eq(4)].explain_match(&vec![2, 3]),
+ displays_as(eq("which has size 2 (expected at least 3)"))
+ )
+}
+
+#[test]
+fn contains_each_explains_missing_element_in_mismatch() -> Result<()> {
+ verify_that!(
+ contains_each![eq(2), eq(3), eq(4)].explain_match(&vec![1, 2, 3]),
+ displays_as(eq("which has no element matching the expected element #2"))
+ )
+}
+
+#[test]
+fn contains_each_explains_missing_elements_in_mismatch() -> Result<()> {
+ verify_that!(
+ contains_each![eq(2), eq(3), eq(4), eq(5)].explain_match(&vec![0, 1, 2, 3]),
+ displays_as(eq("which has no elements matching the expected elements #2, #3"))
+ )
+}
+
+#[test]
+fn contains_each_explains_mismatch_due_to_no_graph_matching_found() -> Result<()> {
+ verify_that!(
+ contains_each![ge(2), ge(2)].explain_match(&vec![1, 2]),
+ displays_as(eq(indoc!(
+ "
+ which does not have a superset match with the expected elements. The best match found was:
+ Actual element 2 at index 1 matched expected element `is greater than or equal to 2` at index 0.
+ Actual element 1 at index 0 did not match any remaining expected element.
+ Expected element `is greater than or equal to 2` at index 1 did not match any remaining actual element."))
+ ))
+}
+
+#[test]
+fn is_contained_in_matches_with_empty_vector() -> Result<()> {
+ let value: Vec<u32> = vec![];
+ verify_that!(value, is_contained_in!())
+}
+
+#[test]
+fn is_contained_in_matches_with_empty_vector_and_trailing_comma() -> Result<()> {
+ let value: Vec<u32> = vec![];
+ verify_that!(value, is_contained_in!(,))
+}
+
+#[test]
+fn is_contained_in_matches_when_one_to_one_correspondence_present() -> Result<()> {
+ verify_that!(vec![2, 3, 4], is_contained_in!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn is_contained_supports_trailing_comma() -> Result<()> {
+ verify_that!(vec![2, 3, 4], is_contained_in!(eq(2), eq(3), eq(4),))
+}
+
+#[test]
+fn is_contained_in_matches_hash_map() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two")]);
+ verify_that!(
+ value,
+ is_contained_in![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
+ )
+}
+
+#[test]
+fn is_contained_in_matches_hash_map_with_trailing_comma() -> Result<()> {
+ let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two")]);
+ verify_that!(
+ value,
+ is_contained_in![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")),]
+ )
+}
+
+#[test]
+fn is_contained_in_matches_when_container_is_empty() -> Result<()> {
+ verify_that!(vec![], is_contained_in!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn is_contained_in_matches_when_excess_matchers_present() -> Result<()> {
+ verify_that!(vec![3, 4], is_contained_in!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn is_contained_in_does_not_match_when_elements_are_unmatched() -> Result<()> {
+ verify_that!(vec![1, 2, 3], not(is_contained_in!(eq(2), eq(3), eq(4))))
+}
+
+#[test]
+fn is_contained_in_explains_mismatch_due_to_wrong_size() -> Result<()> {
+ verify_that!(
+ is_contained_in![eq(2), eq(3)].explain_match(&vec![2, 3, 4]),
+ displays_as(eq("which has size 3 (expected at most 2)"))
+ )
+}
+
+#[test]
+fn is_contained_in_explains_missing_element_in_mismatch() -> Result<()> {
+ verify_that!(
+ is_contained_in![eq(2), eq(3), eq(4)].explain_match(&vec![1, 2, 3]),
+ displays_as(eq("whose element #0 does not match any expected elements"))
+ )
+}
+
+#[test]
+fn is_contained_in_explains_missing_elements_in_mismatch() -> Result<()> {
+ verify_that!(
+ is_contained_in![eq(2), eq(3), eq(4), eq(5)].explain_match(&vec![0, 1, 2, 3]),
+ displays_as(eq("whose elements #0, #1 do not match any expected elements"))
+ )
+}
+
+#[test]
+fn is_contained_in_explains_mismatch_due_to_no_graph_matching_found() -> Result<()> {
+ verify_that!(
+ is_contained_in![ge(1), ge(3)].explain_match(&vec![1, 2]),
+ displays_as(eq(indoc!(
+ "
+ which does not have a subset match with the expected elements. The best match found was:
+ Actual element 1 at index 0 matched expected element `is greater than or equal to 1` at index 0.
+ Actual element 2 at index 1 did not match any remaining expected element.
+ Expected element `is greater than or equal to 3` at index 1 did not match any remaining actual element."))
+ ))
+}