/* * Created by Phil Nash on 15/6/2018. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED #define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED #include "catch_interfaces_generatortracker.h" #include "catch_common.h" #include "catch_enforce.h" #include #include #include #include #include namespace Catch { class GeneratorException : public std::exception { const char* const m_msg = ""; public: GeneratorException(const char* msg): m_msg(msg) {} const char* what() const noexcept override final; }; namespace Generators { // !TBD move this into its own location? namespace pf{ template std::unique_ptr make_unique( Args&&... args ) { return std::unique_ptr(new T(std::forward(args)...)); } } template struct IGenerator : GeneratorUntypedBase { virtual ~IGenerator() = default; // Returns the current element of the generator // // \Precondition The generator is either freshly constructed, // or the last call to `next()` returned true virtual T const& get() const = 0; using type = T; }; template class SingleValueGenerator final : public IGenerator { T m_value; public: SingleValueGenerator(T const& value) : m_value( value ) {} SingleValueGenerator(T&& value) : m_value(std::move(value)) {} T const& get() const override { return m_value; } bool next() override { return false; } }; template class FixedValuesGenerator final : public IGenerator { std::vector m_values; size_t m_idx = 0; public: FixedValuesGenerator( std::initializer_list values ) : m_values( values ) {} T const& get() const override { return m_values[m_idx]; } bool next() override { ++m_idx; return m_idx < m_values.size(); } }; template class GeneratorWrapper final { std::unique_ptr> m_generator; public: GeneratorWrapper(std::unique_ptr> generator): m_generator(std::move(generator)) {} T const& get() const { return m_generator->get(); } bool next() { return m_generator->next(); } }; template GeneratorWrapper value(T&& value) { return GeneratorWrapper(pf::make_unique>(std::forward(value))); } template GeneratorWrapper values(std::initializer_list values) { return GeneratorWrapper(pf::make_unique>(values)); } template class Generators : public IGenerator { std::vector> m_generators; size_t m_current = 0; void populate(GeneratorWrapper&& generator) { m_generators.emplace_back(std::move(generator)); } void populate(T&& val) { m_generators.emplace_back(value(std::move(val))); } template void populate(U&& val) { populate(T(std::move(val))); } template void populate(U&& valueOrGenerator, Gs... moreGenerators) { populate(std::forward(valueOrGenerator)); populate(std::forward(moreGenerators)...); } public: template Generators(Gs... moreGenerators) { m_generators.reserve(sizeof...(Gs)); populate(std::forward(moreGenerators)...); } T const& get() const override { return m_generators[m_current].get(); } bool next() override { if (m_current >= m_generators.size()) { return false; } const bool current_status = m_generators[m_current].next(); if (!current_status) { ++m_current; } return m_current < m_generators.size(); } }; template GeneratorWrapper> table( std::initializer_list::type...>> tuples ) { return values>( tuples ); } // Tag type to signal that a generator sequence should convert arguments to a specific type template struct as {}; template auto makeGenerators( GeneratorWrapper&& generator, Gs... moreGenerators ) -> Generators { return Generators(std::move(generator), std::forward(moreGenerators)...); } template auto makeGenerators( GeneratorWrapper&& generator ) -> Generators { return Generators(std::move(generator)); } template auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators { return makeGenerators( value( std::forward( val ) ), std::forward( moreGenerators )... ); } template auto makeGenerators( as, U&& val, Gs... moreGenerators ) -> Generators { return makeGenerators( value( T( std::forward( val ) ) ), std::forward( moreGenerators )... ); } template class TakeGenerator : public IGenerator { GeneratorWrapper m_generator; size_t m_returned = 0; size_t m_target; public: TakeGenerator(size_t target, GeneratorWrapper&& generator): m_generator(std::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; } }; template GeneratorWrapper take(size_t target, GeneratorWrapper&& generator) { return GeneratorWrapper(pf::make_unique>(target, std::move(generator))); } template class FilterGenerator : public IGenerator { GeneratorWrapper m_generator; Predicate m_predicate; public: template FilterGenerator(P&& pred, GeneratorWrapper&& generator): m_generator(std::move(generator)), m_predicate(std::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) { Catch::throw_exception(GeneratorException("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; } }; template GeneratorWrapper filter(Predicate&& pred, GeneratorWrapper&& generator) { return GeneratorWrapper(std::unique_ptr>(pf::make_unique>(std::forward(pred), std::move(generator)))); } template class RepeatGenerator : public IGenerator { 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(std::move(generator)), m_target_repeats(repeats) { assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); } 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; } }; template GeneratorWrapper repeat(size_t repeats, GeneratorWrapper&& generator) { return GeneratorWrapper(pf::make_unique>(repeats, std::move(generator))); } template class MapGenerator : 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 T m_cache; public: template MapGenerator(F2&& function, GeneratorWrapper&& generator) : m_generator(std::move(generator)), m_function(std::forward(function)), m_cache(m_function(m_generator.get())) {} T const& get() const override { return m_cache; } bool next() override { const auto success = m_generator.next(); if (success) { m_cache = m_function(m_generator.get()); } return success; } }; template GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { return GeneratorWrapper( pf::make_unique>(std::forward(function), std::move(generator)) ); } template GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { return GeneratorWrapper( pf::make_unique>(std::forward(function), std::move(generator)) ); } auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; template // Note: The type after -> is weird, because VS2015 cannot parse // the expression used in the typedef inside, when it is in // return type. Yeah, ¯\_(ツ)_/¯ auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval().get()) { using UnderlyingType = typename decltype(generatorExpression())::type; IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); if (!tracker.hasGenerator()) { tracker.setGenerator(pf::make_unique>(generatorExpression())); } auto const& generator = static_cast const&>( *tracker.getGenerator() ); return generator.get(); } } // namespace Generators } // namespace Catch #define GENERATE( ... ) \ Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) #endif // TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED