Skip to content

Commit

Permalink
Enum support (fix for Issue #121) (#429)
Browse files Browse the repository at this point in the history
Printable enum support

Co-authored-by: Joshua Kriegshauser <[email protected]>
  • Loading branch information
jkriegshauser and jkriegshauser authored Nov 4, 2020
1 parent 301a104 commit 961a542
Show file tree
Hide file tree
Showing 9 changed files with 495 additions and 12 deletions.
25 changes: 20 additions & 5 deletions doctest/doctest.h
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ DOCTEST_GCC_SUPPRESS_WARNING_POP
#endif // DOCTEST_CONFIG_USE_IOSFWD

#ifdef DOCTEST_CONFIG_USE_STD_HEADERS
#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#include <iosfwd>
#include <cstddef>
#include <ostream>
Expand Down Expand Up @@ -752,15 +755,13 @@ struct ContextOptions //!OCLINT too many fields
};

namespace detail {
#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS)
template <bool CONDITION, typename TYPE = void>
struct enable_if
{};

template <typename TYPE>
struct enable_if<true, TYPE>
{ typedef TYPE type; };
#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

// clang-format off
template<class T> struct remove_reference { typedef T type; };
Expand All @@ -769,6 +770,14 @@ namespace detail {

template<class T> struct remove_const { typedef T type; };
template<class T> struct remove_const<const T> { typedef T type; };
#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
template<class T> struct is_enum : public std::is_enum<T> {};
template<class T> struct underlying_type : public std::underlying_type<T> {};
#else
// Use compiler intrinsics
template<class T> struct is_enum { constexpr static bool value = __is_enum(T); };
template<class T> struct underlying_type { typedef __underlying_type(T) type; };
#endif
// clang-format on

template <typename T>
Expand All @@ -783,12 +792,12 @@ namespace detail {

template<class, class = void>
struct check {
static constexpr auto value = false;
static constexpr bool value = false;
};

template<class T>
struct check<T, decltype(os() << val<T>(), void())> {
static constexpr auto value = true;
static constexpr bool value = true;
};
} // namespace has_insertion_operator_impl

Expand Down Expand Up @@ -857,7 +866,7 @@ struct StringMaker<R C::*>
}
};

template <typename T>
template <typename T, typename detail::enable_if<!detail::is_enum<T>::value, bool>::type = true>
String toString(const DOCTEST_REF_WRAP(T) value) {
return StringMaker<T>::convert(value);
}
Expand All @@ -884,6 +893,12 @@ DOCTEST_INTERFACE String toString(int long long in);
DOCTEST_INTERFACE String toString(int long long unsigned in);
DOCTEST_INTERFACE String toString(std::nullptr_t in);

template <typename T, typename detail::enable_if<detail::is_enum<T>::value, bool>::type = true>
String toString(const DOCTEST_REF_WRAP(T) value) {
typedef typename detail::underlying_type<T>::type UT;
return toString(static_cast<UT>(value));
}

#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183
DOCTEST_INTERFACE String toString(const std::string& in);
Expand Down
25 changes: 20 additions & 5 deletions doctest/parts/doctest_fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ DOCTEST_GCC_SUPPRESS_WARNING_POP
#endif // DOCTEST_CONFIG_USE_IOSFWD

#ifdef DOCTEST_CONFIG_USE_STD_HEADERS
#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#include <iosfwd>
#include <cstddef>
#include <ostream>
Expand Down Expand Up @@ -753,15 +756,13 @@ struct ContextOptions //!OCLINT too many fields
};

