Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions src/catch2/generators/catch_generators_tuple.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)

// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_GENERATORS_TUPLE_HPINCLUDED
#define CATCH_GENERATORS_TUPLE_HPINCLUDED

#include <catch2/generators/catch_generators.hpp>

#include <array>
#include <tuple>
#include <type_traits>

namespace Catch {
namespace Generators {
namespace Detail {

template <typename T, typename = void>
struct is_tuple_like : std::false_type {};

template <typename T>
struct is_tuple_like<T,
decltype( void( std::tuple_size<T>::value ),
void( std::get<0>(
std::declval<T>() ) ) )>
: std::true_type {};

template <typename Ret,
typename TupleType,
typename Fun,
std::size_t N>
constexpr Ret access_tuple( TupleType& tuple, Fun& fun ) {
return fun( std::get<N>( tuple ) );
}

template <typename Ret,
typename TupleType,
typename Fun,
std::size_t... Idxs>
constexpr auto tuple_runtime_access_table() {
using AccessorFunPtr = Ret ( * )( TupleType&, Fun& );
constexpr std::size_t table_size{ sizeof...( Idxs ) };
return std::array<AccessorFunPtr, table_size>{
{ access_tuple<Ret, TupleType, Fun, Idxs>... } };
}

template <typename TupleType, typename Fun, std::size_t... Idxs>
constexpr auto
call_access_function( TupleType& tuple,
std::size_t index,
Fun fun,
std::index_sequence<Idxs...> ) {
using FirstTupleIndexType = decltype( std::get<0>( tuple ) );
using FunReturnType =
#ifdef __cpp_lib_is_invocable // C++ >= 17
std::invoke_result_t<Fun, FirstTupleIndexType>;
#else
std::result_of_t<Fun( FirstTupleIndexType )>;
#endif

constexpr auto table{ tuple_runtime_access_table<FunReturnType,
TupleType,
Fun,
Idxs...>() };
return table[index]( tuple, fun );
}

template <typename TupleType, typename Fun>
constexpr auto
runtime_get( TupleType& tuple, std::size_t index, Fun fun ) {
return call_access_function(
tuple,
index,
CATCH_FORWARD( fun ),
std::make_index_sequence<
std::tuple_size<TupleType>::value>{} );
}

template <typename TupleType>
class TupleAccessor {
TupleType m_tuple;
std::size_t m_current_index;

template <typename TupleLike>
struct in_place_tuple {
template <typename... Args>
static constexpr TupleType make( Args&&... args ) {
return TupleType{ CATCH_FORWARD( args )... };
}
};

template <typename T, std::size_t N>
struct in_place_tuple<std::array<T, N>> {
template <typename... Args>
static constexpr TupleType make( Args&&... args ) {
return TupleType{ { CATCH_FORWARD( args )... } };
}
};

public:
template <typename TupleLike>
constexpr TupleAccessor( TupleLike&& tuple ):
m_tuple{ CATCH_FORWARD( tuple ) }, m_current_index{ 0 } {}

template <typename... Args>
constexpr TupleAccessor( Args&&... args ):
m_tuple{ in_place_tuple<TupleType>::make(
CATCH_FORWARD( args )... ) },
m_current_index{ 0 } {}

constexpr bool next() {
++m_current_index;
return m_current_index < std::tuple_size<TupleType>::value;
}

template <typename Fun>
constexpr auto perform( Fun fun ) const {
return runtime_get(
m_tuple, m_current_index, CATCH_FORWARD( fun ) );
}
};

} // namespace Detail

template <typename TupleType>
class TupleGenerator final
: public IGenerator<Detail::TupleAccessor<TupleType>> {
Detail::TupleAccessor<TupleType> m_iterator;

public:
template <typename TupleLike>
TupleGenerator( TupleLike&& tuple ):
m_iterator{ CATCH_FORWARD( tuple ) } {}

template <typename... Args>
TupleGenerator( Args&&... args ):
m_iterator{ CATCH_FORWARD( args )... } {}

const Detail::TupleAccessor<TupleType>& get() const override {
return m_iterator;
}

bool next() override { return m_iterator.next(); }
};

template <typename TupleType,
typename... Args,
typename =
std::enable_if_t<Detail::is_tuple_like<TupleType>::value>>
auto tuple_as_range( Args&&... args ) {
return GeneratorWrapper<Detail::TupleAccessor<TupleType>>(
Catch::Detail::make_unique<TupleGenerator<TupleType>>(
CATCH_FORWARD( args )... ) );
}

template <typename TupleLike,
typename TupleType =
std::remove_cv_t<std::remove_reference_t<TupleLike>>,
typename =
std::enable_if_t<Detail::is_tuple_like<TupleType>::value>>
auto tuple_as_range( TupleLike&& tuple ) {
return tuple_as_range<TupleType>( CATCH_FORWARD( tuple ) );
}

template <typename... Args>
auto tuple_as_range( Args&&... args ) {
return tuple_as_range<std::tuple<Args...>>(
CATCH_FORWARD( args )... );
}

} // namespace Generators
} // namespace Catch

