Compare commits

...

21 Commits

Author SHA1 Message Date
Martin Hořeňovský
036448b4b2 Undo magic-statification of message id counter 2025-12-02 10:51:42 +01:00
Martin Hořeňovský
24e305b83e Undo other trivial types away from functions 2025-12-01 23:21:33 +01:00
Martin Hořeňovský
15d4eb2ebe Keep g_lastAssertionPassed outside of function 2025-12-01 23:11:54 +01:00
Martin Hořeňovský
e075e04cde Use magic statics for thread-local globals 2025-12-01 22:23:51 +01:00
Martin Hořeňovský
d427034253 Only use thread_local in builds with thread safety enabled 2025-12-01 14:18:54 +01:00
Martin Hořeňovský
1e463608ef Test tsan on mac 2025-11-30 23:58:38 +01:00
Martin Hořeňovský
2c381a8819 Add tests for assertion thread safety 2025-11-30 23:58:31 +01:00
Martin Hořeňovský
32bf7b2330 Initialize ReusableStringStream cache before user threads can run
The initialization itself is thread unsafe, and as such we cannot
allow it to be delayed until multiple user-spawned threads need it.
2025-11-30 23:58:27 +01:00
Martin Hořeňovský
86267a7c6d Fix initialization of AtomicCounts for older standards 2025-11-30 23:42:20 +01:00
Martin Hořeňovský
985a3f4460 Fix lazy removal of unscoped messages also removing still valid msgs 2025-11-30 14:30:19 +01:00
Stefan Haller
a1faad9315 Fix the help text for the --order command line argument
It was changed to rand in v3.9.0.
2025-11-07 21:28:41 +01:00
Martin Hořeňovský
31ee3beb0a Small documentation fixes
This includes 2 small typos I found when working on generator skipping,
and 1 typo found by @sfraczek in #3039.

Closes #3039
2025-10-16 20:45:28 +02:00
Martin Hořeňovský
3b853aa9fb Add lifetime attributes to JSON/XML writers 2025-10-16 20:37:05 +02:00
Martin Hořeňovský
49d79e9e9c Fix section filtering to make sense
Specifically, this commit makes the `-c`/`--section` parameter
strictly ordered and hierarchical, unlike how it behaved before,
which was a huge mess -- see #3038 for details.

Closes #3038
2025-10-16 09:16:56 +02:00
ZXShady
33e6fd217a Remove recursion when stringifying std::tuple 2025-10-04 22:10:36 +02:00
ZXShady
a58df2d7c5 Outline part of formatting system_clock's time_point into cpp file 2025-10-04 22:10:36 +02:00
ZXShady
a9223b2bb3 Outline catch_strnlen's definition into catch_tostring.cpp 2025-10-04 22:10:36 +02:00
Martin Hořeňovský
363ca5af18 Add lifetime annotations to more places using StringRef 2025-10-04 16:38:07 +02:00
Martin Hořeňovský
cb6d713774 Add lifetimebound annotation to StringRef 2025-10-04 16:12:17 +02:00
Martin Hořeňovský
8e4ab5dd8f Annotate matcher combinators with CATCH_ATTR_LIFETIMEBOUND
The matcher combinators do not take ownership of the matchers
being combined, which can catch the users off-guard, when code like
this

```cpp
using Catch::Matchers::EndsWith;
using Catch::Matchers::ContainsSubstring;

auto combinedMatcher = EndsWith("as a service")
                       && ContainsSubstring("web scale");

REQUIRE_THAT( getSomeString(), combinedMatcher );
```

leads to use-after-free, as the `combinedMatcher` refers to matcher
temporaries that no longer exists. With this commit, users of Clang,
MSVC or other compiler that understands the `lifetimebound` attribute,
should get a warning.
2025-10-03 22:27:29 +02:00
Martin Hořeňovský
8219ed79f2 Add CATCH_ATTR_LIFETIMEBOUND macro polyfill over lifetimebound attr 2025-10-03 22:15:27 +02:00
49 changed files with 769 additions and 204 deletions

36
.github/workflows/mac-other-builds.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Mac Sanitizer Builds
on: [push, pull_request]
env:
CXXFLAGS: -fsanitize=thread,undefined
jobs:
build:
# From macos-14 forward, the baseline "macos-X" image is Arm based,
# and not Intel based.
runs-on: ${{matrix.image}}
strategy:
fail-fast: false
matrix:
image: [macos-13, macos-14]
build_type: [Debug, Release]
std: [14, 17]
steps:
- uses: actions/checkout@v4
- name: Configure
run: |
cmake --preset all-tests -GNinja \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
-DCATCH_BUILD_EXTRA_TESTS=ON \
-DCATCH_ENABLE_WERROR=OFF
- name: Build
run: cmake --build build
- name: Test
run: ctest --test-dir build -R ThreadSafetyTests --timeout 21600 --verbose

View File

@@ -10,7 +10,7 @@ in-memory logs if they are not needed (the test case passed).
Unlike reporters, each registered event listener is always active. Event
listeners are always notified before reporter(s).
To write your own event listener, you should derive from `Catch::TestEventListenerBase`,
To write your own event listener, you should derive from `Catch::EventListenerBase`,
as it provides empty stubs for all reporter events, allowing you to
only override events you care for. Afterwards you have to register it
with Catch2 using `CATCH_REGISTER_LISTENER` macro, so that Catch2 knows

View File

