mirror of
https://github.com/catchorg/Catch2.git
synced 2024-12-24 12:03:29 +01:00
54089c4c8c
* Deduce map return type implicitly Giving the first template argument to map generator function to deduce return type is now optional even if the return type is different from the type generated by mapped generator.
243 lines
8.6 KiB
C++
243 lines
8.6 KiB
C++
/*
|
|
* Created by Martin on 23/2/2019.
|
|
*
|
|
* 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_GENERIC_HPP_INCLUDED
|
|
#define TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED
|
|
|
|
#include "catch_generators.hpp"
|
|
|
|
namespace Catch {
|
|
namespace Generators {
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703
|
|
// std::result_of is deprecated in C++17 and removed in C++20. Hence, it is
|
|
// replaced with std::invoke_result here. Also *_t format is preferred over
|
|
// typename *::type format.
|
|
template <typename Func, typename U>
|
|
using MapFunctionReturnType = std::remove_reference_t<std::remove_cv_t<std::invoke_result_t<Func, U>>>;
|
|
#else
|
|
template <typename Func, typename U>
|
|
using MapFunctionReturnType = typename std::remove_reference<typename std::remove_cv<typename std::result_of<Func(U)>::type>::type>::type;
|
|
#endif
|
|
|
|
template <typename Func, typename U, typename T = MapFunctionReturnType<Func, U>>
|
|
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 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>
|
|
class ChunkGenerator final : public IGenerator<std::vector<T>> {
|
|
std::vector<T> m_chunk;
|
|
size_t m_chunk_size;
|
|
GeneratorWrapper<T> m_generator;
|
|
bool m_used_up = false;
|
|
public:
|
|
ChunkGenerator(size_t size, GeneratorWrapper<T> generator) :
|
|
m_chunk_size(size), m_generator(std::move(generator))
|
|
{
|
|
m_chunk.reserve(m_chunk_size);
|
|
m_chunk.push_back(m_generator.get());
|
|
for (size_t i = 1; i < m_chunk_size; ++i) {
|
|
if (!m_generator.next()) {
|
|
Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk"));
|
|
}
|
|
m_chunk.push_back(m_generator.get());
|
|
}
|
|
}
|
|
std::vector<T> const& get() const override {
|
|
return m_chunk;
|
|
}
|
|
bool next() override {
|
|
m_chunk.clear();
|
|
for (size_t idx = 0; idx < m_chunk_size; ++idx) {
|
|
if (!m_generator.next()) {
|
|
return false;
|
|
}
|
|
m_chunk.push_back(m_generator.get());
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) {
|
|
return GeneratorWrapper<std::vector<T>>(
|
|
pf::make_unique<ChunkGenerator<T>>(size, std::move(generator))
|
|
);
|
|
}
|
|
|
|
} // namespace Generators
|
|
} // namespace Catch
|
|
|
|
|
|
#endif // TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED
|