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
@@ -540,7 +540,7 @@ Prints the command line arguments to stdout
-g, --generator-index <index in generator>
-p, --path-filter <path filter spec>
-> 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 @@
# 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
@@ -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
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` was introduced in Catch2 2.10.0.
-> `ConcatGenerator` was introduced in Catch2 X.Y.Z
+> `ConcatGenerator` 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`.
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**
+[3.13.0](#3130)
[3.12.0](#3120)
[3.11.0](#3110)
[3.10.0](#3100)
@@ -72,6 +73,32 @@
[Even Older versions](#even-older-versions)
+## 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 const& Config::getTestsOrTags() const { return m_data.testsOrTags; }
- std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }
+ std::vector const& Config::getPathFilters() const { return m_data.pathFilters; }
+ bool Config::useNewFilterBehaviour() const { return m_data.useNewPathFilteringBehaviour; }
std::vector 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(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::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
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(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(
@@ -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();
+
+ // 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;
}
- 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; }
+ // 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( *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,15 +6071,18 @@ namespace Catch {
// we only keep around the raw msg ids.
~MessageHolder() = default;
-
- void addUnscopedMessage(MessageBuilder&& builder) {
+ void addUnscopedMessage( MessageInfo&& info ) {
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 ) );
}
+ void addUnscopedMessage(MessageBuilder&& builder) {
+ MessageInfo info( CATCH_MOVE( builder.m_info ) );
+ info.message = builder.m_stream.str();
+ addUnscopedMessage( CATCH_MOVE( info ) );
+ }
+
void addScopedMessage(MessageInfo&& info) {
messages.push_back( CATCH_MOVE( info ) );
}
@@ -6027,7 +6171,8 @@ namespace Catch {
ITracker& rootTracker = m_trackerContext.startRun();
assert(rootTracker.isSectionTracker());
- static_cast(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( 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(
- CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker );
+ auto newTracker =
+ Catch::Detail::make_unique(
+ 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( *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 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 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
+// Suppress MSVC C++ Core Guidelines checker warning 26426:
+// "Global initializer calls a non-constexpr function (i.22)"
+# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
+ __pragma( warning( disable : 26426 ) )
+
// Universal Windows platform does not support SEH
# if !defined(CATCH_PLATFORM_WINDOWS_UWP)
# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
@@ -1033,7 +1038,7 @@ namespace Catch {
// We cannot forward declare the type with default template argument
// multiple times, so it is split out into a separate header so that
- // we can prevent multiple declarations in dependees
+ // we can prevent multiple declarations in dependencies
template
struct BenchmarkStats;
@@ -1090,6 +1095,7 @@ namespace Catch {
static void pushScopedMessage( MessageInfo&& message );
static void popScopedMessage( unsigned int messageId );
+ static void addUnscopedMessage( MessageInfo&& message );
static void emplaceUnscopedMessage( MessageBuilder&& builder );
virtual void handleFatalErrorCondition( StringRef message ) = 0;
@@ -1189,6 +1195,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 {
@@ -1220,6 +1228,7 @@ namespace Catch {
class TestSpec;
class IStream;
+ struct PathFilter;
class IConfig : public Detail::NonCopyable {
public:
@@ -1231,6 +1240,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;
@@ -1244,7 +1254,9 @@ namespace Catch {
virtual unsigned int shardCount() const = 0;
virtual unsigned int shardIndex() const = 0;
virtual ColourMode defaultColourMode() const = 0;
- virtual std::vector const& getSectionsToRun() const = 0;
+ virtual std::vector const& getPathFilters() const = 0;
+ virtual bool useNewFilterBehaviour() const = 0;
+
virtual Verbosity verbosity() const = 0;
virtual bool skipBenchmarks() const = 0;
@@ -3630,6 +3642,33 @@ namespace Catch {
#endif // CATCH_OPTIONAL_HPP_INCLUDED
+#ifndef CATCH_PATH_FILTER_HPP_INCLUDED
+#define CATCH_PATH_FILTER_HPP_INCLUDED
+
+
+#include
+
+namespace Catch {
+
+ struct PathFilter {
+ enum class For {
+ Section,
+ Generator,
+ };
+ PathFilter( For type_, std::string filter_ ):
+ type( type_ ), filter( CATCH_MOVE( filter_ ) ) {}
+
+ For type;
+ std::string filter;
+
+ friend bool operator==( PathFilter const& lhs, PathFilter const& rhs );
+ };
+
+} // end namespace Catch
+
+#endif // CATCH_PATH_FILTER_HPP_INCLUDED
+
+
#ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED
#define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED
@@ -3797,7 +3836,8 @@ namespace Catch {
std::vector reporterSpecifications;
std::vector testsOrTags;
- std::vector sectionsToRun;
+ std::vector pathFilters;
+ bool useNewPathFilteringBehaviour = false;
std::string prematureExitGuardFilePath;
};
@@ -3820,7 +3860,8 @@ namespace Catch {
getProcessedReporterSpecs() const;
std::vector const& getTestsOrTags() const override;
- std::vector const& getSectionsToRun() const override;
+ std::vector const& getPathFilters() const override;
+ bool useNewFilterBehaviour() const override;
TestSpec const& testSpec() const override;
bool hasTestFilters() const override;
@@ -3835,6 +3876,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;
@@ -3943,9 +3985,9 @@ namespace Catch {
#if !defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS )
-# define DEPRECATED( msg ) [[deprecated( msg )]]
+# define CATCH_DEPRECATED( msg ) [[deprecated( msg )]]
#else
-# define DEPRECATED( msg )
+# define CATCH_DEPRECATED( msg )
#endif
#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED
@@ -3966,11 +4008,11 @@ namespace Catch {
// The "ID" of the message, used to know when to remove it from reporter context.
unsigned int sequence;
- DEPRECATED( "Explicitly use the 'sequence' member instead" )
+ CATCH_DEPRECATED( "Explicitly use the 'sequence' member instead" )
bool operator == (MessageInfo const& other) const {
return sequence == other.sequence;
}
- DEPRECATED( "Explicitly use the 'sequence' member instead" )
+ CATCH_DEPRECATED( "Explicitly use the 'sequence' member instead" )
bool operator < (MessageInfo const& other) const {
return sequence < other.sequence;
}
@@ -4028,8 +4070,9 @@ namespace Catch {
class Capturer {
std::vector m_messages;
size_t m_captured = 0;
+ bool m_isScoped = false;
public:
- Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
+ Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names, bool isScoped );
Capturer(Capturer const&) = delete;
Capturer& operator=(Capturer const&) = delete;
@@ -4061,11 +4104,12 @@ namespace Catch {
} while( false )
///////////////////////////////////////////////////////////////////////////////
-#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \
- Catch::Capturer varName( macroName##_catch_sr, \
- CATCH_INTERNAL_LINEINFO, \
- Catch::ResultWas::Info, \
- #__VA_ARGS__##_catch_sr ); \
+#define INTERNAL_CATCH_CAPTURE( varName, macroName, scopedCapture, ... ) \
+ Catch::Capturer varName( macroName##_catch_sr, \
+ CATCH_INTERNAL_LINEINFO, \
+ Catch::ResultWas::Info, \
+ #__VA_ARGS__##_catch_sr, \
+ scopedCapture ); \
varName.captureValues( 0, __VA_ARGS__ )
///////////////////////////////////////////////////////////////////////////////
@@ -4082,28 +4126,32 @@ namespace Catch {
#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
#define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg )
#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
- #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ )
+ #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", true, __VA_ARGS__ )
+ #define CATCH_UNSCOPED_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_UNSCOPED_CAPTURE", false, __VA_ARGS__ )
#elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE)
- #define CATCH_INFO( msg ) (void)(0)
- #define CATCH_UNSCOPED_INFO( msg ) (void)(0)
- #define CATCH_WARN( msg ) (void)(0)
- #define CATCH_CAPTURE( ... ) (void)(0)
+ #define CATCH_INFO( msg ) (void)(0)
+ #define CATCH_UNSCOPED_INFO( msg ) (void)(0)
+ #define CATCH_WARN( msg ) (void)(0)
+ #define CATCH_CAPTURE( ... ) (void)(0)
+ #define CATCH_UNSCOPED_CAPTURE( ... ) (void)(0)
#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)
#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
#define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg )
#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
- #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ )
+ #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", true, __VA_ARGS__ )
+ #define UNSCOPED_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "UNSCOPED_CAPTURE", false, __VA_ARGS__ )
#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE)
- #define INFO( msg ) (void)(0)
- #define UNSCOPED_INFO( msg ) (void)(0)
- #define WARN( msg ) (void)(0)
- #define CAPTURE( ... ) (void)(0)
+ #define INFO( msg ) (void)(0)
+ #define UNSCOPED_INFO( msg ) (void)(0)
+ #define WARN( msg ) (void)(0)
+ #define CAPTURE( ... ) (void)(0)
+ #define UNSCOPED_CAPTURE( ... ) (void)(0)
#endif // end of user facing macro declarations
@@ -7466,7 +7514,7 @@ namespace Catch {
#define CATCH_VERSION_MACROS_HPP_INCLUDED
#define CATCH_VERSION_MAJOR 3
-#define CATCH_VERSION_MINOR 12
+#define CATCH_VERSION_MINOR 13
#define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
@@ -7521,6 +7569,24 @@ namespace Catch {
+#ifndef CATCH_GENERATORS_THROW_HPP_INCLUDED
+#define CATCH_GENERATORS_THROW_HPP_INCLUDED
+
+namespace Catch {
+ namespace Generators {
+ namespace Detail {
+
+ //! Throws GeneratorException with the provided message
+ [[noreturn]]
+ void throw_generator_exception( char const* msg );
+
+ } // namespace Detail
+ } // namespace Generators
+} // namespace Catch
+
+#endif // CATCH_GENERATORS_THROW_HPP_INCLUDED
+
+
#ifndef CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED
#define CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED
@@ -7549,6 +7615,15 @@ namespace Catch {
//! Customization point for `currentElementAsString`
virtual std::string stringifyImpl() const = 0;
+ /**
+ * Customization point for skipping to the n-th element
+ *
+ * Defaults to successively calling `countedNext`. If there
+ * are not enough elements to reach the nth one, will throw
+ * an error.
+ */
+ virtual void skipToNthElementImpl( std::size_t n );
+
public:
GeneratorUntypedBase() = default;
// Generation of copy ops is deprecated (and Clang will complain)
@@ -7572,6 +7647,13 @@ namespace Catch {
std::size_t currentElementIndex() const { return m_currentElementIndex; }
+ /**
+ * Moves the generator forward **to** the n-th element
+ *
+ * Cannot move backwards. Can stay in place.
+ */
+ void skipToNthElement( std::size_t n );
+
/**
* Returns generator's current element as user-friendly string.
*
@@ -7586,6 +7668,15 @@ namespace Catch {
* comes first.
*/
StringRef currentElementAsString() const;
+
+ /**
+ * Returns true if calls to `next` will eventually return false
+ *
+ * Note that for backwards compatibility this is currently defaulted
+ * to return `true`, but in the future all generators will have to
+ * provide their own implementation.
+ */
+ virtual bool isFinite() const;
};
using GeneratorBasePtr = Catch::Detail::unique_ptr;
@@ -7594,9 +7685,7 @@ namespace Catch {
class IGeneratorTracker {
public:
virtual ~IGeneratorTracker(); // = default;
- virtual auto hasGenerator() const -> bool = 0;
virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0;
- virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0;
};
} // namespace Catch
@@ -7610,14 +7699,6 @@ namespace Catch {
namespace Generators {
-namespace Detail {
-
- //! Throws GeneratorException with the provided message
- [[noreturn]]
- void throw_generator_exception(char const * msg);
-
-} // end namespace detail
-
template
class IGenerator : public GeneratorUntypedBase {
std::string stringifyImpl() const override {
@@ -7652,6 +7733,9 @@ namespace Detail {
bool next() {
return m_generator->countedNext();
}
+
+ bool isFinite() const { return m_generator->isFinite(); }
+ void skipToNthElement( size_t n ) { m_generator->skipToNthElement(n); }
};
@@ -7672,6 +7756,8 @@ namespace Detail {
bool next() override {
return false;
}
+
+ bool isFinite() const override { return true; }
};
template
@@ -7681,6 +7767,15 @@ namespace Detail {
"specialization, use SingleValue Generator instead.");
std::vector m_values;
size_t m_idx = 0;
+
+ void skipToNthElementImpl( std::size_t n ) override {
+ if ( n >= m_values.size() ) {
+ Detail::throw_generator_exception(
+ "Coud not jump to Nth element: not enough elements" );
+ }
+ m_idx = n;
+ }
+
public:
FixedValuesGenerator( std::initializer_list values ) : m_values( values ) {}
@@ -7691,6 +7786,8 @@ namespace Detail {
++m_idx;
return m_idx < m_values.size();
}
+
+ bool isFinite() const override { return true; }
};
template >
@@ -7755,6 +7852,14 @@ namespace Detail {
}
return m_current < m_generators.size();
}
+
+ bool isFinite() const override {
+ for (auto const& gen : m_generators) {
+ if (!gen.isFinite()) { return false;
+ }
+ }
+ return true;
+ }
};
@@ -7846,6 +7951,17 @@ namespace Generators {
GeneratorWrapper m_generator;
size_t m_returned = 0;
size_t m_target;
+
+ void skipToNthElementImpl( std::size_t n ) override {
+ if ( n >= m_target ) {
+ Detail::throw_generator_exception(
+ "Coud not jump to Nth element: not enough elements" );
+ }
+
+ m_generator.skipToNthElement( n );
+ m_returned = n;
+ }
+
public:
TakeGenerator(size_t target, GeneratorWrapper&& generator):
m_generator(CATCH_MOVE(generator)),
@@ -7870,6 +7986,8 @@ namespace Generators {
}
return success;
}
+
+ bool isFinite() const override { return true; }
};
template
@@ -7911,6 +8029,8 @@ namespace Generators {
while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true);
return success;
}
+
+ bool isFinite() const override { return m_generator.isFinite(); }
};
@@ -7935,6 +8055,9 @@ namespace Generators {
m_target_repeats(repeats)
{
assert(m_target_repeats > 0 && "Repeat generator must repeat at least once");
+ if (!m_generator.isFinite()) {
+ Detail::throw_generator_exception( "Cannot repeat infinite generator" );
+ }
}
T const& get() const override {
@@ -7968,6 +8091,8 @@ namespace Generators {
}
return m_current_repeat < m_target_repeats;
}
+
+ bool isFinite() const override { return m_generator.isFinite(); }
};
template
@@ -7981,25 +8106,30 @@ namespace Generators {
GeneratorWrapper m_generator;
Func m_function;
// To avoid returning dangling reference, we have to save the values
- T m_cache;
+ mutable Optional m_cache;
+
+ void skipToNthElementImpl( std::size_t n ) override {
+ m_generator.skipToNthElement( n );
+ m_cache.reset();
+ }
+
public:
template
MapGenerator(F2&& function, GeneratorWrapper&& generator) :
m_generator(CATCH_MOVE(generator)),
- m_function(CATCH_FORWARD(function)),
- m_cache(m_function(m_generator.get()))
+ m_function(CATCH_FORWARD(function))
{}
T const& get() const override {
- return m_cache;
+ if ( !m_cache ) { m_cache = m_function( m_generator.get() ); }
+ return *m_cache;
}
bool next() override {
- const auto success = m_generator.next();
- if (success) {
- m_cache = m_function(m_generator.get());
- }
- return success;
+ m_cache.reset();
+ return m_generator.next();
}
+
+ bool isFinite() const override { return m_generator.isFinite(); }
};
template >
@@ -8021,7 +8151,6 @@ namespace Generators {
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(CATCH_MOVE(generator))
@@ -8050,6 +8179,8 @@ namespace Generators {
}
return true;
}
+
+ bool isFinite() const override { return m_generator.isFinite(); }
};
template
@@ -8059,6 +8190,56 @@ namespace Generators {
);
}
+ template
+ class ConcatGenerator final : public IGenerator {
+ std::vector> m_generators;
+ size_t m_current_generator = 0;
+
+ void InsertGenerators( GeneratorWrapper&& gen ) {
+ m_generators.push_back( CATCH_MOVE( gen ) );
+ }
+
+ template
+ void InsertGenerators( GeneratorWrapper&& gen, Generators&&... gens ) {
+ m_generators.push_back( CATCH_MOVE( gen ) );
+ InsertGenerators( CATCH_MOVE( gens )... );
+ }
+
+ public:
+ template
+ ConcatGenerator( Generators&&... generators ) {
+ InsertGenerators( CATCH_MOVE( generators )... );
+ }
+
+ T const& get() const override {
+ return m_generators[m_current_generator].get();
+ }
+ bool next() override {
+ const bool success = m_generators[m_current_generator].next();
+ if ( success ) { return true; }
+
+ // If current generator is used up, we have to move to the next one
+ ++m_current_generator;
+ return m_current_generator < m_generators.size();
+ }
+
+ bool isFinite() const override {
+ for ( auto const& gen : m_generators ) {
+ if ( !gen.isFinite() ) { return false; }
+ }
+ return true;
+ }
+ };
+
+ template
+ GeneratorWrapper cat( GeneratorWrapper&& generator,
+ Generators&&... generators ) {
+ return GeneratorWrapper(
+ Catch::Detail::make_unique>(
+ CATCH_MOVE( generator ), CATCH_MOVE( generators )... ) );
+ }
+
+
} // namespace Generators
} // namespace Catch
@@ -8701,6 +8882,7 @@ public:
m_current_number = m_dist(m_rng);
return true;
}
+ bool isFinite() const override { return false; }
};
template <>
@@ -8718,6 +8900,7 @@ public:
bool next() override;
~RandomFloatingGenerator() override; // = default
+ bool isFinite() const override;
};
template
@@ -8739,6 +8922,7 @@ public:
m_current_number = m_dist(m_rng);
return true;
}
+ bool isFinite() const override { return false; }
};
template
@@ -8808,6 +8992,8 @@ public:
m_current += m_step;
return (m_positive) ? (m_current < m_end) : (m_current > m_end);
}
+
+ bool isFinite() const override { return true; }
};
template
@@ -8847,6 +9033,8 @@ public:
++m_current;
return m_current != m_elems.size();
}
+
+ bool isFinite() const override { return true; }
};
template
namespace Catch {
+
+struct PathFilter;
+
namespace TestCaseTracking {
struct NameAndLocation {
@@ -10453,12 +10644,23 @@ namespace TestCaseTracking {
Children m_children;
CycleState m_runState = NotStarted;
- public:
- ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ):
- m_nameAndLocation( CATCH_MOVE(nameAndLoc) ),
- m_parent( parent )
- {}
+ // Members for path filtering
+ std::vector const* m_filterRef = nullptr;
+ // Note: There are 2 dummy section trackers (root, test-case) before
+ // the first "real" section tracker can be encountered. We start
+ // the default tracker at -2, so that the first "real" section
+ // tracker overflows to index 0.
+ // Nesting depth of this tracker, used to decide which new-style filter applies.
+ size_t m_allTrackerDepth = static_cast( -2 );
+ // Nesting depth of sections (inc. this tracker), used for old-style filters.
+ // Must be updated by the section tracker on its own.
+ size_t m_sectionOnlyDepth = static_cast( -2 );
+ // Transitory: Remove once we remove backwards compatibility with old-style (v3.x) filters
+ bool m_newStyleFilters = false;
+
+ public:
+ ITracker( NameAndLocation&& nameAndLoc, ITracker* parent );
// static queries
NameAndLocation const& nameAndLocation() const {
@@ -10484,6 +10686,11 @@ namespace TestCaseTracking {
//! Returns true iff tracker has started
bool hasStarted() const;
+ void setFilters( std::vector const* filters, bool newStyleFilters ) {
+ m_filterRef = filters;
+ m_newStyleFilters = newStyleFilters;
+ }
+
// actions
virtual void close() = 0; // Successfully complete
virtual void fail() = 0;
@@ -10569,8 +10776,7 @@ namespace TestCaseTracking {
void moveToThis();
};
- class SectionTracker : public TrackerBase {
- std::vector m_filters;
+ class SectionTracker final : public TrackerBase {
// Note that lifetime-wise we piggy back off the name stored in the `ITracker` parent`.
// Currently it allocates owns the name, so this is safe. If it is later refactored
// to not own the name, the name still has to outlive the `ITracker` parent, so
@@ -10587,10 +10793,6 @@ namespace TestCaseTracking {
void tryOpen();
- void addInitialFilters( std::vector const& filters );
- void addNextFilters( std::vector const& filters );
- //! Returns filters active in this tracker
- std::vector const& getFilters() const { return m_filters; }
//! Returns whitespace-trimmed name of the tracked section
StringRef trimmedName() const;
};
diff --git a/meson.build b/meson.build
index 8a50e339..0997e56d 100644
--- a/meson.build
+++ b/meson.build
@@ -8,7 +8,7 @@
project(
'catch2',
'cpp',
- version: '3.12.0', # CML version placeholder, don't delete
+ version: '3.13.0', # CML version placeholder, don't delete
license: 'BSL-1.0',
meson_version: '>=0.54.1',
)
diff --git a/src/catch2/catch_version.cpp b/src/catch2/catch_version.cpp
index 3b558a37..6e2b7ae1 100644
--- a/src/catch2/catch_version.cpp
+++ b/src/catch2/catch_version.cpp
@@ -36,7 +36,7 @@ namespace Catch {
}
Version const& libraryVersion() {
- static Version version( 3, 12, 0, "", 0 );
+ static Version version( 3, 13, 0, "", 0 );
return version;
}
diff --git a/src/catch2/catch_version_macros.hpp b/src/catch2/catch_version_macros.hpp
index b78f9cfd..33d318a9 100644
--- a/src/catch2/catch_version_macros.hpp
+++ b/src/catch2/catch_version_macros.hpp
@@ -9,7 +9,7 @@
#define CATCH_VERSION_MACROS_HPP_INCLUDED
#define CATCH_VERSION_MAJOR 3
-#define CATCH_VERSION_MINOR 12
+#define CATCH_VERSION_MINOR 13
#define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED