From 43428c609332fcf270cc3c3ada28b2c1427d8e2a Mon Sep 17 00:00:00 2001 From: Phil nash Date: Thu, 4 Apr 2019 15:55:46 +0100 Subject: [PATCH 01/11] First commit of STRINGIFY_ENUM --- .../internal/catch_enum_values_registry.cpp | 68 +++++++ include/internal/catch_enum_values_registry.h | 32 ++++ .../catch_interfaces_enum_values_registry.h | 40 ++++ .../internal/catch_interfaces_registry_hub.h | 4 +- include/internal/catch_registry_hub.cpp | 5 + include/internal/catch_string_manip.cpp | 17 ++ include/internal/catch_string_manip.h | 3 + include/internal/catch_tostring.h | 14 ++ projects/CMakeLists.txt | 3 + .../Baselines/compact.sw.approved.txt | 22 +++ .../Baselines/console.std.approved.txt | 4 +- .../Baselines/console.sw.approved.txt | 137 +++++++++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 8 +- .../SelfTest/Baselines/xml.sw.approved.txt | 173 +++++++++++++++++- .../IntrospectiveTests/String.tests.cpp | 11 ++ .../UsageTests/EnumToString.tests.cpp | 54 ++++++ 16 files changed, 587 insertions(+), 8 deletions(-) create mode 100644 include/internal/catch_enum_values_registry.cpp create mode 100644 include/internal/catch_enum_values_registry.h create mode 100644 include/internal/catch_interfaces_enum_values_registry.h diff --git a/include/internal/catch_enum_values_registry.cpp b/include/internal/catch_enum_values_registry.cpp new file mode 100644 index 00000000..e974a67d --- /dev/null +++ b/include/internal/catch_enum_values_registry.cpp @@ -0,0 +1,68 @@ +/* + * Created by Phil on 4/4/2019. + * Copyright 2019 Two Blue Cubes Ltd. All rights reserved. + * + * 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_enum_values_registry.h" +#include "catch_string_manip.h" +#include "catch_stream.h" + +#include + +namespace Catch { + + IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {} + IEnumInfo::~IEnumInfo() {} + + namespace Detail { + + std::vector parseEnums( StringRef enums ) { + auto enumValues = splitString( enums, ',' ); + std::vector parsed; + parsed.reserve( enumValues.size() ); + for( auto const& enumValue : enumValues ) { + auto identifiers = splitString( enumValue, ':' ); + parsed.push_back( Catch::trim( identifiers.back() ) ); + } + return parsed; + } + + struct EnumInfo : IEnumInfo { + std::string m_name; + std::map m_values; + + ~EnumInfo(); + + std::string lookup( int value ) const override { + auto it = m_values.find( value ); + if( it == m_values.end() ) { + ReusableStringStream rss; + rss << "{** unexpected value for " << m_name << ": " << value << "**}"; + return rss.str(); + } + return it->second; + } + }; + EnumInfo::~EnumInfo() {} + + IEnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector const& values ) { + std::unique_ptr enumInfo( new EnumInfo ); + enumInfo->m_name = enumName; + + const auto valueNames = Catch::Detail::parseEnums( allValueNames ); + assert( valueNames.size() == values.size() ); + std::size_t i = 0; + for( auto value : values ) + enumInfo->m_values.insert({ value, valueNames[i++] }); + + EnumInfo* raw = enumInfo.get(); + m_enumInfos.push_back( std::move( enumInfo ) ); + return *raw; + } + + } // Detail + +} // Catch + diff --git a/include/internal/catch_enum_values_registry.h b/include/internal/catch_enum_values_registry.h new file mode 100644 index 00000000..8103bb9d --- /dev/null +++ b/include/internal/catch_enum_values_registry.h @@ -0,0 +1,32 @@ +/* + * Created by Phil on 4/4/2019. + * Copyright 2019 Two Blue Cubes Ltd. All rights reserved. + * + * 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_ENUMVALUESREGISTRY_H_INCLUDED +#define TWOBLUECUBES_CATCH_ENUMVALUESREGISTRY_H_INCLUDED + +#include "catch_interfaces_enum_values_registry.h" + +#include + +namespace Catch { + + namespace Detail { + + class EnumValuesRegistry : public IMutableEnumValuesRegistry { + + std::vector> m_enumInfos; + + IEnumInfo const& registerEnum(StringRef enumName, StringRef allEnums, std::vector const& values) override; + }; + + std::vector parseEnums( StringRef enums ); + + } // Detail + +} // Catch + +#endif //TWOBLUECUBES_CATCH_ENUMVALUESREGISTRY_H_INCLUDED \ No newline at end of file diff --git a/include/internal/catch_interfaces_enum_values_registry.h b/include/internal/catch_interfaces_enum_values_registry.h new file mode 100644 index 00000000..b94ee69b --- /dev/null +++ b/include/internal/catch_interfaces_enum_values_registry.h @@ -0,0 +1,40 @@ +/* + * Created by Phil on 4/4/2019. + * Copyright 2019 Two Blue Cubes Ltd. All rights reserved. + * + * 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_INTERFACESENUMVALUESREGISTRY_H_INCLUDED +#define TWOBLUECUBES_CATCH_INTERFACESENUMVALUESREGISTRY_H_INCLUDED + +#include "catch_stringref.h" + +#include + +namespace Catch { + + struct IEnumInfo { + virtual ~IEnumInfo(); + + virtual std::string lookup( int value ) const = 0; + }; + + struct IMutableEnumValuesRegistry { + virtual ~IMutableEnumValuesRegistry(); + + virtual IEnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values ) = 0; + + template + IEnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list values ) { + std::vector intValues; + intValues.reserve( values.size() ); + for( auto enumValue : values ) + intValues.push_back( static_cast( enumValue ) ); + return registerEnum( enumName, allEnums, intValues ); + } + }; + +} // Catch + +#endif //TWOBLUECUBES_CATCH_INTERFACESENUMVALUESREGISTRY_H_INCLUDED diff --git a/include/internal/catch_interfaces_registry_hub.h b/include/internal/catch_interfaces_registry_hub.h index 8e1da61e..19ffbf26 100644 --- a/include/internal/catch_interfaces_registry_hub.h +++ b/include/internal/catch_interfaces_registry_hub.h @@ -22,6 +22,8 @@ namespace Catch { struct IReporterRegistry; struct IReporterFactory; struct ITagAliasRegistry; + struct IMutableEnumValuesRegistry; + class StartupExceptionRegistry; using IReporterFactoryPtr = std::shared_ptr; @@ -32,7 +34,6 @@ namespace Catch { virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; - virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; @@ -47,6 +48,7 @@ namespace Catch { virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; virtual void registerStartupException() noexcept = 0; + virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; }; IRegistryHub const& getRegistryHub(); diff --git a/include/internal/catch_registry_hub.cpp b/include/internal/catch_registry_hub.cpp index a5062398..8a3c7a97 100644 --- a/include/internal/catch_registry_hub.cpp +++ b/include/internal/catch_registry_hub.cpp @@ -15,6 +15,7 @@ #include "catch_tag_alias_registry.h" #include "catch_startup_exception_registry.h" #include "catch_singletons.hpp" +#include "catch_enum_values_registry.h" namespace Catch { @@ -60,6 +61,9 @@ namespace Catch { void registerStartupException() noexcept override { m_exceptionRegistry.add(std::current_exception()); } + IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override { + return m_enumValuesRegistry; + } private: TestRegistry m_testCaseRegistry; @@ -67,6 +71,7 @@ namespace Catch { ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; TagAliasRegistry m_tagAliasRegistry; StartupExceptionRegistry m_exceptionRegistry; + Detail::EnumValuesRegistry m_enumValuesRegistry; }; } diff --git a/include/internal/catch_string_manip.cpp b/include/internal/catch_string_manip.cpp index 904d1013..60109335 100644 --- a/include/internal/catch_string_manip.cpp +++ b/include/internal/catch_string_manip.cpp @@ -6,11 +6,13 @@ */ #include "catch_string_manip.h" +#include "catch_stringref.h" #include #include #include #include +#include namespace Catch { @@ -65,6 +67,21 @@ namespace Catch { return replaced; } + std::vector splitString( StringRef str, char delimiter ) { + std::vector subStrings; + std::size_t start = 0; + for(std::size_t pos = 0; pos < str.size(); ++pos ) { + if( str[pos] == delimiter ) { + if( pos - start > 1 ) + subStrings.push_back( str.substr( start, pos-start ) ); + start = pos+1; + } + } + if( start < str.size() ) + subStrings.push_back( str.substr( start, str.size()-start ) ); + return subStrings; + } + pluralise::pluralise( std::size_t count, std::string const& label ) : m_count( count ), m_label( label ) diff --git a/include/internal/catch_string_manip.h b/include/internal/catch_string_manip.h index 6292cd57..0f8176d6 100644 --- a/include/internal/catch_string_manip.h +++ b/include/internal/catch_string_manip.h @@ -12,6 +12,8 @@ namespace Catch { + class StringRef; + bool startsWith( std::string const& s, std::string const& prefix ); bool startsWith( std::string const& s, char prefix ); bool endsWith( std::string const& s, std::string const& suffix ); @@ -21,6 +23,7 @@ namespace Catch { std::string toLower( std::string const& s ); std::string trim( std::string const& str ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + std::vector splitString( StringRef str, char delimiter ); struct pluralise { pluralise( std::size_t count, std::string const& label ); diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index 13a43b0c..dc236dea 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -15,6 +15,7 @@ #include #include "catch_compiler_capabilities.h" #include "catch_stream.h" +#include "catch_interfaces_enum_values_registry.h" #ifdef CATCH_CONFIG_CPP17_STRING_VIEW #include @@ -639,6 +640,19 @@ struct ratio_string { } #endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#define INTERNAL_CATCH_STRINGIFY_ENUM( enumName, ... ) \ + template<> struct ::Catch::StringMaker { \ + static std::string convert( enumName value ) { \ + static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ + return enumInfo.lookup( static_cast( value ) ); \ + } \ + }; + +#ifdef CATCH_CONFIG_PREFIX_ALL +# define CATCH_STRINGIFY_ENUM( enumName, ... ) INTERNAL_CATCH_STRINGIFY_ENUM( enumName, __VA_ARGS__ ) +#else +# define STRINGIFY_ENUM( enumName, ... ) INTERNAL_CATCH_STRINGIFY_ENUM( enumName, __VA_ARGS__ ) +#endif #ifdef _MSC_VER #pragma warning(pop) diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 37b0865a..36263636 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -97,6 +97,7 @@ set(INTERNAL_HEADERS ${HEADER_DIR}/internal/catch_decomposer.h ${HEADER_DIR}/internal/catch_default_main.hpp ${HEADER_DIR}/internal/catch_enforce.h + ${HEADER_DIR}/internal/catch_enum_values_registry.h ${HEADER_DIR}/internal/catch_errno_guard.h ${HEADER_DIR}/internal/catch_exception_translator_registry.h ${HEADER_DIR}/internal/catch_external_interfaces.h @@ -107,6 +108,7 @@ set(INTERNAL_HEADERS ${HEADER_DIR}/internal/catch_impl.hpp ${HEADER_DIR}/internal/catch_interfaces_capture.h ${HEADER_DIR}/internal/catch_interfaces_config.h + ${HEADER_DIR}/internal/catch_interfaces_enum_values_registry.h ${HEADER_DIR}/internal/catch_interfaces_exception.h ${HEADER_DIR}/internal/catch_interfaces_registry_hub.h ${HEADER_DIR}/internal/catch_interfaces_reporter.h @@ -182,6 +184,7 @@ set(IMPL_SOURCES ${HEADER_DIR}/internal/catch_debugger.cpp ${HEADER_DIR}/internal/catch_decomposer.cpp ${HEADER_DIR}/internal/catch_enforce.cpp + ${HEADER_DIR}/internal/catch_enum_values_registry.cpp ${HEADER_DIR}/internal/catch_errno_guard.cpp ${HEADER_DIR}/internal/catch_exception_translator_registry.cpp ${HEADER_DIR}/internal/catch_fatal_condition.cpp diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index 7c60b2af..87674a4b 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -298,6 +298,11 @@ Approx.tests.cpp:: passed: 101.000001 != Approx(100).epsilon(0.01) Approx.tests.cpp:: passed: std::pow(10, -5) != Approx(std::pow(10, -7)) for: 0.00001 != Approx( 0.0000001 ) Matchers.tests.cpp:: failed: testStringForMatching(), EndsWith("Substring") for: "this string contains 'abc' as a substring" ends with: "Substring" Matchers.tests.cpp:: failed: testStringForMatching(), EndsWith("this", Catch::CaseSensitive::No) for: "this string contains 'abc' as a substring" ends with: "this" (case insensitive) +EnumToString.tests.cpp:: passed: enumInfo.lookup(0) == "Value1" for: "Value1" == "Value1" +EnumToString.tests.cpp:: passed: enumInfo.lookup(1) == "Value2" for: "Value2" == "Value2" +EnumToString.tests.cpp:: passed: enumInfo.lookup(3) == "{** unexpected value for EnumName: 3**}" for: "{** unexpected value for EnumName: 3**}" +== +"{** unexpected value for EnumName: 3**}" Approx.tests.cpp:: passed: 101.01 != Approx(100).epsilon(0.01) for: 101.01 != Approx( 100.0 ) Condition.tests.cpp:: failed: data.int_seven == 6 for: 7 == 6 Condition.tests.cpp:: failed: data.int_seven == 8 for: 7 == 8 @@ -912,6 +917,13 @@ Matchers.tests.cpp:: failed: testStringForMatching(), Matches("this Matchers.tests.cpp:: failed: testStringForMatching(), Matches("contains 'abc' as a substring") for: "this string contains 'abc' as a substring" matches "contains 'abc' as a substring" case sensitively Matchers.tests.cpp:: failed: testStringForMatching(), Matches("this string contains 'abc' as a") for: "this string contains 'abc' as a substring" matches "this string contains 'abc' as a" case sensitively Matchers.tests.cpp:: passed: actual, !UnorderedEquals(expected) for: { 'a', 'b' } not UnorderedEquals: { 'c', 'b' } +EnumToString.tests.cpp:: passed: stringify( EnumClass3::Value1 ) == "Value1" for: "Value1" == "Value1" +EnumToString.tests.cpp:: passed: stringify( EnumClass3::Value2 ) == "Value2" for: "Value2" == "Value2" +EnumToString.tests.cpp:: passed: stringify( EnumClass3::Value3 ) == "Value3" for: "Value3" == "Value3" +EnumToString.tests.cpp:: passed: stringify( EnumClass3::Value4 ) == "{** unexpected value for EnumClass3: 3**}" for: "{** unexpected value for EnumClass3: 3**}" +== +"{** unexpected value for EnumClass3: 3**}" +EnumToString.tests.cpp:: passed: stringify( ec3 ) == "Value2" for: "Value2" == "Value2" Message.tests.cpp:: passed: with 1 message: 'this is a success' Message.tests.cpp:: passed: BDD.tests.cpp:: passed: before == 0 for: 0 == 0 @@ -1352,6 +1364,13 @@ Tricky.tests.cpp:: passed: ptr.get() == 0 for: 0 == 0 ToStringPair.tests.cpp:: passed: ::Catch::Detail::stringify( pair ) == "{ { 42, \"Arthur\" }, { \"Ford\", 24 } }" for: "{ { 42, "Arthur" }, { "Ford", 24 } }" == "{ { 42, "Arthur" }, { "Ford", 24 } }" +EnumToString.tests.cpp:: passed: parseEnums( "" ), Equals( std::vector{} ) for: { } Equals: { } +EnumToString.tests.cpp:: passed: parseEnums( "ClassName::EnumName::Value1" ), Equals(std::vector{"Value1"} ) for: { "Value1" } Equals: { "Value1" } +EnumToString.tests.cpp:: passed: parseEnums( "Value1" ), Equals( std::vector{"Value1"} ) for: { "Value1" } Equals: { "Value1" } +EnumToString.tests.cpp:: passed: parseEnums( "EnumName::Value1" ), Equals(std::vector{"Value1"} ) for: { "Value1" } Equals: { "Value1" } +EnumToString.tests.cpp:: passed: parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2" ), Equals( std::vector{"Value1", "Value2"} ) for: { "Value1", "Value2" } Equals: { "Value1", "Value2" } +EnumToString.tests.cpp:: passed: parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2, ClassName::EnumName::Value3" ), Equals( std::vector{"Value1", "Value2", "Value3"} ) for: { "Value1", "Value2", "Value3" } Equals: { "Value1", "Value2", "Value3" } +EnumToString.tests.cpp:: passed: parseEnums( "ClassName::EnumName::Value1,ClassName::EnumName::Value2 , ClassName::EnumName::Value3" ), Equals( std::vector{"Value1", "Value2", "Value3"} ) for: { "Value1", "Value2", "Value3" } Equals: { "Value1", "Value2", "Value3" } Tricky.tests.cpp:: passed: p == 0 for: 0 == 0 Message.tests.cpp:: passed: true with 1 message: 'this MAY be seen IF info is printed for passing assertions' Message.tests.cpp:: failed: false with 2 messages: 'this SHOULD be seen' and 'this SHOULD also be seen' @@ -1379,6 +1398,9 @@ String.tests.cpp:: passed: s == "didn|'t" for: "didn|'t" == "didn|' Misc.tests.cpp:: failed: false with 1 message: '3' Message.tests.cpp:: failed: false with 2 messages: 'hi' and 'i := 7' Tag.tests.cpp:: passed: testcase.tags, Catch::VectorContains(std::string("magic-tag")) && Catch::VectorContains(std::string(".")) for: { ".", "magic-tag" } ( Contains: "magic-tag" and Contains: "." ) +String.tests.cpp:: passed: splitString("", ',' ), Equals(std::vector() ) for: { } Equals: { } +String.tests.cpp:: passed: splitString("abc", ',' ), Equals(std::vector{"abc"} ) for: { "abc" } Equals: { "abc" } +String.tests.cpp:: passed: splitString("abc,def", ',' ), Equals(std::vector{"abc", "def"} ) for: { "abc", "def" } Equals: { "abc", "def" } Message.tests.cpp:: failed: false with 4 messages: 'Count 1 to 3...' and '1' and '2' and '3' Message.tests.cpp:: failed: false with 4 messages: 'Count 4 to 6...' and '4' and '5' and '6' ToStringGeneral.tests.cpp:: passed: Catch::Detail::stringify( emptyMap ) == "{ }" for: "{ }" == "{ }" diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index d69a96c7..8f62dad4 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -1265,6 +1265,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 257 | 191 passed | 62 failed | 4 failed as expected -assertions: 1419 | 1276 passed | 122 failed | 21 failed as expected +test cases: 261 | 195 passed | 62 failed | 4 failed as expected +assertions: 1437 | 1294 passed | 122 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 06290486..77341bed 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -2225,6 +2225,29 @@ with expansion: "this string contains 'abc' as a substring" ends with: "this" (case insensitive) +------------------------------------------------------------------------------- +EnumInfo +------------------------------------------------------------------------------- +EnumToString.tests.cpp: +............................................................................... + +EnumToString.tests.cpp:: PASSED: + CHECK( enumInfo.lookup(0) == "Value1" ) +with expansion: + "Value1" == "Value1" + +EnumToString.tests.cpp:: PASSED: + CHECK( enumInfo.lookup(1) == "Value2" ) +with expansion: + "Value2" == "Value2" + +EnumToString.tests.cpp:: PASSED: + CHECK( enumInfo.lookup(3) == "{** unexpected value for EnumName: 3**}" ) +with expansion: + "{** unexpected value for EnumName: 3**}" + == + "{** unexpected value for EnumName: 3**}" + ------------------------------------------------------------------------------- Epsilon only applies to Approx's value ------------------------------------------------------------------------------- @@ -6684,6 +6707,39 @@ Matchers.tests.cpp:: PASSED: with expansion: { 'a', 'b' } not UnorderedEquals: { 'c', 'b' } +------------------------------------------------------------------------------- +STRINGIFY_ENUM +------------------------------------------------------------------------------- +EnumToString.tests.cpp: +............................................................................... + +EnumToString.tests.cpp:: PASSED: + REQUIRE( stringify( EnumClass3::Value1 ) == "Value1" ) +with expansion: + "Value1" == "Value1" + +EnumToString.tests.cpp:: PASSED: + REQUIRE( stringify( EnumClass3::Value2 ) == "Value2" ) +with expansion: + "Value2" == "Value2" + +EnumToString.tests.cpp:: PASSED: + REQUIRE( stringify( EnumClass3::Value3 ) == "Value3" ) +with expansion: + "Value3" == "Value3" + +EnumToString.tests.cpp:: PASSED: + REQUIRE( stringify( EnumClass3::Value4 ) == "{** unexpected value for EnumClass3: 3**}" ) +with expansion: + "{** unexpected value for EnumClass3: 3**}" + == + "{** unexpected value for EnumClass3: 3**}" + +EnumToString.tests.cpp:: PASSED: + REQUIRE( stringify( ec3 ) == "Value2" ) +with expansion: + "Value2" == "Value2" + ------------------------------------------------------------------------------- SUCCEED counts as a test pass ------------------------------------------------------------------------------- @@ -10025,6 +10081,62 @@ with expansion: == "{ { 42, "Arthur" }, { "Ford", 24 } }" +------------------------------------------------------------------------------- +parseEnums + No enums +------------------------------------------------------------------------------- +EnumToString.tests.cpp: +............................................................................... + +EnumToString.tests.cpp:: PASSED: + CHECK_THAT( parseEnums( "" ), Equals( std::vector{} ) ) +with expansion: + { } Equals: { } + +------------------------------------------------------------------------------- +parseEnums + One enum value +------------------------------------------------------------------------------- +EnumToString.tests.cpp: +............................................................................... + +EnumToString.tests.cpp:: PASSED: + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1" ), Equals(std::vector{"Value1"} ) ) +with expansion: + { "Value1" } Equals: { "Value1" } + +EnumToString.tests.cpp:: PASSED: + CHECK_THAT( parseEnums( "Value1" ), Equals( std::vector{"Value1"} ) ) +with expansion: + { "Value1" } Equals: { "Value1" } + +EnumToString.tests.cpp:: PASSED: + CHECK_THAT( parseEnums( "EnumName::Value1" ), Equals(std::vector{"Value1"} ) ) +with expansion: + { "Value1" } Equals: { "Value1" } + +------------------------------------------------------------------------------- +parseEnums + Multiple enum values +------------------------------------------------------------------------------- +EnumToString.tests.cpp: +............................................................................... + +EnumToString.tests.cpp:: PASSED: + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2" ), Equals( std::vector{"Value1", "Value2"} ) ) +with expansion: + { "Value1", "Value2" } Equals: { "Value1", "Value2" } + +EnumToString.tests.cpp:: PASSED: + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2, ClassName::EnumName::Value3" ), Equals( std::vector{"Value1", "Value2", "Value3"} ) ) +with expansion: + { "Value1", "Value2", "Value3" } Equals: { "Value1", "Value2", "Value3" } + +EnumToString.tests.cpp:: PASSED: + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1,ClassName::EnumName::Value2 , ClassName::EnumName::Value3" ), Equals( std::vector{"Value1", "Value2", "Value3"} ) ) +with expansion: + { "Value1", "Value2", "Value3" } Equals: { "Value1", "Value2", "Value3" } + ------------------------------------------------------------------------------- pointer to class ------------------------------------------------------------------------------- @@ -10272,6 +10384,27 @@ Tag.tests.cpp:: PASSED: with expansion: { ".", "magic-tag" } ( Contains: "magic-tag" and Contains: "." ) +------------------------------------------------------------------------------- +splitString +------------------------------------------------------------------------------- +String.tests.cpp: +............................................................................... + +String.tests.cpp:: PASSED: + CHECK_THAT( splitString("", ',' ), Equals(std::vector() ) ) +with expansion: + { } Equals: { } + +String.tests.cpp:: PASSED: + CHECK_THAT( splitString("abc", ',' ), Equals(std::vector{"abc"} ) ) +with expansion: + { "abc" } Equals: { "abc" } + +String.tests.cpp:: PASSED: + CHECK_THAT( splitString("abc,def", ',' ), Equals(std::vector{"abc", "def"} ) ) +with expansion: + { "abc", "def" } Equals: { "abc", "def" } + ------------------------------------------------------------------------------- stacks unscoped info in loops ------------------------------------------------------------------------------- @@ -11113,6 +11246,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 257 | 176 passed | 77 failed | 4 failed as expected -assertions: 1435 | 1276 passed | 138 failed | 21 failed as expected +test cases: 261 | 180 passed | 77 failed | 4 failed as expected +assertions: 1453 | 1294 passed | 138 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index e985133b..90ee5838 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -5,7 +5,7 @@ loose text artifact - + @@ -234,6 +234,7 @@ Matchers.tests.cpp: Matchers.tests.cpp: + @@ -615,6 +616,7 @@ Matchers.tests.cpp: + @@ -945,6 +947,9 @@ Message.tests.cpp: + + + @@ -983,6 +988,7 @@ Message.tests.cpp: + Count 1 to 3... diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index 70845085..7fc8fc05 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -2670,6 +2670,35 @@ Nor would this + + + + enumInfo.lookup(0) == "Value1" + + + "Value1" == "Value1" + + + + + enumInfo.lookup(1) == "Value2" + + + "Value2" == "Value2" + + + + + enumInfo.lookup(3) == "{** unexpected value for EnumName: 3**}" + + + "{** unexpected value for EnumName: 3**}" +== +"{** unexpected value for EnumName: 3**}" + + + + @@ -8374,6 +8403,51 @@ Nor would this + + + + stringify( EnumClass3::Value1 ) == "Value1" + + + "Value1" == "Value1" + + + + + stringify( EnumClass3::Value2 ) == "Value2" + + + "Value2" == "Value2" + + + + + stringify( EnumClass3::Value3 ) == "Value3" + + + "Value3" == "Value3" + + + + + stringify( EnumClass3::Value4 ) == "{** unexpected value for EnumClass3: 3**}" + + + "{** unexpected value for EnumClass3: 3**}" +== +"{** unexpected value for EnumClass3: 3**}" + + + + + stringify( ec3 ) == "Value2" + + + "Value2" == "Value2" + + + + @@ -12197,6 +12271,74 @@ loose text artifact + +
+ + + parseEnums( "" ), Equals( std::vector<std::string>{} ) + + + { } Equals: { } + + + +
+
+ + + parseEnums( "ClassName::EnumName::Value1" ), Equals(std::vector<std::string>{"Value1"} ) + + + { "Value1" } Equals: { "Value1" } + + + + + parseEnums( "Value1" ), Equals( std::vector<std::string>{"Value1"} ) + + + { "Value1" } Equals: { "Value1" } + + + + + parseEnums( "EnumName::Value1" ), Equals(std::vector<std::string>{"Value1"} ) + + + { "Value1" } Equals: { "Value1" } + + + +
+
+ + + parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2" ), Equals( std::vector<std::string>{"Value1", "Value2"} ) + + + { "Value1", "Value2" } Equals: { "Value1", "Value2" } + + + + + parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2, ClassName::EnumName::Value3" ), Equals( std::vector<std::string>{"Value1", "Value2", "Value3"} ) + + + { "Value1", "Value2", "Value3" } Equals: { "Value1", "Value2", "Value3" } + + + + + parseEnums( "ClassName::EnumName::Value1,ClassName::EnumName::Value2 , ClassName::EnumName::Value3" ), Equals( std::vector<std::string>{"Value1", "Value2", "Value3"} ) + + + { "Value1", "Value2", "Value3" } Equals: { "Value1", "Value2", "Value3" } + + + +
+ +
@@ -12494,6 +12636,33 @@ loose text artifact + + + + splitString("", ',' ), Equals(std::vector<std::string>() ) + + + { } Equals: { } + + + + + splitString("abc", ',' ), Equals(std::vector<std::string>{"abc"} ) + + + { "abc" } Equals: { "abc" } + + + + + splitString("abc,def", ',' ), Equals(std::vector<std::string>{"abc", "def"} ) + + + { "abc", "def" } Equals: { "abc", "def" } + + + + Count 1 to 3... @@ -13388,7 +13557,7 @@ loose text artifact - + - + diff --git a/projects/SelfTest/IntrospectiveTests/String.tests.cpp b/projects/SelfTest/IntrospectiveTests/String.tests.cpp index ae21bb3c..007774a9 100644 --- a/projects/SelfTest/IntrospectiveTests/String.tests.cpp +++ b/projects/SelfTest/IntrospectiveTests/String.tests.cpp @@ -202,3 +202,14 @@ TEST_CASE( "replaceInPlace", "[Strings][StringManip]" ) { CHECK( s == "didn|'t" ); } } + +TEST_CASE( "splitString", "[Strings]" ) { + using namespace Catch::Matchers; + using Catch::splitString; + + CHECK_THAT( splitString("", ',' ), Equals(std::vector() ) ); + CHECK_THAT( splitString("abc", ',' ), Equals(std::vector{"abc"} ) ); + CHECK_THAT( splitString("abc,def", ',' ), Equals(std::vector{"abc", "def"} ) ); +} + + diff --git a/projects/SelfTest/UsageTests/EnumToString.tests.cpp b/projects/SelfTest/UsageTests/EnumToString.tests.cpp index 0b188a82..bdd36315 100644 --- a/projects/SelfTest/UsageTests/EnumToString.tests.cpp +++ b/projects/SelfTest/UsageTests/EnumToString.tests.cpp @@ -64,3 +64,57 @@ TEST_CASE( "toString(enum class w/operator<<)", "[toString][enum][enumClass]" ) EnumClass2 e3 = static_cast(10); CHECK( ::Catch::Detail::stringify(e3) == "Unknown enum value 10" ); } + +enum class EnumClass3 { Value1, Value2, Value3, Value4 }; + +STRINGIFY_ENUM( EnumClass3, EnumClass3::Value1, EnumClass3::Value2, EnumClass3::Value3 ) + + +TEST_CASE( "STRINGIFY_ENUM" ) { + using Catch::Detail::stringify; + REQUIRE( stringify( EnumClass3::Value1 ) == "Value1" ); + REQUIRE( stringify( EnumClass3::Value2 ) == "Value2" ); + REQUIRE( stringify( EnumClass3::Value3 ) == "Value3" ); + REQUIRE( stringify( EnumClass3::Value4 ) == "{** unexpected value for EnumClass3: 3**}" ); + + EnumClass3 ec3 = EnumClass3 ::Value2; + REQUIRE( stringify( ec3 ) == "Value2" ); +} + +#include "internal/catch_interfaces_enum_values_registry.h" + +TEST_CASE( "EnumInfo" ) { + + auto& hub = Catch::getMutableRegistryHub(); + auto& reg = hub.getMutableEnumValuesRegistry(); + auto const& enumInfo = reg.registerEnum( "EnumName", "EnumName::Value1, EnumName::Value2", {0, 1} ); + + CHECK( enumInfo.lookup(0) == "Value1" ); + CHECK( enumInfo.lookup(1) == "Value2" ); + CHECK( enumInfo.lookup(3) == "{** unexpected value for EnumName: 3**}" ); +} + +#include "internal/catch_enum_values_registry.h" + +TEST_CASE( "parseEnums", "[Strings][enums]" ) { + using namespace Catch::Matchers; + using Catch::Detail::parseEnums; + + SECTION( "No enums" ) + CHECK_THAT( parseEnums( "" ), Equals( std::vector{} ) ); + + SECTION( "One enum value" ) { + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1" ), Equals(std::vector{"Value1"} ) ); + CHECK_THAT( parseEnums( "Value1" ), Equals( std::vector{"Value1"} ) ); + CHECK_THAT( parseEnums( "EnumName::Value1" ), Equals(std::vector{"Value1"} ) ); + } + + SECTION( "Multiple enum values" ) { + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2" ), + Equals( std::vector{"Value1", "Value2"} ) ); + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2, ClassName::EnumName::Value3" ), + Equals( std::vector{"Value1", "Value2", "Value3"} ) ); + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1,ClassName::EnumName::Value2 , ClassName::EnumName::Value3" ), + Equals( std::vector{"Value1", "Value2", "Value3"} ) ); + } +} From 02f13cf95ac826dfa129071a27552b359e55f71c Mon Sep 17 00:00:00 2001 From: Phil nash Date: Thu, 4 Apr 2019 16:02:58 +0100 Subject: [PATCH 02/11] Made onto dev build and regenerated single header --- CMakeLists.txt | 2 +- README.md | 4 +- include/internal/catch_version.cpp | 2 +- single_include/catch2/catch.hpp | 255 ++++++++++++++++++++++++++--- 4 files changed, 238 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea35d24e..92209dd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT DEFINED PROJECT_NAME) set(NOT_SUBPROJECT ON) endif() -project(Catch2 LANGUAGES CXX VERSION 2.7.0) +project(Catch2 LANGUAGES CXX VERSION 2.7.0-develop.2) # Provide path for scripts list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake") diff --git a/README.md b/README.md index b4d997d0..4fdcfc7e 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ [![Build Status](https://travis-ci.org/catchorg/Catch2.svg?branch=master)](https://travis-ci.org/catchorg/Catch2) [![Build status](https://ci.appveyor.com/api/projects/status/github/catchorg/Catch2?svg=true)](https://ci.appveyor.com/project/catchorg/catch2) [![codecov](https://codecov.io/gh/catchorg/Catch2/branch/master/graph/badge.svg)](https://codecov.io/gh/catchorg/Catch2) -[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/byNJIivVphHo170P) +[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/nhhpUBE6TQXvJGCW) [![Join the chat in Discord: https://discord.gg/4CWS9zD](https://img.shields.io/badge/Discord-Chat!-brightgreen.svg)](https://discord.gg/4CWS9zD) -The latest version of the single header can be downloaded directly using this link +The latest version of the single header can be downloaded directly using this link ## Catch2 is released! diff --git a/include/internal/catch_version.cpp b/include/internal/catch_version.cpp index a1d08b2c..3a442bc5 100644 --- a/include/internal/catch_version.cpp +++ b/include/internal/catch_version.cpp @@ -37,7 +37,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 7, 0, "", 0 ); + static Version version( 2, 7, 0, "develop", 2 ); return version; } diff --git a/single_include/catch2/catch.hpp b/single_include/catch2/catch.hpp index 1850fff1..9eaeae18 100644 --- a/single_include/catch2/catch.hpp +++ b/single_include/catch2/catch.hpp @@ -1,6 +1,6 @@ /* - * Catch v2.7.0 - * Generated: 2019-03-07 21:34:30.252164 + * Catch v2.7.0-develop.2 + * Generated: 2019-04-04 16:00:49.937519 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2019 Two Blue Cubes Ltd. All rights reserved. @@ -1166,6 +1166,36 @@ namespace Catch { } // end catch_stream.h +// start catch_interfaces_enum_values_registry.h + +#include + +namespace Catch { + + struct IEnumInfo { + virtual ~IEnumInfo(); + + virtual std::string lookup( int value ) const = 0; + }; + + struct IMutableEnumValuesRegistry { + virtual ~IMutableEnumValuesRegistry(); + + virtual IEnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values ) = 0; + + template + IEnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list values ) { + std::vector intValues; + intValues.reserve( values.size() ); + for( auto enumValue : values ) + intValues.push_back( static_cast( enumValue ) ); + return registerEnum( enumName, allEnums, intValues ); + } + }; + +} // Catch + +// end catch_interfaces_enum_values_registry.h #ifdef CATCH_CONFIG_CPP17_STRING_VIEW #include @@ -1827,6 +1857,20 @@ struct ratio_string { } #endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#define INTERNAL_CATCH_STRINGIFY_ENUM( enumName, ... ) \ + template<> struct ::Catch::StringMaker { \ + static std::string convert( enumName value ) { \ + static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ + return enumInfo.lookup( static_cast( value ) ); \ + } \ + }; + +#ifdef CATCH_CONFIG_PREFIX_ALL +# define CATCH_STRINGIFY_ENUM( enumName, ... ) INTERNAL_CATCH_STRINGIFY_ENUM( enumName, __VA_ARGS__ ) +#else +# define STRINGIFY_ENUM( enumName, ... ) INTERNAL_CATCH_STRINGIFY_ENUM( enumName, __VA_ARGS__ ) +#endif + #ifdef _MSC_VER #pragma warning(pop) #endif @@ -2624,6 +2668,8 @@ namespace Catch { struct IReporterRegistry; struct IReporterFactory; struct ITagAliasRegistry; + struct IMutableEnumValuesRegistry; + class StartupExceptionRegistry; using IReporterFactoryPtr = std::shared_ptr; @@ -2634,7 +2680,6 @@ namespace Catch { virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; - virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; @@ -2648,6 +2693,7 @@ namespace Catch { virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; virtual void registerStartupException() noexcept = 0; + virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; }; IRegistryHub const& getRegistryHub(); @@ -2857,6 +2903,8 @@ struct StringMaker { namespace Catch { + class StringRef; + bool startsWith( std::string const& s, std::string const& prefix ); bool startsWith( std::string const& s, char prefix ); bool endsWith( std::string const& s, std::string const& suffix ); @@ -2866,6 +2914,7 @@ namespace Catch { std::string toLower( std::string const& s ); std::string trim( std::string const& str ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + std::vector splitString( StringRef str, char delimiter ); struct pluralise { pluralise( std::size_t count, std::string const& label ); @@ -3686,7 +3735,11 @@ namespace Generators { } // namespace Catch #define GENERATE( ... ) \ - Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) +#define GENERATE_COPY( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) +#define GENERATE_REF( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) // end catch_generators.hpp // start catch_generators_generic.hpp @@ -3849,16 +3902,28 @@ namespace Generators { } }; - template +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 + // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is + // replaced with std::invoke_result here. Also *_t format is preferred over + // typename *::type format. + template + using MapFunctionReturnType = std::remove_reference_t>>; +#else + template + using MapFunctionReturnType = typename std::remove_reference::type>::type>::type; +#endif + + template > GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { return GeneratorWrapper( pf::make_unique>(std::forward(function), std::move(generator)) ); } - template - GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { + + template + GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { return GeneratorWrapper( - pf::make_unique>(std::forward(function), std::move(generator)) + pf::make_unique>(std::forward(function), std::move(generator)) ); } @@ -4022,6 +4087,7 @@ namespace Catch { virtual ShowDurations::OrNot showDurations() const = 0; virtual TestSpec const& testSpec() const = 0; virtual bool hasTestFilters() const = 0; + virtual std::vector const& getTestsOrTags() const = 0; virtual RunTests::InWhatOrder runOrder() const = 0; virtual unsigned int rngSeed() const = 0; virtual int benchmarkResolutionMultiple() const = 0; @@ -4352,7 +4418,7 @@ namespace Catch { arcSafeRelease( m_substr ); } - bool match( NSString* arg ) const override { + bool match( NSString* const& str ) const override { return false; } @@ -4362,7 +4428,7 @@ namespace Catch { struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} - bool match( NSString* str ) const override { + bool match( NSString* const& str ) const override { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } @@ -4375,7 +4441,7 @@ namespace Catch { struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} - bool match( NSString* str ) const { + bool match( NSString* const& str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } @@ -4388,7 +4454,7 @@ namespace Catch { struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} - bool match( NSString* str ) const override { + bool match( NSString* const& str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } @@ -4400,7 +4466,7 @@ namespace Catch { struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} - bool match( NSString* str ) const override { + bool match( NSString* const& str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } @@ -4708,7 +4774,7 @@ namespace Catch { std::string getProcessName() const; std::string const& getReporterName() const; - std::vector const& getTestsOrTags() const; + std::vector const& getTestsOrTags() const override; std::vector const& getSectionsToRun() const override; virtual TestSpec const& testSpec() const override; @@ -5082,6 +5148,8 @@ namespace Catch { // Returns double formatted as %.3f (format expected on output) std::string getFormattedDuration( double duration ); + std::string serializeFilters( std::vector const& container ); + template struct StreamingReporterBase : IStreamingReporter { @@ -5109,6 +5177,7 @@ namespace Catch { void testRunStarting(TestRunInfo const& _testRunInfo) override { currentTestRunInfo = _testRunInfo; } + void testGroupStarting(GroupInfo const& _groupInfo) override { currentGroupInfo = _groupInfo; } @@ -5521,7 +5590,7 @@ namespace Catch { void testCaseEnded(TestCaseStats const& _testCaseStats) override; void testGroupEnded(TestGroupStats const& _testGroupStats) override; void testRunEnded(TestRunStats const& _testRunStats) override; - + void testRunStarting(TestRunInfo const& _testRunInfo) override; private: void lazyPrint(); @@ -5543,6 +5612,7 @@ namespace Catch { void printTotalsDivider(Totals const& totals); void printSummaryDivider(); + void printTestFilters(); private: bool m_headerPrinted = false; @@ -8525,6 +8595,88 @@ namespace Catch { #endif } // namespace Catch; // end catch_enforce.cpp +// start catch_enum_values_registry.cpp +// start catch_enum_values_registry.h + +#include + +namespace Catch { + + namespace Detail { + + class EnumValuesRegistry : public IMutableEnumValuesRegistry { + + std::vector> m_enumInfos; + + IEnumInfo const& registerEnum(StringRef enumName, StringRef allEnums, std::vector const& values) override; + }; + + std::vector parseEnums( StringRef enums ); + + } // Detail + +} // Catch + +// end catch_enum_values_registry.h + +#include + +namespace Catch { + + IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {} + IEnumInfo::~IEnumInfo() {} + + namespace Detail { + + std::vector parseEnums( StringRef enums ) { + auto enumValues = splitString( enums, ',' ); + std::vector parsed; + parsed.reserve( enumValues.size() ); + for( auto const& enumValue : enumValues ) { + auto identifiers = splitString( enumValue, ':' ); + parsed.push_back( Catch::trim( identifiers.back() ) ); + } + return parsed; + } + + struct EnumInfo : IEnumInfo { + std::string m_name; + std::map m_values; + + ~EnumInfo(); + + std::string lookup( int value ) const override { + auto it = m_values.find( value ); + if( it == m_values.end() ) { + ReusableStringStream rss; + rss << "{** unexpected value for " << m_name << ": " << value << "**}"; + return rss.str(); + } + return it->second; + } + }; + EnumInfo::~EnumInfo() {} + + IEnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector const& values ) { + std::unique_ptr enumInfo( new EnumInfo ); + enumInfo->m_name = enumName; + + const auto valueNames = Catch::Detail::parseEnums( allValueNames ); + assert( valueNames.size() == values.size() ); + std::size_t i = 0; + for( auto value : values ) + enumInfo->m_values.insert({ value, valueNames[i++] }); + + EnumInfo* raw = enumInfo.get(); + m_enumInfos.push_back( std::move( enumInfo ) ); + return *raw; + } + + } // Detail + +} // Catch + +// end catch_enum_values_registry.cpp // start catch_errno_guard.cpp #include @@ -8660,10 +8812,10 @@ namespace Catch { // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. static SignalDefs signalDefs[] = { - { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, - { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, - { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, - { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + { static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), "SIGILL - Illegal instruction signal" }, + { static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow" }, + { static_cast(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal" }, + { static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, }; LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { @@ -10157,6 +10309,9 @@ namespace Catch { void registerStartupException() noexcept override { m_exceptionRegistry.add(std::current_exception()); } + IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override { + return m_enumValuesRegistry; + } private: TestRegistry m_testCaseRegistry; @@ -10164,6 +10319,7 @@ namespace Catch { ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; TagAliasRegistry m_tagAliasRegistry; StartupExceptionRegistry m_exceptionRegistry; + Detail::EnumValuesRegistry m_enumValuesRegistry; }; } @@ -11336,6 +11492,7 @@ namespace Catch { #include #include #include +#include namespace Catch { @@ -11390,6 +11547,21 @@ namespace Catch { return replaced; } + std::vector splitString( StringRef str, char delimiter ) { + std::vector subStrings; + std::size_t start = 0; + for(std::size_t pos = 0; pos < str.size(); ++pos ) { + if( str[pos] == delimiter ) { + if( pos - start > 1 ) + subStrings.push_back( str.substr( start, pos-start ) ); + start = pos+1; + } + } + if( start < str.size() ) + subStrings.push_back( str.substr( start, str.size()-start ) ); + return subStrings; + } + pluralise::pluralise( std::size_t count, std::string const& label ) : m_count( count ), m_label( label ) @@ -11657,6 +11829,12 @@ namespace Catch { else if( prop == TestCaseInfo::None ) enforceNotReservedTag( tag, _lineInfo ); + // Merged hide tags like `[.approvals]` should be added as + // `[.][approvals]`. The `[.]` is added at later point, so + // we only strip the prefix + if (startsWith(tag, '.') && tag.size() > 1) { + tag.erase(0, 1); + } tags.push_back( tag ); tag.clear(); inTag = false; @@ -12665,7 +12843,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 7, 0, "", 0 ); + static Version version( 2, 7, 0, "develop", 2 ); return version; } @@ -13022,6 +13200,21 @@ namespace Catch { return std::string(buffer); } + std::string serializeFilters( std::vector const& container ) { + ReusableStringStream oss; + bool first = true; + for (auto&& filter : container) + { + if (!first) + oss << ' '; + else + first = false; + + oss << filter; + } + return oss.str(); + } + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) :StreamingReporterBase(_config) {} @@ -13746,6 +13939,10 @@ void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { stream << std::endl; StreamingReporterBase::testRunEnded(_testRunStats); } +void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) { + StreamingReporterBase::testRunStarting(_testInfo); + printTestFilters(); +} void ConsoleReporter::lazyPrint() { @@ -13927,6 +14124,11 @@ void ConsoleReporter::printSummaryDivider() { stream << getLineOfChars<'-'>() << '\n'; } +void ConsoleReporter::printTestFilters() { + if (m_config->testSpec().hasFilters()) + stream << Colour(Colour::BrightYellow) << "Filters: " << serializeFilters( m_config->getTestsOrTags() ) << '\n'; +} + CATCH_REGISTER_REPORTER("console", ConsoleReporter) } // end namespace Catch @@ -14000,8 +14202,17 @@ namespace Catch { void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); + + if ( m_config->hasTestFilters() || m_config->rngSeed() != 0 ) + xml.startElement("properties"); + + if ( m_config->hasTestFilters() ) { + xml.scopedElement( "property" ) + .writeAttribute( "name" , "filters" ) + .writeAttribute( "value" , serializeFilters( m_config->getTestsOrTags() ) ); + } + if( m_config->rngSeed() != 0 ) { - xml.startElement( "properties" ); xml.scopedElement( "property" ) .writeAttribute( "name", "random-seed" ) .writeAttribute( "value", m_config->rngSeed() ); @@ -14357,6 +14568,8 @@ namespace Catch { m_xml.startElement( "Catch" ); if( !m_config->name().empty() ) m_xml.writeAttribute( "name", m_config->name() ); + if (m_config->testSpec().hasFilters()) + m_xml.writeAttribute( "filters", serializeFilters( m_config->getTestsOrTags() ) ); if( m_config->rngSeed() != 0 ) m_xml.scopedElement( "Randomness" ) .writeAttribute( "seed", m_config->rngSeed() ); From 9d5d71986831d044527fe987ed8c41a4f69a3eea Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Sun, 21 Apr 2019 20:03:44 +0300 Subject: [PATCH 03/11] Changed splitString to splitStringRef Now takes and returns StringRefs --- CMakeLists.txt | 2 +- include/internal/catch_enum_values_registry.cpp | 4 ++-- include/internal/catch_string_manip.cpp | 4 ++-- include/internal/catch_string_manip.h | 4 +++- projects/SelfTest/IntrospectiveTests/String.tests.cpp | 9 +++++---- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92209dd2..ea35d24e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT DEFINED PROJECT_NAME) set(NOT_SUBPROJECT ON) endif() -project(Catch2 LANGUAGES CXX VERSION 2.7.0-develop.2) +project(Catch2 LANGUAGES CXX VERSION 2.7.0) # Provide path for scripts list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake") diff --git a/include/internal/catch_enum_values_registry.cpp b/include/internal/catch_enum_values_registry.cpp index e974a67d..b94a70e5 100644 --- a/include/internal/catch_enum_values_registry.cpp +++ b/include/internal/catch_enum_values_registry.cpp @@ -19,11 +19,11 @@ namespace Catch { namespace Detail { std::vector parseEnums( StringRef enums ) { - auto enumValues = splitString( enums, ',' ); + auto enumValues = splitStringRef( enums, ',' ); std::vector parsed; parsed.reserve( enumValues.size() ); for( auto const& enumValue : enumValues ) { - auto identifiers = splitString( enumValue, ':' ); + auto identifiers = splitStringRef( enumValue, ':' ); parsed.push_back( Catch::trim( identifiers.back() ) ); } return parsed; diff --git a/include/internal/catch_string_manip.cpp b/include/internal/catch_string_manip.cpp index 60109335..9f459de0 100644 --- a/include/internal/catch_string_manip.cpp +++ b/include/internal/catch_string_manip.cpp @@ -67,8 +67,8 @@ namespace Catch { return replaced; } - std::vector splitString( StringRef str, char delimiter ) { - std::vector subStrings; + std::vector splitStringRef( StringRef str, char delimiter ) { + std::vector subStrings; std::size_t start = 0; for(std::size_t pos = 0; pos < str.size(); ++pos ) { if( str[pos] == delimiter ) { diff --git a/include/internal/catch_string_manip.h b/include/internal/catch_string_manip.h index 0f8176d6..32a7ec1e 100644 --- a/include/internal/catch_string_manip.h +++ b/include/internal/catch_string_manip.h @@ -22,8 +22,10 @@ namespace Catch { void toLowerInPlace( std::string& s ); std::string toLower( std::string const& s ); std::string trim( std::string const& str ); + + // !!! Be aware, returns refs into original string - make sure original string outlives them + std::vector splitStringRef( StringRef str, char delimiter ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); - std::vector splitString( StringRef str, char delimiter ); struct pluralise { pluralise( std::size_t count, std::string const& label ); diff --git a/projects/SelfTest/IntrospectiveTests/String.tests.cpp b/projects/SelfTest/IntrospectiveTests/String.tests.cpp index 007774a9..cb2c350c 100644 --- a/projects/SelfTest/IntrospectiveTests/String.tests.cpp +++ b/projects/SelfTest/IntrospectiveTests/String.tests.cpp @@ -205,11 +205,12 @@ TEST_CASE( "replaceInPlace", "[Strings][StringManip]" ) { TEST_CASE( "splitString", "[Strings]" ) { using namespace Catch::Matchers; - using Catch::splitString; + using Catch::splitStringRef; + using Catch::StringRef; - CHECK_THAT( splitString("", ',' ), Equals(std::vector() ) ); - CHECK_THAT( splitString("abc", ',' ), Equals(std::vector{"abc"} ) ); - CHECK_THAT( splitString("abc,def", ',' ), Equals(std::vector{"abc", "def"} ) ); + CHECK_THAT( splitStringRef("", ',' ), Equals(std::vector() ) ); + CHECK_THAT( splitStringRef("abc", ',' ), Equals(std::vector{"abc"} ) ); + CHECK_THAT( splitStringRef("abc,def", ',' ), Equals(std::vector{"abc", "def"} ) ); } From 5a74fcc9c9c4f19c410f38070cefb11576df7386 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Sun, 21 Apr 2019 19:56:19 +0300 Subject: [PATCH 04/11] Removed IEnumInfo (just use EnumInfo directly) --- .../internal/catch_enum_values_registry.cpp | 30 +++++++------------ include/internal/catch_enum_values_registry.h | 4 +-- .../catch_interfaces_enum_values_registry.h | 17 +++++++---- .../UsageTests/EnumToString.tests.cpp | 4 +-- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/include/internal/catch_enum_values_registry.cpp b/include/internal/catch_enum_values_registry.cpp index b94a70e5..9204e047 100644 --- a/include/internal/catch_enum_values_registry.cpp +++ b/include/internal/catch_enum_values_registry.cpp @@ -14,7 +14,6 @@ namespace Catch { IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {} - IEnumInfo::~IEnumInfo() {} namespace Detail { @@ -29,25 +28,17 @@ namespace Catch { return parsed; } - struct EnumInfo : IEnumInfo { - std::string m_name; - std::map m_values; - - ~EnumInfo(); - - std::string lookup( int value ) const override { - auto it = m_values.find( value ); - if( it == m_values.end() ) { - ReusableStringStream rss; - rss << "{** unexpected value for " << m_name << ": " << value << "**}"; - return rss.str(); - } - return it->second; - } - }; EnumInfo::~EnumInfo() {} - IEnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector const& values ) { + StringRef EnumInfo::lookup( int value ) const { + for( auto const& valueToName : m_values ) { + if( valueToName.first == value ) + return valueToName.second; + } + return "{** unexpected enum value **}"; + } + + EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector const& values ) { std::unique_ptr enumInfo( new EnumInfo ); enumInfo->m_name = enumName; @@ -55,7 +46,7 @@ namespace Catch { assert( valueNames.size() == values.size() ); std::size_t i = 0; for( auto value : values ) - enumInfo->m_values.insert({ value, valueNames[i++] }); + enumInfo->m_values.push_back({ value, valueNames[i++] }); EnumInfo* raw = enumInfo.get(); m_enumInfos.push_back( std::move( enumInfo ) ); @@ -63,6 +54,5 @@ namespace Catch { } } // Detail - } // Catch diff --git a/include/internal/catch_enum_values_registry.h b/include/internal/catch_enum_values_registry.h index 8103bb9d..b6943ec2 100644 --- a/include/internal/catch_enum_values_registry.h +++ b/include/internal/catch_enum_values_registry.h @@ -18,9 +18,9 @@ namespace Catch { class EnumValuesRegistry : public IMutableEnumValuesRegistry { - std::vector> m_enumInfos; + std::vector> m_enumInfos; - IEnumInfo const& registerEnum(StringRef enumName, StringRef allEnums, std::vector const& values) override; + EnumInfo const& registerEnum(StringRef enumName, StringRef allEnums, std::vector const& values) override; }; std::vector parseEnums( StringRef enums ); diff --git a/include/internal/catch_interfaces_enum_values_registry.h b/include/internal/catch_interfaces_enum_values_registry.h index b94ee69b..fea6847d 100644 --- a/include/internal/catch_interfaces_enum_values_registry.h +++ b/include/internal/catch_interfaces_enum_values_registry.h @@ -14,19 +14,24 @@ namespace Catch { - struct IEnumInfo { - virtual ~IEnumInfo(); + namespace Detail { + struct EnumInfo { + StringRef m_name; + std::vector> m_values; - virtual std::string lookup( int value ) const = 0; - }; + ~EnumInfo(); + + StringRef lookup( int value ) const; + }; + } // namespace Detail struct IMutableEnumValuesRegistry { virtual ~IMutableEnumValuesRegistry(); - virtual IEnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values ) = 0; + virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values ) = 0; template - IEnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list values ) { + Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list values ) { std::vector intValues; intValues.reserve( values.size() ); for( auto enumValue : values ) diff --git a/projects/SelfTest/UsageTests/EnumToString.tests.cpp b/projects/SelfTest/UsageTests/EnumToString.tests.cpp index bdd36315..f3fd2f6d 100644 --- a/projects/SelfTest/UsageTests/EnumToString.tests.cpp +++ b/projects/SelfTest/UsageTests/EnumToString.tests.cpp @@ -75,7 +75,7 @@ TEST_CASE( "STRINGIFY_ENUM" ) { REQUIRE( stringify( EnumClass3::Value1 ) == "Value1" ); REQUIRE( stringify( EnumClass3::Value2 ) == "Value2" ); REQUIRE( stringify( EnumClass3::Value3 ) == "Value3" ); - REQUIRE( stringify( EnumClass3::Value4 ) == "{** unexpected value for EnumClass3: 3**}" ); + REQUIRE( stringify( EnumClass3::Value4 ) == "{** unexpected enum value **}" ); EnumClass3 ec3 = EnumClass3 ::Value2; REQUIRE( stringify( ec3 ) == "Value2" ); @@ -91,7 +91,7 @@ TEST_CASE( "EnumInfo" ) { CHECK( enumInfo.lookup(0) == "Value1" ); CHECK( enumInfo.lookup(1) == "Value2" ); - CHECK( enumInfo.lookup(3) == "{** unexpected value for EnumName: 3**}" ); + CHECK( enumInfo.lookup(3) == "{** unexpected enum value **}" ); } #include "internal/catch_enum_values_registry.h" From 346723c9b62d06e027c329fa5213e7e6553bb2ac Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Sun, 21 Apr 2019 20:15:26 +0300 Subject: [PATCH 05/11] Renamed STRINGIFY_ENUM to REGISTER_ENUM --- include/internal/catch_tostring.h | 6 +++--- projects/SelfTest/UsageTests/EnumToString.tests.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index dc236dea..d2b43920 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -640,7 +640,7 @@ struct ratio_string { } #endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#define INTERNAL_CATCH_STRINGIFY_ENUM( enumName, ... ) \ +#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ template<> struct ::Catch::StringMaker { \ static std::string convert( enumName value ) { \ static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ @@ -649,9 +649,9 @@ struct ratio_string { }; #ifdef CATCH_CONFIG_PREFIX_ALL -# define CATCH_STRINGIFY_ENUM( enumName, ... ) INTERNAL_CATCH_STRINGIFY_ENUM( enumName, __VA_ARGS__ ) +# define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) #else -# define STRINGIFY_ENUM( enumName, ... ) INTERNAL_CATCH_STRINGIFY_ENUM( enumName, __VA_ARGS__ ) +# define REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) #endif #ifdef _MSC_VER diff --git a/projects/SelfTest/UsageTests/EnumToString.tests.cpp b/projects/SelfTest/UsageTests/EnumToString.tests.cpp index f3fd2f6d..49ea9ca2 100644 --- a/projects/SelfTest/UsageTests/EnumToString.tests.cpp +++ b/projects/SelfTest/UsageTests/EnumToString.tests.cpp @@ -67,10 +67,10 @@ TEST_CASE( "toString(enum class w/operator<<)", "[toString][enum][enumClass]" ) enum class EnumClass3 { Value1, Value2, Value3, Value4 }; -STRINGIFY_ENUM( EnumClass3, EnumClass3::Value1, EnumClass3::Value2, EnumClass3::Value3 ) +REGISTER_ENUM( EnumClass3, EnumClass3::Value1, EnumClass3::Value2, EnumClass3::Value3 ) -TEST_CASE( "STRINGIFY_ENUM" ) { +TEST_CASE( "REGISTER_ENUM" ) { using Catch::Detail::stringify; REQUIRE( stringify( EnumClass3::Value1 ) == "Value1" ); REQUIRE( stringify( EnumClass3::Value2 ) == "Value2" ); From 541f1ed1b3be769044b7cbb0f35770df73a1d219 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Sun, 21 Apr 2019 20:26:46 +0300 Subject: [PATCH 06/11] Only provide CATCH_REGISTER_ENUM No longer have version without the CATCH_ prefix --- include/internal/catch_tostring.h | 6 +----- projects/SelfTest/UsageTests/EnumToString.tests.cpp | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index d2b43920..07bbc3b6 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -648,11 +648,7 @@ struct ratio_string { } \ }; -#ifdef CATCH_CONFIG_PREFIX_ALL -# define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) -#else -# define REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) -#endif +#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) #ifdef _MSC_VER #pragma warning(pop) diff --git a/projects/SelfTest/UsageTests/EnumToString.tests.cpp b/projects/SelfTest/UsageTests/EnumToString.tests.cpp index 49ea9ca2..ee68968c 100644 --- a/projects/SelfTest/UsageTests/EnumToString.tests.cpp +++ b/projects/SelfTest/UsageTests/EnumToString.tests.cpp @@ -67,7 +67,7 @@ TEST_CASE( "toString(enum class w/operator<<)", "[toString][enum][enumClass]" ) enum class EnumClass3 { Value1, Value2, Value3, Value4 }; -REGISTER_ENUM( EnumClass3, EnumClass3::Value1, EnumClass3::Value2, EnumClass3::Value3 ) +CATCH_REGISTER_ENUM( EnumClass3, EnumClass3::Value1, EnumClass3::Value2, EnumClass3::Value3 ) TEST_CASE( "REGISTER_ENUM" ) { From e02d9e788fe201094ed365a140a8beb39cfb97c5 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Sun, 21 Apr 2019 20:32:20 +0300 Subject: [PATCH 07/11] Document CATCH_REGISTER_ENUM --- docs/tostring.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/tostring.md b/docs/tostring.md index 933f2e61..60dc0663 100644 --- a/docs/tostring.md +++ b/docs/tostring.md @@ -6,6 +6,7 @@ [Catch::StringMaker specialisation](#catchstringmaker-specialisation)
[Catch::is_range specialisation](#catchis_range-specialisation)
[Exceptions](#exceptions)
+[Enums](#enums)
Catch needs to be able to convert types you use in assertions and logging expressions into strings (for logging and reporting purposes). Most built-in or std types are supported out of the box but there are two ways that you can tell Catch how to convert your own types (or other, third-party types) into strings. @@ -66,6 +67,44 @@ CATCH_TRANSLATE_EXCEPTION( MyType& ex ) { } ``` +## Enums + +Enums that already have a `<<` overload for `std::ostream` will convert to strings as expected. +If you only need to convert enums to strings for test reporting purposes you can provide a `StringMaker` specialisations as any other type. +However, as a convenience, Catch provides the `REGISTER_ENUM` helper macro that will generate the `StringMaker` specialiation for you with minimal code. +Simply provide it the (qualified) enum name, followed by all the enum values, and you're done! + +E.g. + +``` +enum class Fruits { Banana, Apple, Mango }; + +CATCH_REGISTER_ENUM( Fruits, Fruits::Banana, Fruits::Apple, Fruits::Mango ); + +TEST_CASE() { + REQUIRE( Fruits::Mango == Fruits::Apple ); +} +``` + +... or if the enum is in a namespace: +``` +namespace Bikeshed { + enum class Colours { Red, Green, Blue }; +} + +// Important!: This macro must appear at top level scope - not inside a namespace +// You can fully qualify the names, or use a using if you prefer +CATCH_REGISTER_ENUM( Bikeshed::Colours, + Bikeshed::Colours::Red, + Bikeshed::Colours::Green, + Bikeshed::Colours::Blue ); + +TEST_CASE() { + REQUIRE( Bikeshed::Colours::Red == Bikeshed::Colours::Blue ); +} +``` + + --- [Home](Readme.md#top) From f2ee4f17ad7040160eea73b326d431b4112ad25b Mon Sep 17 00:00:00 2001 From: Phil nash Date: Thu, 25 Apr 2019 10:13:11 +0100 Subject: [PATCH 08/11] Moved enum tests that depend on internals to IntrospectiveTests. - also factored out makeEnumInfo, so tests don't need to touch registry - and added usage test that involves namespace --- .../internal/catch_enum_values_registry.cpp | 8 ++- include/internal/catch_enum_values_registry.h | 4 +- projects/CMakeLists.txt | 1 + .../IntrospectiveTests/ToString.tests.cpp | 42 ++++++++++++++++ .../UsageTests/EnumToString.tests.cpp | 49 ++++++------------- 5 files changed, 67 insertions(+), 37 deletions(-) create mode 100644 projects/SelfTest/IntrospectiveTests/ToString.tests.cpp diff --git a/include/internal/catch_enum_values_registry.cpp b/include/internal/catch_enum_values_registry.cpp index 9204e047..a4dd6127 100644 --- a/include/internal/catch_enum_values_registry.cpp +++ b/include/internal/catch_enum_values_registry.cpp @@ -38,9 +38,10 @@ namespace Catch { return "{** unexpected enum value **}"; } - EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector const& values ) { + std::unique_ptr makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector const& values ) { std::unique_ptr enumInfo( new EnumInfo ); enumInfo->m_name = enumName; + enumInfo->m_values.reserve( values.size() ); const auto valueNames = Catch::Detail::parseEnums( allValueNames ); assert( valueNames.size() == values.size() ); @@ -48,6 +49,11 @@ namespace Catch { for( auto value : values ) enumInfo->m_values.push_back({ value, valueNames[i++] }); + return enumInfo; + } + + EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector const& values ) { + auto enumInfo = makeEnumInfo( enumName, allValueNames, values ); EnumInfo* raw = enumInfo.get(); m_enumInfos.push_back( std::move( enumInfo ) ); return *raw; diff --git a/include/internal/catch_enum_values_registry.h b/include/internal/catch_enum_values_registry.h index b6943ec2..985687bd 100644 --- a/include/internal/catch_enum_values_registry.h +++ b/include/internal/catch_enum_values_registry.h @@ -16,11 +16,13 @@ namespace Catch { namespace Detail { + std::unique_ptr makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector const& values ); + class EnumValuesRegistry : public IMutableEnumValuesRegistry { std::vector> m_enumInfos; - EnumInfo const& registerEnum(StringRef enumName, StringRef allEnums, std::vector const& values) override; + EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values) override; }; std::vector parseEnums( StringRef enums ); diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 36263636..0d1c275d 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -22,6 +22,7 @@ set(TEST_SOURCES ${SELF_TEST_DIR}/IntrospectiveTests/Tag.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/String.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/Xml.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/ToString.tests.cpp ${SELF_TEST_DIR}/UsageTests/Approx.tests.cpp ${SELF_TEST_DIR}/UsageTests/BDD.tests.cpp ${SELF_TEST_DIR}/UsageTests/Benchmark.tests.cpp diff --git a/projects/SelfTest/IntrospectiveTests/ToString.tests.cpp b/projects/SelfTest/IntrospectiveTests/ToString.tests.cpp new file mode 100644 index 00000000..fc0e2a4f --- /dev/null +++ b/projects/SelfTest/IntrospectiveTests/ToString.tests.cpp @@ -0,0 +1,42 @@ +#include "catch.hpp" + +#include "internal/catch_enum_values_registry.h" + +enum class EnumClass3 { Value1, Value2, Value3, Value4 }; + + +TEST_CASE( "parseEnums", "[Strings][enums]" ) { + using namespace Catch::Matchers; + using Catch::Detail::parseEnums; + + SECTION( "No enums" ) + CHECK_THAT( parseEnums( "" ), Equals( std::vector{} ) ); + + SECTION( "One enum value" ) { + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1" ), + Equals(std::vector{"Value1"} ) ); + CHECK_THAT( parseEnums( "Value1" ), + Equals( std::vector{"Value1"} ) ); + CHECK_THAT( parseEnums( "EnumName::Value1" ), + Equals(std::vector{"Value1"} ) ); + } + + SECTION( "Multiple enum values" ) { + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2" ), + Equals( std::vector{"Value1", "Value2"} ) ); + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2, ClassName::EnumName::Value3" ), + Equals( std::vector{"Value1", "Value2", "Value3"} ) ); + CHECK_THAT( parseEnums( "ClassName::EnumName::Value1,ClassName::EnumName::Value2 , ClassName::EnumName::Value3" ), + Equals( std::vector{"Value1", "Value2", "Value3"} ) ); + } +} + +TEST_CASE( "Directly creating an EnumInfo" ) { + + using namespace Catch::Detail; + std::unique_ptr enumInfo = makeEnumInfo( "EnumName", "EnumName::Value1, EnumName::Value2", {0, 1} ); + + CHECK( enumInfo->lookup(0) == "Value1" ); + CHECK( enumInfo->lookup(1) == "Value2" ); + CHECK( enumInfo->lookup(3) == "{** unexpected enum value **}" ); +} diff --git a/projects/SelfTest/UsageTests/EnumToString.tests.cpp b/projects/SelfTest/UsageTests/EnumToString.tests.cpp index ee68968c..81ec5937 100644 --- a/projects/SelfTest/UsageTests/EnumToString.tests.cpp +++ b/projects/SelfTest/UsageTests/EnumToString.tests.cpp @@ -61,7 +61,7 @@ TEST_CASE( "toString(enum class w/operator<<)", "[toString][enum][enumClass]" ) EnumClass2 e1 = EnumClass2::EnumClass2Value1; CHECK( ::Catch::Detail::stringify(e1) == "E2/V1" ); - EnumClass2 e3 = static_cast(10); + auto e3 = static_cast(10); CHECK( ::Catch::Detail::stringify(e3) == "Unknown enum value 10" ); } @@ -70,7 +70,7 @@ enum class EnumClass3 { Value1, Value2, Value3, Value4 }; CATCH_REGISTER_ENUM( EnumClass3, EnumClass3::Value1, EnumClass3::Value2, EnumClass3::Value3 ) -TEST_CASE( "REGISTER_ENUM" ) { +TEST_CASE( "Enums can quickly have stringification enabled using REGISTER_ENUM" ) { using Catch::Detail::stringify; REQUIRE( stringify( EnumClass3::Value1 ) == "Value1" ); REQUIRE( stringify( EnumClass3::Value2 ) == "Value2" ); @@ -81,40 +81,19 @@ TEST_CASE( "REGISTER_ENUM" ) { REQUIRE( stringify( ec3 ) == "Value2" ); } -#include "internal/catch_interfaces_enum_values_registry.h" - -TEST_CASE( "EnumInfo" ) { - - auto& hub = Catch::getMutableRegistryHub(); - auto& reg = hub.getMutableEnumValuesRegistry(); - auto const& enumInfo = reg.registerEnum( "EnumName", "EnumName::Value1, EnumName::Value2", {0, 1} ); - - CHECK( enumInfo.lookup(0) == "Value1" ); - CHECK( enumInfo.lookup(1) == "Value2" ); - CHECK( enumInfo.lookup(3) == "{** unexpected enum value **}" ); +namespace Bikeshed { + enum class Colours { Red, Green, Blue }; } -#include "internal/catch_enum_values_registry.h" +// Important!: This macro must appear at top level scope - not inside a namespace +// You can fully qualify the names, or use a using if you prefer +CATCH_REGISTER_ENUM( Bikeshed::Colours, + Bikeshed::Colours::Red, + Bikeshed::Colours::Green, + Bikeshed::Colours::Blue ); -TEST_CASE( "parseEnums", "[Strings][enums]" ) { - using namespace Catch::Matchers; - using Catch::Detail::parseEnums; - - SECTION( "No enums" ) - CHECK_THAT( parseEnums( "" ), Equals( std::vector{} ) ); - - SECTION( "One enum value" ) { - CHECK_THAT( parseEnums( "ClassName::EnumName::Value1" ), Equals(std::vector{"Value1"} ) ); - CHECK_THAT( parseEnums( "Value1" ), Equals( std::vector{"Value1"} ) ); - CHECK_THAT( parseEnums( "EnumName::Value1" ), Equals(std::vector{"Value1"} ) ); - } - - SECTION( "Multiple enum values" ) { - CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2" ), - Equals( std::vector{"Value1", "Value2"} ) ); - CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2, ClassName::EnumName::Value3" ), - Equals( std::vector{"Value1", "Value2", "Value3"} ) ); - CHECK_THAT( parseEnums( "ClassName::EnumName::Value1,ClassName::EnumName::Value2 , ClassName::EnumName::Value3" ), - Equals( std::vector{"Value1", "Value2", "Value3"} ) ); - } +TEST_CASE( "Enums in namespaces can quickly have stringification enabled using REGISTER_ENUM" ) { + using Catch::Detail::stringify; + REQUIRE( stringify( Bikeshed::Colours::Red ) == "Red" ); + REQUIRE( stringify( Bikeshed::Colours::Blue ) == "Blue" ); } From daeb5a87e61f0c08d354e60ef5b91a64faaab853 Mon Sep 17 00:00:00 2001 From: Phil nash Date: Thu, 25 Apr 2019 10:23:58 +0100 Subject: [PATCH 09/11] Removed global qualification of specialisation --- include/internal/catch_tostring.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index 07bbc3b6..402b6dbc 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -641,7 +641,7 @@ struct ratio_string { #endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER #define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ - template<> struct ::Catch::StringMaker { \ + template<> struct Catch::StringMaker { \ static std::string convert( enumName value ) { \ static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ return enumInfo.lookup( static_cast( value ) ); \ From 08c8df1e3b20b949751dabeb86e5015484f777fc Mon Sep 17 00:00:00 2001 From: Phil nash Date: Thu, 25 Apr 2019 10:32:55 +0100 Subject: [PATCH 10/11] include StringRef, rather than fwd decl, for splitString --- include/internal/catch_string_manip.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/internal/catch_string_manip.h b/include/internal/catch_string_manip.h index 32a7ec1e..f7aa6d4b 100644 --- a/include/internal/catch_string_manip.h +++ b/include/internal/catch_string_manip.h @@ -7,13 +7,13 @@ #ifndef TWOBLUECUBES_CATCH_STRING_MANIP_H_INCLUDED #define TWOBLUECUBES_CATCH_STRING_MANIP_H_INCLUDED +#include "catch_stringref.h" + #include #include namespace Catch { - class StringRef; - bool startsWith( std::string const& s, std::string const& prefix ); bool startsWith( std::string const& s, char prefix ); bool endsWith( std::string const& s, std::string const& suffix ); From 69817831780b9b833c792d8c8ccd60f9b279c343 Mon Sep 17 00:00:00 2001 From: Phil nash Date: Thu, 25 Apr 2019 14:19:00 +0100 Subject: [PATCH 11/11] Added some missing #includes --- include/internal/catch_enum_values_registry.cpp | 1 + include/internal/catch_string_manip.h | 1 + 2 files changed, 2 insertions(+) diff --git a/include/internal/catch_enum_values_registry.cpp b/include/internal/catch_enum_values_registry.cpp index a4dd6127..9e6f2ecb 100644 --- a/include/internal/catch_enum_values_registry.cpp +++ b/include/internal/catch_enum_values_registry.cpp @@ -10,6 +10,7 @@ #include "catch_stream.h" #include +#include namespace Catch { diff --git a/include/internal/catch_string_manip.h b/include/internal/catch_string_manip.h index f7aa6d4b..b551ded6 100644 --- a/include/internal/catch_string_manip.h +++ b/include/internal/catch_string_manip.h @@ -11,6 +11,7 @@ #include #include +#include namespace Catch {