namespace detail {
#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS)
template <bool CONDITION, typename TYPE = void>
struct enable_if
{};

template <typename TYPE>
struct enable_if<true, TYPE>
{ typedef TYPE type; };
#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS

// clang-format off
template<class T> struct remove_reference { typedef T type; };
Expand All @@ -770,6 +771,14 @@ namespace detail {

template<class T> struct remove_const { typedef T type; };
template<class T> struct remove_const<const T> { typedef T type; };
#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
template<class T> struct is_enum : public std::is_enum<T> {};
template<class T> struct underlying_type : public std::underlying_type<T> {};
#else
// Use compiler intrinsics
template<class T> struct is_enum { constexpr static bool value = __is_enum(T); };
template<class T> struct underlying_type { typedef __underlying_type(T) type; };
#endif
// clang-format on

template <typename T>
Expand All @@ -784,12 +793,12 @@ namespace detail {

template<class, class = void>
struct check {
static constexpr auto value = false;
static constexpr bool value = false;
};

template<class T>
struct check<T, decltype(os() << val<T>(), void())> {
static constexpr auto value = true;
static constexpr bool value = true;
};
} // namespace has_insertion_operator_impl

Expand Down Expand Up @@ -858,7 +867,7 @@ struct StringMaker<R C::*>
}
};

template <typename T>
template <typename T, typename detail::enable_if<!detail::is_enum<T>::value, bool>::type = true>
String toString(const DOCTEST_REF_WRAP(T) value) {
return StringMaker<T>::convert(value);
}
Expand All @@ -885,6 +894,12 @@ DOCTEST_INTERFACE String toString(int long long in);
DOCTEST_INTERFACE String toString(int long long unsigned in);
DOCTEST_INTERFACE String toString(std::nullptr_t in);

template <typename T, typename detail::enable_if<detail::is_enum<T>::value, bool>::type = true>
String toString(const DOCTEST_REF_WRAP(T) value) {
typedef typename detail::underlying_type<T>::type UT;
return toString(static_cast<UT>(value));
}

#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183
DOCTEST_INTERFACE String toString(const std::string& in);
Expand Down
1 change: 1 addition & 0 deletions examples/all_features/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set(files_with_output
templated_test_cases.cpp
test_cases_and_suites.cpp
asserts_used_outside_of_tests.cpp
enums.cpp
)

set(files_all
Expand Down
109 changes: 109 additions & 0 deletions examples/all_features/enums.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include <doctest/doctest.h>

#include "header.h"

DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <cstdint>
#include <sstream>
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END

namespace
{

enum StandardEnum
{
Zero,
One,
Two,
};

enum TypedEnum : int64_t
{
TypedZero,
TypedOne,
TypedTwo,
};

enum class EnumClassC : char
{
Zero = '0',
One = '1',
Two = '2',
};

enum class EnumClassSC : signed char
{
Zero = '0',
One = '1',
Two = '2',
};

enum class EnumClassUC : unsigned char
{
Zero = '0',
One = '1',
Two = '2',
};

enum class EnumClassU8 : uint8_t
{
Zero,
One,
Two,
};

template<class E, class T = typename std::underlying_type<E>::type>
T printable(E val)
{
return T(val);
}

}

TEST_CASE("enum 1")
{
std::ostringstream ostr;
ostr << Zero << One << Two;
ostr << TypedZero << TypedOne << TypedTwo;
static_assert(std::is_enum<EnumClassSC>::value, "");
ostr << printable(EnumClassSC::Zero) << printable(EnumClassSC::One) << printable(EnumClassSC::Two);

CHECK_EQ(Zero, 0);
CHECK_EQ(One, 1);
CHECK_EQ(Two, 2);

CHECK_EQ(TypedZero, 0);
CHECK_EQ(TypedOne, 1);
CHECK_EQ(TypedTwo, 2);

CHECK_EQ(EnumClassSC::Zero, EnumClassSC::Zero);
CHECK_EQ(EnumClassSC::One, EnumClassSC::One);
CHECK_EQ(EnumClassSC::Two, EnumClassSC::Two);
}

TEST_CASE("enum 2" * doctest::should_fail())
{
CHECK_EQ(Zero, 1);
CHECK_EQ(One, 2);
CHECK_EQ(Two, 3);

CHECK_EQ(TypedZero, 1);
CHECK_EQ(TypedOne, 2);
CHECK_EQ(TypedTwo, 3);

CHECK_EQ(EnumClassC::Zero, EnumClassC::One);
CHECK_EQ(EnumClassC::One, EnumClassC::Two);
CHECK_EQ(EnumClassC::Two, EnumClassC::Zero);

CHECK_EQ(EnumClassSC::Zero, EnumClassSC::One);
CHECK_EQ(EnumClassSC::One, EnumClassSC::Two);
CHECK_EQ(EnumClassSC::Two, EnumClassSC::Zero);

CHECK_EQ(EnumClassUC::Zero, EnumClassUC::One);
CHECK_EQ(EnumClassUC::One, EnumClassUC::Two);
CHECK_EQ(EnumClassUC::Two, EnumClassUC::Zero);

CHECK_EQ(EnumClassU8::Zero, EnumClassU8::One);
CHECK_EQ(EnumClassU8::One, EnumClassU8::Two);
CHECK_EQ(EnumClassU8::Two, EnumClassU8::Zero);
}
65 changes: 65 additions & 0 deletions examples/all_features/test_output/enums.cpp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
[doctest] run with "--help" for options
===============================================================================
enums.cpp(0):
TEST CASE: enum 2

enums.cpp(0): ERROR: CHECK_EQ( Zero, 1 ) is NOT correct!
values: CHECK_EQ( 0, 1 )

enums.cpp(0): ERROR: CHECK_EQ( One, 2 ) is NOT correct!
values: CHECK_EQ( 1, 2 )

enums.cpp(0): ERROR: CHECK_EQ( Two, 3 ) is NOT correct!
values: CHECK_EQ( 2, 3 )

enums.cpp(0): ERROR: CHECK_EQ( TypedZero, 1 ) is NOT correct!
values: CHECK_EQ( 0, 1 )

enums.cpp(0): ERROR: CHECK_EQ( TypedOne, 2 ) is NOT correct!
values: CHECK_EQ( 1, 2 )

enums.cpp(0): ERROR: CHECK_EQ( TypedTwo, 3 ) is NOT correct!
values: CHECK_EQ( 2, 3 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassC::Zero, EnumClassC::One ) is NOT correct!
values: CHECK_EQ( 48, 49 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassC::One, EnumClassC::Two ) is NOT correct!
values: CHECK_EQ( 49, 50 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassC::Two, EnumClassC::Zero ) is NOT correct!
values: CHECK_EQ( 50, 48 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassSC::Zero, EnumClassSC::One ) is NOT correct!
values: CHECK_EQ( 48, 49 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassSC::One, EnumClassSC::Two ) is NOT correct!
values: CHECK_EQ( 49, 50 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassSC::Two, EnumClassSC::Zero ) is NOT correct!
values: CHECK_EQ( 50, 48 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassUC::Zero, EnumClassUC::One ) is NOT correct!
values: CHECK_EQ( 48, 49 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassUC::One, EnumClassUC::Two ) is NOT correct!
values: CHECK_EQ( 49, 50 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassUC::Two, EnumClassUC::Zero ) is NOT correct!
values: CHECK_EQ( 50, 48 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassU8::Zero, EnumClassU8::One ) is NOT correct!
values: CHECK_EQ( 0, 1 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassU8::One, EnumClassU8::Two ) is NOT correct!
values: CHECK_EQ( 1, 2 )

enums.cpp(0): ERROR: CHECK_EQ( EnumClassU8::Two, EnumClassU8::Zero ) is NOT correct!
values: CHECK_EQ( 2, 0 )

Failed as expected so marking it as not failed
===============================================================================
[doctest] test cases: 2 | 2 passed | 0 failed |
[doctest] assertions: 27 | 9 passed | 18 failed |
[doctest] Status: SUCCESS!
Program code.
Loading

0 comments on commit 961a542

Please sign in to comment.