Compare commits

...

23 Commits

Author SHA1 Message Date
Martin Hořeňovský
c1968b3114 More tsan builds 2025-11-09 23:13:57 +01:00
Martin Hořeňovský
5ed9c45e5f Verbose ctest 2025-11-09 22:53:55 +01:00
Martin Hořeňovský
950ad70f4c Fix 2025-11-09 21:34:41 +01:00
Martin Hořeňovský
d7c67270af Disable werror 2025-11-09 21:32:03 +01:00
Martin Hořeňovský
c748569310 Test tsan on mac 2025-11-09 21:28:25 +01:00
Martin Hořeňovský
cd7e43489e Fewer assertions so that msvc debug build doesn't timeout 2025-11-09 15:18:09 +01:00
Martin Hořeňovský
f6fd079aa3 back to 4 threads 2025-11-09 15:03:54 +01:00
Martin Hořeňovský
22d54b36e0 Just 2 threads for a test 2025-11-09 14:58:10 +01:00
Martin Hořeňovský
a9116c2142 Serial run, CAPTURE back 2025-11-09 13:14:14 +01:00
Martin Hořeňovský
2e3214709a Remove captures 2025-11-09 11:41:27 +01:00
Martin Hořeňovský
41ed8b702a Fix initialization for older standards 2025-11-09 11:19:44 +01:00
Martin Hořeňovský
93ef2b4cb8 Add tests for assertion thread safety 2025-11-09 11:07:32 +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
27 changed files with 477 additions and 161 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 Unlike reporters, each registered event listener is always active. Event
listeners are always notified before reporter(s). 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 as it provides empty stubs for all reporter events, allowing you to
only override events you care for. Afterwards you have to register it only override events you care for. Afterwards you have to register it
with Catch2 using `CATCH_REGISTER_LISTENER` macro, so that Catch2 knows 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. to be an error or not.
* If empty generator **is** an error, throw an exception in constructor. * 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 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 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 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_jsonwriter.hpp
${SOURCES_DIR}/internal/catch_lazy_expr.hpp ${SOURCES_DIR}/internal/catch_lazy_expr.hpp
${SOURCES_DIR}/internal/catch_leak_detector.hpp ${SOURCES_DIR}/internal/catch_leak_detector.hpp
${SOURCES_DIR}/internal/catch_lifetimebound.hpp
${SOURCES_DIR}/internal/catch_list.hpp ${SOURCES_DIR}/internal/catch_list.hpp
${SOURCES_DIR}/internal/catch_logical_traits.hpp ${SOURCES_DIR}/internal/catch_logical_traits.hpp
${SOURCES_DIR}/internal/catch_message_info.hpp ${SOURCES_DIR}/internal/catch_message_info.hpp

View File

@@ -79,6 +79,7 @@
#include <catch2/internal/catch_jsonwriter.hpp> #include <catch2/internal/catch_jsonwriter.hpp>
#include <catch2/internal/catch_lazy_expr.hpp> #include <catch2/internal/catch_lazy_expr.hpp>
#include <catch2/internal/catch_leak_detector.hpp> #include <catch2/internal/catch_leak_detector.hpp>
#include <catch2/internal/catch_lifetimebound.hpp>
#include <catch2/internal/catch_list.hpp> #include <catch2/internal/catch_list.hpp>
#include <catch2/internal/catch_logical_traits.hpp> #include <catch2/internal/catch_logical_traits.hpp>
#include <catch2/internal/catch_message_info.hpp> #include <catch2/internal/catch_message_info.hpp>

View File

@@ -57,6 +57,36 @@ namespace Detail {
} }
} // end unnamed namespace } // 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 convertIntoString(StringRef string, bool escapeInvisibles) {
std::string ret; std::string ret;
// This is enough for the "don't escape invisibles" case, and a good // This is enough for the "don't escape invisibles" case, and a good

View File

@@ -8,7 +8,7 @@
#ifndef CATCH_TOSTRING_HPP_INCLUDED #ifndef CATCH_TOSTRING_HPP_INCLUDED
#define CATCH_TOSTRING_HPP_INCLUDED #define CATCH_TOSTRING_HPP_INCLUDED
#include <ctime>
#include <vector> #include <vector>
#include <cstddef> #include <cstddef>
#include <type_traits> #include <type_traits>
@@ -40,13 +40,9 @@ namespace Catch {
namespace Detail { namespace Detail {
inline std::size_t catch_strnlen(const char *str, std::size_t n) { 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) { std::string formatTimeT( std::time_t time );
return static_cast<std::size_t>(ret - str);
}
return n;
}
constexpr StringRef unprintableString = "{?}"_sr; constexpr StringRef unprintableString = "{?}"_sr;
@@ -412,43 +408,37 @@ namespace Catch {
// Separate std::tuple specialization // Separate std::tuple specialization
#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) #if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)
# include <tuple> # include <tuple>
# include <utility>
namespace Catch { namespace Catch {
namespace Detail { namespace Detail {
template< template <typename Tuple, std::size_t... Is>
typename Tuple, void PrintTuple( const Tuple& tuple,
std::size_t N = 0, std::ostream& os,
bool = (N < std::tuple_size<Tuple>::value) std::index_sequence<Is...> ) {
> // 1 + Account for when the tuple is empty
struct TupleElementPrinter { char a[1 + sizeof...( Is )] = {
static void print(const Tuple& tuple, std::ostream& os) { ( ( os << ( Is ? ", " : " " )
os << (N ? ", " : " ") << ::Catch::Detail::stringify( std::get<Is>( tuple ) ) ),
<< ::Catch::Detail::stringify(std::get<N>(tuple)); '\0' )... };
TupleElementPrinter<Tuple, N + 1>::print(tuple, os); (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...>> { 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; ReusableStringStream rss;
rss << '{'; rss << '{';
Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get()); Detail::PrintTuple(
tuple,
rss.get(),
std::make_index_sequence<sizeof...( Types )>{} );
rss << " }"; rss << " }";
return rss.str(); return rss.str();
} }
}; };
} } // namespace Catch
#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER #endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) #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< const auto systemish = std::chrono::time_point_cast<
std::chrono::system_clock::duration>( time_point ); std::chrono::system_clock::duration>( time_point );
const auto as_time_t = std::chrono::system_clock::to_time_t( systemish ); const auto as_time_t = std::chrono::system_clock::to_time_t( systemish );
return ::Catch::Detail::formatTimeT( as_time_t );
#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);
} }
}; };
} }

View File

