mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 04:07:10 +01:00 
			
		
		
		
	Add PredicateMatcher that takes an arbitrary predicate functions
Also adds `Predicate` helper function to create `PredicateMatcher`.
Because of limitations in type inference it needs to be explicitly
typed, like so
`Predicate<std::string>([](std::string const& str) { ... })`.
It also takes an optional second argument for description of the
predicate.
It is possible to infer the argument with sufficient TMP, see
https://stackoverflow.com/questions/43560492/how-to-extract-lambdas-return-type-and-variadic-parameters-pack-back-from-gener/43561563#43561563
but I don't think that the magic is worth introducing ATM.
Closes #1236
			
			
This commit is contained in:
		| @@ -152,6 +152,7 @@ set(INTERNAL_HEADERS | ||||
|         ${HEADER_DIR}/internal/catch_list.h | ||||
|         ${HEADER_DIR}/internal/catch_matchers.h | ||||
|         ${HEADER_DIR}/internal/catch_matchers_floating.h | ||||
|         ${HEADER_DIR}/internal/catch_matchers_generic.hpp | ||||
|         ${HEADER_DIR}/internal/catch_matchers_string.h | ||||
|         ${HEADER_DIR}/internal/catch_matchers_vector.h | ||||
|         ${HEADER_DIR}/internal/catch_message.h | ||||
| @@ -221,6 +222,7 @@ set(IMPL_SOURCES | ||||
|         ${HEADER_DIR}/internal/catch_leak_detector.cpp | ||||
|         ${HEADER_DIR}/internal/catch_matchers.cpp | ||||
|         ${HEADER_DIR}/internal/catch_matchers_floating.cpp | ||||
|         ${HEADER_DIR}/internal/catch_matchers_generic.cpp | ||||
|         ${HEADER_DIR}/internal/catch_matchers_string.cpp | ||||
|         ${HEADER_DIR}/internal/catch_message.cpp | ||||
|         ${HEADER_DIR}/internal/catch_registry_hub.cpp | ||||
|   | ||||
| @@ -53,6 +53,25 @@ The floating point matchers are `WithinULP` and `WithinAbs`. `WithinAbs` accepts | ||||
| Do note that ULP-based checks only make sense when both compared numbers are of the same type and `WithinULP` will use type of its argument as the target type. This means that `WithinULP(1.f, 1)` will expect to compare `float`s, but `WithinULP(1., 1)` will expect to compare `double`s. | ||||
|  | ||||
|  | ||||
| ### Generic matchers | ||||
| Catch also aims to provide a set of generic matchers. Currently this set | ||||
| contains only a matcher that takes arbitrary callable predicate and applies | ||||
| it onto the provided object. | ||||
|  | ||||
| Because of type inference limitations, the argument type of the predicate | ||||
| has to be provided explicitly. Example: | ||||
| ```cpp | ||||
| REQUIRE_THAT("Hello olleH", | ||||
|              Predicate<std::string>( | ||||
|                  [] (std::string const& str) -> bool { return str.front() == str.back(); }, | ||||
|                  "First and last character should be equal") | ||||
| ); | ||||
| ``` | ||||
|  | ||||
| The second argument is an optional description of the predicate, and is | ||||
| used only during reporting of the result. | ||||
|  | ||||
|  | ||||
| ## Custom matchers | ||||
| It's easy to provide your own matchers to extend Catch or just to work with your own types. | ||||
|  | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| #include "catch_capture.hpp" | ||||
| #include "catch_matchers.h" | ||||
| #include "catch_matchers_floating.h" | ||||
| #include "catch_matchers_generic.hpp" | ||||
| #include "catch_matchers_string.h" | ||||
| #include "catch_matchers_vector.h" | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								include/internal/catch_matchers_generic.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								include/internal/catch_matchers_generic.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| #include "catch_matchers_generic.hpp" | ||||
|  | ||||
| std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) { | ||||
|     if (desc.empty()) { | ||||
|         return "matches undescribed predicate"; | ||||
|     } else { | ||||
|         return "matches predicate: \"" + desc + '"'; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										58
									
								
								include/internal/catch_matchers_generic.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								include/internal/catch_matchers_generic.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| /* | ||||
|  *  Created by Martin Hořeňovský on 03/04/2017. | ||||
|  * | ||||
|  * Distributed under the Boost Software License, Version 1.0. (See accompanying | ||||
|  * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||||
|  */ | ||||
| #ifndef TWOBLUECUBES_CATCH_MATCHERS_GENERIC_HPP_INCLUDED | ||||
| #define TWOBLUECUBES_CATCH_MATCHERS_GENERIC_HPP_INCLUDED | ||||
|  | ||||
| #include "catch_common.h" | ||||
| #include "catch_matchers.h" | ||||
|  | ||||
| #include <functional> | ||||
| #include <string> | ||||
|  | ||||
| namespace Catch { | ||||
| namespace Matchers { | ||||
| namespace Generic { | ||||
|  | ||||
| namespace Detail { | ||||
|     std::string finalizeDescription(const std::string& desc); | ||||
| } | ||||
|  | ||||
| template <typename T> | ||||
| class PredicateMatcher : public MatcherBase<T> { | ||||
|     std::function<bool(T const&)> m_predicate; | ||||
|     std::string m_description; | ||||
| public: | ||||
|  | ||||
|     PredicateMatcher(std::function<bool(T const&)> const& elem, std::string const& descr) | ||||
|         :m_predicate(std::move(elem)), | ||||
|         m_description(Detail::finalizeDescription(descr)) | ||||
|     {} | ||||
|  | ||||
|     bool match( T const& item ) const override { | ||||
|         return m_predicate(item); | ||||
|     } | ||||
|  | ||||
|     std::string describe() const override { | ||||
|         return m_description; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| } // namespace Generic | ||||
|  | ||||
|     // The following functions create the actual matcher objects. | ||||
|     // The user has to explicitly specify type to the function, because | ||||
|     // infering std::function<bool(T const&)> is hard (but possible) and | ||||
|     // requires a lot of TMP. | ||||
|     template<typename T> | ||||
|     Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, std::string const& description = "") { | ||||
|         return Generic::PredicateMatcher<T>(predicate, description); | ||||
|     } | ||||
|  | ||||
| } // namespace Matchers | ||||
| } // namespace Catch | ||||
|  | ||||
| #endif // TWOBLUECUBES_CATCH_MATCHERS_GENERIC_HPP_INCLUDED | ||||
| @@ -97,6 +97,10 @@ Approx.tests.cpp:<line number>: passed: 0 == Approx( dZero) for: 0 == Approx( 0. | ||||
| Approx.tests.cpp:<line number>: passed: 0 == Approx( dSmall ).margin( 0.001 ) for: 0 == Approx( 0.00001 ) | ||||
| Approx.tests.cpp:<line number>: passed: 1.234f == Approx( dMedium ) for: 1.234f == Approx( 1.234 ) | ||||
| Approx.tests.cpp:<line number>: passed: dMedium == Approx( 1.234f ) for: 1.234 == Approx( 1.2339999676 ) | ||||
| Matchers.tests.cpp:<line number>: passed: 1, Predicate<int>(alwaysTrue, "always true") for: 1 matches predicate: "always true" | ||||
| Matchers.tests.cpp:<line number>: passed: 1, !Predicate<int>(alwaysFalse, "always false") for: 1 not matches predicate: "always false" | ||||
| Matchers.tests.cpp:<line number>: passed: "Hello olleH", Predicate<std::string>( [] (std::string const& str) -> bool { return str.front() == str.back(); }, "First and last character should be equal") for: "Hello olleH" matches predicate: "First and last character should be equal" | ||||
| Matchers.tests.cpp:<line number>: passed: "This wouldn't pass", !Predicate<std::string>( [] (std::string const& str) -> bool { return str.front() == str.back(); } ) for: "This wouldn't pass" not matches undescribed predicate | ||||
| Tricky.tests.cpp:<line number>: passed: true | ||||
| Tricky.tests.cpp:<line number>: passed: true | ||||
| Tricky.tests.cpp:<line number>: passed: true | ||||
|   | ||||
| @@ -1084,6 +1084,6 @@ due to unexpected exception with message: | ||||
|   Why would you throw a std::string? | ||||
|  | ||||
| =============================================================================== | ||||
| test cases:  203 | 150 passed |  49 failed |  4 failed as expected | ||||
| assertions: 1057 | 929 passed | 107 failed | 21 failed as expected | ||||
| test cases:  204 | 151 passed |  49 failed |  4 failed as expected | ||||
| assertions: 1061 | 933 passed | 107 failed | 21 failed as expected | ||||
|  | ||||
|   | ||||
| @@ -814,6 +814,44 @@ PASSED: | ||||
| with expansion: | ||||
|   1.234 == Approx( 1.2339999676 ) | ||||
|  | ||||
| ------------------------------------------------------------------------------- | ||||
| Arbitrary predicate matcher | ||||
|   Function pointer | ||||
| ------------------------------------------------------------------------------- | ||||
| Matchers.tests.cpp:<line number> | ||||
| ............................................................................... | ||||
|  | ||||
| Matchers.tests.cpp:<line number>: | ||||
| PASSED: | ||||
|   REQUIRE_THAT( 1, Predicate<int>(alwaysTrue, "always true") ) | ||||
| with expansion: | ||||
|   1 matches predicate: "always true" | ||||
|  | ||||
| Matchers.tests.cpp:<line number>: | ||||
| PASSED: | ||||
|   REQUIRE_THAT( 1, !Predicate<int>(alwaysFalse, "always false") ) | ||||
| with expansion: | ||||
|   1 not matches predicate: "always false" | ||||
|  | ||||
| ------------------------------------------------------------------------------- | ||||
| Arbitrary predicate matcher | ||||
|   Lambdas + different type | ||||
| ------------------------------------------------------------------------------- | ||||
| Matchers.tests.cpp:<line number> | ||||
| ............................................................................... | ||||
|  | ||||
| Matchers.tests.cpp:<line number>: | ||||
| PASSED: | ||||
|   REQUIRE_THAT( "Hello olleH", Predicate<std::string>( [] (std::string const& str) -> bool { return str.front() == str.back(); }, "First and last character should be equal") ) | ||||
| with expansion: | ||||
|   "Hello olleH" matches predicate: "First and last character should be equal" | ||||
|  | ||||
| Matchers.tests.cpp:<line number>: | ||||
| PASSED: | ||||
|   REQUIRE_THAT( "This wouldn't pass", !Predicate<std::string>( [] (std::string const& str) -> bool { return str.front() == str.back(); } ) ) | ||||
| with expansion: | ||||
|   "This wouldn't pass" not matches undescribed predicate | ||||
|  | ||||
| ------------------------------------------------------------------------------- | ||||
| Assertions then sections | ||||
| ------------------------------------------------------------------------------- | ||||
| @@ -8897,6 +8935,6 @@ Misc.tests.cpp:<line number>: | ||||
| PASSED: | ||||
|  | ||||
| =============================================================================== | ||||
| test cases:  203 | 137 passed |  62 failed |  4 failed as expected | ||||
| assertions: 1071 | 929 passed | 121 failed | 21 failed as expected | ||||
| test cases:  204 | 138 passed |  62 failed |  4 failed as expected | ||||
| assertions: 1075 | 933 passed | 121 failed | 21 failed as expected | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <testsuitesloose text artifact | ||||
| > | ||||
|   <testsuite name="<exe-name>" errors="17" failures="105" tests="1072" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}"> | ||||
|   <testsuite name="<exe-name>" errors="17" failures="105" tests="1076" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}"> | ||||
|     <testcase classname="<exe-name>.global" name="# A test name that starts with a #" time="{duration}"/> | ||||
|     <testcase classname="<exe-name>.global" name="#1005: Comparing pointer to int and long (NULL can be either on various systems)" time="{duration}"/> | ||||
|     <testcase classname="<exe-name>.global" name="#1027" time="{duration}"/> | ||||
| @@ -110,6 +110,8 @@ Exception.tests.cpp:<line number> | ||||
|     <testcase classname="<exe-name>.global" name="Approximate comparisons with floats" time="{duration}"/> | ||||
|     <testcase classname="<exe-name>.global" name="Approximate comparisons with ints" time="{duration}"/> | ||||
|     <testcase classname="<exe-name>.global" name="Approximate comparisons with mixed numeric types" time="{duration}"/> | ||||
|     <testcase classname="<exe-name>.global" name="Arbitrary predicate matcher/Function pointer" time="{duration}"/> | ||||
|     <testcase classname="<exe-name>.global" name="Arbitrary predicate matcher/Lambdas + different type" time="{duration}"/> | ||||
|     <testcase classname="<exe-name>.global" name="Assertions then sections" time="{duration}"/> | ||||
|     <testcase classname="<exe-name>.global" name="Assertions then sections/A section" time="{duration}"/> | ||||
|     <testcase classname="<exe-name>.global" name="Assertions then sections/A section/Another section" time="{duration}"/> | ||||
|   | ||||
| @@ -870,6 +870,47 @@ | ||||
|       </Expression> | ||||
|       <OverallResult success="true"/> | ||||
|     </TestCase> | ||||
|     <TestCase name="Arbitrary predicate matcher" tags="[generic][matchers]" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > | ||||
|       <Section name="Function pointer" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > | ||||
|         <Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > | ||||
|           <Original> | ||||
|             1, Predicate<int>(alwaysTrue, "always true") | ||||
|           </Original> | ||||
|           <Expanded> | ||||
|             1 matches predicate: "always true" | ||||
|           </Expanded> | ||||
|         </Expression> | ||||
|         <Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > | ||||
|           <Original> | ||||
|             1, !Predicate<int>(alwaysFalse, "always false") | ||||
|           </Original> | ||||
|           <Expanded> | ||||
|             1 not matches predicate: "always false" | ||||
|           </Expanded> | ||||
|         </Expression> | ||||
|         <OverallResults successes="2" failures="0" expectedFailures="0"/> | ||||
|       </Section> | ||||
|       <Section name="Lambdas + different type" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > | ||||
|         <Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > | ||||
|           <Original> | ||||
|             "Hello olleH", Predicate<std::string>( [] (std::string const& str) -> bool { return str.front() == str.back(); }, "First and last character should be equal") | ||||
|           </Original> | ||||
|           <Expanded> | ||||
|             "Hello olleH" matches predicate: "First and last character should be equal" | ||||
|           </Expanded> | ||||
|         </Expression> | ||||
|         <Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > | ||||
|           <Original> | ||||
|             "This wouldn't pass", !Predicate<std::string>( [] (std::string const& str) -> bool { return str.front() == str.back(); } ) | ||||
|           </Original> | ||||
|           <Expanded> | ||||
|             "This wouldn't pass" not matches undescribed predicate | ||||
|           </Expanded> | ||||
|         </Expression> | ||||
|         <OverallResults successes="2" failures="0" expectedFailures="0"/> | ||||
|       </Section> | ||||
|       <OverallResult success="true"/> | ||||
|     </TestCase> | ||||
|     <TestCase name="Assertions then sections" tags="[Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > | ||||
|       <Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > | ||||
|         <Original> | ||||
| @@ -9841,7 +9882,7 @@ loose text artifact | ||||
|       </Section> | ||||
|       <OverallResult success="true"/> | ||||
|     </TestCase> | ||||
|     <OverallResults successes="929" failures="122" expectedFailures="21"/> | ||||
|     <OverallResults successes="933" failures="122" expectedFailures="21"/> | ||||
|   </Group> | ||||
|   <OverallResults successes="929" failures="121" expectedFailures="21"/> | ||||
|   <OverallResults successes="933" failures="121" expectedFailures="21"/> | ||||
| </Catch> | ||||
|   | ||||
| @@ -32,6 +32,9 @@ namespace { namespace MatchersTests { | ||||
|         return "some completely different text that contains one common word"; | ||||
|     } | ||||
|  | ||||
|     inline bool alwaysTrue(int) { return true; } | ||||
|     inline bool alwaysFalse(int) { return false; } | ||||
|  | ||||
|  | ||||
| #ifdef _MSC_VER | ||||
| #pragma warning(disable:4702) // Unreachable code -- MSVC 19 (VS 2015) sees right through the indirection | ||||
| @@ -396,6 +399,26 @@ namespace { namespace MatchersTests { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         TEST_CASE("Arbitrary predicate matcher", "[matchers][generic]") { | ||||
|             SECTION("Function pointer") { | ||||
|                 REQUIRE_THAT(1,  Predicate<int>(alwaysTrue, "always true")); | ||||
|                 REQUIRE_THAT(1, !Predicate<int>(alwaysFalse, "always false")); | ||||
|             } | ||||
|             SECTION("Lambdas + different type") { | ||||
|                 REQUIRE_THAT("Hello olleH", | ||||
|                              Predicate<std::string>( | ||||
|                                  [] (std::string const& str) -> bool { return str.front() == str.back(); }, | ||||
|                                  "First and last character should be equal") | ||||
|                 ); | ||||
|  | ||||
|                 REQUIRE_THAT("This wouldn't pass", | ||||
|                              !Predicate<std::string>( | ||||
|                                  [] (std::string const& str) -> bool { return str.front() == str.back(); } | ||||
|                              ) | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| } } // namespace MatchersTests | ||||
|  | ||||
| #endif // CATCH_CONFIG_DISABLE_MATCHERS | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský