From daadf42a0e21ac09abc1f286f875078b24e2dd4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Tue, 10 Feb 2026 09:16:03 +0100 Subject: [PATCH] Add --warn InfiniteGenerators --- docs/command-line.md | 3 ++ src/catch2/catch_config.cpp | 3 ++ src/catch2/catch_config.hpp | 1 + .../interfaces/catch_interfaces_config.hpp | 3 ++ src/catch2/internal/catch_commandline.cpp | 3 ++ src/catch2/internal/catch_run_context.cpp | 7 ++++ tests/CMakeLists.txt | 2 +- tests/ExtraTests/CMakeLists.txt | 13 +++++++ tests/ExtraTests/X95-InfiniteGenerators.cpp | 36 +++++++++++++++++++ .../Baselines/compact.sw.approved.txt | 4 +-- .../Baselines/compact.sw.multi.approved.txt | 4 +-- .../Baselines/console.sw.approved.txt | 6 ++-- .../Baselines/console.sw.multi.approved.txt | 6 ++-- tests/SelfTest/Baselines/tap.sw.approved.txt | 4 +-- .../Baselines/tap.sw.multi.approved.txt | 4 +-- tests/SelfTest/Baselines/xml.sw.approved.txt | 6 ++-- .../Baselines/xml.sw.multi.approved.txt | 6 ++-- .../IntrospectiveTests/CmdLine.tests.cpp | 5 +-- 18 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 tests/ExtraTests/X95-InfiniteGenerators.cpp diff --git a/docs/command-line.md b/docs/command-line.md index 690dcaaa..640fb1d9 100644 --- a/docs/command-line.md +++ b/docs/command-line.md @@ -358,10 +358,13 @@ There are currently two warnings implemented: // (e.g. `REQUIRE`) is encountered. UnmatchedTestSpec // Fail test run if any of the CLI test specs did // not match any tests. + InfiniteGenerators // Fail if GENERATE would run infinitely ``` > `UnmatchedTestSpec` was introduced in Catch2 3.0.1. +> `InfiniteGenerators` was introduced in Catch2 vX.Y.Z + ## Reporting timings diff --git a/src/catch2/catch_config.cpp b/src/catch2/catch_config.cpp index fef03d7d..f83821a7 100644 --- a/src/catch2/catch_config.cpp +++ b/src/catch2/catch_config.cpp @@ -197,6 +197,9 @@ namespace Catch { bool Config::warnAboutUnmatchedTestSpecs() const { return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec ); } + bool Config::warnAboutInfiniteGenerators() const { + return !!( m_data.warnings & WarnAbout::InfiniteGenerator ); + } bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; } ShowDurations Config::showDurations() const { return m_data.showDurations; } double Config::minDuration() const { return m_data.minDuration; } diff --git a/src/catch2/catch_config.hpp b/src/catch2/catch_config.hpp index cdf286ad..8014eb9e 100644 --- a/src/catch2/catch_config.hpp +++ b/src/catch2/catch_config.hpp @@ -124,6 +124,7 @@ namespace Catch { bool includeSuccessfulResults() const override; bool warnAboutMissingAssertions() const override; bool warnAboutUnmatchedTestSpecs() const override; + bool warnAboutInfiniteGenerators() const override; bool zeroTestsCountAsSuccess() const override; ShowDurations showDurations() const override; double minDuration() const override; diff --git a/src/catch2/interfaces/catch_interfaces_config.hpp b/src/catch2/interfaces/catch_interfaces_config.hpp index 82a298db..db4745c3 100644 --- a/src/catch2/interfaces/catch_interfaces_config.hpp +++ b/src/catch2/interfaces/catch_interfaces_config.hpp @@ -29,6 +29,8 @@ namespace Catch { NoAssertions = 0x01, //! A command line test spec matched no test cases UnmatchedTestSpec = 0x02, + //! The resulting generator in GENERATE is infinite + InfiniteGenerator = 0x04, }; }; enum class ShowDurations { @@ -71,6 +73,7 @@ namespace Catch { virtual bool shouldDebugBreak() const = 0; virtual bool warnAboutMissingAssertions() const = 0; virtual bool warnAboutUnmatchedTestSpecs() const = 0; + virtual bool warnAboutInfiniteGenerators() const = 0; virtual bool zeroTestsCountAsSuccess() const = 0; virtual int abortAfter() const = 0; virtual bool showInvisibles() const = 0; diff --git a/src/catch2/internal/catch_commandline.cpp b/src/catch2/internal/catch_commandline.cpp index cf9cbf6d..61825df9 100644 --- a/src/catch2/internal/catch_commandline.cpp +++ b/src/catch2/internal/catch_commandline.cpp @@ -32,6 +32,9 @@ namespace Catch { } else if ( warning == "UnmatchedTestSpec" ) { config.warnings = static_cast(config.warnings | WarnAbout::UnmatchedTestSpec); return ParserResult::ok( ParseResultType::Matched ); + } else if ( warning == "InfiniteGenerators" ) { + config.warnings = static_cast(config.warnings | WarnAbout::InfiniteGenerator); + return ParserResult::ok( ParseResultType::Matched ); } return ParserResult ::runtimeError( diff --git a/src/catch2/internal/catch_run_context.cpp b/src/catch2/internal/catch_run_context.cpp index 5e5f95a4..8e04748e 100644 --- a/src/catch2/internal/catch_run_context.cpp +++ b/src/catch2/internal/catch_run_context.cpp @@ -21,7 +21,9 @@ #include #include #include +#include #include +#include #include #include @@ -495,6 +497,11 @@ namespace Catch { currentTracker.addChild( CATCH_MOVE( newTracker ) ); ret->setGenerator( CATCH_MOVE( generator ) ); + if ( m_config->warnAboutInfiniteGenerators() && + !ret->getGenerator()->isFinite() ) { + // TBD: Would it be better to expand this macro inline? + FAIL( "GENERATE() would run infinitely" ); + } ret->open(); return ret; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c4168e3a..887c07f6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -169,7 +169,7 @@ if(CATCH_ENABLE_COVERAGE) endif() # configure unit tests via CTest -add_test(NAME RunTests COMMAND $ --order rand --rng-seed time) +add_test(NAME RunTests COMMAND $ --order rand --rng-seed time --warn InfiniteGenerators) set_tests_properties(RunTests PROPERTIES FAIL_REGULAR_EXPRESSION "Filters:" COST 15 diff --git a/tests/ExtraTests/CMakeLists.txt b/tests/ExtraTests/CMakeLists.txt index 7e761931..d663efc8 100644 --- a/tests/ExtraTests/CMakeLists.txt +++ b/tests/ExtraTests/CMakeLists.txt @@ -589,3 +589,16 @@ set_tests_properties(ThreadSafetyTests::UnscopedMessagesAndAssertions PASS_REGULAR_EXPRESSION "assertions: 401 \\| 401 failed as expected" RUN_SERIAL ON ) + +add_executable(InfiniteGenerators ${TESTS_DIR}/X95-InfiniteGenerators.cpp) +target_link_libraries(InfiniteGenerators PRIVATE Catch2::Catch2WithMain) + +add_test( + NAME Warnings::InfiniteGenerators + COMMAND $ --warn InfiniteGenerators +) +set_tests_properties(Warnings::InfiniteGenerators + PROPERTIES + PASS_REGULAR_EXPRESSION "test cases: 1 \\| 1 failed" + TIMEOUT 5 +) diff --git a/tests/ExtraTests/X95-InfiniteGenerators.cpp b/tests/ExtraTests/X95-InfiniteGenerators.cpp new file mode 100644 index 00000000..31389107 --- /dev/null +++ b/tests/ExtraTests/X95-InfiniteGenerators.cpp @@ -0,0 +1,36 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +/**\file + * Checks that GENERATE over infinite generator errors out when the tests are + * run with `-warn InfiniteGenerators` + */ + +#include +#include + +namespace { + static int ONE = 1; + class infinite_generator : public Catch::Generators::IGenerator { + public: + + int const& get() const override { return ONE; } + bool next() override { return true; } + auto isFinite() const -> bool override { return false; } + }; + + static auto make_infinite_generator() + -> Catch::Generators::GeneratorWrapper { + return { new infinite_generator() }; + } + +} // namespace + +TEST_CASE() { + auto _ = GENERATE( make_infinite_generator() ); +} diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index f4da808e..e0a691f0 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -1446,8 +1446,8 @@ TestSpecParser.tests.cpp:: passed: spec.matches( testCase ) for: tr CmdLine.tests.cpp:: passed: cli.parse( { "test", "-w", "NoAssertions" } ) for: {?} CmdLine.tests.cpp:: passed: config.warnings == WarnAbout::NoAssertions for: 1 == 1 CmdLine.tests.cpp:: passed: !(cli.parse( { "test", "-w", "NoTests" } )) for: !{?} -CmdLine.tests.cpp:: passed: cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec" } ) for: {?} -CmdLine.tests.cpp:: passed: config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) for: 3 == 3 +CmdLine.tests.cpp:: passed: cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec", "--warn", "InfiniteGenerators" } ) for: {?} +CmdLine.tests.cpp:: passed: config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec | WarnAbout::InfiniteGenerator ) for: 7 == 7 Condition.tests.cpp:: passed: p == 0 for: 0 == 0 Condition.tests.cpp:: passed: p == pNULL for: 0 == 0 Condition.tests.cpp:: passed: p != 0 for: 0x != 0 diff --git a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt index d6348ab0..b72f4a5e 100644 --- a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt @@ -1444,8 +1444,8 @@ TestSpecParser.tests.cpp:: passed: spec.matches( testCase ) for: tr CmdLine.tests.cpp:: passed: cli.parse( { "test", "-w", "NoAssertions" } ) for: {?} CmdLine.tests.cpp:: passed: config.warnings == WarnAbout::NoAssertions for: 1 == 1 CmdLine.tests.cpp:: passed: !(cli.parse( { "test", "-w", "NoTests" } )) for: !{?} -CmdLine.tests.cpp:: passed: cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec" } ) for: {?} -CmdLine.tests.cpp:: passed: config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) for: 3 == 3 +CmdLine.tests.cpp:: passed: cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec", "--warn", "InfiniteGenerators" } ) for: {?} +CmdLine.tests.cpp:: passed: config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec | WarnAbout::InfiniteGenerator ) for: 7 == 7 Condition.tests.cpp:: passed: p == 0 for: 0 == 0 Condition.tests.cpp:: passed: p == pNULL for: 0 == 0 Condition.tests.cpp:: passed: p != 0 for: 0x != 0 diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index f5166de0..4af80f47 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -9425,14 +9425,14 @@ CmdLine.tests.cpp: ............................................................................... CmdLine.tests.cpp:: PASSED: - REQUIRE( cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec" } ) ) + REQUIRE( cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec", "--warn", "InfiniteGenerators" } ) ) with expansion: {?} CmdLine.tests.cpp:: PASSED: - REQUIRE( config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) ) + REQUIRE( config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec | WarnAbout::InfiniteGenerator ) ) with expansion: - 3 == 3 + 7 == 7 ------------------------------------------------------------------------------- Pointers can be compared to null diff --git a/tests/SelfTest/Baselines/console.sw.multi.approved.txt b/tests/SelfTest/Baselines/console.sw.multi.approved.txt index bfb0f4bb..2eadff97 100644 --- a/tests/SelfTest/Baselines/console.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.multi.approved.txt @@ -9423,14 +9423,14 @@ CmdLine.tests.cpp: ............................................................................... CmdLine.tests.cpp:: PASSED: - REQUIRE( cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec" } ) ) + REQUIRE( cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec", "--warn", "InfiniteGenerators" } ) ) with expansion: {?} CmdLine.tests.cpp:: PASSED: - REQUIRE( config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) ) + REQUIRE( config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec | WarnAbout::InfiniteGenerator ) ) with expansion: - 3 == 3 + 7 == 7 ------------------------------------------------------------------------------- Pointers can be compared to null diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index 0c303618..c4e9dccb 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -2371,9 +2371,9 @@ ok {test-number} - config.warnings == WarnAbout::NoAssertions for: 1 == 1 # Parsing warnings ok {test-number} - !(cli.parse( { "test", "-w", "NoTests" } )) for: !{?} # Parsing warnings -ok {test-number} - cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec" } ) for: {?} +ok {test-number} - cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec", "--warn", "InfiniteGenerators" } ) for: {?} # Parsing warnings -ok {test-number} - config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) for: 3 == 3 +ok {test-number} - config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec | WarnAbout::InfiniteGenerator ) for: 7 == 7 # Pointers can be compared to null ok {test-number} - p == 0 for: 0 == 0 # Pointers can be compared to null diff --git a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt index 611983af..54f36be0 100644 --- a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt @@ -2369,9 +2369,9 @@ ok {test-number} - config.warnings == WarnAbout::NoAssertions for: 1 == 1 # Parsing warnings ok {test-number} - !(cli.parse( { "test", "-w", "NoTests" } )) for: !{?} # Parsing warnings -ok {test-number} - cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec" } ) for: {?} +ok {test-number} - cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec", "--warn", "InfiniteGenerators" } ) for: {?} # Parsing warnings -ok {test-number} - config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) for: 3 == 3 +ok {test-number} - config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec | WarnAbout::InfiniteGenerator ) for: 7 == 7 # Pointers can be compared to null ok {test-number} - p == 0 for: 0 == 0 # Pointers can be compared to null diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index bd5a725c..f8f1ff29 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -11291,7 +11291,7 @@ Approx( 1.21999999999999997 )
- cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec" } ) + cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec", "--warn", "InfiniteGenerators" } ) {?} @@ -11299,10 +11299,10 @@ Approx( 1.21999999999999997 ) - config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) + config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec | WarnAbout::InfiniteGenerator ) - 3 == 3 + 7 == 7 diff --git a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt index 3bdd4518..f37883d1 100644 --- a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt @@ -11291,7 +11291,7 @@ Approx( 1.21999999999999997 )
- cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec" } ) + cli.parse( { "test", "--warn", "NoAssertions", "--warn", "UnmatchedTestSpec", "--warn", "InfiniteGenerators" } ) {?} @@ -11299,10 +11299,10 @@ Approx( 1.21999999999999997 ) - config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) + config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec | WarnAbout::InfiniteGenerator ) - 3 == 3 + 7 == 7 diff --git a/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp b/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp index 2cb7cae2..c797ce5c 100644 --- a/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp @@ -413,9 +413,10 @@ TEST_CASE( "Parsing warnings", "[cli][warnings]" ) { SECTION( "Combining multiple warnings" ) { REQUIRE( cli.parse( { "test", "--warn", "NoAssertions", - "--warn", "UnmatchedTestSpec" } ) ); + "--warn", "UnmatchedTestSpec", + "--warn", "InfiniteGenerators" } ) ); - REQUIRE( config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec ) ); + REQUIRE( config.warnings == ( WarnAbout::NoAssertions | WarnAbout::UnmatchedTestSpec | WarnAbout::InfiniteGenerator ) ); } }