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