-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First attempt at data generator support
The support is to be considered experimental, that is, the interfaces, the first party generators and helper functions can change or be removed at any point in time. Related to #850
- Loading branch information
1 parent
1cdaa48
commit ea22c1c
Showing
12 changed files
with
656 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* 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) | ||
*/ | ||
|
||
#include "catch_generators.hpp" | ||
#include "catch_random_number_generator.h" | ||
#include "catch_interfaces_capture.h" | ||
|
||
#include <set> | ||
|
||
namespace Catch { | ||
|
||
IGeneratorTracker::~IGeneratorTracker() {} | ||
|
||
namespace Generators { | ||
|
||
GeneratorBase::~GeneratorBase() {} | ||
|
||
std::vector<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ) { | ||
|
||
assert( selectionSize <= sourceSize ); | ||
std::vector<size_t> indices; | ||
indices.reserve( selectionSize ); | ||
std::uniform_int_distribution<size_t> uid( 0, sourceSize-1 ); | ||
|
||
std::set<size_t> seen; | ||
// !TBD: improve this algorithm | ||
while( indices.size() < selectionSize ) { | ||
auto index = uid( rng() ); | ||
if( seen.insert( index ).second ) | ||
indices.push_back( index ); | ||
} | ||
return indices; | ||
} | ||
|
||
auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { | ||
return getResultCapture().acquireGeneratorTracker( lineInfo ); | ||
} | ||
|
||
|
||
} // namespace Generators | ||
} // namespace Catch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
/* | ||
* 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 <memory> | ||
#include <vector> | ||
#include <cassert> | ||
#include <limits> | ||
|
||
#include <utility> | ||
|
||
namespace Catch { | ||
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 { | ||
virtual ~IGenerator() {} | ||
virtual auto get( size_t index ) const -> T = 0; | ||
}; | ||
|
||
template<typename T> | ||
class SingleValueGenerator : public IGenerator<T> { | ||
T m_value; | ||
public: | ||
SingleValueGenerator( T const& value ) : m_value( value ) {} | ||
|
||
auto get( size_t ) const -> T override { | ||
return m_value; | ||
} | ||
}; | ||
|
||
template<typename T> | ||
class FixedValuesGenerator : public IGenerator<T> { | ||
std::vector<T> m_values; | ||
|
||
public: | ||
FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {} | ||
|
||
auto get( size_t index ) const -> T override { | ||
return m_values[index]; | ||
} | ||
}; | ||
|
||
template<typename T> | ||
class RangeGenerator : public IGenerator<T> { | ||
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<T>(m_first+index); | ||
} | ||
}; | ||
|
||
|
||
template<typename T> | ||
struct NullGenerator : IGenerator<T> { | ||
auto get( size_t ) const -> T override { | ||
throw std::logic_error("A Null Generator should always be empty" ); | ||
} | ||
}; | ||
|
||
template<typename T> | ||
class Generator { | ||
std::unique_ptr<IGenerator<T>> m_generator; | ||
size_t m_size; | ||
|
||
public: | ||
Generator( size_t size, std::unique_ptr<IGenerator<T>> 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<size_t> randomiseIndices( size_t selectionSize, size_t sourceSize ); | ||
|
||
template<typename T> | ||
class GeneratorRandomiser : public IGenerator<T> { | ||
Generator<T> m_baseGenerator; | ||
|
||
std::vector<size_t> m_indices; | ||
public: | ||
GeneratorRandomiser( Generator<T>&& 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<typename T> | ||
struct RequiresASpecialisationFor; | ||
|
||
template<typename T> | ||
auto all() -> Generator<T> { return RequiresASpecialisationFor<T>(); } | ||
|
||
|
||
template<typename T> | ||
auto range( T const& first, T const& last ) -> Generator<T> { | ||
return Generator<T>( (last-first), pf::make_unique<RangeGenerator<T>>( first, last ) ); | ||
} | ||
template<> | ||
inline auto all<int>() -> Generator<int> { | ||
return range( std::numeric_limits<int>::min(), std::numeric_limits<int>::max()-1 ); | ||
} | ||
|
||
|
||
template<typename T> | ||
auto random( T const& first, T const& last ) -> Generator<T> { | ||
auto gen = range( first, last ); | ||
auto size = gen.size(); | ||
|
||
return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( std::move( gen ), size ) ); | ||
} | ||
template<typename T> | ||
auto random( size_t size ) -> Generator<T> { | ||
return Generator<T>( size, pf::make_unique<GeneratorRandomiser<T>>( all<T>(), size ) ); | ||
} | ||
|
||
template<typename T> | ||
auto values( std::initializer_list<T> values ) -> Generator<T> { | ||
return Generator<T>( values.size(), pf::make_unique<FixedValuesGenerator<T>>( values ) ); | ||
} | ||
template<typename T> | ||
auto value( T const& val ) -> Generator<T> { | ||
return Generator<T>( 1, pf::make_unique<SingleValueGenerator<T>>( val ) ); | ||
} | ||
|
||
template<typename T> | ||
auto as() -> Generator<T> { | ||
return Generator<T>( 0, pf::make_unique<NullGenerator<T>>() ); | ||
} | ||
|
||
template<typename... Ts> | ||
auto table( std::initializer_list<std::tuple<Ts...>>&& tuples ) -> Generator<std::tuple<Ts...>> { | ||
return values<std::tuple<Ts...>>( std::forward<std::initializer_list<std::tuple<Ts...>>>( tuples ) ); | ||
} | ||
|
||
|
||
template<typename T> | ||
struct Generators : GeneratorBase { | ||
std::vector<Generator<T>> m_generators; | ||
|
||
using type = T; | ||
|
||
Generators() : GeneratorBase( 0 ) {} | ||
|
||
void populate( T&& val ) { | ||
m_size += 1; | ||
m_generators.emplace_back( value( std::move( val ) ) ); | ||
} | ||
template<typename U> | ||
void populate( U&& val ) { | ||
populate( T( std::move( val ) ) ); | ||
} | ||
void populate( Generator<T>&& generator ) { | ||
m_size += generator.size(); | ||
m_generators.emplace_back( std::move( generator ) ); | ||
} | ||
|
||
template<typename U, typename... Gs> | ||
void populate( U&& valueOrGenerator, Gs... moreGenerators ) { | ||
populate( std::forward<U>( valueOrGenerator ) ); | ||
populate( std::forward<Gs>( 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]; | ||
} | ||
throw std::out_of_range("index out of range"); | ||
} | ||
}; | ||
|
||
template<typename T, typename... Gs> | ||
auto makeGenerators( Generator<T>&& generator, Gs... moreGenerators ) -> Generators<T> { | ||
Generators<T> generators; | ||
generators.m_generators.reserve( 1+sizeof...(Gs) ); | ||
generators.populate( std::move( generator ), std::forward<Gs>( moreGenerators )... ); | ||
return generators; | ||
} | ||
template<typename T> | ||
auto makeGenerators( Generator<T>&& generator ) -> Generators<T> { | ||
Generators<T> generators; | ||
generators.populate( std::move( generator ) ); | ||
return generators; | ||
} | ||
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( 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())>()[0]) { | ||
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<Generators<UnderlyingType> 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* Created by Phil Nash on 26/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_INTERFACES_GENERATORTRACKER_INCLUDED | ||
#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORTRACKER_INCLUDED | ||
|
||
#include <memory> | ||
|
||
namespace Catch { | ||
|
||
namespace Generators { | ||
class GeneratorBase { | ||
protected: | ||
size_t m_size = 0; | ||
|
||
public: | ||
GeneratorBase( size_t size ) : m_size( size ) {} | ||
virtual ~GeneratorBase(); | ||
auto size() const -> size_t { return m_size; } | ||
}; | ||
using GeneratorBasePtr = std::unique_ptr<GeneratorBase>; | ||
|
||
} // namespace Generators | ||
|
||
struct IGeneratorTracker { | ||
virtual ~IGeneratorTracker(); | ||
virtual auto hasGenerator() const -> bool = 0; | ||
virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; | ||
virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; | ||
virtual auto getIndex() const -> std::size_t = 0; | ||
}; | ||
|
||
} // namespace Catch | ||
|
||
#endif //TWOBLUECUBES_CATCH_INTERFACES_GENERATORTRACKER_INCLUDED |
Oops, something went wrong.