diff --git a/docs/generators.md b/docs/generators.md index 5881a11e..57fdf44b 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -90,7 +90,11 @@ used with other generators as arguments, such as `auto i = GENERATE(0, 2, take(100, random(300, 3000)));`. This is useful e.g. if you know that specific inputs are problematic and want to test them separately/first. -**For safety reasons, you cannot use variables inside the `GENERATE` macro.** +**For safety reasons, you cannot use variables inside the `GENERATE` macro. +This is done because the generator expression _will_ outlive the outside +scope and thus capturing references is dangerous. If you need to use +variables inside the generator expression, make sure you thought through +the lifetime implications and use `GENERATE_COPY` or `GENERATE_REF`.** You can also override the inferred type by using `as` as the first argument to the macro. This can be useful when dealing with string literals, diff --git a/docs/list-of-examples.md b/docs/list-of-examples.md index 677dec1e..95e7bda0 100644 --- a/docs/list-of-examples.md +++ b/docs/list-of-examples.md @@ -17,6 +17,7 @@ - Generators: [Create your own generator](../examples/300-Gen-OwnGenerator.cpp) - Generators: [Use map to convert types in GENERATE expression](../examples/301-Gen-MapTypeConversion.cpp) - Generators: [Use variables in generator expressions](../examples/310-Gen-VariablesInGenerators.cpp) +- Generators: [Use custom variable capture in generator expressions](../examples/311-Gen-CustomCapture.cpp) ## Planned diff --git a/examples/310-Gen-VariablesInGenerators.cpp b/examples/310-Gen-VariablesInGenerators.cpp index 96840bbb..422815d2 100644 --- a/examples/310-Gen-VariablesInGenerators.cpp +++ b/examples/310-Gen-VariablesInGenerators.cpp @@ -8,41 +8,6 @@ #include -#include - -// Lets start by implementing a parametrizable double generator -class RandomDoubleGenerator : public Catch::Generators::IGenerator { - std::minstd_rand m_rand; - std::uniform_real_distribution<> m_dist; - double current_number; -public: - - RandomDoubleGenerator(double low, double high): - m_rand(std::random_device{}()), - m_dist(low, high) - { - static_cast(next()); - } - - double const& get() const override; - bool next() override { - current_number = m_dist(m_rand); - return true; - } -}; - -// Avoids -Wweak-vtables -double const& RandomDoubleGenerator::get() const { - return current_number; -} - - -// Also provide a nice shortcut for creating the generator -Catch::Generators::GeneratorWrapper random(double low, double high) { - return Catch::Generators::GeneratorWrapper(std::unique_ptr>(new RandomDoubleGenerator(low, high))); -} - - TEST_CASE("Generate random doubles across different ranges", "[generator][example][advanced]") { // Workaround for old libstdc++ @@ -55,16 +20,12 @@ TEST_CASE("Generate random doubles across different ranges", })); // This will not compile (intentionally), because it accesses a variable - // auto number = GENERATE(take(50, random(r.first, r.second))); - - // We have to manually register the generators instead - // Notice that we are using value capture in the lambda, to avoid lifetime issues - auto number = Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, - [=]{ - using namespace Catch::Generators; - return makeGenerators(take(50, random(std::get<0>(r), std::get<1>(r)))); - } - ); + // auto number = GENERATE(take(50, random(std::get<0>(r), std::get<1>(r)))); + + // GENERATE_COPY copies all variables mentioned inside the expression + // thus this will work. + auto number = GENERATE_COPY(take(50, random(std::get<0>(r), std::get<1>(r)))); + REQUIRE(std::abs(number) > 0); } diff --git a/examples/311-Gen-CustomCapture.cpp b/examples/311-Gen-CustomCapture.cpp new file mode 100644 index 00000000..da6d686f --- /dev/null +++ b/examples/311-Gen-CustomCapture.cpp @@ -0,0 +1,41 @@ +// 311-Gen-CustomCapture.cpp +// Shows how to provide custom capture list to the generator expression + +// Note that using variables inside generators is dangerous and should +// be done only if you know what you are doing, because the generators +// _WILL_ outlive the variables. Also, even if you know what you are +// doing, you should probably use GENERATE_COPY or GENERATE_REF macros +// instead. However, if your use case requires having a +// per-variable custom capture list, this example shows how to achieve +// that. + +#include + +TEST_CASE("Generate random doubles across different ranges", + "[generator][example][advanced]") { + // Workaround for old libstdc++ + using record = std::tuple; + // Set up 3 ranges to generate numbers from + auto r1 = GENERATE(table({ + record{3, 4}, + record{-4, -3}, + record{10, 1000} + })); + + auto r2(r1); + + // This will take r1 by reference and r2 by value. + // Note that there are no advantages for doing so in this example, + // it is done only for expository purposes. + auto number = Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, + [&r1, r2]{ + using namespace Catch::Generators; + return makeGenerators(take(50, random(std::get<0>(r1), std::get<1>(r2)))); + } + ); + + REQUIRE(std::abs(number) > 0); +} + +// Compiling and running this file will result in 150 successful assertions + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index dff5c742..65dc5f54 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -47,6 +47,7 @@ set( SOURCES_IDIOMATIC_TESTS 300-Gen-OwnGenerator.cpp 301-Gen-MapTypeConversion.cpp 310-Gen-VariablesInGenerators.cpp + 311-Gen-CustomCapture.cpp ) # main-s for reporter-specific test sources: diff --git a/include/internal/catch_generators.hpp b/include/internal/catch_generators.hpp index 40e434e7..8f06b8c6 100644 --- a/include/internal/catch_generators.hpp +++ b/include/internal/catch_generators.hpp @@ -201,7 +201,10 @@ namespace Generators { } // namespace Catch #define GENERATE( ... ) \ - Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) - + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) +#define GENERATE_COPY( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) +#define GENERATE_REF( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) #endif // TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index 4aace675..7c60b2af 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -637,6 +637,22 @@ Matchers.tests.cpp:: passed: testStringForMatching(), !Contains("di Matchers.tests.cpp:: failed: testStringForMatching(), !Contains("substring") for: "this string contains 'abc' as a substring" not contains: "substring" Exception.tests.cpp:: passed: thisThrows(), "expected exception" for: "expected exception" equals: "expected exception" Exception.tests.cpp:: failed: thisThrows(), "should fail" for: "expected exception" equals: "should fail" +Generators.tests.cpp:: passed: values > -6 for: 3 > -6 +Generators.tests.cpp:: passed: values > -6 for: 4 > -6 +Generators.tests.cpp:: passed: values > -6 for: 5 > -6 +Generators.tests.cpp:: passed: values > -6 for: 6 > -6 +Generators.tests.cpp:: passed: values > -6 for: -5 > -6 +Generators.tests.cpp:: passed: values > -6 for: -4 > -6 +Generators.tests.cpp:: passed: values > -6 for: 90 > -6 +Generators.tests.cpp:: passed: values > -6 for: 91 > -6 +Generators.tests.cpp:: passed: values > -6 for: 92 > -6 +Generators.tests.cpp:: passed: values > -6 for: 93 > -6 +Generators.tests.cpp:: passed: values > -6 for: 94 > -6 +Generators.tests.cpp:: passed: values > -6 for: 95 > -6 +Generators.tests.cpp:: passed: values > -6 for: 96 > -6 +Generators.tests.cpp:: passed: values > -6 for: 97 > -6 +Generators.tests.cpp:: passed: values > -6 for: 98 > -6 +Generators.tests.cpp:: passed: values > -6 for: 99 > -6 Misc.tests.cpp:: warning: 'This one ran' Exception.tests.cpp:: failed: unexpected exception with message: 'custom exception' Tricky.tests.cpp:: passed: True for: {?} diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index 42aa0419..4d67b9f7 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -1264,6 +1264,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 256 | 190 passed | 62 failed | 4 failed as expected -assertions: 1403 | 1260 passed | 122 failed | 21 failed as expected +test cases: 257 | 191 passed | 62 failed | 4 failed as expected +assertions: 1419 | 1276 passed | 122 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 49babee0..9856d7c6 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -4697,6 +4697,182 @@ Exception.tests.cpp:: FAILED: with expansion: "expected exception" equals: "should fail" +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 3 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 4 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 5 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 6 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + -5 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + -4 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 90 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 91 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 92 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 93 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 94 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 95 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 96 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 97 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 98 > -6 + +------------------------------------------------------------------------------- +Nested generators and captured variables +------------------------------------------------------------------------------- +Generators.tests.cpp: +............................................................................... + +Generators.tests.cpp:: PASSED: + REQUIRE( values > -6 ) +with expansion: + 99 > -6 + ------------------------------------------------------------------------------- Nice descriptive name ------------------------------------------------------------------------------- @@ -10936,6 +11112,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 256 | 175 passed | 77 failed | 4 failed as expected -assertions: 1419 | 1260 passed | 138 failed | 21 failed as expected +test cases: 257 | 176 passed | 77 failed | 4 failed as expected +assertions: 1435 | 1276 passed | 138 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index cbef6f92..652c4aac 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -4,7 +4,7 @@ loose text artifact - + @@ -454,6 +454,7 @@ Matchers.tests.cpp: Exception.tests.cpp: + diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index 1aecca38..2e8c3efc 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -5913,6 +5913,137 @@ Nor would this + + + + values > -6 + + + 3 > -6 + + + + + values > -6 + + + 4 > -6 + + + + + values > -6 + + + 5 > -6 + + + + + values > -6 + + + 6 > -6 + + + + + values > -6 + + + -5 > -6 + + + + + values > -6 + + + -4 > -6 + + + + + values > -6 + + + 90 > -6 + + + + + values > -6 + + + 91 > -6 + + + + + values > -6 + + + 92 > -6 + + + + + values > -6 + + + 93 > -6 + + + + + values > -6 + + + 94 > -6 + + + + + values > -6 + + + 95 > -6 + + + + + values > -6 + + + 96 > -6 + + + + + values > -6 + + + 97 > -6 + + + + + values > -6 + + + 98 > -6 + + + + + values > -6 + + + 99 > -6 + + + + This one ran @@ -13257,7 +13388,7 @@ loose text artifact - + - + diff --git a/projects/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp b/projects/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp index a57dfc77..dbc1d957 100644 --- a/projects/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp +++ b/projects/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp @@ -216,3 +216,45 @@ TEST_CASE("Generators internals", "[generators][internals]") { } } + + +// todo: uncopyable type used in a generator +// idea: uncopyable tag type for a stupid generator + +namespace { +struct non_copyable { + non_copyable() = default; + non_copyable(non_copyable const&) = delete; + non_copyable& operator=(non_copyable const&) = delete; + int value = -1; +}; + +// This class shows how to implement a simple generator for Catch tests +class TestGen : public Catch::Generators::IGenerator { + int current_number; +public: + + TestGen(non_copyable const& nc): + current_number(nc.value) {} + + int const& get() const override; + bool next() override { + return false; + } +}; + +// Avoids -Wweak-vtables +int const& TestGen::get() const { + return current_number; +} + +} + +TEST_CASE("GENERATE capture macros", "[generators][internals][.approvals]") { + auto value = GENERATE(take(10, random(0, 10))); + + non_copyable nc; nc.value = value; + // neither `GENERATE_COPY` nor plain `GENERATE` would compile here + auto value2 = GENERATE_REF(Catch::Generators::GeneratorWrapper(std::unique_ptr>(new TestGen(nc)))); + REQUIRE(value == value2); +} diff --git a/projects/SelfTest/UsageTests/Generators.tests.cpp b/projects/SelfTest/UsageTests/Generators.tests.cpp index 18af838d..f5e3f6a5 100644 --- a/projects/SelfTest/UsageTests/Generators.tests.cpp +++ b/projects/SelfTest/UsageTests/Generators.tests.cpp @@ -188,3 +188,21 @@ TEST_CASE("Random generator", "[generators][.][approvals]") { static_cast(val); // Silence VS 2015 unused variable warning } } + + +TEST_CASE("Nested generators and captured variables", "[generators]") { + // Workaround for old libstdc++ + using record = std::tuple; + // Set up 3 ranges to generate numbers from + auto extent = GENERATE(table({ + record{3, 7}, + record{-5, -3}, + record{90, 100} + })); + + auto from = std::get<0>(extent); + auto to = std::get<1>(extent); + + auto values = GENERATE_COPY(range(from, to)); + REQUIRE(values > -6); +}