@@ -275,7 +275,7 @@ There are two ways to handle this, depending on whether you want this
to be an error or not.
* If empty generator **is** an error, throw an exception in constructor.
* If empty generator **is not** an error, use the [`SKIP`](skipping-passing-failing.md#skipping-test-cases-at-runtime) in constructor.
* If empty generator **is not** an error, use the [`SKIP` macro](skipping-passing-failing.md#skipping-test-cases-at-runtime) in constructor.

View File

@@ -87,7 +87,7 @@ TEST_CASE("complex test case") {
```
This test case will report 5 passing assertions; one for each of the three
values in section `a1`, and then two in section `a2`, from values 2 and 4.
values in section `a1`, and then two in section `a2`, from values 2 and 6.
Note that as soon as one section is skipped, the entire test case will
be reported as _skipped_ (unless there is a failing assertion, in which

View File

@@ -98,6 +98,7 @@ set(IMPL_HEADERS
${SOURCES_DIR}/internal/catch_jsonwriter.hpp
${SOURCES_DIR}/internal/catch_lazy_expr.hpp
${SOURCES_DIR}/internal/catch_leak_detector.hpp
${SOURCES_DIR}/internal/catch_lifetimebound.hpp
${SOURCES_DIR}/internal/catch_list.hpp
${SOURCES_DIR}/internal/catch_logical_traits.hpp
${SOURCES_DIR}/internal/catch_message_info.hpp
@@ -139,6 +140,7 @@ set(IMPL_HEADERS
${SOURCES_DIR}/internal/catch_test_registry.hpp
${SOURCES_DIR}/internal/catch_test_spec_parser.hpp
${SOURCES_DIR}/internal/catch_textflow.hpp
${SOURCES_DIR}/internal/catch_thread_local.hpp
${SOURCES_DIR}/internal/catch_thread_support.hpp
${SOURCES_DIR}/internal/catch_to_string.hpp
${SOURCES_DIR}/internal/catch_uncaught_exceptions.hpp

View File

@@ -79,6 +79,7 @@
#include <catch2/internal/catch_jsonwriter.hpp>
#include <catch2/internal/catch_lazy_expr.hpp>
#include <catch2/internal/catch_leak_detector.hpp>
#include <catch2/internal/catch_lifetimebound.hpp>
#include <catch2/internal/catch_list.hpp>
#include <catch2/internal/catch_logical_traits.hpp>
#include <catch2/internal/catch_message_info.hpp>
@@ -121,6 +122,7 @@
#include <catch2/internal/catch_test_registry.hpp>
#include <catch2/internal/catch_test_spec_parser.hpp>
#include <catch2/internal/catch_textflow.hpp>
#include <catch2/internal/catch_thread_local.hpp>
#include <catch2/internal/catch_thread_support.hpp>
#include <catch2/internal/catch_to_string.hpp>
#include <catch2/internal/catch_uncaught_exceptions.hpp>

View File

@@ -57,6 +57,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

View File

@@ -8,7 +8,7 @@
#ifndef CATCH_TOSTRING_HPP_INCLUDED
#define CATCH_TOSTRING_HPP_INCLUDED
#include <ctime>
#include <vector>
#include <cstddef>
#include <type_traits>
@@ -40,13 +40,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;
@@ -411,44 +407,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)
@@ -635,28 +625,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 );
}
};
}

View File

@@ -265,7 +265,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" )

View File

@@ -8,6 +8,7 @@
#ifndef CATCH_JSONWRITER_HPP_INCLUDED
#define CATCH_JSONWRITER_HPP_INCLUDED
#include <catch2/internal/catch_lifetimebound.hpp>
#include <catch2/internal/catch_reusable_string_stream.hpp>
#include <catch2/internal/catch_stringref.hpp>
@@ -27,8 +28,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() &&;
@@ -62,8 +63,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;
@@ -81,8 +82,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;

View File

@@ -0,0 +1,24 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#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

View File

@@ -7,20 +7,24 @@
// SPDX-License-Identifier: BSL-1.0
#include <catch2/internal/catch_message_info.hpp>
#include <catch2/internal/catch_thread_local.hpp>
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

View File

@@ -37,8 +37,6 @@ namespace Catch {
bool operator < (MessageInfo const& other) const {
return sequence < other.sequence;
}
private:
static thread_local unsigned int globalCount;
};
} // end namespace Catch

View File

@@ -20,6 +20,7 @@
#include <catch2/internal/catch_output_redirect.hpp>
#include <catch2/internal/catch_assertion_handler.hpp>
#include <catch2/internal/catch_test_failure_exception.hpp>
#include <catch2/internal/catch_thread_local.hpp>
#include <catch2/internal/catch_result_type.hpp>
#include <cassert>
@@ -129,12 +130,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;
}
}
@@ -177,27 +174,40 @@ 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 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;
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;
static std::vector<MessageInfo>& g_messages() {
static CATCH_INTERNAL_THREAD_LOCAL std::vector<MessageInfo> value;
return value;
}
// Owners for the UNSCOPED_X information macro
static thread_local std::vector<ScopedMessage> g_messageScopes;
static std::vector<ScopedMessage>& g_messageScopes() {
static CATCH_INTERNAL_THREAD_LOCAL std::vector<ScopedMessage> value;
return value;
}
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
} // namespace Detail
@@ -214,6 +224,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() {
@@ -337,7 +354,7 @@ namespace Catch {
}
if ( Detail::g_clearMessageScopes ) {
Detail::g_messageScopes.clear();
Detail::g_messageScopes().clear();
Detail::g_clearMessageScopes = false;
}
@@ -346,11 +363,11 @@ namespace Catch {
{
auto _ = scopedDeactivate( *m_outputRedirect );
updateTotalsFromAtomics();
m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages, m_totals ) );
m_reporter->assertionEnded( AssertionStats( result, Detail::g_messages(), m_totals ) );
}
if ( result.getResultType() != ResultWas::Warning ) {
Detail::g_messageScopes.clear();
Detail::g_messageScopes().clear();
}
// Reset working state. assertion info will be reset after
@@ -639,10 +656,10 @@ namespace Catch {
m_testCaseTracker->close();
handleUnfinishedSections();
Detail::g_messageScopes.clear();
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();
Detail::g_messages().clear();
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
m_reporter->sectionEnded(testCaseSectionStats);
@@ -810,7 +827,7 @@ namespace Catch {
}
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
Detail::g_messages.push_back( CATCH_MOVE( message ) );
Detail::g_messages().push_back( CATCH_MOVE( message ) );
}
void IResultCapture::popScopedMessage( unsigned int messageId ) {
@@ -819,16 +836,25 @@ namespace Catch {
// 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;
} ) );
auto& messages = Detail::g_messages();
messages.erase( std::find_if( messages.begin(),
messages.end(),
[=]( MessageInfo const& msg ) {
return msg.sequence ==
messageId;
} ) );
}
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) );
// Invalid unscoped messages are lazy cleared. If we have any,
// we have to get rid of them before adding new ones, or the
// delayed clear in assertion handling will erase the valid ones
// as well.
if ( Detail::g_clearMessageScopes ) {
Detail::g_messageScopes().clear();
Detail::g_clearMessageScopes = false;
}
Detail::g_messageScopes().emplace_back( CATCH_MOVE( builder ) );
}
void seedRng(IConfig const& config) {

View File

@@ -8,6 +8,7 @@
#ifndef CATCH_STRING_MANIP_HPP_INCLUDED
#define CATCH_STRING_MANIP_HPP_INCLUDED
#include <catch2/internal/catch_lifetimebound.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <cstdint>
@@ -28,10 +29,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 );
/**
@@ -49,7 +50,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)
{}

View File

@@ -8,11 +8,12 @@
#ifndef CATCH_STRINGREF_HPP_INCLUDED
#define CATCH_STRINGREF_HPP_INCLUDED
#include <catch2/internal/catch_lifetimebound.hpp>
#include <cstddef>
#include <string>
#include <iosfwd>
#include <cassert>
#include <cstring>
namespace Catch {
@@ -36,14 +37,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() )
{}
@@ -89,7 +92,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;
}

View File

@@ -172,9 +172,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

@@ -8,6 +8,7 @@
#ifndef CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
#define CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
#include <catch2/internal/catch_lifetimebound.hpp>
#include <catch2/internal/catch_source_line_info.hpp>
#include <catch2/internal/catch_unique_ptr.hpp>
#include <catch2/internal/catch_stringref.hpp>
@@ -48,7 +49,7 @@ namespace TestCaseTracking {
StringRef name;
SourceLineInfo location;
constexpr NameAndLocationRef( StringRef name_,
constexpr NameAndLocationRef( StringRef name_ CATCH_ATTR_LIFETIMEBOUND,
SourceLineInfo location_ ):
name( name_ ), location( location_ ) {}

View File

@@ -0,0 +1,19 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_THREAD_LOCAL_HPP_INCLUDED
#define CATCH_THREAD_LOCAL_HPP_INCLUDED
#include <catch2/catch_user_config.hpp>
#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )
#define CATCH_INTERNAL_THREAD_LOCAL thread_local
#else
#define CATCH_INTERNAL_THREAD_LOCAL
#endif
#endif // CATCH_THREAD_LOCAL_HPP_INCLUDED

View File

@@ -23,10 +23,10 @@ namespace Catch {
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

View File

@@ -8,6 +8,7 @@
#ifndef CATCH_XMLWRITER_HPP_INCLUDED
#define CATCH_XMLWRITER_HPP_INCLUDED
#include <catch2/internal/catch_lifetimebound.hpp>
#include <catch2/internal/catch_reusable_string_stream.hpp>
#include <catch2/internal/catch_stringref.hpp>
@@ -43,7 +44,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 ) {}
@@ -61,7 +62,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;
@@ -93,7 +94,7 @@ namespace Catch {
XmlFormatting m_fmt;
};
XmlWriter( std::ostream& os );
XmlWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
~XmlWriter();
XmlWriter( XmlWriter const& ) = delete;

View File

@@ -10,6 +10,7 @@
#include <catch2/matchers/internal/catch_matchers_impl.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_lifetimebound.hpp>
#include <string>
#include <vector>
@@ -79,11 +80,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);
}
@@ -131,11 +136,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);
}
@@ -155,7 +164,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 )
{}
@@ -171,16 +181,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 };
}

View File

@@ -11,6 +11,7 @@
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_lifetimebound.hpp>
#include <catch2/internal/catch_logical_traits.hpp>
#include <array>
@@ -114,7 +115,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>
@@ -136,8 +138,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))};
}
@@ -145,8 +147,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))};
}
@@ -154,8 +156,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))};
}
};
@@ -169,7 +171,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>
@@ -190,8 +193,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))};
}
@@ -199,8 +202,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)))};
}
@@ -208,8 +211,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))};
}
};
@@ -225,7 +228,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 {
@@ -237,7 +241,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;
}
};
@@ -247,20 +253,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};
}
@@ -268,25 +276,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

@@ -105,6 +105,7 @@ internal_headers = [
'internal/catch_jsonwriter.hpp',
'internal/catch_lazy_expr.hpp',
'internal/catch_leak_detector.hpp',
'internal/catch_lifetimebound.hpp',
'internal/catch_list.hpp',
'internal/catch_logical_traits.hpp',
'internal/catch_message_info.hpp',
@@ -147,6 +148,7 @@ internal_headers = [
'internal/catch_test_registry.hpp',
'internal/catch_test_spec_parser.hpp',
'internal/catch_textflow.hpp',
'internal/catch_thread_local.hpp',
'internal/catch_thread_support.hpp',
'internal/catch_to_string.hpp',
'internal/catch_uncaught_exceptions.hpp',

View File

@@ -310,38 +310,23 @@ set_tests_properties(UnmatchedOutputFilter
PASS_REGULAR_EXPRESSION "No test cases matched '\\[this-tag-does-not-exist\\]'"
)
add_test(NAME FilteredSection-1 COMMAND $<TARGET_FILE:SelfTest> \#1394 -c RunSection)
set_tests_properties(FilteredSection-1 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
add_test(NAME FilteredSection-2 COMMAND $<TARGET_FILE:SelfTest> \#1394\ nested -c NestedRunSection -c s1)
set_tests_properties(FilteredSection-2 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
add_test(NAME FilteredSections::SimpleExample::1 COMMAND $<TARGET_FILE:SelfTest> \#1394 -c RunSection)
set_tests_properties(FilteredSections::SimpleExample::1 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
add_test(NAME FilteredSections::SimpleExample::2 COMMAND $<TARGET_FILE:SelfTest> \#1394\ nested -c NestedRunSection -c s1)
set_tests_properties(FilteredSections::SimpleExample::2 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
add_test(
NAME
FilteredSection::GeneratorsDontCauseInfiniteLoop-1
FilteredSections::GeneratorsDontCauseInfiniteLoop
COMMAND
$<TARGET_FILE:SelfTest> "#2025: original repro" -c "fov_0"
)
set_tests_properties(FilteredSection::GeneratorsDontCauseInfiniteLoop-1
set_tests_properties(FilteredSections::GeneratorsDontCauseInfiniteLoop
PROPERTIES
PASS_REGULAR_EXPRESSION "inside with fov: 0" # This should happen
FAIL_REGULAR_EXPRESSION "inside with fov: 1" # This would mean there was no filtering
)
# GENERATE between filtered sections (both are selected)
add_test(
NAME
FilteredSection::GeneratorsDontCauseInfiniteLoop-2
COMMAND
$<TARGET_FILE:SelfTest> "#2025: same-level sections"
-c "A"
-c "B"
--colour-mode none
)
set_tests_properties(FilteredSection::GeneratorsDontCauseInfiniteLoop-2
PROPERTIES
PASS_REGULAR_EXPRESSION "All tests passed \\(4 assertions in 1 test case\\)"
)
add_test(NAME ApprovalTests
COMMAND
Python3::Interpreter
@@ -694,6 +679,14 @@ set_tests_properties("Bazel::RngSeedEnvVar::MalformedValueIsIgnored"
PASS_REGULAR_EXPRESSION "Randomness seeded to: 17171717"
)
add_test(NAME "FilteredSections::DifferentSimpleFilters"
COMMAND
Python3::Interpreter "${CMAKE_CURRENT_LIST_DIR}/TestScripts/testSectionFiltering.py" $<TARGET_FILE:SelfTest>
)
set_tests_properties("FilteredSections::DifferentSimpleFilters"
PROPERTIES
LABELS "uses-python"
)
list(APPEND CATCH_TEST_TARGETS SelfTest)
set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE)

View File

@@ -177,6 +177,18 @@ set_tests_properties(DeferredStaticChecks
PASS_REGULAR_EXPRESSION "test cases: 1 \\| 1 failed\nassertions: 3 \\| 3 failed"
)
add_executable(MixingClearedAndUnclearedMessages ${TESTS_DIR}/X06-MixingClearedAndUnclearedMessages.cpp)
target_link_libraries(MixingClearedAndUnclearedMessages PRIVATE Catch2WithMain)
add_test(NAME MixingClearedAndUnclearedMessages
COMMAND
MixingClearedAndUnclearedMessages
-r compact)
set_tests_properties(MixingClearedAndUnclearedMessages
PROPERTIES
PASS_REGULAR_EXPRESSION ": false with 1 message: 'b'"
)
add_executable(FallbackStringifier ${TESTS_DIR}/X10-FallbackStringifier.cpp)
target_compile_definitions(FallbackStringifier PRIVATE CATCH_CONFIG_FALLBACK_STRINGIFIER=fallbackStringifier)
target_link_libraries(FallbackStringifier Catch2WithMain)
@@ -528,6 +540,7 @@ set(EXTRA_TEST_BINARIES
DuplicatedTestCases-DuplicatedTestCaseMethods
NoTests
ListenersGetEventsBeforeReporters
MixingClearedAndUnclearedMessages
# DebugBreakMacros
)
@@ -553,3 +566,17 @@ set_tests_properties(AmalgamatedFileTest
PROPERTIES
PASS_REGULAR_EXPRESSION "All tests passed \\(14 assertions in 3 test cases\\)"
)
add_executable(ThreadSafetyTests
${TESTS_DIR}/X94-ThreadSafetyTests.cpp
)
target_link_libraries(ThreadSafetyTests Catch2_buildall_interface)
target_compile_definitions(ThreadSafetyTests PUBLIC CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS)
add_test(NAME ThreadSafetyTests
COMMAND ThreadSafetyTests -r compact "Failed REQUIRE in the main thread is fine"
)
set_tests_properties(ThreadSafetyTests
PROPERTIES
PASS_REGULAR_EXPRESSION "assertions: 801 | 400 passed | 401 failed"
RUN_SERIAL ON
)

View File

@@ -0,0 +1,27 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
/**\file
* Checks that when we use up an unscoped message (e.g. `UNSCOPED_INFO`),
* with an assertion, and then add another message later, it will be
* properly reported with later failing assertion.
*
* This needs separate binary to avoid the main test binary's validating
* listener, which disables the assertion fast path.
*/
#include <catch2/catch_test_macros.hpp>
TEST_CASE(
"Delayed unscoped message clearing does not catch newly inserted messages",
"[messages][unscoped][!shouldfail]" ) {
UNSCOPED_INFO( "a" );
REQUIRE( true );
UNSCOPED_INFO( "b" );
REQUIRE( false );
}

View File

@@ -0,0 +1,43 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
/**\file
* Test that assertions and messages are thread-safe.
*
* This is done by spamming assertions and messages on multiple subthreads.
* In manual, this reliably causes segfaults if the test is linked against
* a non-thread-safe version of Catch2.
*
* The CTest test definition should also verify that the final assertion
* count is correct.
*/
#include <catch2/catch_test_macros.hpp>
#include <thread>
#include <vector>
TEST_CASE( "Failed REQUIRE in the main thread is fine", "[!shouldfail]" ) {
std::vector<std::thread> threads;
for ( size_t t = 0; t < 4; ++t) {
threads.emplace_back( [t]() {
CAPTURE(t);
for (size_t i = 0; i < 100; ++i) {
CAPTURE(i);
CHECK( false );
CHECK( true );
}
} );
}
for (auto& t : threads) {
t.join();
}
REQUIRE( false );
}

View File

@@ -138,6 +138,7 @@ Nor would this
:test-result: FAIL Custom exceptions can be translated when testing for throwing as something else
:test-result: FAIL Custom std-exceptions can be custom translated
:test-result: PASS Default scale is invisible to comparison
:test-result: XFAIL Delayed unscoped message clearing does not catch newly inserted messages
:test-result: PASS Directly creating an EnumInfo
:test-result: SKIP Empty generators can SKIP in constructor
:test-result: PASS Empty stream name opens cout stream

View File

@@ -136,6 +136,7 @@
:test-result: FAIL Custom exceptions can be translated when testing for throwing as something else
:test-result: FAIL Custom std-exceptions can be custom translated
:test-result: PASS Default scale is invisible to comparison
:test-result: XFAIL Delayed unscoped message clearing does not catch newly inserted messages
:test-result: PASS Directly creating an EnumInfo
:test-result: SKIP Empty generators can SKIP in constructor
:test-result: PASS Empty stream name opens cout stream

View File

@@ -562,6 +562,8 @@ Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'c
Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'custom std exception'
Approx.tests.cpp:<line number>: passed: 101.000001 != Approx(100).epsilon(0.01) for: 101.00000099999999748 != Approx( 100.0 )
Approx.tests.cpp:<line number>: passed: std::pow(10, -5) != Approx(std::pow(10, -7)) for: 0.00001 != Approx( 0.0000001 )
Message.tests.cpp:<line number>: passed: true with 1 message: 'a'
Message.tests.cpp:<line number>: failed: false with 1 message: 'b'
ToString.tests.cpp:<line number>: passed: enumInfo->lookup(0) == "Value1" for: Value1 == "Value1"
ToString.tests.cpp:<line number>: passed: enumInfo->lookup(1) == "Value2" for: Value2 == "Value2"
ToString.tests.cpp:<line number>: passed: enumInfo->lookup(3) == "{** unexpected enum value **}" for: {** unexpected enum value **}
@@ -2888,7 +2890,7 @@ InternalBenchmark.tests.cpp:<line number>: passed: med == 18. for: 18.0 == 18.0
InternalBenchmark.tests.cpp:<line number>: passed: q3 == 23. for: 23.0 == 23.0
Misc.tests.cpp:<line number>: passed:
Misc.tests.cpp:<line number>: passed:
test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected
test cases: 436 | 317 passed | 95 failed | 6 skipped | 18 failed as expected
assertions: 2305 | 2106 passed | 157 failed | 42 failed as expected

View File

@@ -560,6 +560,8 @@ Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'c
Exception.tests.cpp:<line number>: failed: unexpected exception with message: 'custom std exception'
Approx.tests.cpp:<line number>: passed: 101.000001 != Approx(100).epsilon(0.01) for: 101.00000099999999748 != Approx( 100.0 )
Approx.tests.cpp:<line number>: passed: std::pow(10, -5) != Approx(std::pow(10, -7)) for: 0.00001 != Approx( 0.0000001 )
Message.tests.cpp:<line number>: passed: true with 1 message: 'a'
Message.tests.cpp:<line number>: failed: false with 1 message: 'b'
ToString.tests.cpp:<line number>: passed: enumInfo->lookup(0) == "Value1" for: Value1 == "Value1"
ToString.tests.cpp:<line number>: passed: enumInfo->lookup(1) == "Value2" for: Value2 == "Value2"
ToString.tests.cpp:<line number>: passed: enumInfo->lookup(3) == "{** unexpected enum value **}" for: {** unexpected enum value **}
@@ -2877,7 +2879,7 @@ InternalBenchmark.tests.cpp:<line number>: passed: med == 18. for: 18.0 == 18.0
InternalBenchmark.tests.cpp:<line number>: passed: q3 == 23. for: 23.0 == 23.0
Misc.tests.cpp:<line number>: passed:
Misc.tests.cpp:<line number>: passed:
test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected
test cases: 436 | 317 passed | 95 failed | 6 skipped | 18 failed as expected
assertions: 2305 | 2106 passed | 157 failed | 42 failed as expected

View File

@@ -450,6 +450,17 @@ Exception.tests.cpp:<line number>: FAILED:
due to unexpected exception with message:
custom std exception
-------------------------------------------------------------------------------
Delayed unscoped message clearing does not catch newly inserted messages
-------------------------------------------------------------------------------
Message.tests.cpp:<line number>
...............................................................................
Message.tests.cpp:<line number>: FAILED:
REQUIRE( false )
with message:
b
-------------------------------------------------------------------------------
Empty generators can SKIP in constructor
-------------------------------------------------------------------------------
@@ -1719,6 +1730,6 @@ due to unexpected exception with message:
Why would you throw a std::string?
===============================================================================
test cases: 435 | 335 passed | 76 failed | 7 skipped | 17 failed as expected
assertions: 2282 | 2105 passed | 136 failed | 41 failed as expected
test cases: 436 | 335 passed | 76 failed | 7 skipped | 18 failed as expected
assertions: 2284 | 2106 passed | 136 failed | 42 failed as expected

View File

@@ -4148,6 +4148,22 @@ Approx.tests.cpp:<line number>: PASSED:
with expansion:
0.00001 != Approx( 0.0000001 )
-------------------------------------------------------------------------------
Delayed unscoped message clearing does not catch newly inserted messages
-------------------------------------------------------------------------------
Message.tests.cpp:<line number>
...............................................................................
Message.tests.cpp:<line number>: PASSED:
REQUIRE( true )
with message:
a
Message.tests.cpp:<line number>: FAILED:
REQUIRE( false )
with message:
b
-------------------------------------------------------------------------------
Directly creating an EnumInfo
-------------------------------------------------------------------------------
@@ -19295,6 +19311,6 @@ Misc.tests.cpp:<line number>
Misc.tests.cpp:<line number>: PASSED:
===============================================================================
test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected
test cases: 436 | 317 passed | 95 failed | 6 skipped | 18 failed as expected
assertions: 2305 | 2106 passed | 157 failed | 42 failed as expected

View File

@@ -4146,6 +4146,22 @@ Approx.tests.cpp:<line number>: PASSED:
with expansion:
0.00001 != Approx( 0.0000001 )
-------------------------------------------------------------------------------
Delayed unscoped message clearing does not catch newly inserted messages
-------------------------------------------------------------------------------
Message.tests.cpp:<line number>
...............................................................................
Message.tests.cpp:<line number>: PASSED:
REQUIRE( true )
with message:
a
Message.tests.cpp:<line number>: FAILED:
REQUIRE( false )
with message:
b
-------------------------------------------------------------------------------
Directly creating an EnumInfo
-------------------------------------------------------------------------------
@@ -19284,6 +19300,6 @@ Misc.tests.cpp:<line number>
Misc.tests.cpp:<line number>: PASSED:
===============================================================================
test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected
assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected
test cases: 436 | 317 passed | 95 failed | 6 skipped | 18 failed as expected
assertions: 2305 | 2106 passed | 157 failed | 42 failed as expected

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuitesloose text artifact
>
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2315" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2317" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<properties>
<property name="random-seed" value="1"/>
<property name="filters" value="&quot;*&quot; ~[!nonportable] ~[!benchmark] ~[approvals]"/>
@@ -547,6 +547,15 @@ at Exception.tests.cpp:<line number>
</error>
</testcase>
<testcase classname="<exe-name>.global" name="Default scale is invisible to comparison" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Delayed unscoped message clearing does not catch newly inserted messages" time="{duration}" status="run">
<skipped message="TEST_CASE tagged with !mayfail"/>
<failure message="false" type="REQUIRE">
FAILED:
REQUIRE( false )
b
at Message.tests.cpp:<line number>
</failure>
</testcase>
<testcase classname="<exe-name>.global" name="Directly creating an EnumInfo" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Empty generators can SKIP in constructor" time="{duration}" status="run">
<skipped type="SKIP">

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2315" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2317" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<properties>
<property name="random-seed" value="1"/>
<property name="filters" value="&quot;*&quot; ~[!nonportable] ~[!benchmark] ~[approvals]"/>
@@ -546,6 +546,15 @@ at Exception.tests.cpp:<line number>
</error>
</testcase>
<testcase classname="<exe-name>.global" name="Default scale is invisible to comparison" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Delayed unscoped message clearing does not catch newly inserted messages" time="{duration}" status="run">
<skipped message="TEST_CASE tagged with !mayfail"/>
<failure message="false" type="REQUIRE">
FAILED:
REQUIRE( false )
b
at Message.tests.cpp:<line number>
</failure>
</testcase>
<testcase classname="<exe-name>.global" name="Directly creating an EnumInfo" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Empty generators can SKIP in constructor" time="{duration}" status="run">
<skipped type="SKIP">

View File

@@ -1699,6 +1699,14 @@ at Message.tests.cpp:<line number>
</failure>
</testCase>
<testCase name="Captures outlive section end/Dummy section" duration="{duration}"/>
<testCase name="Delayed unscoped message clearing does not catch newly inserted messages" duration="{duration}">
<skipped message="REQUIRE(false)">
FAILED:
REQUIRE( false )
b
at Message.tests.cpp:<line number>
</skipped>
</testCase>
<testCase name="FAIL aborts the test" duration="{duration}">
<failure message="FAIL()">
FAILED:

View File

@@ -1698,6 +1698,14 @@ at Message.tests.cpp:<line number>
</failure>
</testCase>
<testCase name="Captures outlive section end/Dummy section" duration="{duration}"/>
<testCase name="Delayed unscoped message clearing does not catch newly inserted messages" duration="{duration}">
<skipped message="REQUIRE(false)">
FAILED:
REQUIRE( false )
b
at Message.tests.cpp:<line number>
</skipped>
</testCase>
<testCase name="FAIL aborts the test" duration="{duration}">
<failure message="FAIL()">
FAILED:

View File

@@ -1024,6 +1024,10 @@ not ok {test-number} - unexpected exception with message: 'custom std exception'
ok {test-number} - 101.000001 != Approx(100).epsilon(0.01) for: 101.00000099999999748 != Approx( 100.0 )
# Default scale is invisible to comparison
ok {test-number} - std::pow(10, -5) != Approx(std::pow(10, -7)) for: 0.00001 != Approx( 0.0000001 )
# Delayed unscoped message clearing does not catch newly inserted messages
ok {test-number} - true with 1 message: 'a'
# Delayed unscoped message clearing does not catch newly inserted messages
not ok {test-number} - false with 1 message: 'b'
# Directly creating an EnumInfo
ok {test-number} - enumInfo->lookup(0) == "Value1" for: Value1 == "Value1"
# Directly creating an EnumInfo
@@ -4627,5 +4631,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0
ok {test-number} -
# xmlentitycheck
ok {test-number} -
1..2315
1..2317

View File

@@ -1022,6 +1022,10 @@ not ok {test-number} - unexpected exception with message: 'custom std exception'
ok {test-number} - 101.000001 != Approx(100).epsilon(0.01) for: 101.00000099999999748 != Approx( 100.0 )
# Default scale is invisible to comparison
ok {test-number} - std::pow(10, -5) != Approx(std::pow(10, -7)) for: 0.00001 != Approx( 0.0000001 )
# Delayed unscoped message clearing does not catch newly inserted messages
ok {test-number} - true with 1 message: 'a'
# Delayed unscoped message clearing does not catch newly inserted messages
not ok {test-number} - false with 1 message: 'b'
# Directly creating an EnumInfo
ok {test-number} - enumInfo->lookup(0) == "Value1" for: Value1 == "Value1"
# Directly creating an EnumInfo
@@ -4616,5 +4620,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0
ok {test-number} -
# xmlentitycheck
ok {test-number} -
1..2315
1..2317

View File

@@ -322,6 +322,9 @@
##teamcity[testFinished name='Custom std-exceptions can be custom translated' duration="{duration}"]
##teamcity[testStarted name='Default scale is invisible to comparison']
##teamcity[testFinished name='Default scale is invisible to comparison' duration="{duration}"]
##teamcity[testStarted name='Delayed unscoped message clearing does not catch newly inserted messages']
##teamcity[testIgnored name='Delayed unscoped message clearing does not catch newly inserted messages' message='Message.tests.cpp:<line number>|n...............................................................................|n|nMessage.tests.cpp:<line number>|nexpression failed with message:|n "b"|n REQUIRE( false )|nwith expansion:|n false|n- failure ignore as test marked as |'ok to fail|'|n']
##teamcity[testFinished name='Delayed unscoped message clearing does not catch newly inserted messages' duration="{duration}"]
##teamcity[testStarted name='Directly creating an EnumInfo']
##teamcity[testFinished name='Directly creating an EnumInfo' duration="{duration}"]
##teamcity[testStarted name='Empty generators can SKIP in constructor']

View File

@@ -322,6 +322,9 @@
##teamcity[testFinished name='Custom std-exceptions can be custom translated' duration="{duration}"]
##teamcity[testStarted name='Default scale is invisible to comparison']
##teamcity[testFinished name='Default scale is invisible to comparison' duration="{duration}"]
##teamcity[testStarted name='Delayed unscoped message clearing does not catch newly inserted messages']
##teamcity[testIgnored name='Delayed unscoped message clearing does not catch newly inserted messages' message='Message.tests.cpp:<line number>|n...............................................................................|n|nMessage.tests.cpp:<line number>|nexpression failed with message:|n "b"|n REQUIRE( false )|nwith expansion:|n false|n- failure ignore as test marked as |'ok to fail|'|n']
##teamcity[testFinished name='Delayed unscoped message clearing does not catch newly inserted messages' duration="{duration}"]
##teamcity[testStarted name='Directly creating an EnumInfo']
##teamcity[testFinished name='Directly creating an EnumInfo' duration="{duration}"]
##teamcity[testStarted name='Empty generators can SKIP in constructor']

View File

@@ -4613,6 +4613,31 @@ C
</Expression>
<OverallResult success="true" skips="0"/>
</TestCase>
<TestCase name="Delayed unscoped message clearing does not catch newly inserted messages" tags="[!shouldfail][messages][unscoped]" filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
<Info filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
a
</Info>
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
<Original>
true
</Original>
<Expanded>
true
</Expanded>
</Expression>
<Info filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
b
</Info>
<Expression success="false" type="REQUIRE" filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
<Original>
false
</Original>
<Expanded>
false
</Expanded>
</Expression>
<OverallResult success="true" skips="0"/>
</TestCase>
<TestCase name="Directly creating an EnumInfo" filename="tests/<exe-name>/IntrospectiveTests/ToString.tests.cpp" >
<Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/ToString.tests.cpp" >
<Original>
@@ -22324,6 +22349,6 @@ Approx( -1.95996398454005449 )
</Section>
<OverallResult success="true" skips="0"/>
</TestCase>
<OverallResults successes="2105" failures="157" expectedFailures="41" skips="12"/>
<OverallResultsCases successes="317" failures="95" expectedFailures="17" skips="6"/>
<OverallResults successes="2106" failures="157" expectedFailures="42" skips="12"/>
<OverallResultsCases successes="317" failures="95" expectedFailures="18" skips="6"/>
</Catch2TestRun>

View File

@@ -4613,6 +4613,31 @@ C
</Expression>
<OverallResult success="true" skips="0"/>
</TestCase>
<TestCase name="Delayed unscoped message clearing does not catch newly inserted messages" tags="[!shouldfail][messages][unscoped]" filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
<Info filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
a
</Info>
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
<Original>
true
</Original>
<Expanded>
true
</Expanded>
</Expression>
<Info filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
b
</Info>
<Expression success="false" type="REQUIRE" filename="tests/<exe-name>/UsageTests/Message.tests.cpp" >
<Original>
false
</Original>
<Expanded>
false
</Expanded>
</Expression>
<OverallResult success="true" skips="0"/>
</TestCase>
<TestCase name="Directly creating an EnumInfo" filename="tests/<exe-name>/IntrospectiveTests/ToString.tests.cpp" >
<Expression success="true" type="CHECK" filename="tests/<exe-name>/IntrospectiveTests/ToString.tests.cpp" >
<Original>
@@ -22323,6 +22348,6 @@ Approx( -1.95996398454005449 )
</Section>
<OverallResult success="true" skips="0"/>
</TestCase>
<OverallResults successes="2105" failures="157" expectedFailures="41" skips="12"/>
<OverallResultsCases successes="317" failures="95" expectedFailures="17" skips="6"/>
<OverallResults successes="2106" failures="157" expectedFailures="42" skips="12"/>
<OverallResultsCases successes="317" failures="95" expectedFailures="18" skips="6"/>
</Catch2TestRun>

View File

@@ -7,6 +7,7 @@
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_case_insensitive_comparisons.hpp>
#include <catch2/internal/catch_optional.hpp>
@@ -170,3 +171,64 @@ TEST_CASE( "Decomposer checks that the argument is 0 when handling "
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
}
TEST_CASE( "foo", "[approvals]" ) {
SECTION( "A" ) {
SECTION( "B1" ) { REQUIRE( true ); }
SECTION( "B2" ) { REQUIRE( true ); }
SECTION( "B3" ) { REQUIRE( true ); }
}
}
TEST_CASE( "bar", "[approvals]" ) {
REQUIRE( true );
SECTION( "A" ) {
SECTION( "B1" ) { REQUIRE( true ); }
SECTION( "B2" ) { REQUIRE( true ); }
SECTION( "B3" ) { REQUIRE( true ); }
}
REQUIRE( true );
}
TEST_CASE( "baz", "[approvals]" ) {
SECTION( "A" ) { REQUIRE( true ); }
auto _ = GENERATE( 1, 2, 3 );
(void)_;
SECTION( "B" ) { REQUIRE( true ); }
}
TEST_CASE( "qux", "[approvals]" ) {
REQUIRE( true );
SECTION( "A" ) { REQUIRE( true ); }
auto _ = GENERATE( 1, 2, 3 );
(void)_;
SECTION( "B" ) { REQUIRE( true ); }
REQUIRE( true );
}
TEST_CASE( "corge", "[approvals]" ) {
REQUIRE( true );
SECTION( "A" ) {
REQUIRE( true );
}
auto i = GENERATE( 1, 2, 3 );
DYNAMIC_SECTION( "i=" << i ) {
REQUIRE( true );
}
REQUIRE( true );
}
TEST_CASE("grault", "[approvals]") {
REQUIRE( true );
SECTION( "A" ) {
REQUIRE( true );
}
SECTION("B") {
auto i = GENERATE( 1, 2, 3 );
DYNAMIC_SECTION( "i=" << i ) {
REQUIRE( true );
}
}
REQUIRE( true );
}

View File

@@ -360,3 +360,12 @@ TEST_CASE( "Scoped message applies to all assertions in scope",
CHECK( false );
CHECK( false );
}
TEST_CASE(
"Delayed unscoped message clearing does not catch newly inserted messages",
"[messages][unscoped][!shouldfail]" ) {
UNSCOPED_INFO( "a" );
REQUIRE( true );
UNSCOPED_INFO( "b" );
REQUIRE( false );
}

View File

@@ -354,27 +354,9 @@ TEST_CASE("#1514: stderr/stdout is not captured in tests aborted by an exception
FAIL("1514");
}
TEST_CASE( "#2025: -c shouldn't cause infinite loop", "[sections][generators][regression][.approvals]" ) {
SECTION( "Check cursor from buffer offset" ) {
auto bufPos = GENERATE_REF( range( 0, 44 ) );
WHEN( "Buffer position is " << bufPos ) { REQUIRE( 1 == 1 ); }
}
}
TEST_CASE("#2025: original repro", "[sections][generators][regression][.approvals]") {
auto fov = GENERATE(true, false);
DYNAMIC_SECTION("fov_" << fov) {
std::cout << "inside with fov: " << fov << '\n';
}
}
TEST_CASE("#2025: same-level sections", "[sections][generators][regression][.approvals]") {
SECTION("A") {
SUCCEED();
}
auto i = GENERATE(1, 2, 3);
SECTION("B") {
REQUIRE(i < 4);
}
}

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
# Copyright Catch2 Authors
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at
# https://www.boost.org/LICENSE_1_0.txt)
# SPDX-License-Identifier: BSL-1.0
"""
This test script verifies the behaviour of the legacy section filtering
using `-c`, `--section` CLI parameters.
This is done by having a hardcoded set of test filter + section filter
combinations, together with the expected number of assertions that will
be run inside the test for given filter combo.
"""
import os
import subprocess
import sys
from typing import Tuple, List
import xml.etree.ElementTree as ET
def make_cli_filter(section_names: Tuple[str, ...]) -> List[str]:
final = []
for name in section_names:
final.append('--section')
final.append(name)
return final
def run_one_test(binary_path: str, test_name: str, section_names: Tuple[str, ...], expected_assertions: int):
cmd = [
binary_path,
'--reporter', 'xml',
test_name
]
cmd.extend(make_cli_filter(section_names))
try:
ret = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
universal_newlines=True,
)
stdout = ret.stdout
except subprocess.SubprocessError as ex:
print('Could not run "{}"'.format(cmd))
print("Return code: {}".format(ex.returncode))
print("stdout: {}".format(ex.stdout))
print("stderr: {}".format(ex.stderr))
raise
try:
tree = ET.fromstring(stdout)
except ET.ParseError as ex:
print("Invalid XML: '{}'".format(ex))
raise
# Validate that we ran exactly 1 test case, and it passed
test_case_stats = tree.find('OverallResultsCases')
expected_testcases = {'successes' : '1', 'failures' : '0', 'expectedFailures': '0', 'skips': '0'}
assert test_case_stats.attrib == expected_testcases, f'We did not run single passing test case as expected. {test_name}: {test_case_stats.attrib}'
# Validate that we got exactly the expected number of passing assertions
expected_assertions = {'successes' : str(expected_assertions), 'failures' : '0', 'expectedFailures': '0', 'skips': '0'}
assertion_stats = tree.find('OverallResults')
assert assertion_stats.attrib == expected_assertions, f'"{test_name}": {assertion_stats.attrib} vs {expected_assertions}'
# Inputs taken from issue #3038
tests = {
'foo': (
((), 3),
(('A',), 3),
(('A', 'B'), 0),
(('A', 'B1'), 1),
(('A', 'B2'), 1),
(('A', 'B1', 'B2'), 1),
(('A', 'B2', 'XXXX'), 1),
),
'bar': (
((), 9),
(('A',), 9),
(('A', 'B1'), 3),
(('XXXX',), 2),
(('B1',), 2),
(('A', 'B1', 'B2'), 3),
),
'baz': (
((), 4),
(('A',), 1),
(('A', 'B'), 1),
(('A', 'XXXX'), 1),
(('B',), 3),
(('XXXX',), 0),
),
'qux': (
((), 12),
(('A',), 7),
(('B',), 9),
(('B', 'XXXX'), 9),
(('XXXX',), 6),
),
'corge': (
((), 12),
(('i=2',), 7),
(('i=3',), 7),
),
'grault': (
((), 12),
(('A',), 3),
(('B',), 9),
(('B', 'i=1'), 7),
(('B', 'XXXX'), 6),
),
}
if len(sys.argv) != 2:
print("Wrong number of arguments, expected just the path to Catch2 SelfTest binary")
exit(1)
bin_path = os.path.abspath(sys.argv[1])
for test_filter, specs in tests.items():
for section_path, expected_assertions in specs:
run_one_test(bin_path, test_filter, section_path, expected_assertions)