From c638c5720970f03033755dd9ebb0e4e6dccb1289 Mon Sep 17 00:00:00 2001 From: melak47 Date: Thu, 20 Sep 2018 13:13:35 +0100 Subject: [PATCH] Add StringMaker for std::variant, std::monostate (#1380) The StringMaker is off by default and can be enabled by a new macro `CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER`, to avoid increasing the footprint of stringification machinery by default. --- .travis.yml | 4 +- docs/configuration.md | 2 + .../internal/catch_compiler_capabilities.h | 21 +++++ include/internal/catch_tostring.h | 29 +++++++ projects/CMakeLists.txt | 1 + .../UsageTests/ToStringVariant.tests.cpp | 84 +++++++++++++++++++ 6 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 projects/SelfTest/UsageTests/ToStringVariant.tests.cpp diff --git a/.travis.yml b/.travis.yml index de031e94..1d38d860 100644 --- a/.travis.yml +++ b/.travis.yml @@ -260,7 +260,7 @@ matrix: addons: apt: sources: *all_sources - packages: ['clang-6.0', 'libstdc++-7-dev'] + packages: ['clang-6.0', 'libstdc++-8-dev'] env: COMPILER='clang++-6.0' CPP17=1 - os: linux @@ -268,7 +268,7 @@ matrix: addons: apt: sources: *all_sources - packages: ['clang-6.0', 'libstdc++-7-dev'] + packages: ['clang-6.0', 'libstdc++-8-dev'] env: COMPILER='clang++-6.0' CPP17=1 EXAMPLES=1 COVERAGE=1 EXTRAS=1 install: diff --git a/docs/configuration.md b/docs/configuration.md index ee17b819..db1ebc72 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -128,6 +128,7 @@ Catch's selection, by defining either `CATCH_CONFIG_CPP11_TO_STRING` or CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS // Use std::uncaught_exceptions instead of std::uncaught_exception CATCH_CONFIG_CPP17_STRING_VIEW // Provide StringMaker specialization for std::string_view + CATCH_CONFIG_CPP17_VARIANT // Override C++17 detection for CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER Catch contains basic compiler/standard detection and attempts to use some C++17 features whenever appropriate. This automatic detection @@ -202,6 +203,7 @@ By default, Catch does not stringify some types from the standard library. This CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER // Provide StringMaker specialization for std::pair CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER // Provide StringMaker specialization for std::tuple CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER // Provide StringMaker specialization for std::chrono::duration, std::chrono::timepoint + CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER // Provide StringMaker specialization for std::variant, std::monostate (on C++17) CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS // Defines all of the above diff --git a/include/internal/catch_compiler_capabilities.h b/include/internal/catch_compiler_capabilities.h index 1ae9635b..74532c59 100644 --- a/include/internal/catch_compiler_capabilities.h +++ b/include/internal/catch_compiler_capabilities.h @@ -163,6 +163,23 @@ #endif #endif +//////////////////////////////////////////////////////////////////////////////// +// Check if variant is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 +# include +# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# define CATCH_CONFIG_NO_CPP17_VARIANT +# else +# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT +# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) +# endif // defined(__clang__) && (__clang_major__ < 8) +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER @@ -191,6 +208,10 @@ # define CATCH_CONFIG_CPP17_STRING_VIEW #endif +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + #if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) # define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index 5f5610cb..137d9759 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -354,6 +354,7 @@ namespace Catch { #if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) # define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER # define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER # define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER #endif @@ -418,6 +419,34 @@ namespace Catch { } #endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include +namespace Catch { + template<> + struct StringMaker { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template + struct StringMaker> { + static std::string convert(const std::variant& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + namespace Catch { struct not_this_one {}; // Tag type for detecting which begin/ end are being selected diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index e28f5cde..8e2e4b13 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -38,6 +38,7 @@ set(TEST_SOURCES ${SELF_TEST_DIR}/UsageTests/ToStringGeneral.tests.cpp ${SELF_TEST_DIR}/UsageTests/ToStringPair.tests.cpp ${SELF_TEST_DIR}/UsageTests/ToStringTuple.tests.cpp + ${SELF_TEST_DIR}/UsageTests/ToStringVariant.tests.cpp ${SELF_TEST_DIR}/UsageTests/ToStringVector.tests.cpp ${SELF_TEST_DIR}/UsageTests/ToStringWhich.tests.cpp ${SELF_TEST_DIR}/UsageTests/Tricky.tests.cpp diff --git a/projects/SelfTest/UsageTests/ToStringVariant.tests.cpp b/projects/SelfTest/UsageTests/ToStringVariant.tests.cpp new file mode 100644 index 00000000..62f923f0 --- /dev/null +++ b/projects/SelfTest/UsageTests/ToStringVariant.tests.cpp @@ -0,0 +1,84 @@ +#define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +#include "catch.hpp" + +#if defined(CATCH_CONFIG_CPP17_VARIANT) + +#include +#include + +TEST_CASE( "variant", "[toString][variant][approvals]") +{ + using type = std::variant; + CHECK( "{ }" == ::Catch::Detail::stringify(type{}) ); + type value {}; + CHECK( "{ }" == ::Catch::Detail::stringify(value) ); + CHECK( "{ }" == ::Catch::Detail::stringify(std::get<0>(value)) ); +} + +TEST_CASE( "variant", "[toString][variant][approvals]") +{ + using type = std::variant; + CHECK( "0" == ::Catch::Detail::stringify(type{0}) ); +} + +TEST_CASE( "variant", "[toString][variant][approvals]") +{ + using type = std::variant; + CHECK( "0.5f" == ::Catch::Detail::stringify(type{0.5f}) ); + CHECK( "0" == ::Catch::Detail::stringify(type{0}) ); + + SECTION("valueless by exception") { + struct sample { + operator int() const { throw 42; } + }; + + type value{1.5f}; + REQUIRE_THROWS_AS( value.emplace(sample{}), int ); + REQUIRE( value.valueless_by_exception() ); + CHECK( "{valueless variant}" == ::Catch::Detail::stringify(value) ); + } +} + +TEST_CASE( "variant", "[toString][variant][approvals]") +{ + using type = std::variant; + CHECK( "\"foo\"" == ::Catch::Detail::stringify(type{"foo"}) ); + CHECK( "0" == ::Catch::Detail::stringify(type{0}) ); +} + +TEST_CASE( "variant, string>", "[toString][variant][approvals]") +{ + using inner = std::variant; + using type = std::variant; + CHECK( "0.5f" == ::Catch::Detail::stringify(type{0.5f}) ); + CHECK( "0" == ::Catch::Detail::stringify(type{0}) ); + CHECK( "\"foo\"" == ::Catch::Detail::stringify(type{"foo"}) ); + + struct sample { + operator int() const { throw 42; } + }; + + SECTION("valueless nested variant") { + type value = inner{0.5f}; + REQUIRE( std::holds_alternative(value) ); + REQUIRE( std::holds_alternative(std::get(value)) ); + + REQUIRE_THROWS_AS( std::get<0>(value).emplace(sample{}), int ); + + // outer variant is still valid and contains inner + REQUIRE( std::holds_alternative(value) ); + // inner variant is valueless + REQUIRE( std::get(value).valueless_by_exception() ); + CHECK( "{valueless variant}" == ::Catch::Detail::stringify(value) ); + } +} + +TEST_CASE( "variant", "[toString][variant][approvals]" ) +{ + using type = std::variant; + CHECK( "nullptr" == ::Catch::Detail::stringify(type{nullptr}) ); + CHECK( "42" == ::Catch::Detail::stringify(type{42}) ); + CHECK( "\"Catch me\"" == ::Catch::Detail::stringify(type{"Catch me"}) ); +} + +#endif // CATCH_INTERNAL_CONFIG_CPP17_VARIANT