Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Catch::toString support for containers #606

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
135 changes: 112 additions & 23 deletions include/internal/catch_tostring.h
Expand Up @@ -13,8 +13,12 @@
#include <sstream>
#include <iomanip>
#include <limits>
#include <vector>
#include <cstddef>
#include <iterator>

#ifndef CATCH_CPP11_OR_GREATER
#include <vector>
#endif

#ifdef __OBJC__
#include "catch_objc_arc.hpp"
Expand All @@ -24,7 +28,7 @@
#include <tuple>
#endif

#ifdef CATCH_CONFIG_CPP11_IS_ENUM
#ifdef CATCH_CPP11_OR_GREATER
#include <type_traits>
#endif

Expand Down Expand Up @@ -76,6 +80,8 @@ namespace Detail {
template<typename T> BorgType( T const& );
};

// Without c++11's `decltype`, rely on function overloading returning
// different sized types that can be discriminated with `sizeof`.
struct TrueType { char sizer[1]; };
struct FalseType { char sizer[2]; };

Expand Down Expand Up @@ -174,18 +180,88 @@ namespace Detail {
std::string rangeToString( InputIterator first, InputIterator last );
}

//template<typename T, typename Allocator>
//struct StringMaker<std::vector<T, Allocator> > {
// static std::string convert( std::vector<T,Allocator> const& v ) {
// return Detail::rangeToString( v.begin(), v.end() );
// }
//};

#ifndef CATCH_CPP11_OR_GREATER
template<typename T, typename Allocator>
std::string toString( std::vector<T,Allocator> const& v ) {
return Detail::rangeToString( v.begin(), v.end() );
}

#else

namespace Detail {
// Importing c++11 std instead of prefixing call by std:: to allow for user
// custom overload find through ADL
using std::begin;
using std::end;

template<typename T>
struct HasBasicContainerApi
{
/// This overload will be chosen if all the operations in it's signature
/// can be resolved at compile time.
template<typename C,
// Test const_iterator lvalue supports operator++
class = decltype(++std::declval<typename C::const_iterator&>()),
// Test that begin and end return comparable values
class = decltype(begin(std::declval<const C>())
!= end(std::declval<const C>()))>
static std::true_type has( typename C::const_iterator*, typename C::value_type*);
/// Lower priority overload will be picked if the previous one fails.
template<typename C>
static std::false_type has( ... );
static const bool value = decltype(has<T>(NULL, NULL))::value;
};

// Compile type function that returns true if a type matches
// the container concept (http://en.cppreference.com/w/cpp/concept/Container),
// false otherwise.
template <class C, bool = HasBasicContainerApi<C>::value>
struct IsContainer {
// C array are containers although not having the standard api.
static const bool value = std::is_array<C>::value;
};

// Specialization for types that have a basic container API
// that can now be checked for type inconsistency.
template <class C>
struct IsContainer<C, true> {
private:
typedef typename C::value_type value_type;
typedef typename C::const_iterator const_iterator;
static const C& c;
public:
static const bool value =
std::is_same<const_iterator, decltype( begin(c) )>::value &&
std::is_same<const_iterator, decltype( end(c) )>::value &&
std::is_same<const value_type&, decltype( *begin(c) )>::value &&
std::is_same<const value_type&, decltype( *end(c) )>::value;
};

/// Print containers by printing their elements in succession
template<typename C,
class = typename std::enable_if<Detail::IsContainer<C>::value>::type,
class = void>
std::string toStringInternal( C const& c ) {
return Detail::rangeToString( begin(c), end(c) );
}

} // end namespace Detail

#endif // CATCH_CPP11_OR_GREATER

// toString for pairs
template<typename T1, typename T2>
struct StringMaker<std::pair<T1,T2> > {
static std::string convert( const std::pair<T1,T2>& pair ) {
std::ostringstream oss;
oss << "{ "
<< toString( pair.first )
<< ", "
<< toString( pair.second )
<< " }";
return oss.str();
}
};

#ifdef CATCH_CONFIG_CPP11_TUPLE

Expand Down Expand Up @@ -234,34 +310,47 @@ namespace Detail {
std::string makeString( T const& value ) {
return StringMaker<T>::convert( value );
}

/// Instead of adding complex template overloading of public toString method,
/// use an internal dispatcher which template can get as complicated as needed
/// without impacting the public api.
template<typename T
#if defined(CATCH_CPP11_OR_GREATER)
, class = typename std::enable_if<!Detail::IsContainer<T>::value>::type
#endif
>
std::string toStringInternal( T const& value ) {
return StringMaker<T>::convert( value );
}

} // end namespace Detail

/// \brief converts any type to a string
///
/// The default template forwards on to ostringstream - except when an
/// ostringstream overload does not exist - in which case it attempts to detect
/// that and writes {?}.
/// Overload (not specialise) this template for custom typs that you don't want
/// Overload (not specialize) this template for custom types that you don't want
/// to provide an ostream overload for.
template<typename T>
std::string toString( T const& value ) {
return StringMaker<T>::convert( value );
return Detail::toStringInternal(value);
}


namespace Detail {
template<typename InputIterator>
std::string rangeToString( InputIterator first, InputIterator last ) {
std::ostringstream oss;
oss << "{ ";
if( first != last ) {
oss << Catch::toString( *first );
for( ++first ; first != last ; ++first )
oss << ", " << Catch::toString( *first );
}
oss << " }";
return oss.str();
namespace Detail {
template<typename InputIterator>
std::string rangeToString( InputIterator first, InputIterator last ) {
std::ostringstream oss;
oss << "{ ";
if( first != last ) {
oss << Catch::toString( *first );
while( ++first != last )
oss << ", " << Catch::toString( *first );
}
oss << " }";
return oss.str();
}
}

} // end namespace Catch
Expand Down
2 changes: 1 addition & 1 deletion projects/CMake/CMakeLists.txt
Expand Up @@ -28,7 +28,7 @@ set(SOURCES
${SELF_TEST_DIR}/VariadicMacrosTests.cpp
${SELF_TEST_DIR}/EnumToString.cpp
${SELF_TEST_DIR}/ToStringPair.cpp
${SELF_TEST_DIR}/ToStringVector.cpp
${SELF_TEST_DIR}/ToStringContainers.cpp
${SELF_TEST_DIR}/ToStringWhich.cpp
${SELF_TEST_DIR}/ToStringTuple.cpp
)
Expand Down
4 changes: 4 additions & 0 deletions projects/SelfTest/SurrogateCpps/catch_tostring.cpp
@@ -0,0 +1,4 @@
// This file is only here to verify (to the extent possible) the self sufficiency of the header
#include "catch_suppress_warnings.h"
#include "catch_tostring.hpp"
#error
194 changes: 194 additions & 0 deletions projects/SelfTest/ToStringContainers.cpp
@@ -0,0 +1,194 @@
#include "catch.hpp"
#include <vector>

// Containers are only pretty print in c++11
#if defined(CATCH_CPP11_OR_GREATER)
#include <deque>
#include <list>
#include <array>
#include <forward_list>
#endif

/// \file Test Catch::to_string for standard containors.

/// \brief Test for sequence containors
/// \tparm Sequence The containor to test.
/// \tparm Allocator The containor element allocator to use.
template <template <class T, class Allocator = std::allocator<T> > class Sequence,
template <class T> class Allocator = std::allocator>
struct SequenceTest {
static void integers() {
Sequence<int, Allocator<int> > integers;
REQUIRE( Catch::toString(integers) == "{ }" );
integers.push_back( 42 );
REQUIRE( Catch::toString(integers) == "{ 42 }" );
integers.push_back( 250 );
REQUIRE( Catch::toString(integers) == "{ 42, 250 }" );
};

static void strings() {
Sequence<std::string, Allocator<std::string> > strings;
REQUIRE( Catch::toString(strings) == "{ }" );
strings.push_back( "hello" );
REQUIRE( Catch::toString(strings) == "{ \"hello\" }" );
strings.push_back( "world" );
REQUIRE( Catch::toString(strings) == "{ \"hello\", \"world\" }" );
}
};

namespace {
/** \brief Custom allocator, should not impact toString. */
template<typename T>
struct MinimalAllocator : std::allocator<T> {
typedef typename std::allocator<T>::size_type size_type;
T *allocate( size_type n ) {
return static_cast<T *>( ::operator new( n * sizeof(T) ) );
}
void deallocate( T *p, size_type /*n*/ ) {
::operator delete( static_cast<void *>(p) );
}
};
}

// vector
TEST_CASE( "vector<int> -> toString", "[toString][containers][vector]" ) {
SequenceTest<std::vector>::integers();
}
TEST_CASE( "vector<string> -> toString", "[toString][containers][vector]" ) {
SequenceTest<std::vector>::strings();
}
TEST_CASE( "vector<int,allocator> -> toString", "[toString][containers][vector][allocator]" ) {
SequenceTest<std::vector, MinimalAllocator>::integers();
}
TEST_CASE( "vector<string,allocator> -> toString", "[toString][containers][vector][allocator]" ) {
SequenceTest<std::vector, MinimalAllocator>::strings();
}
TEST_CASE( "vector<bool> -> toString", "[toString][containers][vector][allocator]" ) {
std::vector<bool> bools;
REQUIRE( Catch::toString(bools) == "{ }");
bools.push_back(true);
REQUIRE( Catch::toString(bools) == "{ true }");
bools.push_back(false);
REQUIRE( Catch::toString(bools) == "{ true, false }");
}

#if defined(CATCH_CPP11_OR_GREATER)

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++98-compat"
#endif

// list
TEST_CASE( "list<int> -> toString", "[toString][containers][list]" ) {
SequenceTest<std::list>::integers();
}
TEST_CASE( "list<string> -> toString", "[toString][containers][list]" ) {
SequenceTest<std::list>::strings();
}
TEST_CASE( "list<int,allocator> -> toString", "[toString][containers][list][allocator]" ) {
SequenceTest<std::list, MinimalAllocator>::integers();
}
TEST_CASE( "list<string,allocator> -> toString", "[toString][containers][list][allocator]" ) {
SequenceTest<std::list, MinimalAllocator>::strings();
}

// deque
TEST_CASE( "deque<int> -> toString", "[toString][containers][deque]" ) {
SequenceTest<std::deque>::integers();
}
TEST_CASE( "deque<string> -> toString", "[toString][containers][deque]" ) {
SequenceTest<std::deque>::strings();
}
TEST_CASE( "deque<int,allocator> -> toString", "[toString][containers][deque][allocator]" ) {
SequenceTest<std::deque, MinimalAllocator>::integers();
}
TEST_CASE( "deque<string,allocator> -> toString", "[toString][containers][deque][allocator]" ) {
SequenceTest<std::deque, MinimalAllocator>::strings();
}


// C array
TEST_CASE( "int [N] -> toString", "[toString][containers][c-array]" ) {
// Arrays of size 0 can not exist in c++
int oneValue[] = { 42 };
REQUIRE( Catch::toString(oneValue) == "{ 42 }" );
int twoValues[] = { 42, 250 };
REQUIRE( Catch::toString(twoValues) == "{ 42, 250 }" );
}

TEST_CASE( "string [N] -> toString", "[toString][containers][c-array]" ) {
std::string oneValue[] = { "hello" };
REQUIRE( Catch::toString(oneValue) == "{ \"hello\" }" );
std::string twoValues[] = { "hello", "world" };
REQUIRE( Catch::toString(twoValues) == "{ \"hello\", \"world\" }" );
}

TEST_CASE( "char [N] -> toString", "[toString][c-string]" ) {
// Do not consider char[] as containers but rather as c-string
char emptyCString[] = "";
REQUIRE( Catch::toString(emptyCString) == "\"\"" );
char cstring[] = "hello";
REQUIRE( Catch::toString(cstring) == "\"hello\"" );
}

// array
TEST_CASE( "array<int, N> -> toString", "[toString][containers][array]" ) {
std::array<int, 0> empty;
REQUIRE( Catch::toString(empty) == "{ }" );
std::array<int, 1> oneValue = { 42 };
REQUIRE( Catch::toString(oneValue) == "{ 42 }" );
std::array<int, 2> twoValues = { 42, 250 };
REQUIRE( Catch::toString(twoValues) == "{ 42, 250 }" );
}

TEST_CASE( "array<string, N> -> toString", "[toString][containers][array]" ) {
std::array<std::string, 0> empty;
REQUIRE( Catch::toString(empty) == "{ }" );
std::array<std::string, 1> oneValue = { "hello" };
REQUIRE( Catch::toString(oneValue) == "{ \"hello\" }" );
std::array<std::string, 2> twoValues = { "hello", "world" };
REQUIRE( Catch::toString(twoValues) == "{ \"hello\", \"world\" }" );
}

/// \brief Specialization for `forward_list` to use
// `push_front` instead of the unsupported `push_back`.
template <template <class T> class Allocator>
struct SequenceTest<std::forward_list, Allocator> {
static void integers() {
std::forward_list<int, Allocator<int> > integers;
REQUIRE( Catch::toString(integers) == "{ }" );
integers.push_front( 42 );
REQUIRE( Catch::toString(integers) == "{ 42 }" );
integers.push_front( 250 );
REQUIRE( Catch::toString(integers) == "{ 250, 42 }" );
};

static void strings() {
std::forward_list<std::string, Allocator<std::string> > strings;
REQUIRE( Catch::toString(strings) == "{ }" );
strings.push_front( "hello" );
REQUIRE( Catch::toString(strings) == "{ \"hello\" }" );
strings.push_front( "world" );
REQUIRE( Catch::toString(strings) == "{ \"world\", \"hello\" }" );
}
};

TEST_CASE( "forward_list<int> -> toString", "[toString][containers][forward_list]" ) {
SequenceTest<std::forward_list>::integers();
}
TEST_CASE( "forward_list<string> -> toString", "[toString][containers][forward_list]" ) {
SequenceTest<std::forward_list>::strings();
}

TEST_CASE( "forward_list<int,allocator> -> toString", "[toString][containers][forward_list][allocator]" ) {
SequenceTest<std::forward_list, MinimalAllocator>::integers();
}
TEST_CASE( "forward_list<string,allocator> -> toString", "[toString][containers][forward_list][allocator]" ) {
SequenceTest<std::forward_list, MinimalAllocator>::strings();
}

#ifdef __clang__
#pragma clang diagnostic pop
#endif
#endif // CATCH_CPP11_OR_GREATER