Skip to content

Commit

Permalink
Add a matcher that checks exception's message
Browse files Browse the repository at this point in the history
Only works for exceptions that publicly derive from `std::exception`
and the matching is done exactly, including case and whitespace.

Closes catchorg#1649
Closes catchorg#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/
#
  • Loading branch information
horenmar authored and amitherman95 committed Oct 18, 2019
1 parent 8bb39bd commit 2703c6b
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 26 deletions.
16 changes: 16 additions & 0 deletions docs/matchers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions include/internal/catch_capture_matchers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
30 changes: 30 additions & 0 deletions include/internal/catch_matchers_exception.cpp
Original file line number Diff line number Diff line change
@@ -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
39 changes: 39 additions & 0 deletions include/internal/catch_matchers_exception.hpp
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions projects/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions projects/SelfTest/Baselines/compact.sw.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
8 changes: 4 additions & 4 deletions projects/SelfTest/Baselines/console.std.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

38 changes: 32 additions & 6 deletions projects/SelfTest/Baselines/console.sw.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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
-------------------------------------------------------------------------------
Expand Down Expand Up @@ -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

3 changes: 2 additions & 1 deletion projects/SelfTest/Baselines/junit.sw.approved.txt
Original file line number Diff line number Diff line change
@@ -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"/>
Expand Down Expand Up @@ -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
Expand Down
47 changes: 41 additions & 6 deletions projects/SelfTest/Baselines/xml.sw.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3464,15 +3464,15 @@ 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
</Expanded>
</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
Expand All @@ -3485,15 +3485,15 @@ 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
</Expanded>
</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
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Loading

0 comments on commit 2703c6b

Please sign in to comment.