mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-22 21:36:11 +01:00
5929d9530c
This means mutiple generic generators and some inference helper * take(n, generator) * filter(predicate, generator) * map(func, generator) * repeat(generator, repeats)
378 lines
13 KiB
C++
378 lines
13 KiB
C++
/*
|
|
* 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 )... );
|
|
}
|
|
|
|
template <typename T>
|
|
class TakeGenerator : public IGenerator<T> {
|
|
GeneratorWrapper<T> m_generator;
|
|
size_t m_returned = 0;
|
|
size_t m_target;
|
|
public:
|
|
TakeGenerator(size_t target, GeneratorWrapper<T>&& 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 <typename T>
|
|
GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) {
|
|
return GeneratorWrapper<T>(pf::make_unique<TakeGenerator<T>>(target, std::move(generator)));
|
|
}
|
|
|
|
|
|
template <typename T, typename Predicate>
|
|
class FilterGenerator : public IGenerator<T> {
|
|
GeneratorWrapper<T> m_generator;
|
|
Predicate m_predicate;
|
|
public:
|
|
template <typename P = Predicate>
|
|
FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator):
|
|
m_generator(std::move(generator)),
|
|
m_predicate(std::forward<P>(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 <typename T, typename Predicate>
|
|
GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) {
|
|
return GeneratorWrapper<T>(std::unique_ptr<IGenerator<T>>(pf::make_unique<FilterGenerator<T, Predicate>>(std::forward<Predicate>(pred), std::move(generator))));
|
|
}
|
|
|
|
template <typename T>
|
|
class RepeatGenerator : public IGenerator<T> {
|
|
GeneratorWrapper<T> m_generator;
|
|
mutable std::vector<T> 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<T>&& 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 <typename T>
|
|
GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) {
|
|
return GeneratorWrapper<T>(pf::make_unique<RepeatGenerator<T>>(repeats, std::move(generator)));
|
|
}
|
|
|
|
template <typename T, typename U, typename Func>
|
|
class MapGenerator : public IGenerator<T> {
|
|
// TBD: provide static assert for mapping function, for friendly error message
|
|
GeneratorWrapper<U> m_generator;
|
|
Func m_function;
|
|
// To avoid returning dangling reference, we have to save the values
|
|
T m_cache;
|
|
public:
|
|
template <typename F2 = Func>
|
|
MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) :
|
|
m_generator(std::move(generator)),
|
|
m_function(std::forward<F2>(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 <typename T, typename U, typename Func>
|
|
GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) {
|
|
return GeneratorWrapper<T>(
|
|
pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator))
|
|
);
|
|
}
|
|
template <typename T, typename Func>
|
|
GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<T>&& generator) {
|
|
return GeneratorWrapper<T>(
|
|
pf::make_unique<MapGenerator<T, T, Func>>(std::forward<Func>(function), std::move(generator))
|
|
);
|
|
}
|
|
|
|
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
|