This commit is contained in:
Martin Hořeňovský
2025-12-28 22:31:54 +01:00
parent 970ec144f5
commit 88abf9bf32
8 changed files with 321 additions and 171 deletions

View File

@@ -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."

View File

@@ -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

View File

@@ -2,6 +2,7 @@
# Release notes
**Contents**<br>
[3.12.0](#3120)<br>
[3.11.0](#3110)<br>
[3.10.0](#3100)<br>
[3.9.1](#391)<br>
@@ -71,6 +72,31 @@
[Even Older versions](#even-older-versions)<br>
## 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

View File

@@ -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<char>::find( str, n, '\0' );
if ( ret != nullptr ) { return static_cast<std::size_t>( 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 <unistd.h>
@@ -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<SectionTracker const&>(
*child )
.trimmedName() ) !=
filters.end() ) {
static_cast<SectionTracker const&>( *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<size_t>(-1));
static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo
g_lastKnownLineInfo( "DummyLocation", static_cast<size_t>( -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<MessageInfo> messages;
// IDs of messages from UNSCOPED_X macros, which we have to
// remove manually.
std::vector<unsigned int> 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<MessageInfo> 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<MessageInfo> g_messages;
// Owners for the UNSCOPED_X information macro
static thread_local std::vector<ScopedMessage> 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;

View File

@@ -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 <cstddef>
#include <string>
#include <iosfwd>
#include <cassert>
#include <cstring>
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 <ctime>
#include <vector>
#include <cstddef>
#include <type_traits>
@@ -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<char>::find(str, n, '\0');
if (ret != nullptr) {
return static_cast<std::size_t>(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 <tuple>
# include <tuple>
# include <utility>
namespace Catch {
namespace Detail {
template<
typename Tuple,
std::size_t N = 0,
bool = (N < std::tuple_size<Tuple>::value)
>
struct TupleElementPrinter {
static void print(const Tuple& tuple, std::ostream& os) {
os << (N ? ", " : " ")
<< ::Catch::Detail::stringify(std::get<N>(tuple));
TupleElementPrinter<Tuple, N + 1>::print(tuple, os);
}
};
template <typename Tuple, std::size_t... Is>
void PrintTuple( const Tuple& tuple,
std::ostream& os,
std::index_sequence<Is...> ) {
// 1 + Account for when the tuple is empty
char a[1 + sizeof...( Is )] = {
( ( os << ( Is ? ", " : " " )
<< ::Catch::Detail::stringify( std::get<Is>( tuple ) ) ),
'\0' )... };
(void)a;
}
template<
typename Tuple,
std::size_t N
>
struct TupleElementPrinter<Tuple, N, false> {
static void print(const Tuple&, std::ostream&) {}
};
} // namespace Detail
}
template<typename ...Types>
template <typename... Types>
struct StringMaker<std::tuple<Types...>> {
static std::string convert(const std::tuple<Types...>& tuple) {
static std::string convert( const std::tuple<Types...>& tuple ) {
ReusableStringStream rss;
rss << '{';
Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get());
Detail::PrintTuple(
tuple,
rss.get(),
std::make_index_sequence<sizeof...( Types )>{} );
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<std::milli> {
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 <atomic>
# include <mutex>
#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<std::mutex>;
struct AtomicCounts {
std::atomic<std::uint64_t> passed = 0;
std::atomic<std::uint64_t> failed = 0;
std::atomic<std::uint64_t> failedButOk = 0;
std::atomic<std::uint64_t> skipped = 0;
std::atomic<std::uint64_t> passed{ 0 };
std::atomic<std::uint64_t> failed{ 0 };
std::atomic<std::uint64_t> failedButOk{ 0 };
std::atomic<std::uint64_t> 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<StringRef> splitStringRef( StringRef str, char delimiter );
std::vector<StringRef> 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<ArgT> const& rhs) {
friend MatchAllOf operator&&( MatchAllOf&& lhs,
MatcherBase<ArgT> const& rhs
CATCH_ATTR_LIFETIMEBOUND ) {
lhs.m_matchers.push_back(&rhs);
return CATCH_MOVE(lhs);
}
friend MatchAllOf operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf&& rhs) {
friend MatchAllOf
operator&&( MatcherBase<ArgT> 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<ArgT> const& rhs) {
friend MatchAnyOf operator||( MatchAnyOf&& lhs,
MatcherBase<ArgT> const& rhs
CATCH_ATTR_LIFETIMEBOUND ) {
lhs.m_matchers.push_back(&rhs);
return CATCH_MOVE(lhs);
}
friend MatchAnyOf operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf&& rhs) {
friend MatchAnyOf
operator||( MatcherBase<ArgT> 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<ArgT> const& m_underlyingMatcher;
public:
explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ):
explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher
CATCH_ATTR_LIFETIMEBOUND ):
m_underlyingMatcher( underlyingMatcher )
{}
@@ -11910,16 +11920,22 @@ namespace Matchers {
} // namespace Detail
template <typename T>
Detail::MatchAllOf<T> operator&& (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) {
Detail::MatchAllOf<T>
operator&&( MatcherBase<T> const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherBase<T> const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return Detail::MatchAllOf<T>{} && lhs && rhs;
}
template <typename T>
Detail::MatchAnyOf<T> operator|| (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) {
Detail::MatchAnyOf<T>
operator||( MatcherBase<T> const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherBase<T> const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return Detail::MatchAnyOf<T>{} || lhs || rhs;
}
template <typename T>
Detail::MatchNotOf<T> operator! (MatcherBase<T> const& matcher) {
Detail::MatchNotOf<T>
operator!( MatcherBase<T> const& matcher CATCH_ATTR_LIFETIMEBOUND ) {
return Detail::MatchNotOf<T>{ 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<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
template<typename Arg>
@@ -12108,8 +12125,8 @@ namespace Matchers {
template<typename... MatchersRHS>
friend
MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && (
MatchAllOfGeneric<MatcherTs...>&& lhs,
MatchAllOfGeneric<MatchersRHS...>&& rhs) {
MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAllOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAllOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};
}
@@ -12117,8 +12134,8 @@ namespace Matchers {
template<typename MatcherRHS>
friend std::enable_if_t<is_matcher_v<MatcherRHS>,
MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && (
MatchAllOfGeneric<MatcherTs...>&& lhs,
MatcherRHS const& rhs) {
MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAllOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(&rhs))};
}
@@ -12126,8 +12143,8 @@ namespace Matchers {
template<typename MatcherLHS>
friend std::enable_if_t<is_matcher_v<MatcherLHS>,
MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && (
MatcherLHS const& lhs,
MatchAllOfGeneric<MatcherTs...>&& rhs) {
MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAllOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAllOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(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<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
template<typename Arg>
@@ -12162,8 +12180,8 @@ namespace Matchers {
//! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case
template<typename... MatchersRHS>
friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || (
MatchAnyOfGeneric<MatcherTs...>&& lhs,
MatchAnyOfGeneric<MatchersRHS...>&& rhs) {
MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAnyOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};
}
@@ -12171,8 +12189,8 @@ namespace Matchers {
template<typename MatcherRHS>
friend std::enable_if_t<is_matcher_v<MatcherRHS>,
MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || (
MatchAnyOfGeneric<MatcherTs...>&& lhs,
MatcherRHS const& rhs) {
MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAnyOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))};
}
@@ -12180,8 +12198,8 @@ namespace Matchers {
template<typename MatcherLHS>
friend std::enable_if_t<is_matcher_v<MatcherLHS>,
MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || (
MatcherLHS const& lhs,
MatchAnyOfGeneric<MatcherTs...>&& rhs) {
MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAnyOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND) {
return MatchAnyOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(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<typename Arg>
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<MatcherT> const& matcher) {
friend MatcherT const&
operator!( MatchNotOfGeneric<MatcherT> const& matcher
CATCH_ATTR_LIFETIMEBOUND ) {
return matcher.m_matcher;
}
};
@@ -12219,20 +12240,22 @@ namespace Matchers {
// compose only generic matchers
template<typename MatcherLHS, typename MatcherRHS>
std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>>
operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) {
operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
template<typename MatcherLHS, typename MatcherRHS>
std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>
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<typename MatcherT>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>, Detail::MatchNotOfGeneric<MatcherT>>
operator ! (MatcherT const& matcher) {
operator!( MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND ) {
return Detail::MatchNotOfGeneric<MatcherT>{matcher};
}
@@ -12240,25 +12263,29 @@ namespace Matchers {
// compose mixed generic and non-generic matchers
template<typename MatcherLHS, typename ArgRHS>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>
operator && (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {
operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherBase<ArgRHS> const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
template<typename ArgLHS, typename MatcherRHS>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAllOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>
operator && (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {
operator&&( MatcherBase<ArgLHS> const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
template<typename MatcherLHS, typename ArgRHS>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>
operator || (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {
operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherBase<ArgRHS> const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
template<typename ArgLHS, typename MatcherRHS>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>
operator || (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {
operator||( MatcherBase<ArgLHS> const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}

View File

@@ -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',
)

View File

@@ -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;
}

View File

@@ -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