diff --git a/docs/generators.md b/docs/generators.md index 21578ac0..8ad47e39 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -36,13 +36,14 @@ a test case, * 2 fundamental generators * `ValueGenerator` -- contains only single element * `ValuesGenerator` -- contains multiple elements -* 4 generic generators that modify other generators +* 5 generic generators that modify other generators * `FilterGenerator` -- filters out elements from a generator for which the predicate returns "false" * `TakeGenerator` -- takes first `n` elements from a generator * `RepeatGenerator` -- repeats output from a generator `n` times * `MapGenerator` -- returns the result of applying `Func` on elements from a different generator + * `ChunkGenerator` -- returns chunks (inside `std::vector`) of n elements from a generator * 3 specific purpose generators * `RandomIntegerGenerator` -- generates random Integrals from range * `RandomFloatGenerator` -- generates random Floats from range @@ -58,6 +59,7 @@ type, making their usage much nicer. These are * `repeat(repeats, GeneratorWrapper&&)` for `RepeatGenerator` * `map(func, GeneratorWrapper&&)` for `MapGenerator` (map `T` to `T`) * `map(func, GeneratorWrapper&&)` for `MapGenerator` (map `U` to `T`) +* `chunk(chunk-size, GeneratorWrapper&&)` for `ChunkGenerator` * `random(IntegerOrFloat a, IntegerOrFloat b)` for `RandomIntegerGenerator` or `RandomFloatGenerator` * `range(start, end)` for `RangeGenerator` with a step size of `1` * `range(start, end, step)` for `RangeGenerator` with a custom step size diff --git a/include/catch.hpp b/include/catch.hpp index 56bd9652..e4fd40fe 100644 --- a/include/catch.hpp +++ b/include/catch.hpp @@ -63,6 +63,7 @@ #include "internal/catch_capture_matchers.h" #endif #include "internal/catch_generators.hpp" +#include "internal/catch_generators_generic.hpp" #include "internal/catch_generators_specific.hpp" // These files are included here so the single_include script doesn't put them diff --git a/include/internal/catch_generators.hpp b/include/internal/catch_generators.hpp index 3deffc3d..40e434e7 100644 --- a/include/internal/catch_generators.hpp +++ b/include/internal/catch_generators.hpp @@ -179,176 +179,6 @@ namespace Generators { return makeGenerators( value( T( std::forward( val ) ) ), std::forward( moreGenerators )... ); } - template - class TakeGenerator : public IGenerator { - GeneratorWrapper m_generator; - size_t m_returned = 0; - size_t m_target; - public: - TakeGenerator(size_t target, GeneratorWrapper&& 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 - GeneratorWrapper take(size_t target, GeneratorWrapper&& generator) { - return GeneratorWrapper(pf::make_unique>(target, std::move(generator))); - } - - - template - class FilterGenerator : public IGenerator { - GeneratorWrapper m_generator; - Predicate m_predicate; - public: - template - FilterGenerator(P&& pred, GeneratorWrapper&& generator): - m_generator(std::move(generator)), - m_predicate(std::forward

