Compare commits

...

7 Commits

Author SHA1 Message Date
Mason Wilie
3bcd0a4e74 Changed GetOptions to use sequence 2025-09-25 21:06:10 +02:00
Duncan Horn
f7e7fa0983 Fix ReusableStringStream to access the container under the lock (#3031)
Commit 582200a made `ReusableStringStream`'s index reservation thread safe, however it's still accessing the `m_streams` vector outside the lock. This makes it so that the `add` call returns the pointer in addition to the index so that the lock doesn't need to get acquired again until destruction.

The issue with accessing `m_streams` outside the lock is that `add` can call `push_back` on the vector, which might re-allocate. If this re-allocation occurs concurrently with anther thread trying to index into this array, you get UB (typically a null pointer read).
2025-09-25 13:31:49 +02:00
Martin Hořeňovský
b626e4c7ae Add simple runtime benchmarks
For now we add just two binaries, one with assertions taking the
fast path, one with assertions taking the slow path, and the ability
to run 1 of `REQUIRE(true)`, `REQUIRE_NOTHROW`, `REQUIRE_THROWS`
in a loop.

I also split off a CMake preset which enables more tests than the
basic `simple-tests` preset, but does not enable the most expensive
tests which force recompilation of Catch2 multiple times.
2025-09-23 17:17:33 +02:00
Martin Hořeňovský
434bf55d47 Make message push/pop static
Since the change to make the message macros thread-safe, and thus
thread-local, there is no need to handle messages through instance
of `RunContext`.
2025-09-23 11:46:26 +02:00
Martin Hořeňovský
9048c24fa7 Improve QNX support
This cherry-picks the source changes from PR made by @pkleymonov-qnx,
without including the build path changes that are irrelevant unless
we can add a QNX target to our CI.

Close #2953
2025-09-22 15:36:46 +02:00
Martin Hořeňovský
8ed8c431c6 Support Bazel's TEST_PREMATURE_EXIT_FILE and add it to CLI as well
This tells Catch2 to create an empty file at specified path before
the tests start, and delete it after the tests finish. This allows
callers to catch cases where the test binary silently exits before
finishing (e.g. via call to `exit(0)` inside the code under test),
by looking whether the file still exists.

Closes #3020
2025-09-22 14:02:45 +02:00
Silent
dc3a4ea41a Fix non-desktop Windows platforms not defining CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 correctly
Fixes building on non-desktop GDK platforms.
2025-09-17 20:25:26 +02:00
26 changed files with 398 additions and 58 deletions

View File

@@ -78,5 +78,5 @@ WarningsAsErrors: >-
readability-duplicate-include, readability-duplicate-include,
HeaderFilterRegex: '.*\.(c|cxx|cpp)$' HeaderFilterRegex: '.*\.(c|cxx|cpp)$'
FormatStyle: none FormatStyle: none
CheckOptions: {} CheckOptions: []
... ...

View File

@@ -20,6 +20,7 @@ cmake_dependent_option(CATCH_BUILD_TESTING "Build the SelfTest project" ON "CATC
cmake_dependent_option(CATCH_BUILD_EXAMPLES "Build code examples" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_BUILD_EXAMPLES "Build code examples" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_BUILD_EXTRA_TESTS "Build extra tests" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_BUILD_EXTRA_TESTS "Build extra tests" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_BUILD_FUZZERS "Build fuzzers" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_BUILD_FUZZERS "Build fuzzers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_BUILD_BENCHMARKS "Build the benchmarks" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_ENABLE_COVERAGE "Generate coverage for codecov.io" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_ENABLE_COVERAGE "Generate coverage for codecov.io" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_ENABLE_WERROR "Enables Werror during build" ON "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_ENABLE_WERROR "Enables Werror during build" ON "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_BUILD_SURROGATES "Enable generating and building surrogate TUs for the main headers" OFF "CATCH_DEVELOPMENT_BUILD" OFF) cmake_dependent_option(CATCH_BUILD_SURROGATES "Enable generating and building surrogate TUs for the main headers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
@@ -77,6 +78,11 @@ set(SELF_TEST_DIR ${CATCH_DIR}/tests/SelfTest)
# We need to bring-in the variables defined there to this scope # We need to bring-in the variables defined there to this scope
add_subdirectory(src) add_subdirectory(src)
if (CATCH_BUILD_BENCHMARKS)
set(CMAKE_FOLDER "benchmarks")
add_subdirectory(benchmarks)
endif()
# Build tests only if requested # Build tests only if requested
if(BUILD_TESTING AND CATCH_BUILD_TESTING AND NOT_SUBPROJECT) if(BUILD_TESTING AND CATCH_BUILD_TESTING AND NOT_SUBPROJECT)
find_package(Python3 REQUIRED COMPONENTS Interpreter) find_package(Python3 REQUIRED COMPONENTS Interpreter)

View File

@@ -15,14 +15,23 @@
} }
}, },
{ {
"name": "all-tests", "name": "most-tests",
"inherits": "basic-tests", "inherits": "basic-tests",
"displayName": "Full development build", "displayName": "Full development build",
"description": "Enables development build with examples and ALL tests", "description": "Enables development build with extended set of tests (still relatively cheap to build)",
"cacheVariables": { "cacheVariables": {
"CATCH_BUILD_EXAMPLES": "ON", "CATCH_BUILD_EXAMPLES": "ON",
"CATCH_BUILD_EXTRA_TESTS": "ON", "CATCH_BUILD_EXTRA_TESTS": "ON",
"CATCH_BUILD_SURROGATES": "ON", "CATCH_BUILD_SURROGATES": "ON",
"CATCH_BUILD_BENCHMARKS": "ON"
}
},
{
"name": "all-tests",
"inherits": "most-tests",
"displayName": "Full development build",
"description": "Enables development build with examples and ALL tests",
"cacheVariables": {
"CATCH_ENABLE_CONFIGURE_TESTS": "ON", "CATCH_ENABLE_CONFIGURE_TESTS": "ON",
"CATCH_ENABLE_CMAKE_HELPER_TESTS": "ON" "CATCH_ENABLE_CMAKE_HELPER_TESTS": "ON"
} }

16
benchmarks/CMakeLists.txt Normal file
View File

@@ -0,0 +1,16 @@
include(CatchMiscFunctions)
add_executable(AssertionsFastPath
runtime_assertion_benches.cpp
)
add_executable(AssertionsSlowPath
runtime_assertion_benches.cpp
assertion_listener.cpp
)
target_link_libraries(AssertionsFastPath PRIVATE Catch2::Catch2WithMain)
target_link_libraries(AssertionsSlowPath PRIVATE Catch2::Catch2WithMain)
list(APPEND CATCH_TEST_TARGETS AssertionsFastPath AssertionsSlowPath)
set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE)

View File

@@ -0,0 +1,28 @@
// 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
#include <catch2/reporters/catch_reporter_event_listener.hpp>
#include <catch2/reporters/catch_reporter_registrars.hpp>
/**
* Event listener that listens to all assertions, forcing assertion slow path
*/
class AssertionSlowPathListener : public Catch::EventListenerBase {
public:
static std::string getDescription() {
return "Validates ordering of Catch2's listener events";
}
AssertionSlowPathListener(Catch::IConfig const* config) :
EventListenerBase(config) {
m_preferences.shouldReportAllAssertions = true;
m_preferences.shouldReportAllAssertionStarts = true;
}
};
CATCH_REGISTER_LISTENER( AssertionSlowPathListener )

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
#include <catch2/catch_test_macros.hpp>
TEST_CASE("Simple REQUIRE - 10M") {
for (size_t i = 0; i < 10'000'000; ++i) {
REQUIRE(true);
}
}
TEST_CASE("Simple NOTHROW - 10M") {
for (size_t i = 0; i < 10'000'000; ++i) {
REQUIRE_NOTHROW([](){}());
}
}
TEST_CASE("Simple THROWS - 10M") {
for (size_t i = 0; i < 10'000'000; ++i) {
REQUIRE_THROWS([]() { throw 1; }());
}
}

View File

@@ -66,11 +66,14 @@ test execution. Specifically it understands
* JUnit output path via `XML_OUTPUT_FILE` * JUnit output path via `XML_OUTPUT_FILE`
* Test filtering via `TESTBRIDGE_TEST_ONLY` * Test filtering via `TESTBRIDGE_TEST_ONLY`
* Test sharding via `TEST_SHARD_INDEX`, `TEST_TOTAL_SHARDS`, and `TEST_SHARD_STATUS_FILE` * Test sharding via `TEST_SHARD_INDEX`, `TEST_TOTAL_SHARDS`, and `TEST_SHARD_STATUS_FILE`
* Creating a file to signal premature test exit via `TEST_PREMATURE_EXIT_FILE`
> Support for `XML_OUTPUT_FILE` was [introduced](https://github.com/catchorg/Catch2/pull/2399) in Catch2 3.0.1 > Support for `XML_OUTPUT_FILE` was [introduced](https://github.com/catchorg/Catch2/pull/2399) in Catch2 3.0.1
> Support for `TESTBRIDGE_TEST_ONLY` and sharding was introduced in Catch2 3.2.0 > Support for `TESTBRIDGE_TEST_ONLY` and sharding was introduced in Catch2 3.2.0
> Support for `TEST_PREMATURE_EXIT_FILE` was introduced in Catch2 X.Y.Z
This integration is enabled via either a [compile time configuration This integration is enabled via either a [compile time configuration
option](configuration.md#bazel-support), or via `BAZEL_TEST` environment option](configuration.md#bazel-support), or via `BAZEL_TEST` environment
variable set to "1". variable set to "1".

View File

@@ -32,6 +32,7 @@
[Test Sharding](#test-sharding)<br> [Test Sharding](#test-sharding)<br>
[Allow running the binary without tests](#allow-running-the-binary-without-tests)<br> [Allow running the binary without tests](#allow-running-the-binary-without-tests)<br>
[Output verbosity](#output-verbosity)<br> [Output verbosity](#output-verbosity)<br>
[Create file to guard against silent early termination](#create-file-to-guard-against-silent-early-termination)<br>
Catch works quite nicely without any command line options at all - but for those times when you want greater control the following options are available. Catch works quite nicely without any command line options at all - but for those times when you want greater control the following options are available.
Click one of the following links to take you straight to that option - or scroll on to browse the available options. Click one of the following links to take you straight to that option - or scroll on to browse the available options.
@@ -649,6 +650,21 @@ ignored.
Verbosity defaults to _normal_. Verbosity defaults to _normal_.
## Create file to guard against silent early termination
<pre>--premature-exit-guard-file &lt;path&gt;</pre>
> Introduced in Catch2 X.Y.Z
Tells Catch2 to create an empty file at specified path before the tests
start, and delete it after the tests finish. If the file is present after
the process stops, it can be assumed that the testing binary exited
prematurely, e.g. due to the OOM killer.
All directories in the path must already exist. If this option is used
and Catch2 cannot create the file (e.g. the location is not writable),
the test run will fail.
--- ---
[Home](Readme.md#top) [Home](Readme.md#top)

View File

@@ -119,6 +119,8 @@ namespace Catch {
m_data.reporterSpecifications.push_back( std::move( *parsed ) ); m_data.reporterSpecifications.push_back( std::move( *parsed ) );
} }
// Reading bazel env vars can change some parts of the config data,
// so we have to process the bazel env before acting on the config.
if ( enableBazelEnvSupport() ) { if ( enableBazelEnvSupport() ) {
readBazelEnvVars(); readBazelEnvVars();
} }
@@ -183,6 +185,8 @@ namespace Catch {
bool Config::showHelp() const { return m_data.showHelp; } bool Config::showHelp() const { return m_data.showHelp; }
std::string const& Config::getExitGuardFilePath() const { return m_data.prematureExitGuardFilePath; }
// IConfig interface // IConfig interface
bool Config::allowThrows() const { return !m_data.noThrow; } bool Config::allowThrows() const { return !m_data.noThrow; }
StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
@@ -244,6 +248,11 @@ namespace Catch {
m_data.shardCount = bazelShardOptions->shardCount; m_data.shardCount = bazelShardOptions->shardCount;
} }
} }
const auto bazelExitGuardFile = Detail::getEnv( "TEST_PREMATURE_EXIT_FILE" );
if (bazelExitGuardFile) {
m_data.prematureExitGuardFilePath = bazelExitGuardFile;
}
} }
} // end namespace Catch } // end namespace Catch

View File

@@ -87,6 +87,8 @@ namespace Catch {
std::vector<std::string> testsOrTags; std::vector<std::string> testsOrTags;
std::vector<std::string> sectionsToRun; std::vector<std::string> sectionsToRun;
std::string prematureExitGuardFilePath;
}; };
@@ -114,6 +116,8 @@ namespace Catch {
bool showHelp() const; bool showHelp() const;
std::string const& getExitGuardFilePath() const;
// IConfig interface // IConfig interface
bool allowThrows() const override; bool allowThrows() const override;
StringRef name() const override; StringRef name() const override;

View File

@@ -22,7 +22,7 @@ namespace Catch {
m_messageId( builder.m_info.sequence ) { m_messageId( builder.m_info.sequence ) {
MessageInfo info( CATCH_MOVE( builder.m_info ) ); MessageInfo info( CATCH_MOVE( builder.m_info ) );
info.message = builder.m_stream.str(); info.message = builder.m_stream.str();
getResultCapture().pushScopedMessage( CATCH_MOVE(info) ); IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
} }
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
@@ -31,15 +31,14 @@ namespace Catch {
} }
ScopedMessage::~ScopedMessage() { ScopedMessage::~ScopedMessage() {
if ( !m_moved ) { getResultCapture().popScopedMessage( m_messageId ); } if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
} }
Capturer::Capturer( StringRef macroName, Capturer::Capturer( StringRef macroName,
SourceLineInfo const& lineInfo, SourceLineInfo const& lineInfo,
ResultWas::OfType resultType, ResultWas::OfType resultType,
StringRef names ): StringRef names ) {
m_resultCapture( getResultCapture() ) {
auto trimmed = [&] (size_t start, size_t end) { auto trimmed = [&] (size_t start, size_t end) {
while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) { while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {
++start; ++start;
@@ -101,14 +100,14 @@ namespace Catch {
Capturer::~Capturer() { Capturer::~Capturer() {
assert( m_captured == m_messages.size() ); assert( m_captured == m_messages.size() );
for (auto const& message : m_messages) { for (auto const& message : m_messages) {
m_resultCapture.popScopedMessage( message.sequence ); IResultCapture::popScopedMessage( message.sequence );
} }
} }
void Capturer::captureValue( size_t index, std::string const& value ) { void Capturer::captureValue( size_t index, std::string const& value ) {
assert( index < m_messages.size() ); assert( index < m_messages.size() );
m_messages[index].message += value; m_messages[index].message += value;
m_resultCapture.pushScopedMessage( CATCH_MOVE(m_messages[index]) ); IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
m_captured++; m_captured++;
} }

View File

@@ -63,7 +63,6 @@ namespace Catch {
class Capturer { class Capturer {
std::vector<MessageInfo> m_messages; std::vector<MessageInfo> m_messages;
IResultCapture& m_resultCapture;
size_t m_captured = 0; size_t m_captured = 0;
public: public:
Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
@@ -111,7 +110,7 @@ namespace Catch {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ #define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \
Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) Catch::IResultCapture::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) #if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)

View File

@@ -26,6 +26,8 @@
#include <catch2/internal/catch_istream.hpp> #include <catch2/internal/catch_istream.hpp>
#include <cassert> #include <cassert>
#include <cstdio>
#include <cstdlib>
#include <exception> #include <exception>
#include <iomanip> #include <iomanip>
#include <set> #include <set>
@@ -140,6 +142,50 @@ namespace Catch {
} }
} }
// Creates empty file at path. The path must be writable, we do not
// try to create directories in path because that's hard in C++14.
void setUpGuardFile( std::string const& guardFilePath ) {
if ( !guardFilePath.empty() ) {
#if defined( _MSC_VER )
std::FILE* file = nullptr;
if ( fopen_s( &file, guardFilePath.c_str(), "w" ) ) {
char msgBuffer[100];
const auto err = errno;
std::string errMsg;
if ( !strerror_s( msgBuffer, err ) ) {
errMsg = msgBuffer;
} else {
errMsg = "Could not translate errno to a string";
}
#else
std::FILE* file = std::fopen( guardFilePath.c_str(), "w" );
if ( !file ) {
const auto err = errno;
const char* errMsg = std::strerror( err );
#endif
CATCH_RUNTIME_ERROR( "Could not open the exit guard file '"
<< guardFilePath << "' because '"
<< errMsg << "' (" << err << ')' );
}
const int ret = std::fclose( file );
CATCH_ENFORCE(
ret == 0,
"Error when closing the exit guard file: " << ret );
}
}
// Removes file at path. Assuming we created it in setUpGuardFile.
void tearDownGuardFile( std::string const& guardFilePath ) {
if ( !guardFilePath.empty() ) {
const int ret = std::remove( guardFilePath.c_str() );
CATCH_ENFORCE(
ret == 0,
"Error when removing the exit guard file: " << ret );
}
}
} // anon namespace } // anon namespace
Session::Session() { Session::Session() {
@@ -258,6 +304,7 @@ namespace Catch {
static_cast<void>(std::getchar()); static_cast<void>(std::getchar());
} }
int exitCode = runInternal(); int exitCode = runInternal();
if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush; Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush;
static_cast<void>(std::getchar()); static_cast<void>(std::getchar());
@@ -298,6 +345,10 @@ namespace Catch {
CATCH_TRY { CATCH_TRY {
config(); // Force config to be constructed config(); // Force config to be constructed
// We need to retrieve potential Bazel config with the full Config
// constructor, so we have to create the guard file after it is created.
setUpGuardFile( m_config->getExitGuardFilePath() );
seedRng( *m_config ); seedRng( *m_config );
if (m_configData.filenamesAsTags) { if (m_configData.filenamesAsTags) {
@@ -327,9 +378,12 @@ namespace Catch {
TestGroup tests { CATCH_MOVE(reporter), m_config.get() }; TestGroup tests { CATCH_MOVE(reporter), m_config.get() };
auto const totals = tests.execute(); auto const totals = tests.execute();
// If we got here, running the tests finished normally-enough.
// They might've failed, but that would've been reported elsewhere.
tearDownGuardFile( m_config->getExitGuardFilePath() );
if ( tests.hadUnmatchedTestSpecs() if ( tests.hadUnmatchedTestSpecs()
&& m_config->warnAboutUnmatchedTestSpecs() ) { && m_config->warnAboutUnmatchedTestSpecs() ) {
// UnmatchedTestSpecExitCode
return UnmatchedTestSpecExitCode; return UnmatchedTestSpecExitCode;
} }

View File

@@ -62,10 +62,9 @@ namespace Catch {
virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0;
virtual void benchmarkFailed( StringRef error ) = 0; virtual void benchmarkFailed( StringRef error ) = 0;
virtual void pushScopedMessage( MessageInfo&& message ) = 0; static void pushScopedMessage( MessageInfo&& message );
virtual void popScopedMessage( unsigned int messageId ) = 0; static void popScopedMessage( unsigned int messageId );
static void emplaceUnscopedMessage( MessageBuilder&& builder );
virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0;
virtual void handleFatalErrorCondition( StringRef message ) = 0; virtual void handleFatalErrorCondition( StringRef message ) = 0;
@@ -102,6 +101,7 @@ namespace Catch {
}; };
IResultCapture& getResultCapture(); IResultCapture& getResultCapture();
} }
#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED #endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED

View File

@@ -305,6 +305,9 @@ namespace Catch {
| Opt( config.allowZeroTests ) | Opt( config.allowZeroTests )
["--allow-running-no-tests"] ["--allow-running-no-tests"]
( "Treat 'No tests run' as a success" ) ( "Treat 'No tests run' as a success" )
| Opt( config.prematureExitGuardFilePath, "path" )
["--premature-exit-guard-file"]
( "create a file before running tests and delete it during clean exit" )
| Arg( config.testsOrTags, "test name|pattern|tags" ) | Arg( config.testsOrTags, "test name|pattern|tags" )
( "which test or tests to use" ); ( "which test or tests to use" );

View File

@@ -220,13 +220,17 @@
# endif # endif
// Universal Windows platform does not support SEH // Universal Windows platform does not support SEH
// Or console colours (or console at all...) # if !defined(CATCH_PLATFORM_WINDOWS_UWP)
# if defined(CATCH_PLATFORM_WINDOWS_UWP)
# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32
# else
# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
# endif # endif
// Only some Windows platform families support the console
# if defined(WINAPI_FAMILY_PARTITION)
# if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32
# endif
# endif
// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // MSVC traditional preprocessor needs some workaround for __VA_ARGS__
// _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 0 means new conformant preprocessor
// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor

View File

@@ -161,7 +161,11 @@ namespace {
#endif // Windows/ ANSI/ None #endif // Windows/ ANSI/ None
#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) || defined( __GLIBC__ ) || defined(__FreeBSD__) #if defined( CATCH_PLATFORM_LINUX ) \
|| defined( CATCH_PLATFORM_MAC ) \
|| defined( __GLIBC__ ) \
|| defined( __FreeBSD__ ) \
|| defined( CATCH_PLATFORM_QNX )
# define CATCH_INTERNAL_HAS_ISATTY # define CATCH_INTERNAL_HAS_ISATTY
# include <unistd.h> # include <unistd.h>
#endif #endif

View File

@@ -69,7 +69,7 @@
#endif #endif
} // namespace Catch } // namespace Catch
#elif defined(CATCH_PLATFORM_LINUX) #elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX)
#include <fstream> #include <fstream>
#include <string> #include <string>

View File

@@ -49,7 +49,7 @@ namespace Catch {
#define CATCH_TRAP() __asm__(".inst 0xde01") #define CATCH_TRAP() __asm__(".inst 0xde01")
#endif #endif
#elif defined(CATCH_PLATFORM_LINUX) #elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX)
// If we can use inline assembler, do it because this allows us to break // If we can use inline assembler, do it because this allows us to break
// directly at the location of the failing check instead of breaking inside // directly at the location of the failing check instead of breaking inside
// raise() called from it, i.e. one stack frame below. // raise() called from it, i.e. one stack frame below.

View File

@@ -25,6 +25,9 @@
#elif defined(linux) || defined(__linux) || defined(__linux__) #elif defined(linux) || defined(__linux) || defined(__linux__)
# define CATCH_PLATFORM_LINUX # define CATCH_PLATFORM_LINUX
#elif defined(__QNX__)
# define CATCH_PLATFORM_QNX
#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
# define CATCH_PLATFORM_WINDOWS # define CATCH_PLATFORM_WINDOWS

View File

@@ -12,6 +12,7 @@
#include <cstdio> #include <cstdio>
#include <sstream> #include <sstream>
#include <tuple>
#include <vector> #include <vector>
namespace Catch { namespace Catch {
@@ -23,16 +24,16 @@ namespace Catch {
std::ostringstream m_referenceStream; // Used for copy state/ flags from std::ostringstream m_referenceStream; // Used for copy state/ flags from
Detail::Mutex m_mutex; Detail::Mutex m_mutex;
auto add() -> std::size_t { auto add() -> std::pair<std::size_t, std::ostringstream*> {
Detail::LockGuard _( m_mutex ); Detail::LockGuard _( m_mutex );
if( m_unused.empty() ) { if( m_unused.empty() ) {
m_streams.push_back( Detail::make_unique<std::ostringstream>() ); m_streams.push_back( Detail::make_unique<std::ostringstream>() );
return m_streams.size()-1; return { m_streams.size()-1, m_streams.back().get() };
} }
else { else {
auto index = m_unused.back(); auto index = m_unused.back();
m_unused.pop_back(); m_unused.pop_back();
return index; return { index, m_streams[index].get() };
} }
} }
@@ -46,10 +47,10 @@ namespace Catch {
} }
}; };
ReusableStringStream::ReusableStringStream() ReusableStringStream::ReusableStringStream() {
: m_index( Singleton<StringStreams>::getMutable().add() ), std::tie( m_index, m_oss ) =
m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() ) Singleton<StringStreams>::getMutable().add();
{} }
ReusableStringStream::~ReusableStringStream() { ReusableStringStream::~ReusableStringStream() {
static_cast<std::ostringstream*>( m_oss )->str(""); static_cast<std::ostringstream*>( m_oss )->str("");

View File

@@ -483,28 +483,6 @@ namespace Catch {
m_reporter->benchmarkFailed( error ); m_reporter->benchmarkFailed( error );
} }
void RunContext::pushScopedMessage( MessageInfo&& message ) {
Detail::g_messages.push_back( CATCH_MOVE(message) );
}
void RunContext::popScopedMessage( unsigned int messageId ) {
// Note: On average, it would probably be better to look for the message
// backwards. However, we do not expect to have to deal with more
// messages than low single digits, so the optimization is tiny,
// and we would have to hand-write the loop to avoid terrible
// codegen of reverse iterators in debug mode.
Detail::g_messages.erase(
std::find_if( Detail::g_messages.begin(),
Detail::g_messages.end(),
[=]( MessageInfo const& msg ) {
return msg.sequence == messageId;
} ) );
}
void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {
Detail::g_messageScopes.emplace_back( CATCH_MOVE(builder) );
}
std::string RunContext::getCurrentTestName() const { std::string RunContext::getCurrentTestName() const {
return m_activeTestCase return m_activeTestCase
? m_activeTestCase->getTestCaseInfo().name ? m_activeTestCase->getTestCaseInfo().name
@@ -838,6 +816,28 @@ namespace Catch {
CATCH_INTERNAL_ERROR("No result capture instance"); CATCH_INTERNAL_ERROR("No result capture instance");
} }
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
Detail::g_messages.push_back( CATCH_MOVE( message ) );
}
void IResultCapture::popScopedMessage( unsigned int messageId ) {
// Note: On average, it would probably be better to look for the message
// backwards. However, we do not expect to have to deal with more
// messages than low single digits, so the optimization is tiny,
// and we would have to hand-write the loop to avoid terrible
// codegen of reverse iterators in debug mode.
Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(),
Detail::g_messages.end(),
[=]( MessageInfo const& msg ) {
return msg.sequence ==
messageId;
} ) );
}
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) );
}
void seedRng(IConfig const& config) { void seedRng(IConfig const& config) {
sharedRng().seed(config.rngSeed()); sharedRng().seed(config.rngSeed());
} }

View File

@@ -94,11 +94,6 @@ namespace Catch {
void benchmarkEnded( BenchmarkStats<> const& stats ) override; void benchmarkEnded( BenchmarkStats<> const& stats ) override;
void benchmarkFailed( StringRef error ) override; void benchmarkFailed( StringRef error ) override;
void pushScopedMessage( MessageInfo&& message ) override;
void popScopedMessage( unsigned int messageId ) override;
void emplaceUnscopedMessage( MessageBuilder&& builder ) override;
std::string getCurrentTestName() const override; std::string getCurrentTestName() const override;
const AssertionResult* getLastResult() const override; const AssertionResult* getLastResult() const override;

View File

@@ -95,7 +95,7 @@ foreach(target DisabledExceptions-DefaultHandler DisabledExceptions-CustomHandle
target_compile_options(${target} target_compile_options(${target}
PUBLIC PUBLIC
$<$<CXX_COMPILER_ID:MSVC>:/EHs-c-;/D_HAS_EXCEPTIONS=0> $<$<CXX_COMPILER_ID:MSVC>:/EHs-c-;/D_HAS_EXCEPTIONS=0>
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:AppleClang>>:-fno-exceptions> $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:QCC>,$<CXX_COMPILER_ID:AppleClang>>:-fno-exceptions>
) )
target_link_libraries(${target} Catch2_buildall_interface) target_link_libraries(${target} Catch2_buildall_interface)
endforeach() endforeach()
@@ -429,6 +429,50 @@ set_tests_properties(Reporters::CrashInJunitReporter
LABELS "uses-signals" LABELS "uses-signals"
) )
add_executable(QuickExitInTest ${TESTS_DIR}/X40-QuickExit.cpp)
target_link_libraries(QuickExitInTest PRIVATE Catch2::Catch2WithMain)
add_test(
NAME BazelEnv::TEST_PREMATURE_EXIT_FILE
COMMAND
Python3::Interpreter
"${CATCH_DIR}/tests/TestScripts/testBazelExitGuardFile.py"
$<TARGET_FILE:QuickExitInTest>
"${CMAKE_CURRENT_BINARY_DIR}"
"bazel"
)
set_tests_properties(BazelEnv::TEST_PREMATURE_EXIT_FILE
PROPERTIES
LABELS "uses-python"
)
add_test(
NAME PrematureExitGuardFileCanBeUsedFromCLI::CheckAfterCrash
COMMAND
Python3::Interpreter
"${CATCH_DIR}/tests/TestScripts/testBazelExitGuardFile.py"
$<TARGET_FILE:QuickExitInTest>
"${CMAKE_CURRENT_BINARY_DIR}"
"cli"
)
set_tests_properties(PrematureExitGuardFileCanBeUsedFromCLI::CheckAfterCrash
PROPERTIES
LABELS "uses-python"
)
add_test(
NAME PrematureExitGuardFileCanBeUsedFromCLI::CheckWithoutCrash
COMMAND
Python3::Interpreter
"${CATCH_DIR}/tests/TestScripts/testBazelExitGuardFile.py"
$<TARGET_FILE:QuickExitInTest>
"${CMAKE_CURRENT_BINARY_DIR}"
"no-crash"
)
set_tests_properties(PrematureExitGuardFileCanBeUsedFromCLI::CheckWithoutCrash
PROPERTIES
LABELS "uses-python"
)
add_executable(AssertionStartingEventGoesBeforeAssertionIsEvaluated add_executable(AssertionStartingEventGoesBeforeAssertionIsEvaluated
X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp
) )

View File

@@ -0,0 +1,28 @@
// 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
* Call ~~quick_exit~~ inside a test.
*
* This is used to test whether Catch2 properly creates the crash guard
* file based on provided arguments.
*/
#include <catch2/catch_test_macros.hpp>
#include <cstdlib>
TEST_CASE("quick_exit", "[quick_exit]") {
// Return 0 as fake "successful" exit, while there should be a guard
// file created and kept.
std::exit(0);
// We cannot use quick_exit because libstdc++ on older MacOS versions didn't support it yet.
// std::quick_exit(0);
}
TEST_CASE("pass") {}

View File

@@ -0,0 +1,88 @@
#!/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
import os
import sys
import subprocess
def generate_path_suffix() -> str:
return os.urandom(16).hex()[:16]
def run_common(cmd, env = None):
cmd_env = env if env is not None else os.environ.copy()
print('Running:', cmd)
try:
ret = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
universal_newlines=True,
env=cmd_env
)
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
def test_bazel_env_vars(bin_path, guard_path):
env = os.environ.copy()
env["TEST_PREMATURE_EXIT_FILE"] = guard_path
env["BAZEL_TEST"] = '1'
run_common([bin_path], env)
def test_cli_parameter(bin_path, guard_path):
cmd = [
bin_path,
'--premature-exit-guard-file',
guard_path
]
run_common(cmd)
def test_no_crash(bin_path, guard_path):
cmd = [
bin_path,
'--premature-exit-guard-file',
guard_path,
# Disable the quick-exit test
'~[quick_exit]'
]
run_common(cmd)
checks = {
'bazel': (test_bazel_env_vars, True),
'cli': (test_cli_parameter, True),
'no-crash': (test_no_crash, False),
}
bin_path = os.path.abspath(sys.argv[1])
output_dir = os.path.abspath(sys.argv[2])
test_kind = sys.argv[3]
guard_file_path = os.path.join(output_dir, f"guard_file.{generate_path_suffix()}")
print(f'Guard file path: "{guard_file_path}"')
check_func, file_should_exist = checks[test_kind]
check_func(bin_path, guard_file_path)
assert os.path.exists(guard_file_path) == file_should_exist
# With randomly generated file suffix, we should not run into name conflicts.
# However, we try to cleanup anyway, to avoid having infinity files in
# long living build directories.
if file_should_exist:
try:
os.remove(guard_file_path)
except Exception as ex:
print(f'Could not remove file {guard_file_path} because: {ex}')