Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMartin Hořeňovský2026-02-15 22:55:48 +0100
committerMartin Hořeňovský2026-02-15 22:55:48 +0100
commit29c9844f688acb27c87338c39cd186ebfe41aa19 (patch)
tree82526279e11e38f055605e0dcdbfc04ebfe63752
parent56024c04e42172d53a1672b9af6a538afdb79801 (diff)
downloadcatch3-3.13.0.tar.gz
v3.13.0v3.13.0
-rw-r--r--CMakeLists.txt2
-rw-r--r--docs/command-line.md4
-rw-r--r--docs/deprecations.md4
-rw-r--r--docs/filtering-execution-path.md6
-rw-r--r--docs/generators.md8
-rw-r--r--docs/logging.md4
-rw-r--r--docs/release-notes.md27
-rw-r--r--extras/catch_amalgamated.cpp324
-rw-r--r--extras/catch_amalgamated.hpp320
-rw-r--r--meson.build2
-rw-r--r--src/catch2/catch_version.cpp2
-rw-r--r--src/catch2/catch_version_macros.hpp2
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 &lt;index in generator&gt;</pre>
<pre>-p, --path-filter &lt;path filter spec&gt;</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, &currentTracker );
+ auto newTracker =
+ Catch::Detail::make_unique<Generators::GeneratorTracker>(
+ CATCH_MOVE( nameAndLoc ),
+ m_trackerContext,
+ &currentTracker,
+ 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