Add a matcher that checks exception's message

Only works for exceptions that publicly derive from `std::exception`
and the matching is done exactly, including case and whitespace.

Closes #1649
Closes #1728

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
#	modified:   ../docs/matchers.md
#	modified:   ../include/internal/catch_capture_matchers.h
#	modified:   ../projects/CMakeLists.txt
#	modified:   ../projects/SelfTest/Baselines/compact.sw.approved.txt
#	modified:   ../projects/SelfTest/Baselines/console.std.approved.txt
#	modified:   ../projects/SelfTest/Baselines/console.sw.approved.txt
#	modified:   ../projects/SelfTest/Baselines/junit.sw.approved.txt
#	modified:   ../projects/SelfTest/Baselines/xml.sw.approved.txt
#	modified:   ../projects/SelfTest/UsageTests/Matchers.tests.cpp
#
# Untracked files:
#	./
#	../clang-full/
#	../clang-test/
#	../clang10-build/
#	../coverage-build/
#	../gcc-build/
#	../gcc-full/
#	../include/internal/catch_matchers_exception.cpp
#	../include/internal/catch_matchers_exception.hpp
#	../misc-build/
#	../msvc-sln/
#	../notes.txt
#	../test-install/
#
This commit is contained in:
Martin Hořeňovský 2019-10-13 20:37:07 +02:00
parent 28663fb959
commit 2cc0c71856
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
11 changed files with 198 additions and 26 deletions

View File

@ -81,6 +81,22 @@ The second argument is an optional description of the predicate, and is
used only during reporting of the result.
### Exception matchers
Catch2 also provides an exception matcher that can be used to verify
that an exception's message exactly matches desired string. The matcher
is `ExceptionMessageMatcher`, and we also provide a helper function
`Message`.
The matched exception must publicly derive from `std::exception` and
the message matching is done _exactly_, including case.
> `ExceptionMessageMatcher` was introduced in Catch X.Y.Z
Example use:
```cpp
REQUIRE_THROWS_MATCHES(throwsDerivedException(), DerivedException, Message("DerivedException::what"));
```
## Custom matchers
It's easy to provide your own matchers to extend Catch or just to work with your own types.

View File

@ -10,6 +10,7 @@
#include "catch_capture.hpp"
#include "catch_matchers.h"
#include "catch_matchers_exception.hpp"
#include "catch_matchers_floating.h"
#include "catch_matchers_generic.hpp"
#include "catch_matchers_string.h"

View File

@ -0,0 +1,30 @@
/*
* Created by Martin Hořeňovský on 13/10/2019.
*
* 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)
*/
#include "catch_matchers_exception.hpp"
namespace Catch {
namespace Matchers {
namespace Exception {
bool ExceptionMessageMatcher::match(std::exception const& ex) const {
return ex.what() == m_message;
}
std::string ExceptionMessageMatcher::describe() const {
return "exception message matches \"" + m_message + "\"";
}
}
Exception::ExceptionMessageMatcher Message(std::string const& message) {
return Exception::ExceptionMessageMatcher(message);
}
// namespace Exception
} // namespace Matchers
} // namespace Catch

View File

@ -0,0 +1,39 @@
/*
* Created by Martin Hořeňovský on 13/10/2019.
*
* 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_EXCEPTION_HPP_INCLUDED
#define TWOBLUECUBES_CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED
#include "catch_matchers.h"
#include <functional>
#include <string>
namespace Catch {
namespace Matchers {
namespace Exception {
class ExceptionMessageMatcher : public MatcherBase<std::exception> {
std::string m_message;
public:
ExceptionMessageMatcher(std::string const& message):
m_message(message)
{}
bool match(std::exception const& ex) const override;
std::string describe() const override;
};
} // namespace Exception
Exception::ExceptionMessageMatcher Message(std::string const& message);
} // namespace Matchers
} // namespace Catch
#endif // TWOBLUECUBES_CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED

View File

@ -151,6 +151,7 @@ set(INTERNAL_HEADERS
${HEADER_DIR}/internal/catch_leak_detector.h
${HEADER_DIR}/internal/catch_list.h
${HEADER_DIR}/internal/catch_matchers.h
${HEADER_DIR}/internal/catch_matchers_exception.hpp
${HEADER_DIR}/internal/catch_matchers_floating.h
${HEADER_DIR}/internal/catch_matchers_generic.hpp
${HEADER_DIR}/internal/catch_matchers_string.h
@ -229,6 +230,7 @@ set(IMPL_SOURCES
${HEADER_DIR}/internal/catch_list.cpp
${HEADER_DIR}/internal/catch_leak_detector.cpp
${HEADER_DIR}/internal/catch_matchers.cpp
${HEADER_DIR}/internal/catch_matchers_exception.cpp
${HEADER_DIR}/internal/catch_matchers_floating.cpp
${HEADER_DIR}/internal/catch_matchers_generic.cpp
${HEADER_DIR}/internal/catch_matchers_string.cpp

View File

@ -385,16 +385,20 @@ Matchers.tests.cpp:<line number>: failed: expected exception, got none; expressi
Matchers.tests.cpp:<line number>: failed: expected exception, got none; expression was: doesNotThrow(), SpecialException, ExceptionMatcher{1}
Matchers.tests.cpp:<line number>: failed: unexpected exception with message: 'Unknown exception'; expression was: throwsAsInt(1), SpecialException, ExceptionMatcher{1}
Matchers.tests.cpp:<line number>: failed: unexpected exception with message: 'Unknown exception'; expression was: throwsAsInt(1), SpecialException, ExceptionMatcher{1}
Matchers.tests.cpp:<line number>: failed: throws(3), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1
Matchers.tests.cpp:<line number>: failed: throws(4), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1
Matchers.tests.cpp:<line number>: passed: throws(1), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1
Matchers.tests.cpp:<line number>: passed: throws(2), SpecialException, ExceptionMatcher{2} for: SpecialException::what special exception has value of 2
Matchers.tests.cpp:<line number>: failed: throwsSpecialException(3), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1
Matchers.tests.cpp:<line number>: failed: throwsSpecialException(4), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1
Matchers.tests.cpp:<line number>: passed: throwsSpecialException(1), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1
Matchers.tests.cpp:<line number>: passed: throwsSpecialException(2), SpecialException, ExceptionMatcher{2} for: SpecialException::what special exception has value of 2
Exception.tests.cpp:<line number>: passed: thisThrows(), "expected exception" for: "expected exception" equals: "expected exception"
Exception.tests.cpp:<line number>: passed: thisThrows(), Equals( "expecteD Exception", Catch::CaseSensitive::No ) for: "expected exception" equals: "expected exception" (case insensitive)
Exception.tests.cpp:<line number>: passed: thisThrows(), StartsWith( "expected" ) for: "expected exception" starts with: "expected"
Exception.tests.cpp:<line number>: passed: thisThrows(), EndsWith( "exception" ) for: "expected exception" ends with: "exception"
Exception.tests.cpp:<line number>: passed: thisThrows(), Contains( "except" ) for: "expected exception" contains: "except"
Exception.tests.cpp:<line number>: passed: thisThrows(), Contains( "exCept", Catch::CaseSensitive::No ) for: "expected exception" contains: "except" (case insensitive)
Matchers.tests.cpp:<line number>: passed: throwsDerivedException(), DerivedException, Message("DerivedException::what") for: DerivedException::what
Matchers.tests.cpp:<line number>: passed: throwsDerivedException(), DerivedException, !Message("derivedexception::what") for: DerivedException::what not
Matchers.tests.cpp:<line number>: passed: throwsSpecialException(2), SpecialException, !Message("DerivedException::what") for: SpecialException::what not
Matchers.tests.cpp:<line number>: passed: throwsSpecialException(2), SpecialException, Message("SpecialException::what") for: SpecialException::what
Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows(), std::string
Exception.tests.cpp:<line number>: failed: expected exception, got none; expression was: thisDoesntThrow(), std::domain_error
Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows()

View File

@ -520,12 +520,12 @@ Matchers.tests.cpp:<line number>
...............................................................................
Matchers.tests.cpp:<line number>: FAILED:
CHECK_THROWS_MATCHES( throws(3), SpecialException, ExceptionMatcher{1} )
CHECK_THROWS_MATCHES( throwsSpecialException(3), SpecialException, ExceptionMatcher{1} )
with expansion:
SpecialException::what special exception has value of 1
Matchers.tests.cpp:<line number>: FAILED:
REQUIRE_THROWS_MATCHES( throws(4), SpecialException, ExceptionMatcher{1} )
REQUIRE_THROWS_MATCHES( throwsSpecialException(4), SpecialException, ExceptionMatcher{1} )
with expansion:
SpecialException::what special exception has value of 1
@ -1380,6 +1380,6 @@ due to unexpected exception with message:
Why would you throw a std::string?
===============================================================================
test cases: 303 | 229 passed | 70 failed | 4 failed as expected
assertions: 1615 | 1463 passed | 131 failed | 21 failed as expected
test cases: 304 | 230 passed | 70 failed | 4 failed as expected
assertions: 1619 | 1467 passed | 131 failed | 21 failed as expected

View File

@ -2930,12 +2930,12 @@ Matchers.tests.cpp:<line number>
...............................................................................
Matchers.tests.cpp:<line number>: FAILED:
CHECK_THROWS_MATCHES( throws(3), SpecialException, ExceptionMatcher{1} )
CHECK_THROWS_MATCHES( throwsSpecialException(3), SpecialException, ExceptionMatcher{1} )
with expansion:
SpecialException::what special exception has value of 1
Matchers.tests.cpp:<line number>: FAILED:
REQUIRE_THROWS_MATCHES( throws(4), SpecialException, ExceptionMatcher{1} )
REQUIRE_THROWS_MATCHES( throwsSpecialException(4), SpecialException, ExceptionMatcher{1} )
with expansion:
SpecialException::what special exception has value of 1
@ -2946,12 +2946,12 @@ Matchers.tests.cpp:<line number>
...............................................................................
Matchers.tests.cpp:<line number>: PASSED:
CHECK_THROWS_MATCHES( throws(1), SpecialException, ExceptionMatcher{1} )
CHECK_THROWS_MATCHES( throwsSpecialException(1), SpecialException, ExceptionMatcher{1} )
with expansion:
SpecialException::what special exception has value of 1
Matchers.tests.cpp:<line number>: PASSED:
REQUIRE_THROWS_MATCHES( throws(2), SpecialException, ExceptionMatcher{2} )
REQUIRE_THROWS_MATCHES( throwsSpecialException(2), SpecialException, ExceptionMatcher{2} )
with expansion:
SpecialException::what special exception has value of 2
@ -3006,6 +3006,32 @@ Exception.tests.cpp:<line number>: PASSED:
with expansion:
"expected exception" contains: "except" (case insensitive)
-------------------------------------------------------------------------------
Exceptions matchers
-------------------------------------------------------------------------------
Matchers.tests.cpp:<line number>
...............................................................................
Matchers.tests.cpp:<line number>: PASSED:
REQUIRE_THROWS_MATCHES( throwsDerivedException(), DerivedException, Message("DerivedException::what") )
with expansion:
DerivedException::what
Matchers.tests.cpp:<line number>: PASSED:
REQUIRE_THROWS_MATCHES( throwsDerivedException(), DerivedException, !Message("derivedexception::what") )
with expansion:
DerivedException::what not
Matchers.tests.cpp:<line number>: PASSED:
REQUIRE_THROWS_MATCHES( throwsSpecialException(2), SpecialException, !Message("DerivedException::what") )
with expansion:
SpecialException::what not
Matchers.tests.cpp:<line number>: PASSED:
REQUIRE_THROWS_MATCHES( throwsSpecialException(2), SpecialException, Message("SpecialException::what") )
with expansion:
SpecialException::what
-------------------------------------------------------------------------------
Expected exceptions that don't throw or unexpected exceptions fail the test
-------------------------------------------------------------------------------
@ -12913,6 +12939,6 @@ Misc.tests.cpp:<line number>
Misc.tests.cpp:<line number>: PASSED:
===============================================================================
test cases: 303 | 213 passed | 86 failed | 4 failed as expected
assertions: 1632 | 1463 passed | 148 failed | 21 failed as expected
test cases: 304 | 214 passed | 86 failed | 4 failed as expected
assertions: 1636 | 1467 passed | 148 failed | 21 failed as expected

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuitesloose text artifact
>
<testsuite name="<exe-name>" errors="17" failures="132" tests="1633" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testsuite name="<exe-name>" errors="17" failures="132" tests="1637" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<properties>
<property name="filters" value="~[!nonportable]~[!benchmark]~[approvals]"/>
<property name="random-seed" value="1"/>
@ -371,6 +371,7 @@ Matchers.tests.cpp:<line number>
<testcase classname="<exe-name>.global" name="Exception messages can be tested for/exact match" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Exception messages can be tested for/different case" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Exception messages can be tested for/wildcarded" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Exceptions matchers" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Expected exceptions that don't throw or unexpected exceptions fail the test" time="{duration}">
<error message="thisThrows(), std::string" type="CHECK_THROWS_AS">
expected exception

View File

@ -3464,7 +3464,7 @@ Nor would this
<Section name="Contents are wrong" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Expression success="false" type="CHECK_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original>
throws(3), SpecialException, ExceptionMatcher{1}
throwsSpecialException(3), SpecialException, ExceptionMatcher{1}
</Original>
<Expanded>
SpecialException::what special exception has value of 1
@ -3472,7 +3472,7 @@ Nor would this
</Expression>
<Expression success="false" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original>
throws(4), SpecialException, ExceptionMatcher{1}
throwsSpecialException(4), SpecialException, ExceptionMatcher{1}
</Original>
<Expanded>
SpecialException::what special exception has value of 1
@ -3485,7 +3485,7 @@ Nor would this
<TestCase name="Exception matchers that succeed" tags="[!throws][exceptions][matchers]" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Expression success="true" type="CHECK_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original>
throws(1), SpecialException, ExceptionMatcher{1}
throwsSpecialException(1), SpecialException, ExceptionMatcher{1}
</Original>
<Expanded>
SpecialException::what special exception has value of 1
@ -3493,7 +3493,7 @@ Nor would this
</Expression>
<Expression success="true" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original>
throws(2), SpecialException, ExceptionMatcher{2}
throwsSpecialException(2), SpecialException, ExceptionMatcher{2}
</Original>
<Expanded>
SpecialException::what special exception has value of 2
@ -3561,6 +3561,41 @@ Nor would this
</Section>
<OverallResult success="true"/>
</TestCase>
<TestCase name="Exceptions matchers" tags="[!throws][exceptions][matchers]" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Expression success="true" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original>
throwsDerivedException(), DerivedException, Message("DerivedException::what")
</Original>
<Expanded>
DerivedException::what
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original>
throwsDerivedException(), DerivedException, !Message("derivedexception::what")
</Original>
<Expanded>
DerivedException::what not
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original>
throwsSpecialException(2), SpecialException, !Message("DerivedException::what")
</Original>
<Expanded>
SpecialException::what not
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original>
throwsSpecialException(2), SpecialException, Message("SpecialException::what")
</Original>
<Expanded>
SpecialException::what
</Expanded>
</Expression>
<OverallResult success="true"/>
</TestCase>
<TestCase name="Expected exceptions that don't throw or unexpected exceptions fail the test" tags="[!throws][.][failing]" filename="projects/<exe-name>/UsageTests/Exception.tests.cpp" >
<Expression success="false" type="CHECK_THROWS_AS" filename="projects/<exe-name>/UsageTests/Exception.tests.cpp" >
<Original>
@ -15376,7 +15411,7 @@ loose text artifact
</Section>
<OverallResult success="true"/>
</TestCase>
<OverallResults successes="1463" failures="149" expectedFailures="21"/>
<OverallResults successes="1467" failures="149" expectedFailures="21"/>
</Group>
<OverallResults successes="1463" failures="148" expectedFailures="21"/>
<OverallResults successes="1467" failures="148" expectedFailures="21"/>
</Catch>

View File

@ -52,10 +52,16 @@ namespace { namespace MatchersTests {
int i;
};
struct DerivedException : std::exception {
char const* what() const noexcept override {
return "DerivedException::what";
}
};
void doesNotThrow() {}
[[noreturn]]
void throws(int i) {
void throwsSpecialException(int i) {
throw SpecialException{i};
}
@ -64,6 +70,11 @@ namespace { namespace MatchersTests {
throw i;
}
[[noreturn]]
void throwsDerivedException() {
throw DerivedException{};
}
class ExceptionMatcher : public Catch::MatcherBase<SpecialException> {
int m_expected;
public:
@ -319,8 +330,8 @@ namespace { namespace MatchersTests {
}
TEST_CASE("Exception matchers that succeed", "[matchers][exceptions][!throws]") {
CHECK_THROWS_MATCHES(throws(1), SpecialException, ExceptionMatcher{1});
REQUIRE_THROWS_MATCHES(throws(2), SpecialException, ExceptionMatcher{2});
CHECK_THROWS_MATCHES(throwsSpecialException(1), SpecialException, ExceptionMatcher{1});
REQUIRE_THROWS_MATCHES(throwsSpecialException(2), SpecialException, ExceptionMatcher{2});
}
TEST_CASE("Exception matchers that fail", "[matchers][exceptions][!throws][.failing]") {
@ -333,8 +344,8 @@ namespace { namespace MatchersTests {
REQUIRE_THROWS_MATCHES(throwsAsInt(1), SpecialException, ExceptionMatcher{1});
}
SECTION("Contents are wrong") {
CHECK_THROWS_MATCHES(throws(3), SpecialException, ExceptionMatcher{1});
REQUIRE_THROWS_MATCHES(throws(4), SpecialException, ExceptionMatcher{1});
CHECK_THROWS_MATCHES(throwsSpecialException(3), SpecialException, ExceptionMatcher{1});
REQUIRE_THROWS_MATCHES(throwsSpecialException(4), SpecialException, ExceptionMatcher{1});
}
}
@ -533,6 +544,13 @@ namespace { namespace MatchersTests {
}
}
TEST_CASE("Exceptions matchers", "[matchers][exceptions][!throws]") {
REQUIRE_THROWS_MATCHES(throwsDerivedException(), DerivedException, Message("DerivedException::what"));
REQUIRE_THROWS_MATCHES(throwsDerivedException(), DerivedException, !Message("derivedexception::what"));
REQUIRE_THROWS_MATCHES(throwsSpecialException(2), SpecialException, !Message("DerivedException::what"));
REQUIRE_THROWS_MATCHES(throwsSpecialException(2), SpecialException, Message("SpecialException::what"));
}
} } // namespace MatchersTests
#endif // CATCH_CONFIG_DISABLE_MATCHERS