(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 - GeneratorWrapper filter(Predicate&& pred, GeneratorWrapper&& generator) { - return GeneratorWrapper(std::unique_ptr>(pf::make_unique>(std::forward(pred), std::move(generator)))); - } - - template - class RepeatGenerator : public IGenerator { - GeneratorWrapper m_generator; - mutable std::vector 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&& 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 - GeneratorWrapper repeat(size_t repeats, GeneratorWrapper&& generator) { - return GeneratorWrapper(pf::make_unique>(repeats, std::move(generator))); - } - - template - class MapGenerator : public IGenerator { - // TBD: provide static assert for mapping function, for friendly error message - GeneratorWrapper m_generator; - Func m_function; - // To avoid returning dangling reference, we have to save the values - T m_cache; - public: - template - MapGenerator(F2&& function, GeneratorWrapper&& generator) : - m_generator(std::move(generator)), - m_function(std::forward(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 - GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { - return GeneratorWrapper( - pf::make_unique>(std::forward(function), std::move(generator)) - ); - } - template - GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { - return GeneratorWrapper( - pf::make_unique>(std::forward(function), std::move(generator)) - ); - } - auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; template diff --git a/include/internal/catch_generators_generic.hpp b/include/internal/catch_generators_generic.hpp new file mode 100644 index 00000000..2226dbc5 --- /dev/null +++ b/include/internal/catch_generators_generic.hpp @@ -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 + class TakeGenerator : public IGenerator { + GeneratorWrapper m_generator; + size_t m_returned = 0; + size_t m_target; + public: + TakeGenerator(size_t target, GeneratorWrapper&& 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 + GeneratorWrapper take(size_t target, GeneratorWrapper&& generator) { + return GeneratorWrapper(pf::make_unique>(target, std::move(generator))); + } + + + template + class FilterGenerator : public IGenerator { + GeneratorWrapper m_generator; + Predicate m_predicate; + public: + template + FilterGenerator(P&& pred, GeneratorWrapper&& generator): + m_generator(std::move(generator)), + m_predicate(std::forward

(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 + GeneratorWrapper filter(Predicate&& pred, GeneratorWrapper&& generator) { + return GeneratorWrapper(std::unique_ptr>(pf::make_unique>(std::forward(pred), std::move(generator)))); + } + + template + class RepeatGenerator : public IGenerator { + GeneratorWrapper m_generator; + mutable std::vector 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&& 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 + GeneratorWrapper repeat(size_t repeats, GeneratorWrapper&& generator) { + return GeneratorWrapper(pf::make_unique>(repeats, std::move(generator))); + } + + template + class MapGenerator : public IGenerator { + // TBD: provide static assert for mapping function, for friendly error message + GeneratorWrapper m_generator; + Func m_function; + // To avoid returning dangling reference, we have to save the values + T m_cache; + public: + template + MapGenerator(F2&& function, GeneratorWrapper&& generator) : + m_generator(std::move(generator)), + m_function(std::forward(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 + GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { + return GeneratorWrapper( + pf::make_unique>(std::forward(function), std::move(generator)) + ); + } + template + GeneratorWrapper map(Func&& function, GeneratorWrapper&& generator) { + return GeneratorWrapper( + pf::make_unique>(std::forward(function), std::move(generator)) + ); + } + + template + class ChunkGenerator final : public IGenerator> { + std::vector m_chunk; + size_t m_chunk_size; + GeneratorWrapper m_generator; + bool m_used_up = false; + public: + ChunkGenerator(size_t size, GeneratorWrapper 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 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 + GeneratorWrapper> chunk(size_t size, GeneratorWrapper&& generator) { + return GeneratorWrapper>( + pf::make_unique>(size, std::move(generator)) + ); + } + +} // namespace Generators +} // namespace Catch + + +#endif // TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 8f4e4c1e..b3caa82f 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -102,6 +102,7 @@ set(INTERNAL_HEADERS ${HEADER_DIR}/internal/catch_external_interfaces.h ${HEADER_DIR}/internal/catch_fatal_condition.h ${HEADER_DIR}/internal/catch_generators.hpp + ${HEADER_DIR}/internal/catch_generators_generic.hpp ${HEADER_DIR}/internal/catch_generators_specific.hpp ${HEADER_DIR}/internal/catch_impl.hpp ${HEADER_DIR}/internal/catch_interfaces_capture.h diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index 69d74ecf..a25cf485 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -417,6 +417,19 @@ Generators.tests.cpp:: passed: j > 0 for: 3 > 0 Generators.tests.cpp:: passed: j > 0 for: 1 > 0 Generators.tests.cpp:: passed: j > 0 for: 2 > 0 Generators.tests.cpp:: passed: j > 0 for: 3 > 0 +Generators.tests.cpp:: passed: chunk2.size() == 2 for: 2 == 2 +Generators.tests.cpp:: passed: chunk2.front() == chunk2.back() for: 1 == 1 +Generators.tests.cpp:: passed: chunk2.size() == 2 for: 2 == 2 +Generators.tests.cpp:: passed: chunk2.front() == chunk2.back() for: 2 == 2 +Generators.tests.cpp:: passed: chunk2.size() == 2 for: 2 == 2 +Generators.tests.cpp:: passed: chunk2.front() == chunk2.back() for: 3 == 3 +Generators.tests.cpp:: passed: chunk2.size() == 2 for: 2 == 2 +Generators.tests.cpp:: passed: chunk2.front() == chunk2.back() for: 1 == 1 +Generators.tests.cpp:: passed: chunk2.front() < 3 for: 1 < 3 +Generators.tests.cpp:: passed: chunk2.size() == 2 for: 2 == 2 +Generators.tests.cpp:: passed: chunk2.front() == chunk2.back() for: 2 == 2 +Generators.tests.cpp:: passed: chunk2.front() < 3 for: 2 < 3 +Generators.tests.cpp:: passed: chunk(2, value(1)), Catch::GeneratorException Generators.tests.cpp:: passed: j < i for: -3 < 1 Generators.tests.cpp:: passed: j < i for: -2 < 1 Generators.tests.cpp:: passed: j < i for: -1 < 1 diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index b2ff3aa5..72baaa26 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -1175,5 +1175,5 @@ due to unexpected exception with message: =============================================================================== test cases: 245 | 185 passed | 56 failed | 4 failed as expected -assertions: 1365 | 1229 passed | 115 failed | 21 failed as expected +assertions: 1378 | 1242 passed | 115 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 6ff22c77..f0edbd6a 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -3080,6 +3080,117 @@ Generators.tests.cpp:: PASSED: with expansion: 3 > 0 +------------------------------------------------------------------------------- +Generators -- adapters + Chunking a generator into sized pieces + Number of elements in source is divisible by chunk size +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.size() == 2 ) +with expansion: + 2 == 2 + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.front() == chunk2.back() ) +with expansion: + 1 == 1 + +------------------------------------------------------------------------------- +Generators -- adapters + Chunking a generator into sized pieces + Number of elements in source is divisible by chunk size +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.size() == 2 ) +with expansion: + 2 == 2 + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.front() == chunk2.back() ) +with expansion: + 2 == 2 + +------------------------------------------------------------------------------- +Generators -- adapters + Chunking a generator into sized pieces + Number of elements in source is divisible by chunk size +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.size() == 2 ) +with expansion: + 2 == 2 + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.front() == chunk2.back() ) +with expansion: + 3 == 3 + +------------------------------------------------------------------------------- +Generators -- adapters + Chunking a generator into sized pieces + Number of elements in source is not divisible by chunk size +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.size() == 2 ) +with expansion: + 2 == 2 + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.front() == chunk2.back() ) +with expansion: + 1 == 1 + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.front() < 3 ) +with expansion: + 1 < 3 + +------------------------------------------------------------------------------- +Generators -- adapters + Chunking a generator into sized pieces + Number of elements in source is not divisible by chunk size +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.size() == 2 ) +with expansion: + 2 == 2 + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.front() == chunk2.back() ) +with expansion: + 2 == 2 + +Generators.tests.cpp:: PASSED: + REQUIRE( chunk2.front() < 3 ) +with expansion: + 2 < 3 + +------------------------------------------------------------------------------- +Generators -- adapters + Chunking a generator into sized pieces + Throws on too small generators +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE_THROWS_AS( chunk(2, value(1)), Catch::GeneratorException ) + ------------------------------------------------------------------------------- Generators -- simple one @@ -10574,5 +10685,5 @@ Misc.tests.cpp:: PASSED: =============================================================================== test cases: 245 | 172 passed | 69 failed | 4 failed as expected -assertions: 1379 | 1229 passed | 129 failed | 21 failed as expected +assertions: 1392 | 1242 passed | 129 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index 17981c7e..08a64c33 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -4,7 +4,7 @@ loose text artifact - + @@ -347,6 +347,9 @@ Message.tests.cpp: + + + diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index bd0d971d..989c17f3 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -3538,7 +3538,7 @@ - +

@@ -3755,6 +3755,146 @@
+
+
+ + + chunk2.size() == 2 + + + 2 == 2 + + + + + chunk2.front() == chunk2.back() + + + 1 == 1 + + + +
+ +
+
+
+ + + chunk2.size() == 2 + + + 2 == 2 + + + + + chunk2.front() == chunk2.back() + + + 2 == 2 + + + +
+ +
+
+
+ + + chunk2.size() == 2 + + + 2 == 2 + + + + + chunk2.front() == chunk2.back() + + + 3 == 3 + + + +
+ +
+
+
+ + + chunk2.size() == 2 + + + 2 == 2 + + + + + chunk2.front() == chunk2.back() + + + 1 == 1 + + + + + chunk2.front() < 3 + + + 1 < 3 + + + +
+ +
+
+
+ + + chunk2.size() == 2 + + + 2 == 2 + + + + + chunk2.front() == chunk2.back() + + + 2 == 2 + + + + + chunk2.front() < 3 + + + 2 < 3 + + + +
+ +
+
+
+ + + chunk(2, value(1)), Catch::GeneratorException + + + chunk(2, value(1)), Catch::GeneratorException + + + +
+ +
@@ -12785,7 +12925,7 @@ loose text artifact - + - + diff --git a/projects/SelfTest/UsageTests/Generators.tests.cpp b/projects/SelfTest/UsageTests/Generators.tests.cpp index a9a6e218..8171e864 100644 --- a/projects/SelfTest/UsageTests/Generators.tests.cpp +++ b/projects/SelfTest/UsageTests/Generators.tests.cpp @@ -114,7 +114,7 @@ SCENARIO("Eating cucumbers", "[generators][approvals]") { #endif // There are also some generic generator manipulators -TEST_CASE("Generators -- adapters", "[generators]") { +TEST_CASE("Generators -- adapters", "[generators][generic]") { // TODO: This won't work yet, introduce GENERATE_VAR? //auto numbers = Catch::Generators::values({ 1, 2, 3, 4, 5, 6 }); SECTION("Filtering by predicate") { @@ -144,6 +144,23 @@ TEST_CASE("Generators -- adapters", "[generators]") { auto j = GENERATE(repeat(2, values({ 1, 2, 3 }))); REQUIRE(j > 0); } + SECTION("Chunking a generator into sized pieces") { + SECTION("Number of elements in source is divisible by chunk size") { + auto chunk2 = GENERATE(chunk(2, values({ 1, 1, 2, 2, 3, 3 }))); + REQUIRE(chunk2.size() == 2); + REQUIRE(chunk2.front() == chunk2.back()); + } + SECTION("Number of elements in source is not divisible by chunk size") { + auto chunk2 = GENERATE(chunk(2, values({ 1, 1, 2, 2, 3 }))); + REQUIRE(chunk2.size() == 2); + REQUIRE(chunk2.front() == chunk2.back()); + REQUIRE(chunk2.front() < 3); + } + SECTION("Throws on too small generators") { + using namespace Catch::Generators; + REQUIRE_THROWS_AS(chunk(2, value(1)), Catch::GeneratorException); + } + } } // Note that because of the non-reproducibility of distributions,