diff --git a/src/catch2/internal/catch_run_context.cpp b/src/catch2/internal/catch_run_context.cpp index bafbbeb4..f755a57e 100644 --- a/src/catch2/internal/catch_run_context.cpp +++ b/src/catch2/internal/catch_run_context.cpp @@ -82,13 +82,53 @@ namespace Catch { // `SECTION`s. // **The check for m_children.empty cannot be removed**. // doing so would break `GENERATE` _not_ followed by `SECTION`s. - const bool should_wait_for_child = - !m_children.empty() && - std::find_if( m_children.begin(), - m_children.end(), - []( TestCaseTracking::ITrackerPtr tracker ) { - return tracker->hasStarted(); - } ) == m_children.end(); + const bool should_wait_for_child = [&]() { + // No children -> nobody to wait for + if ( m_children.empty() ) { + return false; + } + // If at least one child started executing, don't wait + if ( std::find_if( + m_children.begin(), + m_children.end(), + []( TestCaseTracking::ITrackerPtr tracker ) { + return tracker->hasStarted(); + } ) != m_children.end() ) { + return false; + } + + // No children have started. We need to check if they _can_ + // start, and thus we should wait for them, or they cannot + // start (due to filters), and we shouldn't wait for them + auto* parent = m_parent; + // This is safe: there is always at least one section + // tracker in a test case tracking tree + while ( !parent->isSectionTracker() ) { + parent = &( parent->parent() ); + } + assert( parent && + "Missing root (test case) level section" ); + + auto const& parentSection = + static_cast( *parent ); + auto const& filters = parentSection.getFilters(); + // No filters -> no restrictions on running sections + if ( filters.empty() ) { + return true; + } + + for ( auto const& child : m_children ) { + if ( child->isSectionTracker() && + std::find( filters.begin(), + filters.end(), + static_cast( *child ) + .trimmedName() ) != + filters.end() ) { + return true; + } + } + return false; + }(); // This check is a bit tricky, because m_generator->next() // has a side-effect, where it consumes generator's current diff --git a/src/catch2/internal/catch_test_case_tracker.cpp b/src/catch2/internal/catch_test_case_tracker.cpp index 066069a9..3891614d 100644 --- a/src/catch2/internal/catch_test_case_tracker.cpp +++ b/src/catch2/internal/catch_test_case_tracker.cpp @@ -230,6 +230,14 @@ namespace TestCaseTracking { m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() ); } + std::vector const& SectionTracker::getFilters() const { + return m_filters; + } + + std::string const& SectionTracker::trimmedName() const { + return m_trimmed_name; + } + } // namespace TestCaseTracking using TestCaseTracking::ITracker; diff --git a/src/catch2/internal/catch_test_case_tracker.hpp b/src/catch2/internal/catch_test_case_tracker.hpp index d7ca4ffe..7e8d0007 100644 --- a/src/catch2/internal/catch_test_case_tracker.hpp +++ b/src/catch2/internal/catch_test_case_tracker.hpp @@ -173,6 +173,10 @@ namespace TestCaseTracking { void addInitialFilters( std::vector const& filters ); void addNextFilters( std::vector const& filters ); + //! Returns filters active in this tracker + std::vector const& getFilters() const; + //! Returns whitespace-trimmed name of the tracked section + std::string const& trimmedName() const; }; } // namespace TestCaseTracking diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c213dc67..6d6a00c8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -160,6 +160,32 @@ set_tests_properties(FilteredSection-1 PROPERTIES FAIL_REGULAR_EXPRESSION "No te add_test(NAME FilteredSection-2 COMMAND $ \#1394\ nested -c NestedRunSection -c s1) set_tests_properties(FilteredSection-2 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran") +add_test( + NAME + FilteredSection::GeneratorsDontCauseInfiniteLoop-1 + COMMAND + $ "#2025: original repro" -c "fov_0" +) +set_tests_properties(FilteredSection::GeneratorsDontCauseInfiniteLoop-1 + PROPERTIES + PASS_REGULAR_EXPRESSION "inside with fov: 0" # This should happen + FAIL_REGULAR_EXPRESSION "inside with fov: 1" # This would mean there was no filtering +) + +# GENERATE between filtered sections (both are selected) +add_test( + NAME + FilteredSection::GeneratorsDontCauseInfiniteLoop-2 + COMMAND + $ "#2025: same-level sections" + -c "A" + -c "B" +) +set_tests_properties(FilteredSection::GeneratorsDontCauseInfiniteLoop-2 + PROPERTIES + PASS_REGULAR_EXPRESSION "All tests passed \\(4 assertions in 1 test case\\)" +) + # AppVeyor has a Python 2.7 in path, but doesn't have .py files as autorunnable add_test(NAME ApprovalTests COMMAND ${PYTHON_EXECUTABLE} ${CATCH_DIR}/tools/scripts/approvalTests.py $) set_tests_properties(ApprovalTests diff --git a/tests/SelfTest/UsageTests/Tricky.tests.cpp b/tests/SelfTest/UsageTests/Tricky.tests.cpp index 7627f0a0..eed763f2 100644 --- a/tests/SelfTest/UsageTests/Tricky.tests.cpp +++ b/tests/SelfTest/UsageTests/Tricky.tests.cpp @@ -13,6 +13,8 @@ #endif #include +#include +#include #include #include @@ -399,3 +401,28 @@ TEST_CASE("#1514: stderr/stdout is not captured in tests aborted by an exception // FAIL aborts the test by throwing a Catch exception FAIL("1514"); } + + +TEST_CASE( "#2025: -c shouldn't cause infinite loop", "[sections][generators][regression][.approvals]" ) { + SECTION( "Check cursor from buffer offset" ) { + auto bufPos = GENERATE_REF( range( 0, 44 ) ); + WHEN( "Buffer position is " << bufPos ) { REQUIRE( 1 == 1 ); } + } +} + +TEST_CASE("#2025: original repro", "[sections][generators][regression][.approvals]") { + auto fov = GENERATE(true, false); + DYNAMIC_SECTION("fov_" << fov) { + std::cout << "inside with fov: " << fov << '\n'; + } +} + +TEST_CASE("#2025: same-level sections", "[sections][generators][regression][.approvals]") { + SECTION("A") { + SUCCEED(); + } + auto i = GENERATE(1, 2, 3); + SECTION("B") { + REQUIRE(i < 4); + } +}