/* * 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 <memory> #include <vector> #include <cassert> #include <utility> #include <exception> 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<typename T, typename... Args> std::unique_ptr<T> make_unique( Args&&... args ) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); } } template<typename T> 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<typename T> class SingleValueGenerator final : public IGenerator<T> { 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<typename T> class FixedValuesGenerator final : public IGenerator<T> { std::vector<T> m_values; size_t m_idx = 0; public: FixedValuesGenerator( std::initializer_list<T> 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 <typename T> class GeneratorWrapper final { std::unique_ptr<IGenerator<T>> m_generator; public: GeneratorWrapper(std::unique_ptr<IGenerator<T>> generator): m_generator(std::move(generator)) {} T const& get() const { return m_generator->get(); } bool next() { return m_generator->next(); } }; template <typename T> GeneratorWrapper<T> value(T&& value) { return GeneratorWrapper<T>(pf::make_unique<SingleValueGenerator<T>>(std::forward<T>(value))); } template <typename T> GeneratorWrapper<T> values(std::initializer_list<T> values) { return GeneratorWrapper<T>(pf::make_unique<FixedValuesGenerator<T>>(values)); } template<typename T> class Generators : public IGenerator<T> { std::vector<GeneratorWrapper<T>> m_generators; size_t m_current = 0; void populate(GeneratorWrapper<T>&& generator) { m_generators.emplace_back(std::move(generator)); } void populate(T&& val) { m_generators.emplace_back(value(std::move(val))); } template<typename U> void populate(U&& val) { populate(T(std::move(val))); } template<typename U, typename... Gs> void populate(U&& valueOrGenerator, Gs... moreGenerators) { populate(std::forward<U>(valueOrGenerator)); populate(std::forward<Gs>(moreGenerators)...); } public: template <typename... Gs> Generators(Gs... moreGenerators) { m_generators.reserve(sizeof...(Gs)); populate(std::forward<Gs>(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<typename... Ts> GeneratorWrapper<std::tuple<Ts...>> table( std::initializer_list<std::tuple<typename std::decay<Ts>::type...>> tuples ) { return values<std::tuple<Ts...>>( tuples ); } // Tag type to signal that a generator sequence should convert arguments to a specific type template <typename T> struct as {}; template<typename T, typename... Gs> auto makeGenerators( GeneratorWrapper<T>&& generator, Gs... moreGenerators ) -> Generators<T> { return Generators<T>(std::move(generator), std::forward<Gs>(moreGenerators)...); } template<typename T> auto makeGenerators( GeneratorWrapper<T>&& generator ) -> Generators<T> { return Generators<T>(std::move(generator)); } template<typename T, typename... Gs> auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators<T> { return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... ); } template<typename T, typename U, typename... Gs> auto makeGenerators( as<T>, U&& val, Gs... moreGenerators ) -> Generators<T> { return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... ); } auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; template<typename L> // 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<decltype(generatorExpression())>().get()) { using UnderlyingType = typename decltype(generatorExpression())::type; IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); if (!tracker.hasGenerator()) { tracker.setGenerator(pf::make_unique<Generators<UnderlyingType>>(generatorExpression())); } auto const& generator = static_cast<IGenerator<UnderlyingType> 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