Merge branch enum stringification work

This commit is contained in:
Phil nash 2019-04-26 11:26:45 +01:00
commit 46066ede17
13 changed files with 316 additions and 2 deletions

View File

@ -6,6 +6,7 @@
[Catch::StringMaker specialisation](#catchstringmaker-specialisation)<br> [Catch::StringMaker specialisation](#catchstringmaker-specialisation)<br>
[Catch::is_range specialisation](#catchis_range-specialisation)<br> [Catch::is_range specialisation](#catchis_range-specialisation)<br>
[Exceptions](#exceptions)<br> [Exceptions](#exceptions)<br>
[Enums](#enums)<br>
Catch needs to be able to convert types you use in assertions and logging expressions into strings (for logging and reporting purposes). 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. 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) [Home](Readme.md#top)

View File

@ -0,0 +1,65 @@
/*
* 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 <map>
#include <cassert>
namespace Catch {
IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {}
namespace Detail {
std::vector<std::string> parseEnums( StringRef enums ) {
auto enumValues = splitStringRef( enums, ',' );
std::vector<std::string> parsed;
parsed.reserve( enumValues.size() );
for( auto const& enumValue : enumValues ) {
auto identifiers = splitStringRef( enumValue, ':' );
parsed.push_back( Catch::trim( identifiers.back() ) );
}
return parsed;
}
EnumInfo::~EnumInfo() {}
StringRef EnumInfo::lookup( int value ) const {
for( auto const& valueToName : m_values ) {
if( valueToName.first == value )
return valueToName.second;
}
return "{** unexpected enum value **}";
}
std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {
std::unique_ptr<EnumInfo> 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() );
std::size_t i = 0;
for( auto value : values )
enumInfo->m_values.push_back({ value, valueNames[i++] });
return enumInfo;
}
EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {
auto enumInfo = makeEnumInfo( enumName, allValueNames, values );
EnumInfo* raw = enumInfo.get();
m_enumInfos.push_back( std::move( enumInfo ) );
return *raw;
}
} // Detail
} // Catch

View File

@ -0,0 +1,34 @@
/*
* 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 <vector>
namespace Catch {
namespace Detail {
std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values );
class EnumValuesRegistry : public IMutableEnumValuesRegistry {
std::vector<std::unique_ptr<EnumInfo>> m_enumInfos;
EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values) override;
};
std::vector<std::string> parseEnums( StringRef enums );
} // Detail
} // Catch
#endif //TWOBLUECUBES_CATCH_ENUMVALUESREGISTRY_H_INCLUDED

View File

@ -0,0 +1,45 @@
/*
* 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 <vector>
namespace Catch {
namespace Detail {
struct EnumInfo {
StringRef m_name;
std::vector<std::pair<int, std::string>> m_values;
~EnumInfo();
StringRef lookup( int value ) const;
};
} // namespace Detail
struct IMutableEnumValuesRegistry {
virtual ~IMutableEnumValuesRegistry();
virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values ) = 0;
template<typename E>
Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list<E> values ) {
std::vector<int> intValues;
intValues.reserve( values.size() );
for( auto enumValue : values )
intValues.push_back( static_cast<int>( enumValue ) );
return registerEnum( enumName, allEnums, intValues );
}
};
} // Catch
#endif //TWOBLUECUBES_CATCH_INTERFACESENUMVALUESREGISTRY_H_INCLUDED

View File

@ -22,6 +22,8 @@ namespace Catch {
struct IReporterRegistry; struct IReporterRegistry;
struct IReporterFactory; struct IReporterFactory;
struct ITagAliasRegistry; struct ITagAliasRegistry;
struct IMutableEnumValuesRegistry;
class StartupExceptionRegistry; class StartupExceptionRegistry;
using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;
@ -32,7 +34,6 @@ namespace Catch {
virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual IReporterRegistry const& getReporterRegistry() const = 0;
virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;
virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0;
virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0;
@ -47,6 +48,7 @@ namespace Catch {
virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;
virtual void registerStartupException() noexcept = 0; virtual void registerStartupException() noexcept = 0;
virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0;
}; };
IRegistryHub const& getRegistryHub(); IRegistryHub const& getRegistryHub();

View File

@ -15,6 +15,7 @@
#include "catch_tag_alias_registry.h" #include "catch_tag_alias_registry.h"
#include "catch_startup_exception_registry.h" #include "catch_startup_exception_registry.h"
#include "catch_singletons.hpp" #include "catch_singletons.hpp"
#include "catch_enum_values_registry.h"
namespace Catch { namespace Catch {
@ -60,6 +61,9 @@ namespace Catch {
void registerStartupException() noexcept override { void registerStartupException() noexcept override {
m_exceptionRegistry.add(std::current_exception()); m_exceptionRegistry.add(std::current_exception());
} }
IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override {
return m_enumValuesRegistry;
}
private: private:
TestRegistry m_testCaseRegistry; TestRegistry m_testCaseRegistry;
@ -67,6 +71,7 @@ namespace Catch {
ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
TagAliasRegistry m_tagAliasRegistry; TagAliasRegistry m_tagAliasRegistry;
StartupExceptionRegistry m_exceptionRegistry; StartupExceptionRegistry m_exceptionRegistry;
Detail::EnumValuesRegistry m_enumValuesRegistry;
}; };
} }

View File

@ -6,11 +6,13 @@
*/ */
#include "catch_string_manip.h" #include "catch_string_manip.h"
#include "catch_stringref.h"
#include <algorithm> #include <algorithm>
#include <ostream> #include <ostream>
#include <cstring> #include <cstring>
#include <cctype> #include <cctype>
#include <vector>
namespace Catch { namespace Catch {
@ -65,6 +67,21 @@ namespace Catch {
return replaced; return replaced;
} }
std::vector<StringRef> splitStringRef( StringRef str, char delimiter ) {
std::vector<StringRef> 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 ) pluralise::pluralise( std::size_t count, std::string const& label )
: m_count( count ), : m_count( count ),
m_label( label ) m_label( label )

View File

@ -7,8 +7,11 @@
#ifndef TWOBLUECUBES_CATCH_STRING_MANIP_H_INCLUDED #ifndef TWOBLUECUBES_CATCH_STRING_MANIP_H_INCLUDED
#define TWOBLUECUBES_CATCH_STRING_MANIP_H_INCLUDED #define TWOBLUECUBES_CATCH_STRING_MANIP_H_INCLUDED
#include "catch_stringref.h"
#include <string> #include <string>
#include <iosfwd> #include <iosfwd>
#include <vector>
namespace Catch { namespace Catch {
@ -20,6 +23,9 @@ namespace Catch {
void toLowerInPlace( std::string& s ); void toLowerInPlace( std::string& s );
std::string toLower( std::string const& s ); std::string toLower( std::string const& s );
std::string trim( std::string const& str ); std::string trim( std::string const& str );
// !!! Be aware, returns refs into original string - make sure original string outlives them
std::vector<StringRef> splitStringRef( StringRef str, char delimiter );
bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
struct pluralise { struct pluralise {

View File

@ -15,6 +15,7 @@
#include <string> #include <string>
#include "catch_compiler_capabilities.h" #include "catch_compiler_capabilities.h"
#include "catch_stream.h" #include "catch_stream.h"
#include "catch_interfaces_enum_values_registry.h"
#ifdef CATCH_CONFIG_CPP17_STRING_VIEW #ifdef CATCH_CONFIG_CPP17_STRING_VIEW
#include <string_view> #include <string_view>
@ -639,6 +640,15 @@ struct ratio_string<std::milli> {
} }
#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER #endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \
template<> struct Catch::StringMaker<enumName> { \
static std::string convert( enumName value ) { \
static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \
return enumInfo.lookup( static_cast<int>( value ) ); \
} \
};
#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ )
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning(pop) #pragma warning(pop)

View File

@ -22,6 +22,7 @@ set(TEST_SOURCES
${SELF_TEST_DIR}/IntrospectiveTests/Tag.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/Tag.tests.cpp
${SELF_TEST_DIR}/IntrospectiveTests/String.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/String.tests.cpp
${SELF_TEST_DIR}/IntrospectiveTests/Xml.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/Approx.tests.cpp
${SELF_TEST_DIR}/UsageTests/BDD.tests.cpp ${SELF_TEST_DIR}/UsageTests/BDD.tests.cpp
${SELF_TEST_DIR}/UsageTests/Benchmark.tests.cpp ${SELF_TEST_DIR}/UsageTests/Benchmark.tests.cpp
@ -97,6 +98,7 @@ set(INTERNAL_HEADERS
${HEADER_DIR}/internal/catch_decomposer.h ${HEADER_DIR}/internal/catch_decomposer.h
${HEADER_DIR}/internal/catch_default_main.hpp ${HEADER_DIR}/internal/catch_default_main.hpp
${HEADER_DIR}/internal/catch_enforce.h ${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_errno_guard.h
${HEADER_DIR}/internal/catch_exception_translator_registry.h ${HEADER_DIR}/internal/catch_exception_translator_registry.h
${HEADER_DIR}/internal/catch_external_interfaces.h ${HEADER_DIR}/internal/catch_external_interfaces.h
@ -107,6 +109,7 @@ set(INTERNAL_HEADERS
${HEADER_DIR}/internal/catch_impl.hpp ${HEADER_DIR}/internal/catch_impl.hpp
${HEADER_DIR}/internal/catch_interfaces_capture.h ${HEADER_DIR}/internal/catch_interfaces_capture.h
${HEADER_DIR}/internal/catch_interfaces_config.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_exception.h
${HEADER_DIR}/internal/catch_interfaces_registry_hub.h ${HEADER_DIR}/internal/catch_interfaces_registry_hub.h
${HEADER_DIR}/internal/catch_interfaces_reporter.h ${HEADER_DIR}/internal/catch_interfaces_reporter.h
@ -182,6 +185,7 @@ set(IMPL_SOURCES
${HEADER_DIR}/internal/catch_debugger.cpp ${HEADER_DIR}/internal/catch_debugger.cpp
${HEADER_DIR}/internal/catch_decomposer.cpp ${HEADER_DIR}/internal/catch_decomposer.cpp
${HEADER_DIR}/internal/catch_enforce.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_errno_guard.cpp
${HEADER_DIR}/internal/catch_exception_translator_registry.cpp ${HEADER_DIR}/internal/catch_exception_translator_registry.cpp
${HEADER_DIR}/internal/catch_fatal_condition.cpp ${HEADER_DIR}/internal/catch_fatal_condition.cpp

View File

@ -202,3 +202,15 @@ TEST_CASE( "replaceInPlace", "[Strings][StringManip]" ) {
CHECK( s == "didn|'t" ); CHECK( s == "didn|'t" );
} }
} }
TEST_CASE( "splitString", "[Strings]" ) {
using namespace Catch::Matchers;
using Catch::splitStringRef;
using Catch::StringRef;
CHECK_THAT( splitStringRef("", ',' ), Equals(std::vector<StringRef>() ) );
CHECK_THAT( splitStringRef("abc", ',' ), Equals(std::vector<StringRef>{"abc"} ) );
CHECK_THAT( splitStringRef("abc,def", ',' ), Equals(std::vector<StringRef>{"abc", "def"} ) );
}

View File

@ -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<std::string>{} ) );
SECTION( "One enum value" ) {
CHECK_THAT( parseEnums( "ClassName::EnumName::Value1" ),
Equals(std::vector<std::string>{"Value1"} ) );
CHECK_THAT( parseEnums( "Value1" ),
Equals( std::vector<std::string>{"Value1"} ) );
CHECK_THAT( parseEnums( "EnumName::Value1" ),
Equals(std::vector<std::string>{"Value1"} ) );
}
SECTION( "Multiple enum values" ) {
CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2" ),
Equals( std::vector<std::string>{"Value1", "Value2"} ) );
CHECK_THAT( parseEnums( "ClassName::EnumName::Value1, ClassName::EnumName::Value2, ClassName::EnumName::Value3" ),
Equals( std::vector<std::string>{"Value1", "Value2", "Value3"} ) );
CHECK_THAT( parseEnums( "ClassName::EnumName::Value1,ClassName::EnumName::Value2 , ClassName::EnumName::Value3" ),
Equals( std::vector<std::string>{"Value1", "Value2", "Value3"} ) );
}
}
TEST_CASE( "Directly creating an EnumInfo" ) {
using namespace Catch::Detail;
std::unique_ptr<EnumInfo> 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 **}" );
}

View File

@ -61,6 +61,39 @@ TEST_CASE( "toString(enum class w/operator<<)", "[toString][enum][enumClass]" )
EnumClass2 e1 = EnumClass2::EnumClass2Value1; EnumClass2 e1 = EnumClass2::EnumClass2Value1;
CHECK( ::Catch::Detail::stringify(e1) == "E2/V1" ); CHECK( ::Catch::Detail::stringify(e1) == "E2/V1" );
EnumClass2 e3 = static_cast<EnumClass2>(10); auto e3 = static_cast<EnumClass2>(10);
CHECK( ::Catch::Detail::stringify(e3) == "Unknown enum value 10" ); CHECK( ::Catch::Detail::stringify(e3) == "Unknown enum value 10" );
} }
enum class EnumClass3 { Value1, Value2, Value3, Value4 };
CATCH_REGISTER_ENUM( EnumClass3, EnumClass3::Value1, EnumClass3::Value2, EnumClass3::Value3 )
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" );
REQUIRE( stringify( EnumClass3::Value3 ) == "Value3" );
REQUIRE( stringify( EnumClass3::Value4 ) == "{** unexpected enum value **}" );
EnumClass3 ec3 = EnumClass3 ::Value2;
REQUIRE( stringify( ec3 ) == "Value2" );
}
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( "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" );
}