@@ -265,7 +265,7 @@ namespace Catch {
( "list all listeners" ) ( "list all listeners" )
| Opt( setTestOrder, "decl|lex|rand" ) | Opt( setTestOrder, "decl|lex|rand" )
["--order"] ["--order"]
( "test case order (defaults to decl)" ) ( "test case order (defaults to rand)" )
| Opt( setRngSeed, "'time'|'random-device'|number" ) | Opt( setRngSeed, "'time'|'random-device'|number" )
["--rng-seed"] ["--rng-seed"]
( "set a specific seed for random numbers" ) ( "set a specific seed for random numbers" )

View File

@@ -8,6 +8,7 @@
#ifndef CATCH_JSONWRITER_HPP_INCLUDED #ifndef CATCH_JSONWRITER_HPP_INCLUDED
#define 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_reusable_string_stream.hpp>
#include <catch2/internal/catch_stringref.hpp> #include <catch2/internal/catch_stringref.hpp>
@@ -27,8 +28,8 @@ namespace Catch {
class JsonValueWriter { class JsonValueWriter {
public: public:
JsonValueWriter( std::ostream& os ); JsonValueWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
JsonValueWriter( std::ostream& os, std::uint64_t indent_level ); JsonValueWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level );
JsonObjectWriter writeObject() &&; JsonObjectWriter writeObject() &&;
JsonArrayWriter writeArray() &&; JsonArrayWriter writeArray() &&;
@@ -62,8 +63,8 @@ namespace Catch {
class JsonObjectWriter { class JsonObjectWriter {
public: public:
JsonObjectWriter( std::ostream& os ); JsonObjectWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
JsonObjectWriter( std::ostream& os, std::uint64_t indent_level ); JsonObjectWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level );
JsonObjectWriter( JsonObjectWriter&& source ) noexcept; JsonObjectWriter( JsonObjectWriter&& source ) noexcept;
JsonObjectWriter& operator=( JsonObjectWriter&& source ) = delete; JsonObjectWriter& operator=( JsonObjectWriter&& source ) = delete;
@@ -81,8 +82,8 @@ namespace Catch {
class JsonArrayWriter { class JsonArrayWriter {
public: public:
JsonArrayWriter( std::ostream& os ); JsonArrayWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
JsonArrayWriter( std::ostream& os, std::uint64_t indent_level ); JsonArrayWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level );
JsonArrayWriter( JsonArrayWriter&& source ) noexcept; JsonArrayWriter( JsonArrayWriter&& source ) noexcept;
JsonArrayWriter& operator=( JsonArrayWriter&& source ) = delete; 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

@@ -129,12 +129,8 @@ namespace Catch {
for ( auto const& child : m_children ) { for ( auto const& child : m_children ) {
if ( child->isSectionTracker() && if ( child->isSectionTracker() &&
std::find( filters.begin(), static_cast<SectionTracker const&>( *child )
filters.end(), .trimmedName() == filters[0] ) {
static_cast<SectionTracker const&>(
*child )
.trimmedName() ) !=
filters.end() ) {
return true; return true;
} }
} }

View File

@@ -8,6 +8,7 @@
#ifndef CATCH_STRING_MANIP_HPP_INCLUDED #ifndef CATCH_STRING_MANIP_HPP_INCLUDED
#define CATCH_STRING_MANIP_HPP_INCLUDED #define CATCH_STRING_MANIP_HPP_INCLUDED
#include <catch2/internal/catch_lifetimebound.hpp>
#include <catch2/internal/catch_stringref.hpp> #include <catch2/internal/catch_stringref.hpp>
#include <cstdint> #include <cstdint>
@@ -28,10 +29,10 @@ namespace Catch {
//! Returns a new string without whitespace at the start/end //! Returns a new string without whitespace at the start/end
std::string trim( std::string const& str ); std::string trim( std::string const& str );
//! Returns a substring of the original ref without whitespace. Beware lifetimes! //! 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 // !!! 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 ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
/** /**
@@ -49,7 +50,7 @@ namespace Catch {
StringRef m_label; StringRef m_label;
public: public:
constexpr pluralise(std::uint64_t count, StringRef label): constexpr pluralise(std::uint64_t count, StringRef label CATCH_ATTR_LIFETIMEBOUND):
m_count(count), m_count(count),
m_label(label) m_label(label)
{} {}

View File

@@ -8,11 +8,12 @@
#ifndef CATCH_STRINGREF_HPP_INCLUDED #ifndef CATCH_STRINGREF_HPP_INCLUDED
#define CATCH_STRINGREF_HPP_INCLUDED #define CATCH_STRINGREF_HPP_INCLUDED
#include <catch2/internal/catch_lifetimebound.hpp>
#include <cstddef> #include <cstddef>
#include <string> #include <string>
#include <iosfwd> #include <iosfwd>
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
namespace Catch { namespace Catch {
@@ -36,14 +37,16 @@ namespace Catch {
public: // construction public: // construction
constexpr StringRef() noexcept = default; 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_start( rawChars ),
m_size( size ) m_size( size )
{} {}
StringRef( std::string const& stdString ) noexcept StringRef(
std::string const& stdString CATCH_ATTR_LIFETIMEBOUND ) noexcept
: m_start( stdString.c_str() ), : m_start( stdString.c_str() ),
m_size( stdString.size() ) m_size( stdString.size() )
{} {}
@@ -89,7 +92,7 @@ namespace Catch {
} }
// Returns the current start pointer. May not be null-terminated. // 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; return m_start;
} }

View File

@@ -174,7 +174,7 @@ namespace TestCaseTracking {
if ( m_filters.empty() if ( m_filters.empty()
|| m_filters[0].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(); complete = TrackerBase::isComplete();
} }
return complete; return complete;

View File

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

View File

@@ -23,10 +23,10 @@ namespace Catch {
using Mutex = std::mutex; using Mutex = std::mutex;
using LockGuard = std::lock_guard<std::mutex>; using LockGuard = std::lock_guard<std::mutex>;
struct AtomicCounts { struct AtomicCounts {
std::atomic<std::uint64_t> passed = 0; std::atomic<std::uint64_t> passed{ 0 };
std::atomic<std::uint64_t> failed = 0; std::atomic<std::uint64_t> failed{ 0 };
std::atomic<std::uint64_t> failedButOk = 0; std::atomic<std::uint64_t> failedButOk{ 0 };
std::atomic<std::uint64_t> skipped = 0; std::atomic<std::uint64_t> skipped{ 0 };
}; };
#else // ^^ Use actual mutex, lock and atomics #else // ^^ Use actual mutex, lock and atomics
// vv Dummy implementations for single-thread performance // vv Dummy implementations for single-thread performance

View File

@@ -8,6 +8,7 @@
#ifndef CATCH_XMLWRITER_HPP_INCLUDED #ifndef CATCH_XMLWRITER_HPP_INCLUDED
#define 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_reusable_string_stream.hpp>
#include <catch2/internal/catch_stringref.hpp> #include <catch2/internal/catch_stringref.hpp>
@@ -43,7 +44,7 @@ namespace Catch {
public: public:
enum ForWhat { ForTextNodes, ForAttributes }; 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 ) {} m_str( str ), m_forWhat( forWhat ) {}
@@ -61,7 +62,7 @@ namespace Catch {
class ScopedElement { class ScopedElement {
public: public:
ScopedElement( XmlWriter* writer, XmlFormatting fmt ); ScopedElement( XmlWriter* writer CATCH_ATTR_LIFETIMEBOUND, XmlFormatting fmt );
ScopedElement( ScopedElement&& other ) noexcept; ScopedElement( ScopedElement&& other ) noexcept;
ScopedElement& operator=( ScopedElement&& other ) noexcept; ScopedElement& operator=( ScopedElement&& other ) noexcept;
@@ -93,7 +94,7 @@ namespace Catch {
XmlFormatting m_fmt; XmlFormatting m_fmt;
}; };
XmlWriter( std::ostream& os ); XmlWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
~XmlWriter(); ~XmlWriter();
XmlWriter( XmlWriter const& ) = delete; XmlWriter( XmlWriter const& ) = delete;

View File

@@ -10,6 +10,7 @@
#include <catch2/matchers/internal/catch_matchers_impl.hpp> #include <catch2/matchers/internal/catch_matchers_impl.hpp>
#include <catch2/internal/catch_move_and_forward.hpp> #include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_lifetimebound.hpp>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -79,11 +80,15 @@ namespace Matchers {
return description; 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); lhs.m_matchers.push_back(&rhs);
return CATCH_MOVE(lhs); 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); rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs);
return CATCH_MOVE(rhs); return CATCH_MOVE(rhs);
} }
@@ -131,11 +136,15 @@ namespace Matchers {
return description; 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); lhs.m_matchers.push_back(&rhs);
return CATCH_MOVE(lhs); 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); rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs);
return CATCH_MOVE(rhs); return CATCH_MOVE(rhs);
} }
@@ -155,7 +164,8 @@ namespace Matchers {
MatcherBase<ArgT> const& m_underlyingMatcher; MatcherBase<ArgT> const& m_underlyingMatcher;
public: public:
explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ): explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher
CATCH_ATTR_LIFETIMEBOUND ):
m_underlyingMatcher( underlyingMatcher ) m_underlyingMatcher( underlyingMatcher )
{} {}
@@ -171,16 +181,22 @@ namespace Matchers {
} // namespace Detail } // namespace Detail
template <typename T> 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; return Detail::MatchAllOf<T>{} && lhs && rhs;
} }
template <typename T> 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; return Detail::MatchAnyOf<T>{} || lhs || rhs;
} }
template <typename T> 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 }; return Detail::MatchNotOf<T>{ matcher };
} }

View File

@@ -11,6 +11,7 @@
#include <catch2/matchers/catch_matchers.hpp> #include <catch2/matchers/catch_matchers.hpp>
#include <catch2/internal/catch_stringref.hpp> #include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_move_and_forward.hpp> #include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_lifetimebound.hpp>
#include <catch2/internal/catch_logical_traits.hpp> #include <catch2/internal/catch_logical_traits.hpp>
#include <array> #include <array>
@@ -114,7 +115,8 @@ namespace Matchers {
MatchAllOfGeneric(MatchAllOfGeneric&&) = default; MatchAllOfGeneric(MatchAllOfGeneric&&) = default;
MatchAllOfGeneric& operator=(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} {} explicit MatchAllOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
template<typename Arg> template<typename Arg>
@@ -136,8 +138,8 @@ namespace Matchers {
template<typename... MatchersRHS> template<typename... MatchersRHS>
friend friend
MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && ( MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && (
MatchAllOfGeneric<MatcherTs...>&& lhs, MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAllOfGeneric<MatchersRHS...>&& rhs) { MatchAllOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAllOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; 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> template<typename MatcherRHS>
friend std::enable_if_t<is_matcher_v<MatcherRHS>, friend std::enable_if_t<is_matcher_v<MatcherRHS>,
MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && ( MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && (
MatchAllOfGeneric<MatcherTs...>&& lhs, MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs) { MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAllOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(&rhs))}; 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> template<typename MatcherLHS>
friend std::enable_if_t<is_matcher_v<MatcherLHS>, friend std::enable_if_t<is_matcher_v<MatcherLHS>,
MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && ( MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && (
MatcherLHS const& lhs, MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAllOfGeneric<MatcherTs...>&& rhs) { MatchAllOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAllOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; 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(MatchAnyOfGeneric&&) = default;
MatchAnyOfGeneric& operator=(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} {} explicit MatchAnyOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
template<typename Arg> template<typename Arg>
@@ -190,8 +193,8 @@ namespace Matchers {
//! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case //! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case
template<typename... MatchersRHS> template<typename... MatchersRHS>
friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || ( friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || (
MatchAnyOfGeneric<MatcherTs...>&& lhs, MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAnyOfGeneric<MatchersRHS...>&& rhs) { MatchAnyOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; 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> template<typename MatcherRHS>
friend std::enable_if_t<is_matcher_v<MatcherRHS>, friend std::enable_if_t<is_matcher_v<MatcherRHS>,
MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || ( MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || (
MatchAnyOfGeneric<MatcherTs...>&& lhs, MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs) { MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAnyOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))}; 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> template<typename MatcherLHS>
friend std::enable_if_t<is_matcher_v<MatcherLHS>, friend std::enable_if_t<is_matcher_v<MatcherLHS>,
MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || ( MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || (
MatcherLHS const& lhs, MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAnyOfGeneric<MatcherTs...>&& rhs) { MatchAnyOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND) {
return MatchAnyOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; 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(MatchNotOfGeneric&&) = default;
MatchNotOfGeneric& operator=(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> template<typename Arg>
bool match(Arg&& arg) const { bool match(Arg&& arg) const {
@@ -237,7 +241,9 @@ namespace Matchers {
} }
//! Negating negation can just unwrap and return underlying matcher //! 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; return matcher.m_matcher;
} }
}; };
@@ -247,20 +253,22 @@ namespace Matchers {
// compose only generic matchers // compose only generic matchers
template<typename MatcherLHS, typename MatcherRHS> template<typename MatcherLHS, typename MatcherRHS>
std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAllOfGeneric<MatcherLHS, 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 }; return { lhs, rhs };
} }
template<typename MatcherLHS, typename MatcherRHS> template<typename MatcherLHS, typename MatcherRHS>
std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherLHS, 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 }; return { lhs, rhs };
} }
//! Wrap provided generic matcher in generic negator //! Wrap provided generic matcher in generic negator
template<typename MatcherT> template<typename MatcherT>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>, Detail::MatchNotOfGeneric<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}; return Detail::MatchNotOfGeneric<MatcherT>{matcher};
} }
@@ -268,25 +276,29 @@ namespace Matchers {
// compose mixed generic and non-generic matchers // compose mixed generic and non-generic matchers
template<typename MatcherLHS, typename ArgRHS> template<typename MatcherLHS, typename ArgRHS>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherBase<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 }; return { lhs, rhs };
} }
template<typename ArgLHS, typename MatcherRHS> template<typename ArgLHS, typename MatcherRHS>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAllOfGeneric<MatcherBase<ArgLHS>, 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 }; return { lhs, rhs };
} }
template<typename MatcherLHS, typename ArgRHS> template<typename MatcherLHS, typename ArgRHS>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherBase<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 }; return { lhs, rhs };
} }
template<typename ArgLHS, typename MatcherRHS> template<typename ArgLHS, typename MatcherRHS>
std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherBase<ArgLHS>, 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 }; return { lhs, rhs };
} }

View File

@@ -105,6 +105,7 @@ internal_headers = [
'internal/catch_jsonwriter.hpp', 'internal/catch_jsonwriter.hpp',
'internal/catch_lazy_expr.hpp', 'internal/catch_lazy_expr.hpp',
'internal/catch_leak_detector.hpp', 'internal/catch_leak_detector.hpp',
'internal/catch_lifetimebound.hpp',
'internal/catch_list.hpp', 'internal/catch_list.hpp',
'internal/catch_logical_traits.hpp', 'internal/catch_logical_traits.hpp',
'internal/catch_message_info.hpp', 'internal/catch_message_info.hpp',

View File

@@ -310,38 +310,23 @@ set_tests_properties(UnmatchedOutputFilter
PASS_REGULAR_EXPRESSION "No test cases matched '\\[this-tag-does-not-exist\\]'" PASS_REGULAR_EXPRESSION "No test cases matched '\\[this-tag-does-not-exist\\]'"
) )
add_test(NAME FilteredSection-1 COMMAND $<TARGET_FILE:SelfTest> \#1394 -c RunSection) add_test(NAME FilteredSections::SimpleExample::1 COMMAND $<TARGET_FILE:SelfTest> \#1394 -c RunSection)
set_tests_properties(FilteredSection-1 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran") set_tests_properties(FilteredSections::SimpleExample::1 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
add_test(NAME FilteredSection-2 COMMAND $<TARGET_FILE:SelfTest> \#1394\ nested -c NestedRunSection -c s1) add_test(NAME FilteredSections::SimpleExample::2 COMMAND $<TARGET_FILE:SelfTest> \#1394\ nested -c NestedRunSection -c s1)
set_tests_properties(FilteredSection-2 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran") set_tests_properties(FilteredSections::SimpleExample::2 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
add_test( add_test(
NAME NAME
FilteredSection::GeneratorsDontCauseInfiniteLoop-1 FilteredSections::GeneratorsDontCauseInfiniteLoop
COMMAND COMMAND
$<TARGET_FILE:SelfTest> "#2025: original repro" -c "fov_0" $<TARGET_FILE:SelfTest> "#2025: original repro" -c "fov_0"
) )
set_tests_properties(FilteredSection::GeneratorsDontCauseInfiniteLoop-1 set_tests_properties(FilteredSections::GeneratorsDontCauseInfiniteLoop
PROPERTIES PROPERTIES
PASS_REGULAR_EXPRESSION "inside with fov: 0" # This should happen PASS_REGULAR_EXPRESSION "inside with fov: 0" # This should happen
FAIL_REGULAR_EXPRESSION "inside with fov: 1" # This would mean there was no filtering 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 add_test(NAME ApprovalTests
COMMAND COMMAND
Python3::Interpreter Python3::Interpreter
@@ -694,6 +679,14 @@ set_tests_properties("Bazel::RngSeedEnvVar::MalformedValueIsIgnored"
PASS_REGULAR_EXPRESSION "Randomness seeded to: 17171717" 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) list(APPEND CATCH_TEST_TARGETS SelfTest)
set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE) set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE)

View File

@@ -553,3 +553,17 @@ set_tests_properties(AmalgamatedFileTest
PROPERTIES PROPERTIES
PASS_REGULAR_EXPRESSION "All tests passed \\(14 assertions in 3 test cases\\)" 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
)
set_tests_properties(ThreadSafetyTests
PROPERTIES
PASS_REGULAR_EXPRESSION "assertions: 801 | 400 passed | 401 failed"
RUN_SERIAL ON
)

View File

@@ -0,0 +1,44 @@
// 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 <atomic>
#include <thread>
#include <vector>
TEST_CASE( "Failed REQUIRE in the main thread is fine" ) {
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

@@ -7,6 +7,7 @@
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/internal/catch_enforce.hpp> #include <catch2/internal/catch_enforce.hpp>
#include <catch2/internal/catch_case_insensitive_comparisons.hpp> #include <catch2/internal/catch_case_insensitive_comparisons.hpp>
#include <catch2/internal/catch_optional.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 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

@@ -354,27 +354,9 @@ TEST_CASE("#1514: stderr/stdout is not captured in tests aborted by an exception
FAIL("1514"); 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]") { TEST_CASE("#2025: original repro", "[sections][generators][regression][.approvals]") {
auto fov = GENERATE(true, false); auto fov = GENERATE(true, false);
DYNAMIC_SECTION("fov_" << fov) { DYNAMIC_SECTION("fov_" << fov) {
std::cout << "inside with fov: " << fov << '\n'; 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)