// 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 #pragma once #include #include #include #include #include namespace Catch { namespace Generators { template class TakeGenerator final : public IGenerator { GeneratorWrapper m_generator; size_t m_returned = 0; size_t m_target; void skipToNthElementImpl( std::size_t n ) override { if ( n >= m_target ) { Detail::throw_generator_exception( "Could not jump to Nth element: not enough elements" ); } m_generator.skipToNthElement( n ); m_returned = n; } public: TakeGenerator(size_t target, GeneratorWrapper&& generator): m_generator(CATCH_MOVE(generator)), m_target(target) { assert(target != 0 && "Empty generators are not allowed"); } T const& get() const override { return m_generator.get(); } bool next() override { ++m_returned; if (m_returned >= m_target) { return false; } const auto success = m_generator.next(); // If the underlying generator does not contain enough values // then we cut short as well if (!success) { m_returned = m_target; } return success; } bool isFinite() const override { return true; } }; template GeneratorWrapper take(size_t target, GeneratorWrapper&& generator) { return GeneratorWrapper(Catch::Detail::make_unique>(target, CATCH_MOVE(generator))); } template class FilterGenerator final : public IGenerator { GeneratorWrapper m_generator; Predicate m_predicate; static_assert(!std::is_reference::value, "This would most likely result in a dangling reference"); public: template FilterGenerator(P&& pred, GeneratorWrapper&& generator): m_generator(CATCH_MOVE(generator)), m_predicate(CATCH_FORWARD(pred)) { if (!m_predicate(m_generator.get())) { // It might happen that there are no values that pass the // filter. In that case we throw an exception. auto has_initial_value = next(); if (!has_initial_value) { Detail::throw_generator_exception("No valid value found in filtered generator"); } } } T const& get() const override { return m_generator.get(); } bool next() override { bool success = m_generator.next(); if (!success) { return false; } while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true); return success; } bool isFinite() const override { return m_generator.isFinite(); } }; template GeneratorWrapper filter(Predicate&& pred, GeneratorWrapper&& generator) { return GeneratorWrapper(Catch::Detail::make_unique::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); } template class RepeatGenerator final : public IGenerator { static_assert(!std::is_same::value, "RepeatGenerator currently does not support bools" "because of std::vector specialization"); GeneratorWrapper m_generator; mutable std::vector m_returned; size_t m_target_repeats; size_t m_current_repeat = 0; size_t m_repeat_index = 0; public: RepeatGenerator(size_t repeats, GeneratorWrapper&& generator): m_generator(CATCH_MOVE(generator)), m_target_repeats(repeats) { assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); if (!m_generator.isFinite()) { Detail::throw_generator_exception( "Cannot repeat infinite generator" ); } } T const& get() const override { if (m_current_repeat == 0) { m_returned.push_back(m_generator.get()); return m_returned.back(); } return m_returned[m_repeat_index]; } bool next() override { // There are 2 basic cases: // 1) We are still reading the generator // 2) We are reading our own cache // In the first case, we need to poke the underlying generator. // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache if (m_current_repeat == 0) { const auto success = m_generator.next(); if (!success) { ++m_current_repeat; } return m_current_repeat < m_target_repeats; } // In the second case, we need to move indices forward and check that we haven't run up against the end ++m_repeat_index; if (m_repeat_index == m_returned.size()) { m_repeat_index = 0; ++m_current_repeat; } return m_current_repeat < m_target_repeats; } bool isFinite() const override { return m_generator.isFinite(); } }; template GeneratorWrapper repeat(size_t repeats, GeneratorWrapper&& generator) { return GeneratorWrapper(Catch::Detail::make_unique>(repeats, CATCH_MOVE(generator))); } template class MapGenerator final : public IGenerator { // TBD: provide static assert for mapping function, for friendly error message GeneratorWrapper m_generator; Func m_function; // To avoid returning dangling reference, we have to save the values mutable Optional m_cache; void skipToNthElementImpl( std::size_t n ) override { m_generator.skipToNthElement( n ); m_cache.reset(); } public: template MapGenerator(F2&& function, GeneratorWrapper&& generator) : m_generator(CATCH_MOVE(generator)), m_function(CATCH_FORWARD(function)) {} T const& get() const override { if ( !m_cache ) { m_cache = m_function( m_generator.get() ); } return *m_cache; } bool next() override { m_cache.reset(); return m_generator.next(); } bool isFinite() const override { return m_generator.isFinite(); } }; template > GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { return GeneratorWrapper( Catch::Detail::make_unique>(CATCH_FORWARD(function), CATCH_MOVE(generator)) ); } template GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { return GeneratorWrapper( Catch::Detail::make_unique>(CATCH_FORWARD(function), CATCH_MOVE(generator)) ); } template class ChunkGenerator final : public IGenerator> { std::vector m_chunk; size_t m_chunk_size; GeneratorWrapper m_generator; public: ChunkGenerator(size_t size, GeneratorWrapper generator) : m_chunk_size(size), m_generator(CATCH_MOVE(generator)) { m_chunk.reserve(m_chunk_size); if (m_chunk_size != 0) { m_chunk.push_back(m_generator.get()); for (size_t i = 1; i < m_chunk_size; ++i) { if (!m_generator.next()) { Detail::throw_generator_exception("Not enough values to initialize the first chunk"); } m_chunk.push_back(m_generator.get()); } } } std::vector const& get() const override { return m_chunk; } bool next() override { m_chunk.clear(); for (size_t idx = 0; idx < m_chunk_size; ++idx) { if (!m_generator.next()) { return false; } m_chunk.push_back(m_generator.get()); } return true; } bool isFinite() const override { return m_generator.isFinite(); } }; template GeneratorWrapper> chunk(size_t size, GeneratorWrapper&& generator) { return GeneratorWrapper>( Catch::Detail::make_unique>(size, CATCH_MOVE(generator)) ); } template class ConcatGenerator final : public IGenerator { std::vector> m_generators; size_t m_current_generator = 0; void InsertGenerators( GeneratorWrapper&& gen ) { m_generators.push_back( CATCH_MOVE( gen ) ); } template void InsertGenerators( GeneratorWrapper&& gen, Generators&&... gens ) { m_generators.push_back( CATCH_MOVE( gen ) ); InsertGenerators( CATCH_MOVE( gens )... ); } public: template ConcatGenerator( Generators&&... generators ) { InsertGenerators( CATCH_MOVE( generators )... ); } T const& get() const override { return m_generators[m_current_generator].get(); } bool next() override { const bool success = m_generators[m_current_generator].next(); if ( success ) { return true; } // If current generator is used up, we have to move to the next one ++m_current_generator; return m_current_generator < m_generators.size(); } bool isFinite() const override { for ( auto const& gen : m_generators ) { if ( !gen.isFinite() ) { return false; } } return true; } }; template GeneratorWrapper cat( GeneratorWrapper&& generator, Generators&&... generators ) { return GeneratorWrapper( Catch::Detail::make_unique>( CATCH_MOVE( generator ), CATCH_MOVE( generators )... ) ); } } // namespace Generators } // namespace Catch