From 2cc0c718562d41a9900daca3ba53b6753515ee49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sun, 13 Oct 2019 20:37:07 +0200 Subject: [PATCH] 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/ # --- docs/matchers.md | 16 +++++++ include/internal/catch_capture_matchers.h | 1 + include/internal/catch_matchers_exception.cpp | 30 ++++++++++++ include/internal/catch_matchers_exception.hpp | 39 +++++++++++++++ projects/CMakeLists.txt | 2 + .../Baselines/compact.sw.approved.txt | 12 +++-- .../Baselines/console.std.approved.txt | 8 ++-- .../Baselines/console.sw.approved.txt | 38 ++++++++++++--- .../SelfTest/Baselines/junit.sw.approved.txt | 3 +- .../SelfTest/Baselines/xml.sw.approved.txt | 47 ++++++++++++++++--- .../SelfTest/UsageTests/Matchers.tests.cpp | 28 +++++++++-- 11 files changed, 198 insertions(+), 26 deletions(-) create mode 100644 include/internal/catch_matchers_exception.cpp create mode 100644 include/internal/catch_matchers_exception.hpp diff --git a/docs/matchers.md b/docs/matchers.md index f0fe06a2..3d7f0c1e 100644 --- a/docs/matchers.md +++ b/docs/matchers.md @@ -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. diff --git a/include/internal/catch_capture_matchers.h b/include/internal/catch_capture_matchers.h index 0ced01d5..5386e5e1 100644 --- a/include/internal/catch_capture_matchers.h +++ b/include/internal/catch_capture_matchers.h @@ -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" diff --git a/include/internal/catch_matchers_exception.cpp b/include/internal/catch_matchers_exception.cpp new file mode 100644 index 00000000..b18810e8 --- /dev/null +++ b/include/internal/catch_matchers_exception.cpp @@ -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 diff --git a/include/internal/catch_matchers_exception.hpp b/include/internal/catch_matchers_exception.hpp new file mode 100644 index 00000000..93f011af --- /dev/null +++ b/include/internal/catch_matchers_exception.hpp @@ -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 +#include + +namespace Catch { +namespace Matchers { +namespace Exception { + +class ExceptionMessageMatcher : public MatcherBase { + 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 diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 8d0f94d4..0a14006c 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -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 diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index ed47d349..f7613ce3 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -385,16 +385,20 @@ Matchers.tests.cpp:: failed: expected exception, got none; expressi Matchers.tests.cpp:: failed: expected exception, got none; expression was: doesNotThrow(), SpecialException, ExceptionMatcher{1} Matchers.tests.cpp:: failed: unexpected exception with message: 'Unknown exception'; expression was: throwsAsInt(1), SpecialException, ExceptionMatcher{1} Matchers.tests.cpp:: failed: unexpected exception with message: 'Unknown exception'; expression was: throwsAsInt(1), SpecialException, ExceptionMatcher{1} -Matchers.tests.cpp:: failed: throws(3), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1 -Matchers.tests.cpp:: failed: throws(4), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1 -Matchers.tests.cpp:: passed: throws(1), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1 -Matchers.tests.cpp:: passed: throws(2), SpecialException, ExceptionMatcher{2} for: SpecialException::what special exception has value of 2 +Matchers.tests.cpp:: failed: throwsSpecialException(3), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1 +Matchers.tests.cpp:: failed: throwsSpecialException(4), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1 +Matchers.tests.cpp:: passed: throwsSpecialException(1), SpecialException, ExceptionMatcher{1} for: SpecialException::what special exception has value of 1 +Matchers.tests.cpp:: passed: throwsSpecialException(2), SpecialException, ExceptionMatcher{2} for: SpecialException::what special exception has value of 2 Exception.tests.cpp:: passed: thisThrows(), "expected exception" for: "expected exception" equals: "expected exception" Exception.tests.cpp:: passed: thisThrows(), Equals( "expecteD Exception", Catch::CaseSensitive::No ) for: "expected exception" equals: "expected exception" (case insensitive) Exception.tests.cpp:: passed: thisThrows(), StartsWith( "expected" ) for: "expected exception" starts with: "expected" Exception.tests.cpp:: passed: thisThrows(), EndsWith( "exception" ) for: "expected exception" ends with: "exception" Exception.tests.cpp:: passed: thisThrows(), Contains( "except" ) for: "expected exception" contains: "except" Exception.tests.cpp:: passed: thisThrows(), Contains( "exCept", Catch::CaseSensitive::No ) for: "expected exception" contains: "except" (case insensitive) +Matchers.tests.cpp:: passed: throwsDerivedException(), DerivedException, Message("DerivedException::what") for: DerivedException::what +Matchers.tests.cpp:: passed: throwsDerivedException(), DerivedException, !Message("derivedexception::what") for: DerivedException::what not +Matchers.tests.cpp:: passed: throwsSpecialException(2), SpecialException, !Message("DerivedException::what") for: SpecialException::what not +Matchers.tests.cpp:: passed: throwsSpecialException(2), SpecialException, Message("SpecialException::what") for: SpecialException::what Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows(), std::string Exception.tests.cpp:: failed: expected exception, got none; expression was: thisDoesntThrow(), std::domain_error Exception.tests.cpp:: failed: unexpected exception with message: 'expected exception'; expression was: thisThrows() diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index 0143bbd2..77acca01 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -520,12 +520,12 @@ Matchers.tests.cpp: ............................................................................... Matchers.tests.cpp:: 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:: 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 diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 5c140a96..1a50efae 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -2930,12 +2930,12 @@ Matchers.tests.cpp: ............................................................................... Matchers.tests.cpp:: 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:: 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: ............................................................................... Matchers.tests.cpp:: 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:: 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:: PASSED: with expansion: "expected exception" contains: "except" (case insensitive) +------------------------------------------------------------------------------- +Exceptions matchers +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THROWS_MATCHES( throwsDerivedException(), DerivedException, Message("DerivedException::what") ) +with expansion: + DerivedException::what + +Matchers.tests.cpp:: PASSED: + REQUIRE_THROWS_MATCHES( throwsDerivedException(), DerivedException, !Message("derivedexception::what") ) +with expansion: + DerivedException::what not + +Matchers.tests.cpp:: PASSED: + REQUIRE_THROWS_MATCHES( throwsSpecialException(2), SpecialException, !Message("DerivedException::what") ) +with expansion: + SpecialException::what not + +Matchers.tests.cpp:: 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: Misc.tests.cpp:: 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 diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index 6003cd73..247816c3 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -371,6 +371,7 @@ Matchers.tests.cpp: + expected exception diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index 27a3a2ad..07aaee3d 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -3464,7 +3464,7 @@ Nor would this
- throws(3), SpecialException, ExceptionMatcher{1} + throwsSpecialException(3), SpecialException, ExceptionMatcher{1} SpecialException::what special exception has value of 1 @@ -3472,7 +3472,7 @@ Nor would this - throws(4), SpecialException, ExceptionMatcher{1} + throwsSpecialException(4), SpecialException, ExceptionMatcher{1} SpecialException::what special exception has value of 1 @@ -3485,7 +3485,7 @@ Nor would this - throws(1), SpecialException, ExceptionMatcher{1} + throwsSpecialException(1), SpecialException, ExceptionMatcher{1} SpecialException::what special exception has value of 1 @@ -3493,7 +3493,7 @@ Nor would this - throws(2), SpecialException, ExceptionMatcher{2} + throwsSpecialException(2), SpecialException, ExceptionMatcher{2} SpecialException::what special exception has value of 2 @@ -3561,6 +3561,41 @@ Nor would this
+ + + + throwsDerivedException(), DerivedException, Message("DerivedException::what") + + + DerivedException::what + + + + + throwsDerivedException(), DerivedException, !Message("derivedexception::what") + + + DerivedException::what not + + + + + throwsSpecialException(2), SpecialException, !Message("DerivedException::what") + + + SpecialException::what not + + + + + throwsSpecialException(2), SpecialException, Message("SpecialException::what") + + + SpecialException::what + + + + @@ -15376,7 +15411,7 @@ loose text artifact - + - + diff --git a/projects/SelfTest/UsageTests/Matchers.tests.cpp b/projects/SelfTest/UsageTests/Matchers.tests.cpp index 9917bd78..6f954ca0 100644 --- a/projects/SelfTest/UsageTests/Matchers.tests.cpp +++ b/projects/SelfTest/UsageTests/Matchers.tests.cpp @@ -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 { 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