diff options
| author | Martin Hořeňovský | 2026-02-15 22:55:48 +0100 |
|---|---|---|
| committer | Martin Hořeňovský | 2026-02-15 22:55:48 +0100 |
| commit | 29c9844f688acb27c87338c39cd186ebfe41aa19 (patch) | |
| tree | 82526279e11e38f055605e0dcdbfc04ebfe63752 | |
| parent | 56024c04e42172d53a1672b9af6a538afdb79801 (diff) | |
| download | catch3-3.13.0.tar.gz | |
v3.13.0v3.13.0
| -rw-r--r-- | CMakeLists.txt | 2 | ||||
| -rw-r--r-- | docs/command-line.md | 4 | ||||
| -rw-r--r-- | docs/deprecations.md | 4 | ||||
| -rw-r--r-- | docs/filtering-execution-path.md | 6 | ||||
| -rw-r--r-- | docs/generators.md | 8 | ||||
| -rw-r--r-- | docs/logging.md | 4 | ||||
| -rw-r--r-- | docs/release-notes.md | 27 | ||||
| -rw-r--r-- | extras/catch_amalgamated.cpp | 324 | ||||
| -rw-r--r-- | extras/catch_amalgamated.hpp | 320 | ||||
| -rw-r--r-- | meson.build | 2 | ||||
| -rw-r--r-- | src/catch2/catch_version.cpp | 2 | ||||
| -rw-r--r-- | src/catch2/catch_version_macros.hpp | 2 |
12 files changed, 549 insertions, 156 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 84929314..4d497cc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) endif() project(Catch2 - VERSION 3.12.0 # CML version placeholder, don't delete + VERSION 3.13.0 # CML version placeholder, don't delete LANGUAGES CXX HOMEPAGE_URL "https://github.com/catchorg/Catch2" DESCRIPTION "A modern, C++-native, unit test framework." diff --git a/docs/command-line.md b/docs/command-line.md index fbc4609d..9a2b9e13 100644 --- a/docs/command-line.md +++ b/docs/command-line.md @@ -365,7 +365,7 @@ There are currently two warnings implemented: > `UnmatchedTestSpec` was introduced in Catch2 3.0.1. -> `InfiniteGenerators` was introduced in Catch2 vX.Y.Z +> `InfiniteGenerators` was introduced in Catch2 3.13.0 <a id="reporting-timings"></a> @@ -540,7 +540,7 @@ Prints the command line arguments to stdout <pre>-g, --generator-index <index in generator></pre> <pre>-p, --path-filter <path filter spec></pre> -> The generator and generic path filtering was added in Catch2 vX.Y.Z +> The generator and generic path filtering was added in Catch2 3.13.0 These arguments allow you to run specific section(s) in a test case, or only get specific element from a generator. All the variants form a shared diff --git a/docs/deprecations.md b/docs/deprecations.md index 149f33b3..99fef27f 100644 --- a/docs/deprecations.md +++ b/docs/deprecations.md @@ -50,7 +50,7 @@ want to keep doing so in the future, mark the mutated members as `mutable`. ### Section-only filtering with `-c/--section` -> Deprecated in Catch2 vX.Y.Z +> Deprecated in Catch2 3.13.0 Currently, if you use only `-c/--section` parameters to decide which sections to enter, the filtering ignores generators completely. In the @@ -63,7 +63,7 @@ means that generators are taken into account. #### Defaulted `UntypedGeneratorBase::isFinite()` -> Deprecated in Catch2 vX.Y.Z +> Deprecated in Catch2 3.13.0 The `UntypedGeneratorBase` currently provides a default implementation for `isFinite` that always returns `true`. This was done to keep backwards diff --git a/docs/filtering-execution-path.md b/docs/filtering-execution-path.md index a3dd675b..fad02624 100644 --- a/docs/filtering-execution-path.md +++ b/docs/filtering-execution-path.md @@ -1,7 +1,7 @@ <a id="top"></a> # How to run specific section/generator -> The generator and generic path filtering was added in Catch2 vX.Y.Z +> The generator and generic path filtering was added in Catch2 3.13.0 Catch2 supports picking specific path through a test case by filtering sections and generator indices to run through. This is done by using one @@ -33,7 +33,7 @@ things: ## Old behaviour -> The old behaviour was deprecated in Catch2 vX.Y.Z +> The old behaviour was deprecated in Catch2 3.13.0 ``` -c, --section <section name> @@ -119,7 +119,7 @@ TEST_CASE( "qux" ) { ## New behaviour -> The new behaviour was introduced in Catch2 vX.Y.Z +> The new behaviour was introduced in Catch2 3.13.0 ``` -g, --generator-index <index in generator> diff --git a/docs/generators.md b/docs/generators.md index 5db2941b..b6ea0db2 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -132,7 +132,7 @@ a test case, > `IteratorGenerator<T>` was introduced in Catch2 2.10.0. -> `ConcatGenerator<T>` was introduced in Catch2 X.Y.Z +> `ConcatGenerator<T>` was introduced in Catch2 3.13.0 The generators also have associated helper functions that infer their type, making their usage much nicer. These are @@ -159,7 +159,7 @@ type, making their usage much nicer. These are > `range()` for floating point numbers has been introduced in Catch2 2.11.0 -> `cat` has been introduced in Catch2 X.Y.Z +> `cat` has been introduced in Catch2 3.13.0 And can be used as shown in the example below to create a generator that returns 100 odd random number: @@ -288,9 +288,9 @@ struct IGenerator : GeneratorUntypedBase { }; ``` -> `skipToNthElementImpl` was added in Catch2 vX.Y.Z +> `skipToNthElementImpl` was added in Catch2 3.13.0 -> `isFinite` was added in Catch2 vX.Y.Z +> `isFinite` was added in Catch2 3.13.0 However, to be able to use your custom generator inside `GENERATE`, it will need to be wrapped inside a `GeneratorWrapper<T>`. diff --git a/docs/logging.md b/docs/logging.md index 371c80ad..fc3a7aca 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -30,7 +30,7 @@ started" as the extra info. > `UNSCOPED_INFO` was [introduced](https://github.com/catchorg/Catch2/issues/1522) in Catch2 2.7.0. -> `UNSCOPED_CAPTURE` was introduced in Catch2 X.Y.Z. +> `UNSCOPED_CAPTURE` was introduced in Catch2 3.13.0. The `UNSCOPED_X` macros are similar to their plain `X` macro counterparts, with two key differences: @@ -137,7 +137,7 @@ AS `FAIL`, but does not abort the test **UNSCOPED_CAPTURE(** _expression1_, _expression2_, ... **)** -> `UNSCOPED_CAPTURE` was introduced in Catch2 X.Y.Z. +> `UNSCOPED_CAPTURE` was introduced in Catch2 3.13.0. Sometimes you just want to log a value of variable, or expression. For convenience, we provide the `CAPTURE` macro, that can take a variable, diff --git a/docs/release-notes.md b/docs/release-notes.md index 35a76774..3060f1ab 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,7 @@ # Release notes **Contents**<br> +[3.13.0](#3130)<br> [3.12.0](#3120)<br> [3.11.0](#3110)<br> [3.10.0](#3100)<br> @@ -72,6 +73,32 @@ [Even Older versions](#even-older-versions)<br> +## 3.13.0 + +### Fixes +* `--benchmark-samples 0` no longer hard crashes (#3056) + * The CLI validation fails instead. +* Fixed warning suppression macros being doubly defined when using Clang on Windows (#3060) + + +### Improvements +* Suppressed static analysis 26426 diagnostic for MSVC (#3057) +* Renamed the internal deprecation macro from `DEPRECATED` to `CATCH_DEPRECATED` to avoid conflicts (#3058) +* Added `UNSCOPED_CAPTURE` macro (#2954) +* Added `ConcatGenerator` to combine multiple separate generator into one + * The short form is `cat` +* Generators can now jump forward to nth element efficiently + * Custom generators that can jump forward efficiently should override `skipToNthElementImpl` +* Generators can declare themselves infinite + * The generator base defaults to declaring itself finite for backwards compatibility + * Custom generators should override `isFinite()` to return the proper value +* Added `--warn InfiniteGenerators` to error out on `GENERATE` being given an infinite generator +* Extended options for section filtering from CLI to include generators + * The user can specify which element from the generator to use in the test case + * See documentation for how the new filters work and how they can be specified +* `MapGenerator` only calls the mapping function if the output will be used + + ## 3.12.0 ### Fixes diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp index a4406d0c..adafc64b 100644 --- a/extras/catch_amalgamated.cpp +++ b/extras/catch_amalgamated.cpp @@ -6,8 +6,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.12.0 -// Generated: 2025-12-28 22:27:25.828797 +// Catch v3.13.0 +// Generated: 2026-02-15 22:55:00.269529 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -798,6 +798,10 @@ namespace Catch { lhs.customOptions == rhs.customOptions; } + bool operator==( PathFilter const& lhs, PathFilter const& rhs ) { + return lhs.type == rhs.type && lhs.filter == rhs.filter; + } + Config::Config( ConfigData const& data ): m_data( data ) { // We need to trim filter specs to avoid trouble with superfluous @@ -807,9 +811,6 @@ namespace Catch { for (auto& elem : m_data.testsOrTags) { elem = trim(elem); } - for (auto& elem : m_data.sectionsToRun) { - elem = trim(elem); - } // Insert the default reporter if user hasn't asked for a specific one if ( m_data.reporterSpecifications.empty() ) { @@ -875,7 +876,8 @@ namespace Catch { bool Config::listListeners() const { return m_data.listListeners; } std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; } - std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + std::vector<PathFilter> const& Config::getPathFilters() const { return m_data.pathFilters; } + bool Config::useNewFilterBehaviour() const { return m_data.useNewPathFilteringBehaviour; } std::vector<ReporterSpec> const& Config::getReporterSpecs() const { return m_data.reporterSpecifications; @@ -903,6 +905,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; } @@ -1018,7 +1023,9 @@ namespace Catch { Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, - StringRef names ) { + StringRef names, + bool isScoped): + m_isScoped(isScoped) { auto trimmed = [&] (size_t start, size_t end) { while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) { ++start; @@ -1079,15 +1086,21 @@ namespace Catch { } Capturer::~Capturer() { assert( m_captured == m_messages.size() ); - for (auto const& message : m_messages) { - IResultCapture::popScopedMessage( message.sequence ); + if ( m_isScoped ) { + for ( auto const& message : m_messages ) { + IResultCapture::popScopedMessage( message.sequence ); + } } } void Capturer::captureValue( size_t index, std::string const& value ) { assert( index < m_messages.size() ); m_messages[index].message += value; - IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) ); + if ( m_isScoped ) { + IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) ); + } else { + IResultCapture::addUnscopedMessage( CATCH_MOVE( m_messages[index] ) ); + } m_captured++; } @@ -2384,7 +2397,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 3, 12, 0, "", 0 ); + static Version version( 3, 13, 0, "", 0 ); return version; } @@ -2410,14 +2423,6 @@ namespace Catch { namespace Generators { -namespace Detail { - - [[noreturn]] - void throw_generator_exception(char const* msg) { - Catch::throw_exception(GeneratorException{ msg }); - } -} // end namespace Detail - GeneratorUntypedBase::~GeneratorUntypedBase() = default; IGeneratorTracker* acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const& lineInfo ) { @@ -2465,6 +2470,27 @@ namespace Catch { m_current_number = m_pimpl->dist( m_pimpl->rng ); return true; } + + bool RandomFloatingGenerator<long double>::isFinite() const { + return false; + } + + } // namespace Generators +} // namespace Catch + + + + +namespace Catch { + namespace Generators { + namespace Detail { + + [[noreturn]] + void throw_generator_exception( char const* msg ) { + Catch::throw_exception( GeneratorException{ msg } ); + } + + } // namespace Detail } // namespace Generators } // namespace Catch @@ -2498,6 +2524,7 @@ namespace Catch { + #include <string> namespace Catch { @@ -2512,6 +2539,31 @@ namespace Catch { return ret; } + void GeneratorUntypedBase::skipToNthElementImpl( std::size_t n ) { + for ( size_t i = m_currentElementIndex; i < n; ++i ) { + bool isValid = next(); + if ( !isValid ) { + Detail::throw_generator_exception( + "Coud not jump to Nth element: not enough elements" ); + } + } + } + + void GeneratorUntypedBase::skipToNthElement( std::size_t n ) { + if ( n < m_currentElementIndex ) { + Detail::throw_generator_exception( + "Tried to jump generator backwards" ); + } + if ( n == m_currentElementIndex ) { return; } + + skipToNthElementImpl(n); + // Fixup tracking after moving the generator forward + // * Ensure that the correct element index is set after skipping + // * Invalidate cache + m_currentElementIndex = n; + m_stringReprCache.clear(); + } + StringRef GeneratorUntypedBase::currentElementAsString() const { if ( m_stringReprCache.empty() ) { m_stringReprCache = stringifyImpl(); @@ -2519,6 +2571,8 @@ namespace Catch { return m_stringReprCache; } + bool GeneratorUntypedBase::isFinite() const { return true; } + } // namespace Generators } // namespace Catch @@ -3199,6 +3253,9 @@ namespace Catch { } else if ( warning == "UnmatchedTestSpec" ) { config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::UnmatchedTestSpec); return ParserResult::ok( ParseResultType::Matched ); + } else if ( warning == "InfiniteGenerators" ) { + config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::InfiniteGenerator); + return ParserResult::ok( ParseResultType::Matched ); } return ParserResult ::runtimeError( @@ -3356,6 +3413,19 @@ namespace Catch { config.shardCount = *parsedCount; return ParserResult::ok( ParseResultType::Matched ); }; + auto const setBenchmarkSamples = [&]( std::string const& samples ) { + auto parsedSamples = parseUInt( samples ); + if ( !parsedSamples ) { + return ParserResult::runtimeError( + "Could not parse '" + samples + "' as benchmark samples" ); + } + if ( *parsedSamples == 0 ) { + return ParserResult::runtimeError( + "Benchmark samples must be greater than 0" ); + } + config.benchmarkSamples = *parsedSamples; + return ParserResult::ok( ParseResultType::Matched ); + }; auto const setShardIndex = [&](std::string const& shardIndex) { auto parsedIndex = parseUInt( shardIndex ); @@ -3367,6 +3437,43 @@ namespace Catch { return ParserResult::ok( ParseResultType::Matched ); }; + auto const setSectionFilter = [&]( std::string const& sectionFilter ) { + config.pathFilters.emplace_back( PathFilter::For::Section, trim(sectionFilter) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setGeneratorFilter = [&]( std::string const& generatorFilter ) { + if (generatorFilter != "*") { + // TODO: avoid re-parsing the index? + auto parsedIndex = parseUInt( generatorFilter ); + if ( !parsedIndex ) { + return ParserResult::runtimeError( "Could not parse '" + + generatorFilter + + "' as generator index" ); + } + } + config.useNewPathFilteringBehaviour = true; + config.pathFilters.emplace_back( PathFilter::For::Generator, trim(generatorFilter) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + // Copy-capturing other `setFoo` functions enables calling them later, + // as the config ref remains valid, but the local lambda vars won't. + auto const setPathFilter = [=, &config]( std::string const& pathFilter ) { + config.useNewPathFilteringBehaviour = true; + if ( pathFilter.size() < 3 ) { + return ParserResult::runtimeError( + "Path filter '" + pathFilter + "' is too short" ); + } + if ( startsWith( pathFilter, "g:" ) ) { + return setGeneratorFilter( pathFilter.substr( 2 ) ); + } + if ( startsWith( pathFilter, "c:" ) ) { + return setSectionFilter( pathFilter.substr( 2 ) ); + } + return ParserResult::runtimeError( "Path filter '" + pathFilter + + "' has unknown type prefix" ); + }; + + auto cli = ExeName( config.processName ) | Help( config.showHelp ) @@ -3412,9 +3519,15 @@ namespace Catch { | Opt( config.filenamesAsTags ) ["-#"]["--filenames-as-tags"] ( "adds a tag for the filename" ) - | Opt( config.sectionsToRun, "section name" ) + | Opt( accept_many, setSectionFilter, "section name" ) ["-c"]["--section"] ( "specify section to run" ) + | Opt( accept_many, setGeneratorFilter, "index spec" ) + ["-g"]["--generator-index"] + ( "specify generator elements to try" ) + | Opt( accept_many, setPathFilter, "path filter spec" ) + ["-p"]["--path-filter"] + ( "qualified path filter" ) | Opt( setVerbosity, "quiet|normal|high" ) ["-v"]["--verbosity"] ( "set output verbosity" ) @@ -3448,7 +3561,7 @@ namespace Catch { | Opt( config.skipBenchmarks) ["--skip-benchmarks"] ( "disable running benchmarks") - | Opt( config.benchmarkSamples, "samples" ) + | Opt( setBenchmarkSamples, "samples" ) ["--benchmark-samples"] ( "number of samples to collect (default: 100)" ) | Opt( config.benchmarkResamples, "resamples" ) @@ -5752,12 +5865,46 @@ namespace Catch { struct GeneratorTracker final : TestCaseTracking::TrackerBase, IGeneratorTracker { GeneratorBasePtr m_generator; + // Filtered generator has moved to specific index due to + // a filter, it needs special handling of `countedNext()` + bool m_isFiltered = false; GeneratorTracker( TestCaseTracking::NameAndLocation&& nameAndLocation, TrackerContext& ctx, - ITracker* parent ): - TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ) {} + ITracker* parent, + GeneratorBasePtr&& generator ): + TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ), + m_generator( CATCH_MOVE( generator ) ) { + assert( m_generator && + "Cannot create tracker without generator" ); + + // Handle potential filter and move forward here... + // Old style filters do not affect generators at all + if (m_newStyleFilters && m_allTrackerDepth < m_filterRef->size()) { + auto const& filter = + ( *m_filterRef )[m_allTrackerDepth]; + // Generator cannot be un-entered the way a section + // can be, so the tracker has to throw for a wrong + // filter to stop the execution flow. + if (filter.type == PathFilter::For::Section) { + // TBD: Explicit SKIP, or new exception that says + // "don't continue", but doesn't show in totals? + SKIP(); + } + // '*' is the wildcard for "all elements in generator" + // used for filtering sections below the generator, but + // not the generator itself. + if ( filter.filter != "*" ) { + m_isFiltered = true; + // TBD: We assume that the filter was validated as + // number during parsing. We should pass it + // as number from the CLI parser. + size_t targetIndex = std::stoul( filter.filter ); + m_generator->skipToNthElement( targetIndex ); + } + } + } static GeneratorTracker* acquire( TrackerContext& ctx, @@ -5801,9 +5948,6 @@ namespace Catch { // TrackerBase interface bool isGeneratorTracker() const override { return true; } - auto hasGenerator() const -> bool override { - return !!m_generator; - } void close() override { TrackerBase::close(); // If a generator has a child (it is followed by a section) @@ -5832,25 +5976,24 @@ namespace Catch { // _can_ start, and thus we should wait for them, or // they cannot start (due to filters), and we shouldn't // wait for them - ITracker* 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<SectionTracker const&>( *parent ); - auto const& filters = parentSection.getFilters(); - // No filters -> no restrictions on running sections - if ( filters.empty() ) { return true; } + // No filters left -> no restrictions on running sections + size_t childDepth = 1 + (m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth); + if ( childDepth >= m_filterRef->size() ) { + return true; + } + // If we are using the new style filters, we need to check + // whether the successive filter is for section or a generator. + if ( m_newStyleFilters + && (*m_filterRef)[childDepth].type != PathFilter::For::Section ) { + return false; + } + // Look for any child section that could match the remaining filters for ( auto const& child : m_children ) { if ( child->isSectionTracker() && static_cast<SectionTracker const&>( *child ) - .trimmedName() == filters[0] ) { + .trimmedName() == StringRef((*m_filterRef)[childDepth].filter) ) { return true; } } @@ -5862,9 +6005,10 @@ namespace Catch { // value, but we do not want to invoke the side-effect if // this generator is still waiting for any child to start. assert( m_generator && "Tracker without generator" ); - if ( should_wait_for_child || - ( m_runState == CompletedSuccessfully && - m_generator->countedNext() ) ) { + if ( should_wait_for_child + || ( m_runState == CompletedSuccessfully + && !m_isFiltered // filtered generators cannot meaningfully move forward, as they would get past the filter + && m_generator->countedNext() ) ) { m_children.clear(); m_runState = Executing; } @@ -5874,9 +6018,6 @@ namespace Catch { auto getGenerator() const -> GeneratorBasePtr const& override { return m_generator; } - void setGenerator( GeneratorBasePtr&& generator ) override { - m_generator = CATCH_MOVE( generator ); - } }; } // namespace } @@ -5930,13 +6071,16 @@ namespace Catch { // we only keep around the raw msg ids. ~MessageHolder() = default; + void addUnscopedMessage( MessageInfo&& info ) { + repairUnscopedMessageInvariant(); + unscoped_ids.push_back( info.sequence ); + messages.push_back( CATCH_MOVE( info ) ); + } void addUnscopedMessage(MessageBuilder&& builder) { - repairUnscopedMessageInvariant(); MessageInfo info( CATCH_MOVE( builder.m_info ) ); info.message = builder.m_stream.str(); - unscoped_ids.push_back( info.sequence ); - messages.push_back( CATCH_MOVE( info ) ); + addUnscopedMessage( CATCH_MOVE( info ) ); } void addScopedMessage(MessageInfo&& info) { @@ -6027,7 +6171,8 @@ namespace Catch { ITracker& rootTracker = m_trackerContext.startRun(); assert(rootTracker.isSectionTracker()); - static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + rootTracker.setFilters( &m_config->getPathFilters(), + m_config->useNewFilterBehaviour() ); // We intentionally only seed the internal RNG once per test case, // before it is first invoked. The reason for that is a complex @@ -6199,18 +6344,28 @@ namespace Catch { SourceLineInfo lineInfo, Generators::GeneratorBasePtr&& generator ) { + // TBD: Do we want to avoid the warning if the generator is filtered? + if ( m_config->warnAboutInfiniteGenerators() && + !generator->isFinite() ) { + // TBD: Would it be better to expand this macro inline? + FAIL( "GENERATE() would run infinitely" ); + } + auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast<std::string>( generatorName ), lineInfo ); auto& currentTracker = m_trackerContext.currentTracker(); assert( currentTracker.nameAndLocation() != nameAndLoc && "Trying to create tracker for a generator that already has one" ); - auto newTracker = Catch::Detail::make_unique<Generators::GeneratorTracker>( - CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker ); + auto newTracker = + Catch::Detail::make_unique<Generators::GeneratorTracker>( + CATCH_MOVE( nameAndLoc ), + m_trackerContext, + ¤tTracker, + CATCH_MOVE( generator ) ); auto ret = newTracker.get(); currentTracker.addChild( CATCH_MOVE( newTracker ) ); - ret->setGenerator( CATCH_MOVE( generator ) ); ret->open(); return ret; } @@ -6613,6 +6768,10 @@ namespace Catch { Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) ); } + void IResultCapture::addUnscopedMessage( MessageInfo&& message ) { + Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( message ) ); + } + void seedRng(IConfig const& config) { sharedRng().seed(config.rngSeed()); } @@ -7171,6 +7330,17 @@ namespace TestCaseTracking { location( _location ) {} + ITracker::ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ): + m_nameAndLocation( CATCH_MOVE( nameAndLoc ) ), m_parent( parent ) { + if ( m_parent ) { + m_allTrackerDepth = m_parent->m_allTrackerDepth + 1; + // We leave section trackers to bump themselves up, as + // we cannot use `isSectionTracker` in constructor + m_sectionOnlyDepth = m_parent->m_sectionOnlyDepth; + m_filterRef = m_parent->m_filterRef; + m_newStyleFilters = m_parent->m_newStyleFilters; + } + } ITracker::~ITracker() = default; @@ -7303,25 +7473,32 @@ namespace TestCaseTracking { : TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ), m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name))) { - if( parent ) { - while ( !parent->isSectionTracker() ) { - parent = parent->parent(); - } - - SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); - addNextFilters( parentSection.m_filters ); + if( m_parent ) { + ++m_sectionOnlyDepth; } } bool SectionTracker::isComplete() const { - bool complete = true; - - if ( m_filters.empty() - || m_filters[0].empty() - || m_filters[0] == m_trimmed_name ) { - complete = TrackerBase::isComplete(); + // If there are active filters AND we do not pass them, + // the section is always "completed" + const size_t filterIndex = + m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth; + if ( filterIndex < m_filterRef->size() ) { + // There is active filter, check it + // 1) New style filter must explicitly target section + if ( m_newStyleFilters && ( *m_filterRef )[filterIndex].type != + PathFilter::For::Section ) { + return true; + } + // 2) Both style filters must match the trimmed name exactly + if ( m_trimmed_name != + StringRef( ( *m_filterRef )[filterIndex].filter ) ) { + return true; + } } - return complete; + + // Otherwise we delegate to the generic processing + return TrackerBase::isComplete(); } bool SectionTracker::isSectionTracker() const { return true; } @@ -7357,19 +7534,6 @@ namespace TestCaseTracking { open(); } - void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) { - if( !filters.empty() ) { - m_filters.reserve( m_filters.size() + filters.size() + 2 ); - m_filters.emplace_back(StringRef{}); // Root - should never be consulted - m_filters.emplace_back(StringRef{}); // Test Case - not a section filter - m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); - } - } - void SectionTracker::addNextFilters( std::vector<StringRef> const& filters ) { - if( filters.size() > 1 ) - m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() ); - } - StringRef SectionTracker::trimmedName() const { return m_trimmed_name; } diff --git a/extras/catch_amalgamated.hpp b/extras/catch_amalgamated.hpp index 00fc3ee1..6de74f80 100644 --- a/extras/catch_amalgamated.hpp +++ b/extras/catch_amalgamated.hpp @@ -6,8 +6,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.12.0 -// Generated: 2025-12-28 22:27:25.408132 +// Catch v3.13.0 +// Generated: 2026-02-15 22:54:59.817776 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -299,7 +299,7 @@ //////////////////////////////////////////////////////////////////////////////// // Visual C++ -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) // We want to defer to nvcc-specific warning suppression if we are compiled // with nvcc masquerading for MSVC. @@ -310,6 +310,11 @@ __pragma( warning( pop ) ) # endif |