diff --git a/src/catch2/catch_tostring.cpp b/src/catch2/catch_tostring.cpp index 775d4a77..4c24234e 100644 --- a/src/catch2/catch_tostring.cpp +++ b/src/catch2/catch_tostring.cpp @@ -54,6 +54,48 @@ namespace Detail { } } // end unnamed namespace + std::string convertIntoString(StringRef string, bool escape_invisibles) { + std::string ret; + // This is enough for the "don't escape invisibles" case, and a good + // lower bound on the "escape invisibles" case. + ret.reserve(string.size() + 2); + + if (!escape_invisibles) { + ret += '"'; + ret += string; + ret += '"'; + return ret; + } + + ret += '"'; + for (char c : string) { + switch (c) { + case '\r': + ret.append("\\r"); + break; + case '\n': + ret.append("\\n"); + break; + case '\t': + ret.append("\\t"); + break; + case '\f': + ret.append("\\f"); + break; + default: + ret.push_back(c); + break; + } + } + ret += '"'; + + return ret; + } + + std::string convertIntoString(StringRef string) { + return convertIntoString(string, getCurrentContext().getConfig()->showInvisibles()); + } + std::string rawMemoryToString( const void *object, std::size_t size ) { // Reverse order for little endian architectures int i = 0, end = static_cast( size ), inc = 1; @@ -80,44 +122,25 @@ namespace Detail { //// ======================================================= //// std::string StringMaker::convert(const std::string& str) { - if (!getCurrentContext().getConfig()->showInvisibles()) { - return '"' + str + '"'; - } - - std::string s("\""); - for (char c : str) { - switch (c) { - case '\n': - s.append("\\n"); - break; - case '\t': - s.append("\\t"); - break; - default: - s.push_back(c); - break; - } - } - s.append("\""); - return s; + return Detail::convertIntoString( str ); } #ifdef CATCH_CONFIG_CPP17_STRING_VIEW std::string StringMaker::convert(std::string_view str) { - return ::Catch::Detail::stringify(std::string{ str }); + return Detail::convertIntoString( StringRef( str.data(), str.size() ) ); } #endif std::string StringMaker::convert(char const* str) { if (str) { - return ::Catch::Detail::stringify(std::string{ str }); + return Detail::convertIntoString( str ); } else { return{ "{null string}" }; } } std::string StringMaker::convert(char* str) { if (str) { - return ::Catch::Detail::stringify(std::string{ str }); + return Detail::convertIntoString( str ); } else { return{ "{null string}" }; } diff --git a/src/catch2/catch_tostring.hpp b/src/catch2/catch_tostring.hpp index 9a450525..8fbe67a6 100644 --- a/src/catch2/catch_tostring.hpp +++ b/src/catch2/catch_tostring.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,13 @@ namespace Catch { constexpr StringRef unprintableString = "{?}"_sr; + //! Encases `string in quotes, and optionally escapes invisibles + std::string convertIntoString( StringRef string, bool escapeInvisibles ); + + //! Encases `string` in quotes, and escapes invisibles if user requested + //! it via CLI + std::string convertIntoString( StringRef string ); + std::string rawMemoryToString( const void *object, std::size_t size ); template @@ -186,24 +194,31 @@ namespace Catch { }; #endif // CATCH_CONFIG_WCHAR - // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, - // while keeping string semantics? template struct StringMaker { static std::string convert(char const* str) { - return ::Catch::Detail::stringify(std::string{ str }); + // Note that `strnlen` is not actually part of standard C++, + // but both POSIX and Windows cstdlib provide it. + return Detail::convertIntoString( + StringRef( str, strnlen( str, SZ ) ) ); } }; template struct StringMaker { static std::string convert(signed char const* str) { - return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + // See the plain `char const*` overload + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, strnlen(reinterpreted, SZ))); } }; template struct StringMaker { static std::string convert(unsigned char const* str) { - return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); + // See the plain `char const*` overload + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, strnlen(reinterpreted, SZ))); } }; diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index 588ab112..7cf2f2c8 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -205,6 +205,9 @@ Message from section two :test-result: PASS String matchers :test-result: PASS StringRef :test-result: PASS StringRef at compilation time +:test-result: PASS Stringifying char arrays with statically known sizes - char +:test-result: PASS Stringifying char arrays with statically known sizes - signed char +:test-result: PASS Stringifying char arrays with statically known sizes - unsigned char :test-result: PASS Stringifying std::chrono::duration helpers :test-result: PASS Stringifying std::chrono::duration with weird ratios :test-result: PASS Stringifying std::chrono::time_point diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index c846bbb3..41e6dc96 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -1532,6 +1532,12 @@ String.tests.cpp:: passed: with 1 message: '!(sr1.empty())' String.tests.cpp:: passed: with 1 message: 'sr1.size() == 3' String.tests.cpp:: passed: with 1 message: 'sr2.empty()' String.tests.cpp:: passed: with 1 message: 'sr2.size() == 0' +ToString.tests.cpp:: passed: ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +ToString.tests.cpp:: passed: ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +ToString.tests.cpp:: passed: ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +ToString.tests.cpp:: passed: ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +ToString.tests.cpp:: passed: ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +ToString.tests.cpp:: passed: ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" ToStringChrono.tests.cpp:: passed: minute == seconds for: 1 m == 60 s ToStringChrono.tests.cpp:: passed: hour != seconds for: 1 h != 60 s ToStringChrono.tests.cpp:: passed: micro != milli for: 1 us != 1 ms diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index 316fedd1..1d928cf9 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -1386,6 +1386,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 360 | 284 passed | 70 failed | 6 failed as expected -assertions: 2084 | 1932 passed | 129 failed | 23 failed as expected +test cases: 363 | 287 passed | 70 failed | 6 failed as expected +assertions: 2090 | 1938 passed | 129 failed | 23 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index 9570aecb..98394d06 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -10865,6 +10865,54 @@ String.tests.cpp:: PASSED: with message: sr2.size() == 0 +------------------------------------------------------------------------------- +Stringifying char arrays with statically known sizes - char +------------------------------------------------------------------------------- +ToString.tests.cpp: +............................................................................... + +ToString.tests.cpp:: PASSED: + CHECK( ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s ) +with expansion: + ""abc"" == ""abc"" + +ToString.tests.cpp:: PASSED: + CHECK( ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s ) +with expansion: + ""abc"" == ""abc"" + +------------------------------------------------------------------------------- +Stringifying char arrays with statically known sizes - signed char +------------------------------------------------------------------------------- +ToString.tests.cpp: +............................................................................... + +ToString.tests.cpp:: PASSED: + CHECK( ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s ) +with expansion: + ""abc"" == ""abc"" + +ToString.tests.cpp:: PASSED: + CHECK( ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s ) +with expansion: + ""abc"" == ""abc"" + +------------------------------------------------------------------------------- +Stringifying char arrays with statically known sizes - unsigned char +------------------------------------------------------------------------------- +ToString.tests.cpp: +............................................................................... + +ToString.tests.cpp:: PASSED: + CHECK( ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s ) +with expansion: + ""abc"" == ""abc"" + +ToString.tests.cpp:: PASSED: + CHECK( ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s ) +with expansion: + ""abc"" == ""abc"" + ------------------------------------------------------------------------------- Stringifying std::chrono::duration helpers ------------------------------------------------------------------------------- @@ -16782,6 +16830,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 360 | 268 passed | 86 failed | 6 failed as expected -assertions: 2101 | 1932 passed | 146 failed | 23 failed as expected +test cases: 363 | 271 passed | 86 failed | 6 failed as expected +assertions: 2107 | 1938 passed | 146 failed | 23 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index dada5c30..7c3e2f1f 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -1194,6 +1194,9 @@ Matchers.tests.cpp: + + + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index 8c1873f2..10d00b02 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -222,6 +222,9 @@ + + + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index a615c103..1977fa8f 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -2743,6 +2743,18 @@ ok {test-number} - with 1 message: 'sr1.size() == 3' ok {test-number} - with 1 message: 'sr2.empty()' # StringRef at compilation time ok {test-number} - with 1 message: 'sr2.size() == 0' +# Stringifying char arrays with statically known sizes - char +ok {test-number} - ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +# Stringifying char arrays with statically known sizes - char +ok {test-number} - ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +# Stringifying char arrays with statically known sizes - signed char +ok {test-number} - ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +# Stringifying char arrays with statically known sizes - signed char +ok {test-number} - ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +# Stringifying char arrays with statically known sizes - unsigned char +ok {test-number} - ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" +# Stringifying char arrays with statically known sizes - unsigned char +ok {test-number} - ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s for: ""abc"" == ""abc"" # Stringifying std::chrono::duration helpers ok {test-number} - minute == seconds for: 1 m == 60 s # Stringifying std::chrono::duration helpers @@ -4204,5 +4216,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2101 +1..2107 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index 1eaa89ee..e0794baf 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -510,6 +510,12 @@ Matchers.tests.cpp:|nexpression failed|n CHECK_THAT( testStringFor ##teamcity[testFinished name='StringRef' duration="{duration}"] ##teamcity[testStarted name='StringRef at compilation time'] ##teamcity[testFinished name='StringRef at compilation time' duration="{duration}"] +##teamcity[testStarted name='Stringifying char arrays with statically known sizes - char'] +##teamcity[testFinished name='Stringifying char arrays with statically known sizes - char' duration="{duration}"] +##teamcity[testStarted name='Stringifying char arrays with statically known sizes - signed char'] +##teamcity[testFinished name='Stringifying char arrays with statically known sizes - signed char' duration="{duration}"] +##teamcity[testStarted name='Stringifying char arrays with statically known sizes - unsigned char'] +##teamcity[testFinished name='Stringifying char arrays with statically known sizes - unsigned char' duration="{duration}"] ##teamcity[testStarted name='Stringifying std::chrono::duration helpers'] ##teamcity[testFinished name='Stringifying std::chrono::duration helpers' duration="{duration}"] ##teamcity[testStarted name='Stringifying std::chrono::duration with weird ratios'] diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index c0807418..ebfca6dc 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -12900,6 +12900,63 @@ Message from section two + + + + ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s + + + ""abc"" == ""abc"" + + + + + ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s + + + ""abc"" == ""abc"" + + + + + + + + ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s + + + ""abc"" == ""abc"" + + + + + ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s + + + ""abc"" == ""abc"" + + + + + + + + ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s + + + ""abc"" == ""abc"" + + + + + ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s + + + ""abc"" == ""abc"" + + + + @@ -19745,9 +19802,9 @@ loose text artifact - - + + - - + + diff --git a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp index 47dfdd37..c0415dc3 100644 --- a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp @@ -1,6 +1,7 @@ #include #include #include +#include enum class EnumClass3 { Value1, Value2, Value3, Value4 }; @@ -50,4 +51,39 @@ TEST_CASE( "Directly creating an EnumInfo" ) { TEST_CASE("Range type with sentinel") { CHECK( Catch::Detail::stringify(UsesSentinel{}) == "{ }" ); -} \ No newline at end of file +} + +TEST_CASE("convertIntoString stringification helper", "[toString][approvals]") { + using namespace std::string_literals; + using Catch::Detail::convertIntoString; + using namespace Catch; + + SECTION("No escaping") { + CHECK(convertIntoString(""_sr, false) == R"("")"s); + CHECK(convertIntoString("abcd"_sr, false) == R"("abcd")"s); + CHECK(convertIntoString("ab\ncd"_sr, false) == "\"ab\ncd\""s); + CHECK(convertIntoString("ab\r\ncd"_sr, false) == "\"ab\r\ncd\""s); + CHECK(convertIntoString("ab\"cd"_sr, false) == R"("ab"cd")"s); + } + SECTION("Escaping invisibles") { + CHECK(convertIntoString(""_sr, true) == R"("")"s); + CHECK(convertIntoString("ab\ncd"_sr, true) == R"("ab\ncd")"s); + CHECK(convertIntoString("ab\r\ncd"_sr, true) == R"("ab\r\ncd")"s); + CHECK(convertIntoString("ab\tcd"_sr, true) == R"("ab\tcd")"s); + CHECK(convertIntoString("ab\fcd"_sr, true) == R"("ab\fcd")"s); + CHECK(convertIntoString("ab\"cd"_sr, true) == R"("ab"cd")"s); + } +} + +TEMPLATE_TEST_CASE( "Stringifying char arrays with statically known sizes", + "[toString]", + char, + signed char, + unsigned char ) { + using namespace std::string_literals; + TestType with_null_terminator[10] = "abc"; + CHECK( ::Catch::Detail::stringify( with_null_terminator ) == R"("abc")"s ); + + TestType no_null_terminator[3] = { 'a', 'b', 'c' }; + CHECK( ::Catch::Detail::stringify( no_null_terminator ) == R"("abc")"s ); +}