From 6c5c4c43a0056b82b2e94b5664c44074348ea8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sat, 12 May 2018 17:46:25 +0200 Subject: [PATCH] Add stringification support to std::exception and deriving classes This support is based on overriden `std::exception::what` method, so if an exception does not do so meaningfully, the message is still pointless. This is only used as a fallback, both `StringMaker` specialization and `operator<<` overload have priority.. --- include/internal/catch_tostring.h | 17 ++++++-- .../Baselines/compact.sw.approved.txt | 15 +++++-- .../Baselines/console.std.approved.txt | 8 ++-- .../Baselines/console.sw.approved.txt | 40 ++++++++++++++--- .../SelfTest/Baselines/junit.sw.approved.txt | 7 +-- .../SelfTest/Baselines/xml.sw.approved.txt | 43 ++++++++++++++++--- .../UsageTests/ToStringGeneral.tests.cpp | 43 +++++++++++++++++++ 7 files changed, 147 insertions(+), 26 deletions(-) diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index 79e1001f..4c7f0366 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -62,7 +62,9 @@ namespace Catch { std::string convertUnknownEnumToString( E e ); template - typename std::enable_if::value, std::string>::type convertUnstreamable( T const& value ) { + typename std::enable_if< + !std::is_enum::value && !std::is_base_of::value, + std::string>::type convertUnstreamable( T const& value ) { #if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) (void)value; return Detail::unprintableString; @@ -71,11 +73,20 @@ namespace Catch { #endif } template - typename std::enable_if::value, std::string>::type convertUnstreamable( T const& value ) { - return convertUnknownEnumToString( value ); + typename std::enable_if< + !std::is_enum::value && std::is_base_of::value, + std::string>::type convertUnstreamable(T const& ex) { + return ex.what(); } + template + typename std::enable_if< + std::is_enum::value + , std::string>::type convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + #if defined(_MANAGED) //! Convert a CLR string to a utf8 std::string template diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index 6247761c..87a43f5f 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -211,14 +211,21 @@ Matchers.tests.cpp:: passed: testStringForMatching(), Equals("this Matchers.tests.cpp:: passed: testStringForMatching(), Equals("this string contains 'ABC' as a substring", Catch::CaseSensitive::No) for: "this string contains 'abc' as a substring" equals: "this string contains 'abc' as a substring" (case insensitive) Matchers.tests.cpp:: failed: testStringForMatching(), Equals("this string contains 'ABC' as a substring") for: "this string contains 'abc' as a substring" equals: "this string contains 'ABC' as a substring" Matchers.tests.cpp:: failed: testStringForMatching(), Equals("something else", Catch::CaseSensitive::No) for: "this string contains 'abc' as a substring" equals: "something else" (case insensitive) +ToStringGeneral.tests.cpp:: passed: ::Catch::Detail::stringify(WhatException{}) == "This exception has overriden what() method" for: "This exception has overriden what() method" +== +"This exception has overriden what() method" +ToStringGeneral.tests.cpp:: passed: ::Catch::Detail::stringify(OperatorException{}) == "OperatorException" for: "OperatorException" == "OperatorException" +ToStringGeneral.tests.cpp:: passed: ::Catch::Detail::stringify(StringMakerException{}) == "StringMakerException" for: "StringMakerException" +== +"StringMakerException" Matchers.tests.cpp:: failed: expected exception, got none; expression was: doesNotThrow(), SpecialException, ExceptionMatcher{1} 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: {?} special exception has value of 1 -Matchers.tests.cpp:: failed: throws(4), SpecialException, ExceptionMatcher{1} for: {?} special exception has value of 1 -Matchers.tests.cpp:: passed: throws(1), SpecialException, ExceptionMatcher{1} for: {?} special exception has value of 1 -Matchers.tests.cpp:: passed: throws(2), SpecialException, ExceptionMatcher{2} for: {?} special exception has value of 2 +Matchers.tests.cpp:: failed: throws(3), SpecialException, ExceptionMatcher{1} for: std::exception special exception has value of 1 +Matchers.tests.cpp:: failed: throws(4), SpecialException, ExceptionMatcher{1} for: std::exception special exception has value of 1 +Matchers.tests.cpp:: passed: throws(1), SpecialException, ExceptionMatcher{1} for: std::exception special exception has value of 1 +Matchers.tests.cpp:: passed: throws(2), SpecialException, ExceptionMatcher{2} for: std::exception 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" diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index 79e1bf89..694762ab 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -335,12 +335,12 @@ Matchers.tests.cpp: Matchers.tests.cpp:: FAILED: CHECK_THROWS_MATCHES( throws(3), SpecialException, ExceptionMatcher{1} ) with expansion: - {?} special exception has value of 1 + std::exception special exception has value of 1 Matchers.tests.cpp:: FAILED: REQUIRE_THROWS_MATCHES( throws(4), SpecialException, ExceptionMatcher{1} ) with expansion: - {?} special exception has value of 1 + std::exception special exception has value of 1 ------------------------------------------------------------------------------- Expected exceptions that don't throw or unexpected exceptions fail the test @@ -1084,6 +1084,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 206 | 153 passed | 49 failed | 4 failed as expected -assertions: 1061 | 933 passed | 107 failed | 21 failed as expected +test cases: 207 | 154 passed | 49 failed | 4 failed as expected +assertions: 1064 | 936 passed | 107 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 5a62613d..10f50906 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -1680,6 +1680,34 @@ with expansion: "this string contains 'abc' as a substring" equals: "something else" (case insensitive) +------------------------------------------------------------------------------- +Exception as a value (e.g. in REQUIRE_THROWS_MATCHES) can be stringified +------------------------------------------------------------------------------- +ToStringGeneral.tests.cpp: +............................................................................... + +ToStringGeneral.tests.cpp:: +PASSED: + REQUIRE( ::Catch::Detail::stringify(WhatException{}) == "This exception has overriden what() method" ) +with expansion: + "This exception has overriden what() method" + == + "This exception has overriden what() method" + +ToStringGeneral.tests.cpp:: +PASSED: + REQUIRE( ::Catch::Detail::stringify(OperatorException{}) == "OperatorException" ) +with expansion: + "OperatorException" == "OperatorException" + +ToStringGeneral.tests.cpp:: +PASSED: + REQUIRE( ::Catch::Detail::stringify(StringMakerException{}) == "StringMakerException" ) +with expansion: + "StringMakerException" + == + "StringMakerException" + ------------------------------------------------------------------------------- Exception matchers that fail No exception @@ -1722,12 +1750,12 @@ Matchers.tests.cpp: Matchers.tests.cpp:: FAILED: CHECK_THROWS_MATCHES( throws(3), SpecialException, ExceptionMatcher{1} ) with expansion: - {?} special exception has value of 1 + std::exception special exception has value of 1 Matchers.tests.cpp:: FAILED: REQUIRE_THROWS_MATCHES( throws(4), SpecialException, ExceptionMatcher{1} ) with expansion: - {?} special exception has value of 1 + std::exception special exception has value of 1 ------------------------------------------------------------------------------- Exception matchers that succeed @@ -1739,13 +1767,13 @@ Matchers.tests.cpp:: PASSED: CHECK_THROWS_MATCHES( throws(1), SpecialException, ExceptionMatcher{1} ) with expansion: - {?} special exception has value of 1 + std::exception special exception has value of 1 Matchers.tests.cpp:: PASSED: REQUIRE_THROWS_MATCHES( throws(2), SpecialException, ExceptionMatcher{2} ) with expansion: - {?} special exception has value of 2 + std::exception special exception has value of 2 ------------------------------------------------------------------------------- Exception messages can be tested for @@ -8950,6 +8978,6 @@ Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 206 | 140 passed | 62 failed | 4 failed as expected -assertions: 1075 | 933 passed | 121 failed | 21 failed as expected +test cases: 207 | 141 passed | 62 failed | 4 failed as expected +assertions: 1078 | 936 passed | 121 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index 02579344..54783914 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -218,6 +218,7 @@ Matchers.tests.cpp: Matchers.tests.cpp: + Matchers.tests.cpp: @@ -237,10 +238,10 @@ Matchers.tests.cpp: - + Matchers.tests.cpp: - + Matchers.tests.cpp: diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index 001cb0e1..72d06ac8 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -1901,6 +1901,37 @@ + + + + ::Catch::Detail::stringify(WhatException{}) == "This exception has overriden what() method" + + + "This exception has overriden what() method" +== +"This exception has overriden what() method" + + + + + ::Catch::Detail::stringify(OperatorException{}) == "OperatorException" + + + "OperatorException" == "OperatorException" + + + + + ::Catch::Detail::stringify(StringMakerException{}) == "StringMakerException" + + + "StringMakerException" +== +"StringMakerException" + + + +
@@ -1952,7 +1983,7 @@ throws(3), SpecialException, ExceptionMatcher{1} - {?} special exception has value of 1 + std::exception special exception has value of 1 @@ -1960,7 +1991,7 @@ throws(4), SpecialException, ExceptionMatcher{1} - {?} special exception has value of 1 + std::exception special exception has value of 1 @@ -1973,7 +2004,7 @@ throws(1), SpecialException, ExceptionMatcher{1} - {?} special exception has value of 1 + std::exception special exception has value of 1 @@ -1981,7 +2012,7 @@ throws(2), SpecialException, ExceptionMatcher{2} - {?} special exception has value of 2 + std::exception special exception has value of 2 @@ -9892,7 +9923,7 @@ loose text artifact
- + - + diff --git a/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp b/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp index 8ab33249..a1f8c233 100644 --- a/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp +++ b/projects/SelfTest/UsageTests/ToStringGeneral.tests.cpp @@ -115,3 +115,46 @@ TEST_CASE("Static arrays are convertible to string", "[toString]") { REQUIRE(Catch::Detail::stringify(arr) == R"({ { "1:1", "1:2", "1:3" }, { "2:1", "2:2" } })"); } } + +struct WhatException : std::exception { + char const* what() const noexcept override { + return "This exception has overriden what() method"; + } + ~WhatException() override; +}; + +struct OperatorException : std::exception { + ~OperatorException() override; +}; + +std::ostream& operator<<(std::ostream& out, OperatorException const&) { + out << "OperatorException"; + return out; +} + +struct StringMakerException : std::exception { + ~StringMakerException() override; +}; + +namespace Catch { +template <> +struct StringMaker { + static std::string convert(StringMakerException const&) { + return "StringMakerException"; + } +}; +} + +// Avoid -Wweak-tables +WhatException::~WhatException() = default; +OperatorException::~OperatorException() = default; +StringMakerException::~StringMakerException() = default; + + + + +TEST_CASE("Exception as a value (e.g. in REQUIRE_THROWS_MATCHES) can be stringified", "[toString][exception]") { + REQUIRE(::Catch::Detail::stringify(WhatException{}) == "This exception has overriden what() method"); + REQUIRE(::Catch::Detail::stringify(OperatorException{}) == "OperatorException"); + REQUIRE(::Catch::Detail::stringify(StringMakerException{}) == "StringMakerException"); +}