Skip to content

Commit 88abf9b

Browse files
committed
v3.12.0
1 parent 970ec14 commit 88abf9b

File tree

8 files changed

+321
-171
lines changed

8 files changed

+321
-171
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
3535
endif()
3636

3737
project(Catch2
38-
VERSION 3.11.0 # CML version placeholder, don't delete
38+
VERSION 3.12.0 # CML version placeholder, don't delete
3939
LANGUAGES CXX
4040
HOMEPAGE_URL "https://github.com/catchorg/Catch2"
4141
DESCRIPTION "A modern, C++-native, unit test framework."

docs/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ are not meant to be runnable, only "scannable".
322322
323323
> Introduced in Catch2 3.9.0
324324
325-
> Made non-experimental in Catch2 vX.Y.Z
325+
> Made non-experimental in Catch2 3.12.0
326326
327327
Catch2 can optionally support thread-safe assertions, that means, multiple
328328
user-spawned threads can use the assertion macros at the same time. Due

docs/release-notes.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Release notes
44
**Contents**<br>
5+
[3.12.0](#3120)<br>
56
[3.11.0](#3110)<br>
67
[3.10.0](#3100)<br>
78
[3.9.1](#391)<br>
@@ -71,6 +72,31 @@
7172
[Even Older versions](#even-older-versions)<br>
7273

7374

75+
## 3.12.0
76+
77+
### Fixes
78+
* Fixed unscoped messages after a passing fast-pathed assertion being lost.
79+
* Fixed the help string for `--order` to mention random order as the default. (#3045)
80+
* Fixed small documentation typos. (#3039)
81+
* Fixed compilation with `CATCH_CONFIG_THREAD_SAFE_ASSERTIONS` for older C++ standards.
82+
* Fixed a thread-safety issue with message macros being used too early after the process starts.
83+
* Fixed automatic configuration to properly handle PlayStation platform. (#3054)
84+
* **Fixed the _weird_ behaviour of section filtering when specifying multiple filters.** (#3038)
85+
* See #3038 for more details.
86+
87+
### Improvements
88+
* Added `lifetimebound` attribute to various places.
89+
* As an example, compiler that supports lifetime analysis will now diagnose invalid use of Matcher combinators.
90+
* Minor compile-time improvements to stringification. (#3028)
91+
* `std::tuple` printer does not recurse.
92+
* Some implementation details were outlined into the cpp file.
93+
* Global variables will only be marked with `thread_local` in thread-safe builds. (#3044)
94+
95+
### Miscellaneous
96+
* The thread safety support is no longer experimental.
97+
* The new CMake option and C++ define is now `CATCH_CONFIG_THREAD_SAFE_ASSERTIONS`.
98+
99+
74100
## 3.11.0
75101

76102
### Fixes

extras/catch_amalgamated.cpp

Lines changed: 146 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
// SPDX-License-Identifier: BSL-1.0
88

9-
// Catch v3.11.0
10-
// Generated: 2025-09-30 10:49:12.549018
9+
// Catch v3.12.0
10+
// Generated: 2025-12-28 22:27:25.828797
1111
// ----------------------------------------------------------
1212
// This file is an amalgamation of multiple different files.
1313
// You probably shouldn't edit it directly.
@@ -2045,6 +2045,36 @@ namespace Detail {
20452045
}
20462046
} // end unnamed namespace
20472047

2048+
std::size_t catch_strnlen( const char* str, std::size_t n ) {
2049+
auto ret = std::char_traits<char>::find( str, n, '\0' );
2050+
if ( ret != nullptr ) { return static_cast<std::size_t>( ret - str ); }
2051+
return n;
2052+
}
2053+
2054+
std::string formatTimeT(std::time_t time) {
2055+
#ifdef _MSC_VER
2056+
std::tm timeInfo = {};
2057+
const auto err = gmtime_s( &timeInfo, &time );
2058+
if ( err ) {
2059+
return "gmtime from provided timepoint has failed. This "
2060+
"happens e.g. with pre-1970 dates using Microsoft libc";
2061+
}
2062+
#else
2063+
std::tm* timeInfo = std::gmtime( &time );
2064+
#endif
2065+
2066+
auto const timeStampSize = sizeof( "2017-01-16T17:06:45Z" );
2067+
char timeStamp[timeStampSize];
2068+
const char* const fmt = "%Y-%m-%dT%H:%M:%SZ";
2069+
2070+
#ifdef _MSC_VER
2071+
std::strftime( timeStamp, timeStampSize, fmt, &timeInfo );
2072+
#else
2073+
std::strftime( timeStamp, timeStampSize, fmt, timeInfo );
2074+
#endif
2075+
return std::string( timeStamp, timeStampSize - 1 );
2076+
}
2077+
20482078
std::string convertIntoString(StringRef string, bool escapeInvisibles) {
20492079
std::string ret;
20502080
// This is enough for the "don't escape invisibles" case, and a good
@@ -2354,7 +2384,7 @@ namespace Catch {
23542384
}
23552385

23562386
Version const& libraryVersion() {
2357-
static Version version( 3, 11, 0, "", 0 );
2387+
static Version version( 3, 12, 0, "", 0 );
23582388
return version;
23592389
}
23602390

@@ -3402,7 +3432,7 @@ namespace Catch {
34023432
( "list all listeners" )
34033433
| Opt( setTestOrder, "decl|lex|rand" )
34043434
["--order"]
3405-
( "test case order (defaults to decl)" )
3435+
( "test case order (defaults to rand)" )
34063436
| Opt( setRngSeed, "'time'|'random-device'|number" )
34073437
["--rng-seed"]
34083438
( "set a specific seed for random numbers" )
@@ -3602,7 +3632,9 @@ namespace {
36023632
#if defined( CATCH_PLATFORM_LINUX ) \
36033633
|| defined( CATCH_PLATFORM_MAC ) \
36043634
|| defined( __GLIBC__ ) \
3605-
|| defined( __FreeBSD__ ) \
3635+
|| (defined( __FreeBSD__ ) \
3636+
/* PlayStation platform does not have `isatty()` */ \
3637+
&& !defined(CATCH_PLATFORM_PLAYSTATION)) \
36063638
|| defined( CATCH_PLATFORM_QNX )
36073639
# define CATCH_INTERNAL_HAS_ISATTY
36083640
# include <unistd.h>
@@ -4886,19 +4918,22 @@ int main (int argc, char * argv[]) {
48864918

48874919
namespace Catch {
48884920

4921+
namespace {
4922+
// Messages are owned by their individual threads, so the counter should
4923+
// be thread-local as well. Alternative consideration: atomic counter,
4924+
// so threads don't share IDs and things are easier to debug.
4925+
static CATCH_INTERNAL_THREAD_LOCAL unsigned int messageIDCounter = 0;
4926+
}
4927+
48894928
MessageInfo::MessageInfo( StringRef _macroName,
48904929
SourceLineInfo const& _lineInfo,
48914930
ResultWas::OfType _type )
48924931
: macroName( _macroName ),
48934932
lineInfo( _lineInfo ),
48944933
type( _type ),
4895-
sequence( ++globalCount )
4934+
sequence( ++messageIDCounter )
48964935
{}
48974936

4898-
// Messages are owned by their individual threads, so the counter should be thread-local as well.
4899-
// Alternative consideration: atomic, so threads don't share IDs and things are easier to debug.
4900-
thread_local unsigned int MessageInfo::globalCount = 0;
4901-
49024937
} // end namespace Catch
49034938

49044939

@@ -5814,12 +5849,8 @@ namespace Catch {
58145849

58155850
for ( auto const& child : m_children ) {
58165851
if ( child->isSectionTracker() &&
5817-
std::find( filters.begin(),
5818-
filters.end(),
5819-
static_cast<SectionTracker const&>(
5820-
*child )
5821-
.trimmedName() ) !=
5822-
filters.end() ) {
5852+
static_cast<SectionTracker const&>( *child )
5853+
.trimmedName() == filters[0] ) {
58235854
return true;
58245855
}
58255856
}
@@ -5862,27 +5893,98 @@ namespace Catch {
58625893
// should also be thread local. For now we just use naked globals
58635894
// below, in the future we will want to allocate piece of memory
58645895
// from heap, to avoid consuming too much thread-local storage.
5896+
//
5897+
// Note that we also don't want non-trivial the thread-local variables
5898+
// below be initialized for every thread, only for those that touch
5899+
// Catch2. To make this work with both GCC/Clang and MSVC, we have to
5900+
// make them thread-local magic statics. (Class-level statics have the
5901+
// desired semantics on GCC, but not on MSVC).
58655902

58665903
// This is used for the "if" part of CHECKED_IF/CHECKED_ELSE
5867-
static thread_local bool g_lastAssertionPassed = false;
5904+
static CATCH_INTERNAL_THREAD_LOCAL bool g_lastAssertionPassed = false;
58685905

58695906
// This is the source location for last encountered macro. It is
58705907
// used to provide the users with more precise location of error
58715908
// when an unexpected exception/fatal error happens.
5872-
static thread_local SourceLineInfo g_lastKnownLineInfo("DummyLocation", static_cast<size_t>(-1));
5909+
static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo
5910+
g_lastKnownLineInfo( "DummyLocation", static_cast<size_t>( -1 ) );
58735911

58745912
// Should we clear message scopes before sending off the messages to
58755913
// reporter? Set in `assertionPassedFastPath` to avoid doing the full
58765914
// clear there for performance reasons.
5877-
static thread_local bool g_clearMessageScopes = false;
5915+
static CATCH_INTERNAL_THREAD_LOCAL bool g_clearMessageScopes = false;
5916+
5917+
5918+
// Holds the data for both scoped and unscoped messages together,
5919+
// to avoid issues where their lifetimes start in wrong order,
5920+
// and then are destroyed in wrong order.
5921+
class MessageHolder {
5922+
// The actual message vector passed to the reporters
5923+
std::vector<MessageInfo> messages;
5924+
// IDs of messages from UNSCOPED_X macros, which we have to
5925+
// remove manually.
5926+
std::vector<unsigned int> unscoped_ids;
5927+
5928+
public:
5929+
// We do not need to special-case the unscoped messages when
5930+
// we only keep around the raw msg ids.
5931+
~MessageHolder() = default;
5932+
5933+
5934+
void addUnscopedMessage(MessageBuilder&& builder) {
5935+
repairUnscopedMessageInvariant();
5936+
MessageInfo info( CATCH_MOVE( builder.m_info ) );
5937+
info.message = builder.m_stream.str();
5938+
unscoped_ids.push_back( info.sequence );
5939+
messages.push_back( CATCH_MOVE( info ) );
5940+
}
5941+
5942+
void addScopedMessage(MessageInfo&& info) {
5943+
messages.push_back( CATCH_MOVE( info ) );
5944+
}
5945+
5946+
std::vector<MessageInfo> const& getMessages() const {
5947+
return messages;
5948+
}
5949+
5950+
void removeMessage( unsigned int messageId ) {
5951+
// Note: On average, it would probably be better to look for
5952+
// the message backwards. However, we do not expect to have
5953+
// to deal with more messages than low single digits, so
5954+
// the improvement is tiny, and we would have to hand-write
5955+
// the loop to avoid terrible codegen of reverse iterators
5956+
// in debug mode.
5957+
auto iter =
5958+
std::find_if( messages.begin(),
5959+
messages.end(),
5960+
[messageId]( MessageInfo const& msg ) {
5961+
return msg.sequence == messageId;
5962+
} );
5963+
assert( iter != messages.end() &&
5964+
"Trying to remove non-existent message." );
5965+
messages.erase( iter );
5966+
}
5967+
5968+
void removeUnscopedMessages() {
5969+
for ( const auto messageId : unscoped_ids ) {
5970+
removeMessage( messageId );
5971+
}
5972+
unscoped_ids.clear();
5973+
g_clearMessageScopes = false;
5974+
}
5975+
5976+
void repairUnscopedMessageInvariant() {
5977+
if ( g_clearMessageScopes ) { removeUnscopedMessages(); }
5978+
g_clearMessageScopes = false;
5979+
}
5980+
};
58785981

58795982
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
58805983
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
5881-
// Actual messages to be provided to the reporter
5882-
static thread_local std::vector<MessageInfo> g_messages;
5883-
5884-
// Owners for the UNSCOPED_X information macro
5885-
static thread_local std::vector<ScopedMessage> g_messageScopes;
5984+
static MessageHolder& g_messageHolder() {
5985+
static CATCH_INTERNAL_THREAD_LOCAL MessageHolder value;
5986+
return value;
5987+
}
58865988
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
58875989

58885990
} // namespace Detail
@@ -5899,6 +6001,13 @@ namespace Catch {
58996001
{
59006002
getCurrentMutableContext().setResultCapture( this );
59016003
m_reporter->testRunStarting(m_runInfo);
6004+
6005+
// TODO: HACK!
6006+
// We need to make sure the underlying cache is initialized
6007+
// while we are guaranteed to be running in a single thread,
6008+
// because the initialization is not thread-safe.
6009+
ReusableStringStream rss;
6010+
(void)rss;
59026011
}
59036012

59046013
RunContext::~RunContext() {
@@ -6021,21 +6130,19 @@ namespace Catch {
60216130
Detail::g_lastAssertionPassed = true;
60226131
}
60236132

6024-
if ( Detail::g_clearMessageScopes ) {
6025-
Detail::g_messageScopes.clear();
6026-
Detail::g_clearMessageScopes = false;
6027-
}
6133+
auto& msgHolder = Detail::g_messageHolder();
6134+
msgHolder.repairUnscopedMessageInvariant();
60286135

60296136
// From here, we are touching shared state and need mutex.
60306137
Detail::LockGuard lock( m_assertionMutex );
60316138
{
60326139
auto _ = scopedDeactivate( *m_outputRedirect );
60336140
updateTotalsFromAtomics();
6034-
m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages, m_totals ) );
6141+
m_reporter->assertionEnded( AssertionStats( result, msgHolder.getMessages(), m_totals ) );
60356142
}
60366143

60376144
if ( result.getResultType() != ResultWas::Warning ) {
6038-
Detail::g_messageScopes.clear();
6145+
msgHolder.removeUnscopedMessages();
60396146
}
60406147

60416148
// Reset working state. assertion info will be reset after
@@ -6324,10 +6431,10 @@ namespace Catch {
63246431

63256432
m_testCaseTracker->close();
63266433
handleUnfinishedSections();
6327-
Detail::g_messageScopes.clear();
6328-
// TBD: At this point, m_messages should be empty. Do we want to
6329-
// assert that this is true, or keep the defensive clear call?
6330-
Detail::g_messages.clear();
6434+
auto& msgHolder = Detail::g_messageHolder();
6435+
msgHolder.removeUnscopedMessages();
6436+
assert( msgHolder.getMessages().empty() &&
6437+
"There should be no leftover messages after the test ends" );
63316438

63326439
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
63336440
m_reporter->sectionEnded(testCaseSectionStats);
@@ -6495,25 +6602,15 @@ namespace Catch {
64956602
}
64966603

64976604
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
6498-
Detail::g_messages.push_back( CATCH_MOVE( message ) );
6605+
Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) );
64996606
}
65006607

65016608
void IResultCapture::popScopedMessage( unsigned int messageId ) {
6502-
// Note: On average, it would probably be better to look for the message
6503-
// backwards. However, we do not expect to have to deal with more
6504-
// messages than low single digits, so the optimization is tiny,
6505-
// and we would have to hand-write the loop to avoid terrible
6506-
// codegen of reverse iterators in debug mode.
6507-
Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(),
6508-
Detail::g_messages.end(),
6509-
[=]( MessageInfo const& msg ) {
6510-
return msg.sequence ==
6511-
messageId;
6512-
} ) );
6609+
Detail::g_messageHolder().removeMessage( messageId );
65136610
}
65146611

65156612
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
6516-
Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) );
6613+
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) );
65176614
}
65186615

65196616
void seedRng(IConfig const& config) {
@@ -7219,9 +7316,9 @@ namespace TestCaseTracking {
72197316
bool SectionTracker::isComplete() const {
72207317
bool complete = true;
72217318

7222-
if (m_filters.empty()
7319+
if ( m_filters.empty()
72237320
|| m_filters[0].empty()
7224-
|| std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) {
7321+
|| m_filters[0] == m_trimmed_name ) {
72257322
complete = TrackerBase::isComplete();
72267323
}
72277324
return complete;

0 commit comments

Comments
 (0)