mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 12:17:11 +01:00 
			
		
		
		
	Add ChunkGenerator
This generator collects values from the underlying generator until it has a specified amount of them, and then returns them in one "chunk". In case the underlying generator does not have enough elements for a specific chunk, the left-over elements are discarded. Closes #1538
This commit is contained in:
		| @@ -179,176 +179,6 @@ namespace Generators { | ||||
|         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> | ||||
|   | ||||
							
								
								
									
										230
									
								
								include/internal/catch_generators_generic.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								include/internal/catch_generators_generic.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,230 @@ | ||||
| /* | ||||
|  *  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; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     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)) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     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 | ||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský