diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4d497cc6..7946bc52 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.13.0 # CML version placeholder, don't delete
+ VERSION 3.14.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/release-notes.md b/docs/release-notes.md
index 3060f1ab..d2c4ce63 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -2,6 +2,7 @@
# Release notes
**Contents**
+[3.14.0](#3140)
[3.13.0](#3130)
[3.12.0](#3120)
[3.11.0](#3110)
@@ -73,6 +74,22 @@
[Even Older versions](#even-older-versions)
+## 3.14.0
+
+### Fixes
+* Added missing `` includes. (#3078)
+* Fixed suppression of empty variadic macro arguments warning on Clang <19. (#3085)
+* Fixed `catch_discover_tests` failing during `PRE_TEST` discovery if a target does not have discoverable tests. (#3075)
+* Fixed build of the main library failing with `CATCH_CONFIG_PREFIX_ALL` defined. (#3087)
+* JUnit reporter outputs single failed (errored/skipped) assertion per test case. (#1919)
+
+### Improvements
+* The default implementation of `--list-tags` and `--list-listeners` has a quiet variant.
+* Suppressed the new Clang warning about `__COUNTER__` usage. (#3076)
+* Line-wrapping counts utf-8 codepoints instead of bytes. (#1022, #3086)
+ * Combining character sequences are still miscounted, but Catch2 does not aim to fully support Unicode.
+
+
## 3.13.0
### Fixes
@@ -80,7 +97,6 @@
* 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)
diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp
index adafc64b..b0c5c7ca 100644
--- a/extras/catch_amalgamated.cpp
+++ b/extras/catch_amalgamated.cpp
@@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0
-// Catch v3.13.0
-// Generated: 2026-02-15 22:55:00.269529
+// Catch v3.14.0
+// Generated: 2026-04-05 15:03:01.631668
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
@@ -2397,7 +2397,7 @@ namespace Catch {
}
Version const& libraryVersion() {
- static Version version( 3, 13, 0, "", 0 );
+ static Version version( 3, 14, 0, "", 0 );
return version;
}
@@ -5888,9 +5888,14 @@ namespace Catch {
// 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();
+ // We want the semantics of `SKIP()`, but we inline it
+ // to avoid issues with conditionally prefixed macros
+ INTERNAL_CATCH_MSG(
+ "SKIP",
+ Catch::ResultWas::ExplicitSkip,
+ Catch::ResultDisposition::Normal,
+ "" );
+ Catch::Detail::Unreachable();
}
// '*' is the wildcard for "all elements in generator"
// used for filtering sections below the generator, but
@@ -6347,8 +6352,12 @@ namespace Catch {
// 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" );
+ // We want the semantics of `FAIL()`, but we inline it
+ // to avoid issues with conditionally prefixed macros
+ INTERNAL_CATCH_MSG( "FAIL",
+ Catch::ResultWas::ExplicitFailure,
+ Catch::ResultDisposition::Normal,
+ "GENERATE() would run infinitely" );
}
auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast( generatorName ), lineInfo );
@@ -7893,6 +7902,10 @@ namespace {
return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr;
}
+ bool isUtf8ContinuationByte( char c ) {
+ return ( static_cast( c ) & 0xC0 ) == 0x80;
+ }
+
} // namespace
namespace Catch {
@@ -7919,6 +7932,11 @@ namespace Catch {
if ( it != m_string.end() ) {
++m_size;
++it;
+ // Skip UTF-8 continuation bytes
+ while ( it != m_string.end() &&
+ isUtf8ContinuationByte( *it ) ) {
+ ++it;
+ }
}
}
}
@@ -7981,6 +7999,11 @@ namespace Catch {
void AnsiSkippingString::const_iterator::advance() {
assert( m_it != m_string->end() );
m_it++;
+ // Skip UTF-8 continuation bytes
+ while ( m_it != m_string->end() &&
+ isUtf8ContinuationByte( *m_it ) ) {
+ m_it++;
+ }
tryParseAnsiEscapes();
}
@@ -8000,6 +8023,11 @@ namespace Catch {
assert( *m_it == '\033' );
m_it--;
}
+ // Skip back over UTF-8 continuation bytes to the leading byte
+ while ( isUtf8ContinuationByte( *m_it ) ) {
+ assert( m_it != m_string->begin() );
+ m_it--;
+ }
}
static bool isBoundary( AnsiSkippingString const& line,
@@ -9152,12 +9180,12 @@ namespace Catch {
void ReporterBase::listReporters(
std::vector const& descriptions ) {
- defaultListReporters(m_stream, descriptions, m_config->verbosity());
+ defaultListReporters( m_stream, descriptions, m_config->verbosity() );
}
void ReporterBase::listListeners(
std::vector const& descriptions ) {
- defaultListListeners( m_stream, descriptions );
+ defaultListListeners( m_stream, descriptions, m_config->verbosity() );
}
void ReporterBase::listTests(std::vector const& tests) {
@@ -9169,7 +9197,7 @@ namespace Catch {
}
void ReporterBase::listTags(std::vector const& tags) {
- defaultListTags( m_stream, tags, m_config->hasTestFilters() );
+ defaultListTags( m_stream, tags, m_config->hasTestFilters(), m_config->verbosity() );
}
} // namespace Catch
@@ -10380,7 +10408,15 @@ namespace Catch {
}
void defaultListListeners( std::ostream& out,
- std::vector const& descriptions ) {
+ std::vector const& descriptions,
+ Verbosity verbosity ) {
+ if ( verbosity == Verbosity::Quiet ) {
+ for ( auto const& desc : descriptions ) {
+ out << desc.name << '\n';
+ }
+ return;
+ }
+
out << "Registered listeners:\n";
if(descriptions.empty()) {
@@ -10413,7 +10449,14 @@ namespace Catch {
void defaultListTags( std::ostream& out,
std::vector const& tags,
- bool isFiltered ) {
+ bool isFiltered,
+ Verbosity verbosity ) {
+ if (verbosity == Verbosity::Quiet) {
+ for (auto const& tagCount : tags) {
+ out << tagCount.all() << '\n';
+ }
+ return;
+ }
if ( isFiltered ) {
out << "Tags for matching test cases:\n";
} else {
@@ -10432,7 +10475,7 @@ namespace Catch {
return lhs.count < rhs.count;
} )
->count;
-
+
// more padding necessary for 3+ digits
if (maxTagCount >= 100) {
auto numDigits = 1 + std::floor( std::log10( maxTagCount ) );
@@ -11189,70 +11232,77 @@ namespace Catch {
void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {
for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) {
if (assertionOrBenchmark.isAssertion()) {
- writeAssertion(assertionOrBenchmark.asAssertion());
+ // JUnit XML format supports only 1 error/failure/skip
+ // assertion elements per test case
+ if (writeAssertion(assertionOrBenchmark.asAssertion())) {
+ break;
+ }
}
}
}
- void JunitReporter::writeAssertion( AssertionStats const& stats ) {
+ bool JunitReporter::writeAssertion( AssertionStats const& stats ) {
AssertionResult const& result = stats.assertionResult;
- if ( !result.isOk() ||
- result.getResultType() == ResultWas::ExplicitSkip ) {
- std::string elementName;
- switch( result.getResultType() ) {
- case ResultWas::ThrewException:
- case ResultWas::FatalErrorCondition:
- elementName = "error";
- break;
- case ResultWas::ExplicitFailure:
- case ResultWas::ExpressionFailed:
- case ResultWas::DidntThrowException:
- elementName = "failure";
- break;
- case ResultWas::ExplicitSkip:
- elementName = "skipped";
- break;
- // We should never see these here:
- case ResultWas::Info:
- case ResultWas::Warning:
- case ResultWas::Ok:
- case ResultWas::Unknown:
- case ResultWas::FailureBit:
- case ResultWas::Exception:
- elementName = "internalError";
- break;
- }
-
- XmlWriter::ScopedElement e = xml.scopedElement( elementName );
-
- xml.writeAttribute( "message"_sr, result.getExpression() );
- xml.writeAttribute( "type"_sr, result.getTestMacroName() );
-
- ReusableStringStream rss;
- if ( result.getResultType() == ResultWas::ExplicitSkip ) {
- rss << "SKIPPED\n";
- } else {
- rss << "FAILED" << ":\n";
- if (result.hasExpression()) {
- rss << " ";
- rss << result.getExpressionInMacro();
- rss << '\n';
- }
- if (result.hasExpandedExpression()) {
- rss << "with expansion:\n";
- rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n';
- }
- }
-
- if( result.hasMessage() )
- rss << result.getMessage() << '\n';
- for( auto const& msg : stats.infoMessages )
- if( msg.type == ResultWas::Info )
- rss << msg.message << '\n';
-
- rss << "at " << result.getSourceInfo();
- xml.writeText( rss.str(), XmlFormatting::Newline );
+ if ( result.isOk() &&
+ result.getResultType() != ResultWas::ExplicitSkip ) {
+ return false;
}
+ std::string elementName;
+ switch ( result.getResultType() ) {
+ case ResultWas::ThrewException:
+ case ResultWas::FatalErrorCondition:
+ elementName = "error";
+ break;
+ case ResultWas::ExplicitFailure:
+ case ResultWas::ExpressionFailed:
+ case ResultWas::DidntThrowException:
+ elementName = "failure";
+ break;
+ case ResultWas::ExplicitSkip:
+ elementName = "skipped";
+ break;
+ // We should never see these here:
+ case ResultWas::Info:
+ case ResultWas::Warning:
+ case ResultWas::Ok:
+ case ResultWas::Unknown:
+ case ResultWas::FailureBit:
+ case ResultWas::Exception:
+ elementName = "internalError";
+ break;
+ }
+
+ XmlWriter::ScopedElement e = xml.scopedElement( elementName );
+
+ xml.writeAttribute( "message"_sr, result.getExpression() );
+ xml.writeAttribute( "type"_sr, result.getTestMacroName() );
+
+ ReusableStringStream rss;
+ if ( result.getResultType() == ResultWas::ExplicitSkip ) {
+ rss << "SKIPPED\n";
+ } else {
+ rss << "FAILED:\n";
+ if ( result.hasExpression() ) {
+ rss << " ";
+ rss << result.getExpressionInMacro();
+ rss << '\n';
+ }
+ if ( result.hasExpandedExpression() ) {
+ rss << "with expansion:\n";
+ rss << TextFlow::Column( result.getExpandedExpression() )
+ .indent( 2 )
+ << '\n';
+ }
+ }
+
+ if ( result.hasMessage() ) { rss << result.getMessage() << '\n'; }
+ for ( auto const& msg : stats.infoMessages ) {
+ if ( msg.type == ResultWas::Info ) { rss << msg.message << '\n'; }
+ }
+
+ rss << "at " << result.getSourceInfo();
+ xml.writeText( rss.str(), XmlFormatting::Newline );
+ return true;
}
} // end namespace Catch
diff --git a/extras/catch_amalgamated.hpp b/extras/catch_amalgamated.hpp
index 6de74f80..ba6cf8ff 100644
--- a/extras/catch_amalgamated.hpp
+++ b/extras/catch_amalgamated.hpp
@@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0
-// Catch v3.13.0
-// Generated: 2026-02-15 22:54:59.817776
+// Catch v3.14.0
+// Generated: 2026-04-05 15:03:01.150393
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
@@ -201,10 +201,15 @@
# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
_Pragma( "clang diagnostic ignored \"-Wc++20-extensions\"" )
# else
-# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS
+# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
_Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" )
# endif
+# if ( __clang_major__ >= 22 )
+# define CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS \
+ _Pragma( "clang diagnostic ignored \"-Wc2y-extensions\"" )
+# endif
+
# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \
_Pragma( "clang diagnostic ignored \"-Wunused-template\"" )
@@ -520,6 +525,9 @@
#if !defined( CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS )
# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS
#endif
+#if !defined( CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS )
+# define CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS
+#endif
#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10)
# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS
@@ -678,8 +686,20 @@ namespace Catch {
#endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED
+
+// Fixme: Clang 22 has an annoying bug where the localized suppression
+// below does not actually suppress the extension warning from
+// using __COUNTER__, so we have to leak the suppression for the
+// whole TU. Hopefully Clang 23 fixes this before full release.
+// As AppleClang does its own thing version-wise, we ignore it
+// completely.
+#if defined( __clang__ ) && ( __clang_major__ >= 22 ) && !defined( __APPLE__ )
+CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS
+#endif
+
#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
+
#ifdef CATCH_CONFIG_COUNTER
# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
#else
@@ -1011,35 +1031,16 @@ namespace Detail {
#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED
#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED
-
-
-// Adapted from donated nonius code.
-
-#ifndef CATCH_CLOCK_HPP_INCLUDED
-#define CATCH_CLOCK_HPP_INCLUDED
-
-#include
-
namespace Catch {
- namespace Benchmark {
- using IDuration = std::chrono::nanoseconds;
- using FDuration = std::chrono::duration;
- template
- using TimePoint = typename Clock::time_point;
-
- using default_clock = std::chrono::steady_clock;
- } // namespace Benchmark
-} // namespace Catch
-
-#endif // CATCH_CLOCK_HPP_INCLUDED
-
-namespace Catch {
+ namespace Detail {
+ struct DummyTemplateArgPlaceholder;
+ }
// 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 dependencies
- template
+ template
struct BenchmarkStats;
} // end namespace Catch
@@ -1336,6 +1337,28 @@ namespace Catch {
+// Adapted from donated nonius code.
+
+#ifndef CATCH_CLOCK_HPP_INCLUDED
+#define CATCH_CLOCK_HPP_INCLUDED
+
+#include
+
+namespace Catch {
+ namespace Benchmark {
+ using IDuration = std::chrono::nanoseconds;
+ using FDuration = std::chrono::duration;
+
+ template
+ using TimePoint = typename Clock::time_point;
+
+ using default_clock = std::chrono::steady_clock;
+ } // namespace Benchmark
+} // namespace Catch
+
+#endif // CATCH_CLOCK_HPP_INCLUDED
+
+
// Adapted from donated nonius code.
#ifndef CATCH_ESTIMATE_HPP_INCLUDED
@@ -7514,7 +7537,7 @@ namespace Catch {
#define CATCH_VERSION_MACROS_HPP_INCLUDED
#define CATCH_VERSION_MAJOR 3
-#define CATCH_VERSION_MINOR 13
+#define CATCH_VERSION_MINOR 14
#define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
@@ -13967,7 +13990,8 @@ namespace Catch {
* format
*/
void defaultListListeners( std::ostream& out,
- std::vector const& descriptions );
+ std::vector const& descriptions,
+ Verbosity verbosity );
/**
* Lists tag information to the provided stream in user-friendly format
@@ -13976,7 +14000,10 @@ namespace Catch {
* bases. The output should be backwards compatible with the output of
* Catch2 v2 binaries.
*/
- void defaultListTags( std::ostream& out, std::vector const& tags, bool isFiltered );
+ void defaultListTags( std::ostream& out,
+ std::vector const& tags,
+ bool isFiltered,
+ Verbosity verbosity );
/**
* Lists test case information to the provided stream in user-friendly
@@ -14126,7 +14153,7 @@ namespace Catch {
bool testOkToFail );
void writeAssertions(SectionNode const& sectionNode);
- void writeAssertion(AssertionStats const& stats);
+ bool writeAssertion(AssertionStats const& stats);
XmlWriter xml;
Timer suiteTimer;
diff --git a/meson.build b/meson.build
index 0997e56d..1b315e17 100644
--- a/meson.build
+++ b/meson.build
@@ -8,7 +8,7 @@
project(
'catch2',
'cpp',
- version: '3.13.0', # CML version placeholder, don't delete
+ version: '3.14.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 6e2b7ae1..403db2eb 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, 13, 0, "", 0 );
+ static Version version( 3, 14, 0, "", 0 );
return version;
}
diff --git a/src/catch2/catch_version_macros.hpp b/src/catch2/catch_version_macros.hpp
index 33d318a9..4b288c60 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 13
+#define CATCH_VERSION_MINOR 14
#define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED