mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-25 14:56: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:
parent
dfb83f20e9
commit
1ca8f43b01
@ -152,6 +152,7 @@ set(INTERNAL_HEADERS
|
|||||||
${HEADER_DIR}/internal/catch_list.h
|
${HEADER_DIR}/internal/catch_list.h
|
||||||
${HEADER_DIR}/internal/catch_matchers.h
|
${HEADER_DIR}/internal/catch_matchers.h
|
||||||
${HEADER_DIR}/internal/catch_matchers_floating.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_string.h
|
||||||
${HEADER_DIR}/internal/catch_matchers_vector.h
|
${HEADER_DIR}/internal/catch_matchers_vector.h
|
||||||
${HEADER_DIR}/internal/catch_message.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_leak_detector.cpp
|
||||||
${HEADER_DIR}/internal/catch_matchers.cpp
|
${HEADER_DIR}/internal/catch_matchers.cpp
|
||||||
${HEADER_DIR}/internal/catch_matchers_floating.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_matchers_string.cpp
|
||||||
${HEADER_DIR}/internal/catch_message.cpp
|
${HEADER_DIR}/internal/catch_message.cpp
|
||||||
${HEADER_DIR}/internal/catch_registry_hub.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.
|
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
|
## Custom matchers
|
||||||
It's easy to provide your own matchers to extend Catch or just to work with your own types.
|
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_capture.hpp"
|
||||||
#include "catch_matchers.h"
|
#include "catch_matchers.h"
|
||||||
#include "catch_matchers_floating.h"
|
#include "catch_matchers_floating.h"
|
||||||
|
#include "catch_matchers_generic.hpp"
|
||||||
#include "catch_matchers_string.h"
|
#include "catch_matchers_string.h"
|
||||||
#include "catch_matchers_vector.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: 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: 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 )
|
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
|
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?
|
Why would you throw a std::string?
|
||||||
|
|
||||||
===============================================================================
|
===============================================================================
|
||||||
test cases: 203 | 150 passed | 49 failed | 4 failed as expected
|
test cases: 204 | 151 passed | 49 failed | 4 failed as expected
|
||||||
assertions: 1057 | 929 passed | 107 failed | 21 failed as expected
|
assertions: 1061 | 933 passed | 107 failed | 21 failed as expected
|
||||||
|
|
||||||
|
@ -814,6 +814,44 @@ PASSED:
|
|||||||
with expansion:
|
with expansion:
|
||||||
1.234 == Approx( 1.2339999676 )
|
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
|
Assertions then sections
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
@ -8897,6 +8935,6 @@ Misc.tests.cpp:<line number>:
|
|||||||
PASSED:
|
PASSED:
|
||||||
|
|
||||||
===============================================================================
|
===============================================================================
|
||||||
test cases: 203 | 137 passed | 62 failed | 4 failed as expected
|
test cases: 204 | 138 passed | 62 failed | 4 failed as expected
|
||||||
assertions: 1071 | 929 passed | 121 failed | 21 failed as expected
|
assertions: 1075 | 933 passed | 121 failed | 21 failed as expected
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<testsuitesloose text artifact
|
<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="# 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="#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}"/>
|
<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 floats" time="{duration}"/>
|
||||||
<testcase classname="<exe-name>.global" name="Approximate comparisons with ints" 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="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" 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" time="{duration}"/>
|
||||||
<testcase classname="<exe-name>.global" name="Assertions then sections/A section/Another section" time="{duration}"/>
|
<testcase classname="<exe-name>.global" name="Assertions then sections/A section/Another section" time="{duration}"/>
|
||||||
|
@ -870,6 +870,47 @@
|
|||||||
</Expression>
|
</Expression>
|
||||||
<OverallResult success="true"/>
|
<OverallResult success="true"/>
|
||||||
</TestCase>
|
</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" >
|
<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" >
|
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
|
||||||
<Original>
|
<Original>
|
||||||
@ -9841,7 +9882,7 @@ loose text artifact
|
|||||||
</Section>
|
</Section>
|
||||||
<OverallResult success="true"/>
|
<OverallResult success="true"/>
|
||||||
</TestCase>
|
</TestCase>
|
||||||
<OverallResults successes="929" failures="122" expectedFailures="21"/>
|
<OverallResults successes="933" failures="122" expectedFailures="21"/>
|
||||||
</Group>
|
</Group>
|
||||||
<OverallResults successes="929" failures="121" expectedFailures="21"/>
|
<OverallResults successes="933" failures="121" expectedFailures="21"/>
|
||||||
</Catch>
|
</Catch>
|
||||||
|
@ -32,6 +32,9 @@ namespace { namespace MatchersTests {
|
|||||||
return "some completely different text that contains one common word";
|
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
|
#ifdef _MSC_VER
|
||||||
#pragma warning(disable:4702) // Unreachable code -- MSVC 19 (VS 2015) sees right through the indirection
|
#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
|
} } // namespace MatchersTests
|
||||||
|
|
||||||
#endif // CATCH_CONFIG_DISABLE_MATCHERS
|
#endif // CATCH_CONFIG_DISABLE_MATCHERS
|
||||||
|
Loading…
Reference in New Issue
Block a user