mirror of
https://github.com/catchorg/Catch2.git
synced 2024-12-22 11:23:29 +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:
parent
dfb83f20e9
commit
1ca8f43b01
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user