/* * 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 namespace Catch { 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 { virtual ~IGenerator() {} virtual auto get( size_t index ) const -> T = 0; }; template class SingleValueGenerator : public IGenerator { T m_value; public: SingleValueGenerator( T const& value ) : m_value( value ) {} auto get( size_t ) const -> T override { return m_value; } }; template class FixedValuesGenerator : public IGenerator { std::vector m_values; public: FixedValuesGenerator( std::initializer_list values ) : m_values( values ) {} auto get( size_t index ) const -> T override { return m_values[index]; } }; template class RangeGenerator : public IGenerator { T const m_first; T const m_last; public: RangeGenerator( T const& first, T const& last ) : m_first( first ), m_last( last ) { assert( m_last > m_first ); } auto get( size_t index ) const -> T override { // ToDo:: introduce a safe cast to catch potential overflows return static_cast(m_first+index); } }; template struct NullGenerator : IGenerator { auto get( size_t ) const -> T override { CATCH_INTERNAL_ERROR("A Null Generator is always empty"); } }; template class Generator { std::unique_ptr> m_generator; size_t m_size; public: Generator( size_t size, std::unique_ptr> generator ) : m_generator( std::move( generator ) ), m_size( size ) {} auto size() const -> size_t { return m_size; } auto operator[]( size_t index ) const -> T { assert( index < m_size ); return m_generator->get( index ); } }; std::vector randomiseIndices( size_t selectionSize, size_t sourceSize ); template class GeneratorRandomiser : public IGenerator { Generator m_baseGenerator; std::vector m_indices; public: GeneratorRandomiser( Generator&& baseGenerator, size_t numberOfItems ) : m_baseGenerator( std::move( baseGenerator ) ), m_indices( randomiseIndices( numberOfItems, m_baseGenerator.size() ) ) {} auto get( size_t index ) const -> T override { return m_baseGenerator[m_indices[index]]; } }; template struct RequiresASpecialisationFor; template auto all() -> Generator { return RequiresASpecialisationFor(); } template<> auto all() -> Generator; template auto range( T const& first, T const& last ) -> Generator { return Generator( (last-first), pf::make_unique>( first, last ) ); } template auto random( T const& first, T const& last ) -> Generator { auto gen = range( first, last ); auto size = gen.size(); return Generator( size, pf::make_unique>( std::move( gen ), size ) ); } template auto random( size_t size ) -> Generator { return Generator( size, pf::make_unique>( all(), size ) ); } template auto values( std::initializer_list values ) -> Generator { return Generator( values.size(), pf::make_unique>( values ) ); } template auto value( T const& val ) -> Generator { return Generator( 1, pf::make_unique>( val ) ); } template auto as() -> Generator { return Generator( 0, pf::make_unique>() ); } template auto table( std::initializer_list>&& tuples ) -> Generator> { return values>( std::forward>>( tuples ) ); } template struct Generators : GeneratorBase { std::vector> m_generators; using type = T; Generators() : GeneratorBase( 0 ) {} void populate( T&& val ) { m_size += 1; m_generators.emplace_back( value( std::move( val ) ) ); } template void populate( U&& val ) { populate( T( std::move( val ) ) ); } void populate( Generator&& generator ) { m_size += generator.size(); m_generators.emplace_back( std::move( generator ) ); } template void populate( U&& valueOrGenerator, Gs... moreGenerators ) { populate( std::forward( valueOrGenerator ) ); populate( std::forward( moreGenerators )... ); } auto operator[]( size_t index ) const -> T { size_t sizes = 0; for( auto const& gen : m_generators ) { auto localIndex = index-sizes; sizes += gen.size(); if( index < sizes ) return gen[localIndex]; } CATCH_INTERNAL_ERROR("Index '" << index << "' is out of range (" << sizes << ')'); } }; template auto makeGenerators( Generator&& generator, Gs... moreGenerators ) -> Generators { Generators generators; generators.m_generators.reserve( 1+sizeof...(Gs) ); generators.populate( std::move( generator ), std::forward( moreGenerators )... ); return generators; } template auto makeGenerators( Generator&& generator ) -> Generators { Generators generators; generators.populate( std::move( generator ) ); return generators; } template auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators { return makeGenerators( value( std::forward( val ) ), std::forward( moreGenerators )... ); } template auto makeGenerators( U&& val, Gs... moreGenerators ) -> Generators { return makeGenerators( value( T( std::forward( val ) ) ), std::forward( moreGenerators )... ); } 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()[0]) { 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[tracker.getIndex()]; } } // 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