#endif // CATCH_GENERATORS_TUPLE_HPINCLUDED
93 changes: 93 additions & 0 deletions tests/SelfTest/UsageTests/Generators.tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
// SPDX-License-Identifier: BSL-1.0

#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_tostring.hpp>
#include <catch2/generators/catch_generator_exception.hpp>
#include <catch2/generators/catch_generators_adapters.hpp>
#include <catch2/generators/catch_generators_random.hpp>
#include <catch2/generators/catch_generators_range.hpp>
#include <catch2/generators/catch_generators_tuple.hpp>

#include <cstring>
#include <sstream>


// Generators and sections can be nested freely
Expand Down Expand Up @@ -321,3 +324,93 @@ TEST_CASE( "GENERATE can combine literals and generators", "[generators]" ) {
random( -100, 100 ) ) ) );
REQUIRE( i % 2 == 0 );
}

TEST_CASE( "Tuple", "[generators]" ) {
SECTION( "Basic Use Case" ) {
// imagine two different serialization to test
// once a serialization is tested, the second must be identical
static const auto std_serialization{ []( const auto& element ) {
std::stringstream ss;
ss << element;
return ss.str();
} };
static const auto catch2_serialization{ []( const auto& element ) {
return Catch::StringMaker<decltype( element )>::convert( element );
} };

int counter{ 0 };

SECTION( "Tuple Implicit" ) {
const auto accessor = GENERATE( tuple_as_range( 42, "foo", 3.14 ) );
accessor.perform( [&]( const auto& element ) {
REQUIRE( std_serialization( element ) ==
catch2_serialization( element ) );
++counter;
} );
}

SECTION( "Tuple Explicit" ) {
const auto accessor =
GENERATE( tuple_as_range<std::tuple<int, std::string, double>>(
42, "foo", 3.14 ) );
accessor.perform( [&]( const auto& element ) {
REQUIRE( std_serialization( element ) ==
catch2_serialization( element ) );
++counter;
} );
}

// also work with std::pair or a tuple-like

SECTION( "Pair" ) {
const auto accessor_pair = GENERATE(
tuple_as_range<std::pair<int, std::string>>( 42, "foo" ) );
accessor_pair.perform( [&]( const auto& element ) {
REQUIRE( std_serialization( element ) ==
catch2_serialization( element ) );
++counter;
} );
}

SECTION( "Array" ) {
const auto accessor_array =
GENERATE( tuple_as_range<std::array<int, 3>>( 1, 2, 3 ) );
accessor_array.perform( [&]( const auto& element ) {
REQUIRE( std_serialization( element ) ==
catch2_serialization( element ) );
++counter;
} );
}

REQUIRE( counter == 1 );
}

SECTION( "Single Parameter" ) {
SECTION( "Tuple" ) {
auto test = std::tuple<int, double, float>( 1, 2.0, 3.0f );
const auto accessor = GENERATE_REF( tuple_as_range( test ) );
SUCCEED();
std::ignore = accessor;
}

SECTION( "Pair" ) {
auto test = std::pair<int, std::string>( 42, "foo" );
const auto accessor = GENERATE_REF( tuple_as_range( test ) );
SUCCEED();
std::ignore = accessor;
}

SECTION( "Array" ) {
auto test = std::array<int, 3>{ { 1, 2, 3 } };
const auto accessor = GENERATE_REF( tuple_as_range( test ) );
SUCCEED();
std::ignore = accessor;
}

SECTION( "Any" ) {
const auto accessor = GENERATE( tuple_as_range( 1 ) );
SUCCEED();
std::ignore = accessor;
}
}
}