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..
This commit is contained in:
Martin Hořeňovský 2018-05-12 17:46:25 +02:00
parent c323658483
commit 6c5c4c43a0
7 changed files with 147 additions and 26 deletions

View File

@ -62,7 +62,9 @@ namespace Catch {
std::string convertUnknownEnumToString( E e ); std::string convertUnknownEnumToString( E e );
template<typename T> template<typename T>
typename std::enable_if<!std::is_enum<T>::value, std::string>::type convertUnstreamable( T const& value ) { typename std::enable_if<
!std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value,
std::string>::type convertUnstreamable( T const& value ) {
#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) #if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)
(void)value; (void)value;
return Detail::unprintableString; return Detail::unprintableString;
@ -71,11 +73,20 @@ namespace Catch {
#endif #endif
} }
template<typename T> template<typename T>
typename std::enable_if<std::is_enum<T>::value, std::string>::type convertUnstreamable( T const& value ) { typename std::enable_if<
return convertUnknownEnumToString( value ); !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value,
std::string>::type convertUnstreamable(T const& ex) {
return ex.what();
} }
template<typename T>
typename std::enable_if<
std::is_enum<T>::value
, std::string>::type convertUnstreamable( T const& value ) {
return convertUnknownEnumToString( value );
}
#if defined(_MANAGED) #if defined(_MANAGED)
//! Convert a CLR string to a utf8 std::string //! Convert a CLR string to a utf8 std::string
template<typename T> template<typename T>

View File

@ -211,14 +211,21 @@ Matchers.tests.cpp:<line number>: passed: testStringForMatching(), Equals("this
Matchers.tests.cpp:<line number>: 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:<line number>: 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:<line number>: 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:<line number>: 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:<line number>: failed: testStringForMatching(), Equals("something else", Catch::CaseSensitive::No) for: "this string contains 'abc' as a substring" equals: "something else" (case insensitive) Matchers.tests.cpp:<line number>: failed: testStringForMatching(), Equals("something else", Catch::CaseSensitive::No) for: "this string contains 'abc' as a substring" equals: "something else" (case insensitive)
ToStringGeneral.tests.cpp:<line number>: 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:<line number>: passed: ::Catch::Detail::stringify(OperatorException{}) == "OperatorException" for: "OperatorException" == "OperatorException"
ToStringGeneral.tests.cpp:<line number>: passed: ::Catch::Detail::stringify(StringMakerException{}) == "StringMakerException" for: "StringMakerException"
==
"StringMakerException"
Matchers.tests.cpp:<line number>: failed: expected exception, got none; expression was: doesNotThrow(), SpecialException, ExceptionMatcher{1} Matchers.tests.cpp:<line number>: failed: expected exception, got none; expression was: doesNotThrow(), SpecialException, ExceptionMatcher{1}
Matchers.tests.cpp:<line number>: failed: expected exception, got none; expression was: doesNotThrow(), SpecialException, ExceptionMatcher{1} 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: 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: {?} special exception has value of 1 Matchers.tests.cpp:<line number>: failed: throws(3), SpecialException, ExceptionMatcher{1} for: std::exception special exception has value of 1
Matchers.tests.cpp:<line number>: failed: throws(4), SpecialException, ExceptionMatcher{1} for: {?} special exception has value of 1 Matchers.tests.cpp:<line number>: failed: throws(4), SpecialException, ExceptionMatcher{1} for: std::exception special exception has value of 1
Matchers.tests.cpp:<line number>: passed: throws(1), SpecialException, ExceptionMatcher{1} for: {?} special exception has value of 1 Matchers.tests.cpp:<line number>: passed: throws(1), SpecialException, ExceptionMatcher{1} for: std::exception special exception has value of 1
Matchers.tests.cpp:<line number>: passed: throws(2), SpecialException, ExceptionMatcher{2} for: {?} special exception has value of 2 Matchers.tests.cpp:<line number>: passed: throws(2), SpecialException, ExceptionMatcher{2} for: std::exception 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(), "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(), 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(), StartsWith( "expected" ) for: "expected exception" starts with: "expected"

View File

@ -335,12 +335,12 @@ Matchers.tests.cpp:<line number>
Matchers.tests.cpp:<line number>: FAILED: Matchers.tests.cpp:<line number>: FAILED:
CHECK_THROWS_MATCHES( throws(3), SpecialException, ExceptionMatcher{1} ) CHECK_THROWS_MATCHES( throws(3), SpecialException, ExceptionMatcher{1} )
with expansion: with expansion:
{?} special exception has value of 1 std::exception special exception has value of 1
Matchers.tests.cpp:<line number>: FAILED: Matchers.tests.cpp:<line number>: FAILED:
REQUIRE_THROWS_MATCHES( throws(4), SpecialException, ExceptionMatcher{1} ) REQUIRE_THROWS_MATCHES( throws(4), SpecialException, ExceptionMatcher{1} )
with expansion: 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 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? Why would you throw a std::string?
=============================================================================== ===============================================================================
test cases: 206 | 153 passed | 49 failed | 4 failed as expected test cases: 207 | 154 passed | 49 failed | 4 failed as expected
assertions: 1061 | 933 passed | 107 failed | 21 failed as expected assertions: 1064 | 936 passed | 107 failed | 21 failed as expected

View File

@ -1680,6 +1680,34 @@ with expansion:
"this string contains 'abc' as a substring" equals: "something else" (case "this string contains 'abc' as a substring" equals: "something else" (case
insensitive) insensitive)
-------------------------------------------------------------------------------
Exception as a value (e.g. in REQUIRE_THROWS_MATCHES) can be stringified
-------------------------------------------------------------------------------
ToStringGeneral.tests.cpp:<line number>
...............................................................................
ToStringGeneral.tests.cpp:<line number>:
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:<line number>:
PASSED:
REQUIRE( ::Catch::Detail::stringify(OperatorException{}) == "OperatorException" )
with expansion:
"OperatorException" == "OperatorException"
ToStringGeneral.tests.cpp:<line number>:
PASSED:
REQUIRE( ::Catch::Detail::stringify(StringMakerException{}) == "StringMakerException" )
with expansion:
"StringMakerException"
==
"StringMakerException"
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Exception matchers that fail Exception matchers that fail
No exception No exception
@ -1722,12 +1750,12 @@ Matchers.tests.cpp:<line number>
Matchers.tests.cpp:<line number>: FAILED: Matchers.tests.cpp:<line number>: FAILED:
CHECK_THROWS_MATCHES( throws(3), SpecialException, ExceptionMatcher{1} ) CHECK_THROWS_MATCHES( throws(3), SpecialException, ExceptionMatcher{1} )
with expansion: with expansion:
{?} special exception has value of 1 std::exception special exception has value of 1
Matchers.tests.cpp:<line number>: FAILED: Matchers.tests.cpp:<line number>: FAILED:
REQUIRE_THROWS_MATCHES( throws(4), SpecialException, ExceptionMatcher{1} ) REQUIRE_THROWS_MATCHES( throws(4), SpecialException, ExceptionMatcher{1} )
with expansion: with expansion:
{?} special exception has value of 1 std::exception special exception has value of 1
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Exception matchers that succeed Exception matchers that succeed
@ -1739,13 +1767,13 @@ Matchers.tests.cpp:<line number>:
PASSED: PASSED:
CHECK_THROWS_MATCHES( throws(1), SpecialException, ExceptionMatcher{1} ) CHECK_THROWS_MATCHES( throws(1), SpecialException, ExceptionMatcher{1} )
with expansion: with expansion:
{?} special exception has value of 1 std::exception special exception has value of 1
Matchers.tests.cpp:<line number>: Matchers.tests.cpp:<line number>:
PASSED: PASSED:
REQUIRE_THROWS_MATCHES( throws(2), SpecialException, ExceptionMatcher{2} ) REQUIRE_THROWS_MATCHES( throws(2), SpecialException, ExceptionMatcher{2} )
with expansion: with expansion:
{?} special exception has value of 2 std::exception special exception has value of 2
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Exception messages can be tested for Exception messages can be tested for
@ -8950,6 +8978,6 @@ Misc.tests.cpp:<line number>:
PASSED: PASSED:
=============================================================================== ===============================================================================
test cases: 206 | 140 passed | 62 failed | 4 failed as expected test cases: 207 | 141 passed | 62 failed | 4 failed as expected
assertions: 1075 | 933 passed | 121 failed | 21 failed as expected assertions: 1078 | 936 passed | 121 failed | 21 failed as expected

View File

@ -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="1076" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}"> <testsuite name="<exe-name>" errors="17" failures="105" tests="1079" 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}"/>
@ -218,6 +218,7 @@ Matchers.tests.cpp:<line number>
Matchers.tests.cpp:<line number> Matchers.tests.cpp:<line number>
</failure> </failure>
</testcase> </testcase>
<testcase classname="<exe-name>.global" name="Exception as a value (e.g. in REQUIRE_THROWS_MATCHES) can be stringified" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Exception matchers that fail/No exception" time="{duration}"> <testcase classname="<exe-name>.global" name="Exception matchers that fail/No exception" time="{duration}">
<failure message="doesNotThrow(), SpecialException, ExceptionMatcher{1}" type="CHECK_THROWS_MATCHES"> <failure message="doesNotThrow(), SpecialException, ExceptionMatcher{1}" type="CHECK_THROWS_MATCHES">
Matchers.tests.cpp:<line number> Matchers.tests.cpp:<line number>
@ -237,10 +238,10 @@ Matchers.tests.cpp:<line number>
</error> </error>
</testcase> </testcase>
<testcase classname="<exe-name>.global" name="Exception matchers that fail/Contents are wrong" time="{duration}"> <testcase classname="<exe-name>.global" name="Exception matchers that fail/Contents are wrong" time="{duration}">
<failure message="{?} special exception has value of 1" type="CHECK_THROWS_MATCHES"> <failure message="std::exception special exception has value of 1" type="CHECK_THROWS_MATCHES">
Matchers.tests.cpp:<line number> Matchers.tests.cpp:<line number>
</failure> </failure>
<failure message="{?} special exception has value of 1" type="REQUIRE_THROWS_MATCHES"> <failure message="std::exception special exception has value of 1" type="REQUIRE_THROWS_MATCHES">
Matchers.tests.cpp:<line number> Matchers.tests.cpp:<line number>
</failure> </failure>
</testcase> </testcase>

View File

@ -1901,6 +1901,37 @@
</Expression> </Expression>
<OverallResult success="false"/> <OverallResult success="false"/>
</TestCase> </TestCase>
<TestCase name="Exception as a value (e.g. in REQUIRE_THROWS_MATCHES) can be stringified" tags="[exception][toString]" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
::Catch::Detail::stringify(WhatException{}) == "This exception has overriden what() method"
</Original>
<Expanded>
"This exception has overriden what() method"
==
"This exception has overriden what() method"
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
::Catch::Detail::stringify(OperatorException{}) == "OperatorException"
</Original>
<Expanded>
"OperatorException" == "OperatorException"
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
::Catch::Detail::stringify(StringMakerException{}) == "StringMakerException"
</Original>
<Expanded>
"StringMakerException"
==
"StringMakerException"
</Expanded>
</Expression>
<OverallResult success="true"/>
</TestCase>
<TestCase name="Exception matchers that fail" tags="[!throws][.][.failing][exceptions][matchers]" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > <TestCase name="Exception matchers that fail" tags="[!throws][.][.failing][exceptions][matchers]" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Section name="No exception" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > <Section name="No exception" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Expression success="false" type="CHECK_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > <Expression success="false" type="CHECK_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
@ -1952,7 +1983,7 @@
throws(3), SpecialException, ExceptionMatcher{1} throws(3), SpecialException, ExceptionMatcher{1}
</Original> </Original>
<Expanded> <Expanded>
{?} special exception has value of 1 std::exception special exception has value of 1
</Expanded> </Expanded>
</Expression> </Expression>
<Expression success="false" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > <Expression success="false" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
@ -1960,7 +1991,7 @@
throws(4), SpecialException, ExceptionMatcher{1} throws(4), SpecialException, ExceptionMatcher{1}
</Original> </Original>
<Expanded> <Expanded>
{?} special exception has value of 1 std::exception special exception has value of 1
</Expanded> </Expanded>
</Expression> </Expression>
<OverallResults successes="0" failures="2" expectedFailures="0"/> <OverallResults successes="0" failures="2" expectedFailures="0"/>
@ -1973,7 +2004,7 @@
throws(1), SpecialException, ExceptionMatcher{1} throws(1), SpecialException, ExceptionMatcher{1}
</Original> </Original>
<Expanded> <Expanded>
{?} special exception has value of 1 std::exception special exception has value of 1
</Expanded> </Expanded>
</Expression> </Expression>
<Expression success="true" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > <Expression success="true" type="REQUIRE_THROWS_MATCHES" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
@ -1981,7 +2012,7 @@
throws(2), SpecialException, ExceptionMatcher{2} throws(2), SpecialException, ExceptionMatcher{2}
</Original> </Original>
<Expanded> <Expanded>
{?} special exception has value of 2 std::exception special exception has value of 2
</Expanded> </Expanded>
</Expression> </Expression>
<OverallResult success="true"/> <OverallResult success="true"/>
@ -9892,7 +9923,7 @@ loose text artifact
</Section> </Section>
<OverallResult success="true"/> <OverallResult success="true"/>
</TestCase> </TestCase>
<OverallResults successes="933" failures="122" expectedFailures="21"/> <OverallResults successes="936" failures="122" expectedFailures="21"/>
</Group> </Group>
<OverallResults successes="933" failures="121" expectedFailures="21"/> <OverallResults successes="936" failures="121" expectedFailures="21"/>
</Catch> </Catch>

View File

@ -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" } })"); 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<StringMakerException> {
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");
}