diff --git a/src/catch2/generators/catch_generators_tuple.hpp b/src/catch2/generators/catch_generators_tuple.hpp new file mode 100644 index 0000000000..62cbfe93d9 --- /dev/null +++ b/src/catch2/generators/catch_generators_tuple.hpp @@ -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 + +#include +#include +#include + +namespace Catch { + namespace Generators { + namespace Detail { + + template + struct is_tuple_like : std::false_type {}; + + template + struct is_tuple_like::value ), + void( std::get<0>( + std::declval() ) ) )> + : std::true_type {}; + + template + constexpr Ret access_tuple( TupleType& tuple, Fun& fun ) { + return fun( std::get( tuple ) ); + } + + template + constexpr auto tuple_runtime_access_table() { + using AccessorFunPtr = Ret ( * )( TupleType&, Fun& ); + constexpr std::size_t table_size{ sizeof...( Idxs ) }; + return std::array{ + { access_tuple... } }; + } + + template + constexpr auto + call_access_function( TupleType& tuple, + std::size_t index, + Fun fun, + std::index_sequence ) { + using FirstTupleIndexType = decltype( std::get<0>( tuple ) ); + using FunReturnType = +#ifdef __cpp_lib_is_invocable // C++ >= 17 + std::invoke_result_t; +#else + std::result_of_t; +#endif + + constexpr auto table{ tuple_runtime_access_table() }; + return table[index]( tuple, fun ); + } + + template + 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::value>{} ); + } + + template + class TupleAccessor { + TupleType m_tuple; + std::size_t m_current_index; + + template + struct in_place_tuple { + template + static constexpr TupleType make( Args&&... args ) { + return TupleType{ CATCH_FORWARD( args )... }; + } + }; + + template + struct in_place_tuple> { + template + static constexpr TupleType make( Args&&... args ) { + return TupleType{ { CATCH_FORWARD( args )... } }; + } + }; + + public: + template + constexpr TupleAccessor( TupleLike&& tuple ): + m_tuple{ CATCH_FORWARD( tuple ) }, m_current_index{ 0 } {} + + template + constexpr TupleAccessor( Args&&... args ): + m_tuple{ in_place_tuple::make( + CATCH_FORWARD( args )... ) }, + m_current_index{ 0 } {} + + constexpr bool next() { + ++m_current_index; + return m_current_index < std::tuple_size::value; + } + + template + constexpr auto perform( Fun fun ) const { + return runtime_get( + m_tuple, m_current_index, CATCH_FORWARD( fun ) ); + } + }; + + } // namespace Detail + + template + class TupleGenerator final + : public IGenerator> { + Detail::TupleAccessor m_iterator; + + public: + template + TupleGenerator( TupleLike&& tuple ): + m_iterator{ CATCH_FORWARD( tuple ) } {} + + template + TupleGenerator( Args&&... args ): + m_iterator{ CATCH_FORWARD( args )... } {} + + const Detail::TupleAccessor& get() const override { + return m_iterator; + } + + bool next() override { return m_iterator.next(); } + }; + + template ::value>> + auto tuple_as_range( Args&&... args ) { + return GeneratorWrapper>( + Catch::Detail::make_unique>( + CATCH_FORWARD( args )... ) ); + } + + template >, + typename = + std::enable_if_t::value>> + auto tuple_as_range( TupleLike&& tuple ) { + return tuple_as_range( CATCH_FORWARD( tuple ) ); + } + + template + auto tuple_as_range( Args&&... args ) { + return tuple_as_range>( + CATCH_FORWARD( args )... ); + } + + } // namespace Generators +} // namespace Catch + +#endif // CATCH_GENERATORS_TUPLE_HPINCLUDED diff --git a/tests/SelfTest/UsageTests/Generators.tests.cpp b/tests/SelfTest/UsageTests/Generators.tests.cpp index f04cf4f099..06920e86e9 100644 --- a/tests/SelfTest/UsageTests/Generators.tests.cpp +++ b/tests/SelfTest/UsageTests/Generators.tests.cpp @@ -7,12 +7,15 @@ // SPDX-License-Identifier: BSL-1.0 #include +#include #include #include #include #include +#include #include +#include // Generators and sections can be nested freely @@ -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::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>( + 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>( 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>( 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( 1, 2.0, 3.0f ); + const auto accessor = GENERATE_REF( tuple_as_range( test ) ); + SUCCEED(); + std::ignore = accessor; + } + + SECTION( "Pair" ) { + auto test = std::pair( 42, "foo" ); + const auto accessor = GENERATE_REF( tuple_as_range( test ) ); + SUCCEED(); + std::ignore = accessor; + } + + SECTION( "Array" ) { + auto test = std::array{ { 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; + } + } +}