diff --git a/CMakeLists.txt b/CMakeLists.txt
index 918927c4..84929314 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.11.0 # CML version placeholder, don't delete
+ VERSION 3.12.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/configuration.md b/docs/configuration.md
index 71496db0..527a3f82 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -322,7 +322,7 @@ are not meant to be runnable, only "scannable".
> Introduced in Catch2 3.9.0
-> Made non-experimental in Catch2 vX.Y.Z
+> Made non-experimental in Catch2 3.12.0
Catch2 can optionally support thread-safe assertions, that means, multiple
user-spawned threads can use the assertion macros at the same time. Due
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 9da1a2e1..35a76774 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -2,6 +2,7 @@
# Release notes
**Contents**
+[3.12.0](#3120)
[3.11.0](#3110)
[3.10.0](#3100)
[3.9.1](#391)
@@ -71,6 +72,31 @@
[Even Older versions](#even-older-versions)
+## 3.12.0
+
+### Fixes
+* Fixed unscoped messages after a passing fast-pathed assertion being lost.
+* Fixed the help string for `--order` to mention random order as the default. (#3045)
+* Fixed small documentation typos. (#3039)
+* Fixed compilation with `CATCH_CONFIG_THREAD_SAFE_ASSERTIONS` for older C++ standards.
+* Fixed a thread-safety issue with message macros being used too early after the process starts.
+* Fixed automatic configuration to properly handle PlayStation platform. (#3054)
+* **Fixed the _weird_ behaviour of section filtering when specifying multiple filters.** (#3038)
+ * See #3038 for more details.
+
+### Improvements
+* Added `lifetimebound` attribute to various places.
+ * As an example, compiler that supports lifetime analysis will now diagnose invalid use of Matcher combinators.
+* Minor compile-time improvements to stringification. (#3028)
+ * `std::tuple` printer does not recurse.
+ * Some implementation details were outlined into the cpp file.
+* Global variables will only be marked with `thread_local` in thread-safe builds. (#3044)
+
+### Miscellaneous
+* The thread safety support is no longer experimental.
+ * The new CMake option and C++ define is now `CATCH_CONFIG_THREAD_SAFE_ASSERTIONS`.
+
+
## 3.11.0
### Fixes
diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp
index 892d637d..a4406d0c 100644
--- a/extras/catch_amalgamated.cpp
+++ b/extras/catch_amalgamated.cpp
@@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0
-// Catch v3.11.0
-// Generated: 2025-09-30 10:49:12.549018
+// Catch v3.12.0
+// Generated: 2025-12-28 22:27:25.828797
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
@@ -2045,6 +2045,36 @@ namespace Detail {
}
} // end unnamed namespace
+ std::size_t catch_strnlen( const char* str, std::size_t n ) {
+ auto ret = std::char_traits::find( str, n, '\0' );
+ if ( ret != nullptr ) { return static_cast( ret - str ); }
+ return n;
+ }
+
+ std::string formatTimeT(std::time_t time) {
+#ifdef _MSC_VER
+ std::tm timeInfo = {};
+ const auto err = gmtime_s( &timeInfo, &time );
+ if ( err ) {
+ return "gmtime from provided timepoint has failed. This "
+ "happens e.g. with pre-1970 dates using Microsoft libc";
+ }
+#else
+ std::tm* timeInfo = std::gmtime( &time );
+#endif
+
+ auto const timeStampSize = sizeof( "2017-01-16T17:06:45Z" );
+ char timeStamp[timeStampSize];
+ const char* const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+#ifdef _MSC_VER
+ std::strftime( timeStamp, timeStampSize, fmt, &timeInfo );
+#else
+ std::strftime( timeStamp, timeStampSize, fmt, timeInfo );
+#endif
+ return std::string( timeStamp, timeStampSize - 1 );
+ }
+
std::string convertIntoString(StringRef string, bool escapeInvisibles) {
std::string ret;
// This is enough for the "don't escape invisibles" case, and a good
@@ -2354,7 +2384,7 @@ namespace Catch {
}
Version const& libraryVersion() {
- static Version version( 3, 11, 0, "", 0 );
+ static Version version( 3, 12, 0, "", 0 );
return version;
}
@@ -3402,7 +3432,7 @@ namespace Catch {
( "list all listeners" )
| Opt( setTestOrder, "decl|lex|rand" )
["--order"]
- ( "test case order (defaults to decl)" )
+ ( "test case order (defaults to rand)" )
| Opt( setRngSeed, "'time'|'random-device'|number" )
["--rng-seed"]
( "set a specific seed for random numbers" )
@@ -3602,7 +3632,9 @@ namespace {
#if defined( CATCH_PLATFORM_LINUX ) \
|| defined( CATCH_PLATFORM_MAC ) \
|| defined( __GLIBC__ ) \
- || defined( __FreeBSD__ ) \
+ || (defined( __FreeBSD__ ) \
+ /* PlayStation platform does not have `isatty()` */ \
+ && !defined(CATCH_PLATFORM_PLAYSTATION)) \
|| defined( CATCH_PLATFORM_QNX )
# define CATCH_INTERNAL_HAS_ISATTY
# include
@@ -4886,19 +4918,22 @@ int main (int argc, char * argv[]) {
namespace Catch {
+ namespace {
+ // Messages are owned by their individual threads, so the counter should
+ // be thread-local as well. Alternative consideration: atomic counter,
+ // so threads don't share IDs and things are easier to debug.
+ static CATCH_INTERNAL_THREAD_LOCAL unsigned int messageIDCounter = 0;
+ }
+
MessageInfo::MessageInfo( StringRef _macroName,
SourceLineInfo const& _lineInfo,
ResultWas::OfType _type )
: macroName( _macroName ),
lineInfo( _lineInfo ),
type( _type ),
- sequence( ++globalCount )
+ sequence( ++messageIDCounter )
{}
- // Messages are owned by their individual threads, so the counter should be thread-local as well.
- // Alternative consideration: atomic, so threads don't share IDs and things are easier to debug.
- thread_local unsigned int MessageInfo::globalCount = 0;
-
} // end namespace Catch
@@ -5814,12 +5849,8 @@ namespace Catch {
for ( auto const& child : m_children ) {
if ( child->isSectionTracker() &&
- std::find( filters.begin(),
- filters.end(),
- static_cast(
- *child )
- .trimmedName() ) !=
- filters.end() ) {
+ static_cast( *child )
+ .trimmedName() == filters[0] ) {
return true;
}
}
@@ -5862,27 +5893,98 @@ namespace Catch {
// should also be thread local. For now we just use naked globals
// below, in the future we will want to allocate piece of memory
// from heap, to avoid consuming too much thread-local storage.
+ //
+ // Note that we also don't want non-trivial the thread-local variables
+ // below be initialized for every thread, only for those that touch
+ // Catch2. To make this work with both GCC/Clang and MSVC, we have to
+ // make them thread-local magic statics. (Class-level statics have the
+ // desired semantics on GCC, but not on MSVC).
// This is used for the "if" part of CHECKED_IF/CHECKED_ELSE
- static thread_local bool g_lastAssertionPassed = false;
+ static CATCH_INTERNAL_THREAD_LOCAL bool g_lastAssertionPassed = false;
// This is the source location for last encountered macro. It is
// used to provide the users with more precise location of error
// when an unexpected exception/fatal error happens.
- static thread_local SourceLineInfo g_lastKnownLineInfo("DummyLocation", static_cast(-1));
+ static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo
+ g_lastKnownLineInfo( "DummyLocation", static_cast( -1 ) );
// Should we clear message scopes before sending off the messages to
// reporter? Set in `assertionPassedFastPath` to avoid doing the full
// clear there for performance reasons.
- static thread_local bool g_clearMessageScopes = false;
+ static CATCH_INTERNAL_THREAD_LOCAL bool g_clearMessageScopes = false;
+
+
+ // Holds the data for both scoped and unscoped messages together,
+ // to avoid issues where their lifetimes start in wrong order,
+ // and then are destroyed in wrong order.
+ class MessageHolder {
+ // The actual message vector passed to the reporters
+ std::vector messages;
+ // IDs of messages from UNSCOPED_X macros, which we have to
+ // remove manually.
+ std::vector unscoped_ids;
+
+ public:
+ // We do not need to special-case the unscoped messages when
+ // we only keep around the raw msg ids.
+ ~MessageHolder() = default;
+
+
+ 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 ) );
+ }
+
+ void addScopedMessage(MessageInfo&& info) {
+ messages.push_back( CATCH_MOVE( info ) );
+ }
+
+ std::vector const& getMessages() const {
+ return messages;
+ }
+
+ void removeMessage( unsigned int messageId ) {
+ // Note: On average, it would probably be better to look for
+ // the message backwards. However, we do not expect to have
+ // to deal with more messages than low single digits, so
+ // the improvement is tiny, and we would have to hand-write
+ // the loop to avoid terrible codegen of reverse iterators
+ // in debug mode.
+ auto iter =
+ std::find_if( messages.begin(),
+ messages.end(),
+ [messageId]( MessageInfo const& msg ) {
+ return msg.sequence == messageId;
+ } );
+ assert( iter != messages.end() &&
+ "Trying to remove non-existent message." );
+ messages.erase( iter );
+ }
+
+ void removeUnscopedMessages() {
+ for ( const auto messageId : unscoped_ids ) {
+ removeMessage( messageId );
+ }
+ unscoped_ids.clear();
+ g_clearMessageScopes = false;
+ }
+
+ void repairUnscopedMessageInvariant() {
+ if ( g_clearMessageScopes ) { removeUnscopedMessages(); }
+ g_clearMessageScopes = false;
+ }
+ };
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
- // Actual messages to be provided to the reporter
- static thread_local std::vector g_messages;
-
- // Owners for the UNSCOPED_X information macro
- static thread_local std::vector g_messageScopes;
+ static MessageHolder& g_messageHolder() {
+ static CATCH_INTERNAL_THREAD_LOCAL MessageHolder value;
+ return value;
+ }
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
} // namespace Detail
@@ -5899,6 +6001,13 @@ namespace Catch {
{
getCurrentMutableContext().setResultCapture( this );
m_reporter->testRunStarting(m_runInfo);
+
+ // TODO: HACK!
+ // We need to make sure the underlying cache is initialized
+ // while we are guaranteed to be running in a single thread,
+ // because the initialization is not thread-safe.
+ ReusableStringStream rss;
+ (void)rss;
}
RunContext::~RunContext() {
@@ -6021,21 +6130,19 @@ namespace Catch {
Detail::g_lastAssertionPassed = true;
}
- if ( Detail::g_clearMessageScopes ) {
- Detail::g_messageScopes.clear();
- Detail::g_clearMessageScopes = false;
- }
+ auto& msgHolder = Detail::g_messageHolder();
+ msgHolder.repairUnscopedMessageInvariant();
// From here, we are touching shared state and need mutex.
Detail::LockGuard lock( m_assertionMutex );
{
auto _ = scopedDeactivate( *m_outputRedirect );
updateTotalsFromAtomics();
- m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages, m_totals ) );
+ m_reporter->assertionEnded( AssertionStats( result, msgHolder.getMessages(), m_totals ) );
}
if ( result.getResultType() != ResultWas::Warning ) {
- Detail::g_messageScopes.clear();
+ msgHolder.removeUnscopedMessages();
}
// Reset working state. assertion info will be reset after
@@ -6324,10 +6431,10 @@ namespace Catch {
m_testCaseTracker->close();
handleUnfinishedSections();
- Detail::g_messageScopes.clear();
- // TBD: At this point, m_messages should be empty. Do we want to
- // assert that this is true, or keep the defensive clear call?
- Detail::g_messages.clear();
+ auto& msgHolder = Detail::g_messageHolder();
+ msgHolder.removeUnscopedMessages();
+ assert( msgHolder.getMessages().empty() &&
+ "There should be no leftover messages after the test ends" );
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
m_reporter->sectionEnded(testCaseSectionStats);
@@ -6495,25 +6602,15 @@ namespace Catch {
}
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
- Detail::g_messages.push_back( CATCH_MOVE( message ) );
+ Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) );
}
void IResultCapture::popScopedMessage( unsigned int messageId ) {
- // Note: On average, it would probably be better to look for the message
- // backwards. However, we do not expect to have to deal with more
- // messages than low single digits, so the optimization is tiny,
- // and we would have to hand-write the loop to avoid terrible
- // codegen of reverse iterators in debug mode.
- Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(),
- Detail::g_messages.end(),
- [=]( MessageInfo const& msg ) {
- return msg.sequence ==
- messageId;
- } ) );
+ Detail::g_messageHolder().removeMessage( messageId );
}
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
- Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) );
+ Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) );
}
void seedRng(IConfig const& config) {
@@ -7219,9 +7316,9 @@ namespace TestCaseTracking {
bool SectionTracker::isComplete() const {
bool complete = true;
- if (m_filters.empty()
+ if ( m_filters.empty()
|| m_filters[0].empty()
- || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) {
+ || m_filters[0] == m_trimmed_name ) {
complete = TrackerBase::isComplete();
}
return complete;
diff --git a/extras/catch_amalgamated.hpp b/extras/catch_amalgamated.hpp
index d5eb1bb7..00fc3ee1 100644
--- a/extras/catch_amalgamated.hpp
+++ b/extras/catch_amalgamated.hpp
@@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0
-// Catch v3.11.0
-// Generated: 2025-09-30 10:49:11.225746
+// Catch v3.12.0
+// Generated: 2025-12-28 22:27:25.408132
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
@@ -694,11 +694,30 @@ namespace Catch {
#ifndef CATCH_STRINGREF_HPP_INCLUDED
#define CATCH_STRINGREF_HPP_INCLUDED
+
+
+
+#ifndef CATCH_LIFETIMEBOUND_HPP_INCLUDED
+#define CATCH_LIFETIMEBOUND_HPP_INCLUDED
+
+#if !defined( __has_cpp_attribute )
+# define CATCH_ATTR_LIFETIMEBOUND
+#elif __has_cpp_attribute( msvc::lifetimebound )
+# define CATCH_ATTR_LIFETIMEBOUND [[msvc::lifetimebound]]
+#elif __has_cpp_attribute( clang::lifetimebound )
+# define CATCH_ATTR_LIFETIMEBOUND [[clang::lifetimebound]]
+#elif __has_cpp_attribute( lifetimebound )
+# define CATCH_ATTR_LIFETIMEBOUND [[lifetimebound]]
+#else
+# define CATCH_ATTR_LIFETIMEBOUND
+#endif
+
+#endif // CATCH_LIFETIMEBOUND_HPP_INCLUDED
+
#include
#include
#include
#include
-
#include
namespace Catch {
@@ -722,14 +741,16 @@ namespace Catch {
public: // construction
constexpr StringRef() noexcept = default;
- StringRef( char const* rawChars ) noexcept;
+ StringRef( char const* rawChars CATCH_ATTR_LIFETIMEBOUND ) noexcept;
- constexpr StringRef( char const* rawChars, size_type size ) noexcept
+ constexpr StringRef( char const* rawChars CATCH_ATTR_LIFETIMEBOUND,
+ size_type size ) noexcept
: m_start( rawChars ),
m_size( size )
{}
- StringRef( std::string const& stdString ) noexcept
+ StringRef(
+ std::string const& stdString CATCH_ATTR_LIFETIMEBOUND ) noexcept
: m_start( stdString.c_str() ),
m_size( stdString.size() )
{}
@@ -775,7 +796,7 @@ namespace Catch {
}
// Returns the current start pointer. May not be null-terminated.
- constexpr char const* data() const noexcept {
+ constexpr char const* data() const noexcept CATCH_ATTR_LIFETIMEBOUND {
return m_start;
}
@@ -2305,7 +2326,7 @@ namespace Catch {
#ifndef CATCH_TOSTRING_HPP_INCLUDED
#define CATCH_TOSTRING_HPP_INCLUDED
-
+#include
#include
#include
#include
@@ -2473,13 +2494,9 @@ namespace Catch {
namespace Detail {
- inline std::size_t catch_strnlen(const char *str, std::size_t n) {
- auto ret = std::char_traits::find(str, n, '\0');
- if (ret != nullptr) {
- return static_cast(ret - str);
- }
- return n;
- }
+ std::size_t catch_strnlen(const char *str, std::size_t n);
+
+ std::string formatTimeT( std::time_t time );
constexpr StringRef unprintableString = "{?}"_sr;
@@ -2844,44 +2861,38 @@ namespace Catch {
// Separate std::tuple specialization
#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)
-#include
+# include
+# include
namespace Catch {
namespace Detail {
- template<
- typename Tuple,
- std::size_t N = 0,
- bool = (N < std::tuple_size::value)
- >
- struct TupleElementPrinter {
- static void print(const Tuple& tuple, std::ostream& os) {
- os << (N ? ", " : " ")
- << ::Catch::Detail::stringify(std::get(tuple));
- TupleElementPrinter::print(tuple, os);
- }
- };
+ template
+ void PrintTuple( const Tuple& tuple,
+ std::ostream& os,
+ std::index_sequence ) {
+ // 1 + Account for when the tuple is empty
+ char a[1 + sizeof...( Is )] = {
+ ( ( os << ( Is ? ", " : " " )
+ << ::Catch::Detail::stringify( std::get( tuple ) ) ),
+ '\0' )... };
+ (void)a;
+ }
- template<
- typename Tuple,
- std::size_t N
- >
- struct TupleElementPrinter {
- static void print(const Tuple&, std::ostream&) {}
- };
+ } // namespace Detail
- }
-
-
- template
+ template
struct StringMaker> {
- static std::string convert(const std::tuple& tuple) {
+ static std::string convert( const std::tuple& tuple ) {
ReusableStringStream rss;
rss << '{';
- Detail::TupleElementPrinter>::print(tuple, rss.get());
+ Detail::PrintTuple(
+ tuple,
+ rss.get(),
+ std::make_index_sequence{} );
rss << " }";
return rss.str();
}
};
-}
+} // namespace Catch
#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT)
@@ -3068,28 +3079,7 @@ struct ratio_string {
const auto systemish = std::chrono::time_point_cast<
std::chrono::system_clock::duration>( time_point );
const auto as_time_t = std::chrono::system_clock::to_time_t( systemish );
-
-#ifdef _MSC_VER
- std::tm timeInfo = {};
- const auto err = gmtime_s( &timeInfo, &as_time_t );
- if ( err ) {
- return "gmtime from provided timepoint has failed. This "
- "happens e.g. with pre-1970 dates using Microsoft libc";
- }
-#else
- std::tm* timeInfo = std::gmtime( &as_time_t );
-#endif
-
- auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
- char timeStamp[timeStampSize];
- const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
-
-#ifdef _MSC_VER
- std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
-#else
- std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
-#endif
- return std::string(timeStamp, timeStampSize - 1);
+ return ::Catch::Detail::formatTimeT( as_time_t );
}
};
}
@@ -3984,8 +3974,6 @@ namespace Catch {
bool operator < (MessageInfo const& other) const {
return sequence < other.sequence;
}
- private:
- static thread_local unsigned int globalCount;
};
} // end namespace Catch
@@ -7478,7 +7466,7 @@ namespace Catch {
#define CATCH_VERSION_MACROS_HPP_INCLUDED
#define CATCH_VERSION_MAJOR 3
-#define CATCH_VERSION_MINOR 11
+#define CATCH_VERSION_MINOR 12
#define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
@@ -10094,8 +10082,8 @@ namespace Catch {
class JsonValueWriter {
public:
- JsonValueWriter( std::ostream& os );
- JsonValueWriter( std::ostream& os, std::uint64_t indent_level );
+ JsonValueWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
+ JsonValueWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level );
JsonObjectWriter writeObject() &&;
JsonArrayWriter writeArray() &&;
@@ -10129,8 +10117,8 @@ namespace Catch {
class JsonObjectWriter {
public:
- JsonObjectWriter( std::ostream& os );
- JsonObjectWriter( std::ostream& os, std::uint64_t indent_level );
+ JsonObjectWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
+ JsonObjectWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level );
JsonObjectWriter( JsonObjectWriter&& source ) noexcept;
JsonObjectWriter& operator=( JsonObjectWriter&& source ) = delete;
@@ -10148,8 +10136,8 @@ namespace Catch {
class JsonArrayWriter {
public:
- JsonArrayWriter( std::ostream& os );
- JsonArrayWriter( std::ostream& os, std::uint64_t indent_level );
+ JsonArrayWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
+ JsonArrayWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level );
JsonArrayWriter( JsonArrayWriter&& source ) noexcept;
JsonArrayWriter& operator=( JsonArrayWriter&& source ) = delete;
@@ -10422,7 +10410,7 @@ namespace TestCaseTracking {
StringRef name;
SourceLineInfo location;
- constexpr NameAndLocationRef( StringRef name_,
+ constexpr NameAndLocationRef( StringRef name_ CATCH_ATTR_LIFETIMEBOUND,
SourceLineInfo location_ ):
name( name_ ), location( location_ ) {}
@@ -10622,7 +10610,7 @@ using TestCaseTracking::SectionTracker;
#define CATCH_THREAD_SUPPORT_HPP_INCLUDED
-#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )
+#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS )
# include
# include
#endif
@@ -10630,14 +10618,14 @@ using TestCaseTracking::SectionTracker;
namespace Catch {
namespace Detail {
-#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )
+#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS )
using Mutex = std::mutex;
using LockGuard = std::lock_guard;
struct AtomicCounts {
- std::atomic passed = 0;
- std::atomic failed = 0;
- std::atomic failedButOk = 0;
- std::atomic skipped = 0;
+ std::atomic passed{ 0 };
+ std::atomic failed{ 0 };
+ std::atomic failedButOk{ 0 };
+ std::atomic skipped{ 0 };
};
#else // ^^ Use actual mutex, lock and atomics
// vv Dummy implementations for single-thread performance
@@ -10942,10 +10930,10 @@ namespace Catch {
//! Returns a new string without whitespace at the start/end
std::string trim( std::string const& str );
//! Returns a substring of the original ref without whitespace. Beware lifetimes!
- StringRef trim(StringRef ref);
+ StringRef trim( StringRef ref CATCH_ATTR_LIFETIMEBOUND );
// !!! Be aware, returns refs into original string - make sure original string outlives them
- std::vector splitStringRef( StringRef str, char delimiter );
+ std::vector splitStringRef( StringRef str CATCH_ATTR_LIFETIMEBOUND, char delimiter );
bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
/**
@@ -10963,7 +10951,7 @@ namespace Catch {
StringRef m_label;
public:
- constexpr pluralise(std::uint64_t count, StringRef label):
+ constexpr pluralise(std::uint64_t count, StringRef label CATCH_ATTR_LIFETIMEBOUND):
m_count(count),
m_label(label)
{}
@@ -11442,6 +11430,19 @@ namespace Catch {
#endif // CATCH_TEXTFLOW_HPP_INCLUDED
+#ifndef CATCH_THREAD_LOCAL_HPP_INCLUDED
+#define CATCH_THREAD_LOCAL_HPP_INCLUDED
+
+
+#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS )
+#define CATCH_INTERNAL_THREAD_LOCAL thread_local
+#else
+#define CATCH_INTERNAL_THREAD_LOCAL
+#endif
+
+#endif // CATCH_THREAD_LOCAL_HPP_INCLUDED
+
+
#ifndef CATCH_TO_STRING_HPP_INCLUDED
#define CATCH_TO_STRING_HPP_INCLUDED
@@ -11510,7 +11511,7 @@ namespace Catch {
public:
enum ForWhat { ForTextNodes, ForAttributes };
- constexpr XmlEncode( StringRef str, ForWhat forWhat = ForTextNodes ):
+ constexpr XmlEncode( StringRef str CATCH_ATTR_LIFETIMEBOUND, ForWhat forWhat = ForTextNodes ):
m_str( str ), m_forWhat( forWhat ) {}
@@ -11528,7 +11529,7 @@ namespace Catch {
class ScopedElement {
public:
- ScopedElement( XmlWriter* writer, XmlFormatting fmt );
+ ScopedElement( XmlWriter* writer CATCH_ATTR_LIFETIMEBOUND, XmlFormatting fmt );
ScopedElement( ScopedElement&& other ) noexcept;
ScopedElement& operator=( ScopedElement&& other ) noexcept;
@@ -11560,7 +11561,7 @@ namespace Catch {
XmlFormatting m_fmt;
};
- XmlWriter( std::ostream& os );
+ XmlWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
~XmlWriter();
XmlWriter( XmlWriter const& ) = delete;
@@ -11818,11 +11819,15 @@ namespace Matchers {
return description;
}
- friend MatchAllOf operator&& (MatchAllOf&& lhs, MatcherBase const& rhs) {
+ friend MatchAllOf operator&&( MatchAllOf&& lhs,
+ MatcherBase const& rhs
+ CATCH_ATTR_LIFETIMEBOUND ) {
lhs.m_matchers.push_back(&rhs);
return CATCH_MOVE(lhs);
}
- friend MatchAllOf operator&& (MatcherBase const& lhs, MatchAllOf&& rhs) {
+ friend MatchAllOf
+ operator&&( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatchAllOf&& rhs ) {
rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs);
return CATCH_MOVE(rhs);
}
@@ -11870,11 +11875,15 @@ namespace Matchers {
return description;
}
- friend MatchAnyOf operator|| (MatchAnyOf&& lhs, MatcherBase const& rhs) {
+ friend MatchAnyOf operator||( MatchAnyOf&& lhs,
+ MatcherBase const& rhs
+ CATCH_ATTR_LIFETIMEBOUND ) {
lhs.m_matchers.push_back(&rhs);
return CATCH_MOVE(lhs);
}
- friend MatchAnyOf operator|| (MatcherBase const& lhs, MatchAnyOf&& rhs) {
+ friend MatchAnyOf
+ operator||( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatchAnyOf&& rhs ) {
rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs);
return CATCH_MOVE(rhs);
}
@@ -11894,7 +11903,8 @@ namespace Matchers {
MatcherBase const& m_underlyingMatcher;
public:
- explicit MatchNotOf( MatcherBase const& underlyingMatcher ):
+ explicit MatchNotOf( MatcherBase const& underlyingMatcher
+ CATCH_ATTR_LIFETIMEBOUND ):
m_underlyingMatcher( underlyingMatcher )
{}
@@ -11910,16 +11920,22 @@ namespace Matchers {
} // namespace Detail
template
- Detail::MatchAllOf operator&& (MatcherBase const& lhs, MatcherBase const& rhs) {
+ Detail::MatchAllOf
+ operator&&( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherBase const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return Detail::MatchAllOf{} && lhs && rhs;
}
+
template
- Detail::MatchAnyOf operator|| (MatcherBase const& lhs, MatcherBase const& rhs) {
+ Detail::MatchAnyOf
+ operator||( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherBase const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return Detail::MatchAnyOf{} || lhs || rhs;
}
template
- Detail::MatchNotOf operator! (MatcherBase const& matcher) {
+ Detail::MatchNotOf
+ operator!( MatcherBase const& matcher CATCH_ATTR_LIFETIMEBOUND ) {
return Detail::MatchNotOf{ matcher };
}
@@ -12086,7 +12102,8 @@ namespace Matchers {
MatchAllOfGeneric(MatchAllOfGeneric&&) = default;
MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default;
- MatchAllOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {}
+ MatchAllOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND)
+ : m_matchers{ {std::addressof(matchers)...} } {}
explicit MatchAllOfGeneric(std::array matchers) : m_matchers{matchers} {}
template
@@ -12108,8 +12125,8 @@ namespace Matchers {
template
friend
MatchAllOfGeneric operator && (
- MatchAllOfGeneric&& lhs,
- MatchAllOfGeneric&& rhs) {
+ MatchAllOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatchAllOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAllOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};
}
@@ -12117,8 +12134,8 @@ namespace Matchers {
template
friend std::enable_if_t,
MatchAllOfGeneric> operator && (
- MatchAllOfGeneric&& lhs,
- MatcherRHS const& rhs) {
+ MatchAllOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAllOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast(&rhs))};
}
@@ -12126,8 +12143,8 @@ namespace Matchers {
template
friend std::enable_if_t,
MatchAllOfGeneric> operator && (
- MatcherLHS const& lhs,
- MatchAllOfGeneric&& rhs) {
+ MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatchAllOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAllOfGeneric{array_cat(static_cast(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))};
}
};
@@ -12141,7 +12158,8 @@ namespace Matchers {
MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default;
MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default;
- MatchAnyOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {}
+ MatchAnyOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND)
+ : m_matchers{ {std::addressof(matchers)...} } {}
explicit MatchAnyOfGeneric(std::array matchers) : m_matchers{matchers} {}
template
@@ -12162,8 +12180,8 @@ namespace Matchers {
//! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case
template
friend MatchAnyOfGeneric operator || (
- MatchAnyOfGeneric&& lhs,
- MatchAnyOfGeneric&& rhs) {
+ MatchAnyOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatchAnyOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAnyOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};
}
@@ -12171,8 +12189,8 @@ namespace Matchers {
template
friend std::enable_if_t,
MatchAnyOfGeneric> operator || (
- MatchAnyOfGeneric&& lhs,
- MatcherRHS const& rhs) {
+ MatchAnyOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAnyOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast(std::addressof(rhs)))};
}
@@ -12180,8 +12198,8 @@ namespace Matchers {
template
friend std::enable_if_t,
MatchAnyOfGeneric> operator || (
- MatcherLHS const& lhs,
- MatchAnyOfGeneric&& rhs) {
+ MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatchAnyOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND) {
return MatchAnyOfGeneric{array_cat(static_cast(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))};
}
};
@@ -12197,7 +12215,8 @@ namespace Matchers {
MatchNotOfGeneric(MatchNotOfGeneric&&) = default;
MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default;
- explicit MatchNotOfGeneric(MatcherT const& matcher) : m_matcher{matcher} {}
+ explicit MatchNotOfGeneric(MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND)
+ : m_matcher{matcher} {}
template
bool match(Arg&& arg) const {
@@ -12209,7 +12228,9 @@ namespace Matchers {
}
//! Negating negation can just unwrap and return underlying matcher
- friend MatcherT const& operator ! (MatchNotOfGeneric const& matcher) {
+ friend MatcherT const&
+ operator!( MatchNotOfGeneric const& matcher
+ CATCH_ATTR_LIFETIMEBOUND ) {
return matcher.m_matcher;
}
};
@@ -12219,20 +12240,22 @@ namespace Matchers {
// compose only generic matchers
template
std::enable_if_t, Detail::MatchAllOfGeneric>
- operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) {
+ operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
template
std::enable_if_t, Detail::MatchAnyOfGeneric>
- operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) {
+ operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
//! Wrap provided generic matcher in generic negator
template
std::enable_if_t, Detail::MatchNotOfGeneric>
- operator ! (MatcherT const& matcher) {
+ operator!( MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND ) {
return Detail::MatchNotOfGeneric{matcher};
}
@@ -12240,25 +12263,29 @@ namespace Matchers {
// compose mixed generic and non-generic matchers
template
std::enable_if_t, Detail::MatchAllOfGeneric>>
- operator && (MatcherLHS const& lhs, MatcherBase const& rhs) {
+ operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherBase const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
template
std::enable_if_t, Detail::MatchAllOfGeneric, MatcherRHS>>
- operator && (MatcherBase const& lhs, MatcherRHS const& rhs) {
+ operator&&( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
template
std::enable_if_t, Detail::MatchAnyOfGeneric>>
- operator || (MatcherLHS const& lhs, MatcherBase const& rhs) {
+ operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherBase const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
template
std::enable_if_t, Detail::MatchAnyOfGeneric, MatcherRHS>>
- operator || (MatcherBase const& lhs, MatcherRHS const& rhs) {
+ operator||( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND,
+ MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
diff --git a/meson.build b/meson.build
index 16b38796..8a50e339 100644
--- a/meson.build
+++ b/meson.build
@@ -8,7 +8,7 @@
project(
'catch2',
'cpp',
- version: '3.11.0', # CML version placeholder, don't delete
+ version: '3.12.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 39b0c0be..3b558a37 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, 11, 0, "", 0 );
+ static Version version( 3, 12, 0, "", 0 );
return version;
}
diff --git a/src/catch2/catch_version_macros.hpp b/src/catch2/catch_version_macros.hpp
index 630f5b0e..b78f9cfd 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 11
+#define CATCH_VERSION_MINOR 12
#define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED