mirror of
https://github.com/catchorg/Catch2.git
synced 2025-10-05 19:41:09 +02:00
Compare commits
22 Commits
v3.10.0
...
devel-more
Author | SHA1 | Date | |
---|---|---|---|
|
363ca5af18 | ||
|
cb6d713774 | ||
|
8e4ab5dd8f | ||
|
8219ed79f2 | ||
|
b3fb4b9fea | ||
|
6500dc8149 | ||
|
4be9de6c54 | ||
|
91b3b3bf40 | ||
|
a00d654437 | ||
|
756ae05d30 | ||
|
a2e41916f2 | ||
|
0e772cc0d2 | ||
|
3bcd0a4e74 | ||
|
f7e7fa0983 | ||
|
b626e4c7ae | ||
|
434bf55d47 | ||
|
9048c24fa7 | ||
|
8ed8c431c6 | ||
|
dc3a4ea41a | ||
|
cd93d202e0 | ||
|
227af796b4 | ||
|
33adb4c779 |
@@ -78,5 +78,5 @@ WarningsAsErrors: >-
|
||||
readability-duplicate-include,
|
||||
HeaderFilterRegex: '.*\.(c|cxx|cpp)$'
|
||||
FormatStyle: none
|
||||
CheckOptions: {}
|
||||
CheckOptions: []
|
||||
...
|
||||
|
@@ -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_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_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_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)
|
||||
@@ -34,7 +35,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
endif()
|
||||
|
||||
project(Catch2
|
||||
VERSION 3.10.0 # CML version placeholder, don't delete
|
||||
VERSION 3.11.0 # CML version placeholder, don't delete
|
||||
LANGUAGES CXX
|
||||
HOMEPAGE_URL "https://github.com/catchorg/Catch2"
|
||||
DESCRIPTION "A modern, C++-native, unit test framework."
|
||||
@@ -77,6 +78,11 @@ set(SELF_TEST_DIR ${CATCH_DIR}/tests/SelfTest)
|
||||
# We need to bring-in the variables defined there to this scope
|
||||
add_subdirectory(src)
|
||||
|
||||
if (CATCH_BUILD_BENCHMARKS)
|
||||
set(CMAKE_FOLDER "benchmarks")
|
||||
add_subdirectory(benchmarks)
|
||||
endif()
|
||||
|
||||
# Build tests only if requested
|
||||
if(BUILD_TESTING AND CATCH_BUILD_TESTING AND NOT_SUBPROJECT)
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
|
@@ -15,14 +15,23 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "all-tests",
|
||||
"name": "most-tests",
|
||||
"inherits": "basic-tests",
|
||||
"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": {
|
||||
"CATCH_BUILD_EXAMPLES": "ON",
|
||||
"CATCH_BUILD_EXTRA_TESTS": "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_CMAKE_HELPER_TESTS": "ON"
|
||||
}
|
||||
|
13
README.md
13
README.md
@@ -1,5 +1,16 @@
|
||||
<a id="top"></a>
|
||||

|
||||
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td align="center" width="50%"><img src="/data/artwork/catch2-logo-full-with-background.svg" width="100%"></td>
|
||||
<td align="center" width="50%">
|
||||
<figure>
|
||||
<figcaption>Special thanks to:</figcaption>
|
||||
<a href="https://tuple.app/catch2"><img src="/data/sponsors/github_repo_sponsorship.png" width="100%"></a>
|
||||
</figure>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[](https://github.com/catchorg/catch2/releases)
|
||||
[](https://github.com/catchorg/Catch2/actions/workflows/linux-simple-builds.yml)
|
||||
|
16
benchmarks/CMakeLists.txt
Normal file
16
benchmarks/CMakeLists.txt
Normal 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)
|
28
benchmarks/assertion_listener.cpp
Normal file
28
benchmarks/assertion_listener.cpp
Normal 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 )
|
27
benchmarks/runtime_assertion_benches.cpp
Normal file
27
benchmarks/runtime_assertion_benches.cpp
Normal 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; }());
|
||||
}
|
||||
}
|
BIN
data/sponsors/github_repo_sponsorship.png
Normal file
BIN
data/sponsors/github_repo_sponsorship.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 264 KiB |
@@ -66,17 +66,24 @@ test execution. Specifically it understands
|
||||
* JUnit output path via `XML_OUTPUT_FILE`
|
||||
* Test filtering via `TESTBRIDGE_TEST_ONLY`
|
||||
* 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`
|
||||
* Setting the RNG seed via `TEST_RANDOM_SEED`
|
||||
|
||||
> 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 `TEST_PREMATURE_EXIT_FILE` and `TEST_RANDOM_SEED` was introduced in Catch2 3.11.0
|
||||
|
||||
This integration is enabled via either a [compile time configuration
|
||||
option](configuration.md#bazel-support), or via `BAZEL_TEST` environment
|
||||
variable set to "1".
|
||||
|
||||
> Support for `BAZEL_TEST` was [introduced](https://github.com/catchorg/Catch2/pull/2459) in Catch2 3.1.0
|
||||
|
||||
Note that if both the Bazel environment var and command line option for
|
||||
something are used, the environment variable wins.
|
||||
|
||||
|
||||
## Low-level tools
|
||||
|
||||
|
@@ -32,6 +32,7 @@
|
||||
[Test Sharding](#test-sharding)<br>
|
||||
[Allow running the binary without tests](#allow-running-the-binary-without-tests)<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.
|
||||
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_.
|
||||
|
||||
|
||||
## Create file to guard against silent early termination
|
||||
<pre>--premature-exit-guard-file <path></pre>
|
||||
|
||||
> Introduced in Catch2 3.11.0
|
||||
|
||||
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)
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
# Release notes
|
||||
**Contents**<br>
|
||||
[3.11.0](#3110)<br>
|
||||
[3.10.0](#3100)<br>
|
||||
[3.9.1](#391)<br>
|
||||
[3.9.0](#390)<br>
|
||||
@@ -70,6 +71,28 @@
|
||||
[Even Older versions](#even-older-versions)<br>
|
||||
|
||||
|
||||
## 3.11.0
|
||||
|
||||
### Fixes
|
||||
* Fixed building on non-desktop GDK platforms (#3029)
|
||||
* Fixed message macros being susceptible to race in specific scenario (#3031)
|
||||
* Catch2's SEH filter will call the previously installed filter after reporting the error (#3033)
|
||||
|
||||
### Improvements
|
||||
* Handling of scoped messages (e.g. `CAPTURE`) is a bit faster.
|
||||
* Better out-of-the-box support for QNX (#2953)
|
||||
* Improved performance of assertions by up-to 10%
|
||||
* Release mode assertion fast-path sees the biggest improvement.
|
||||
* Faster processing of non-escaped strings in `--invisibles` mode.
|
||||
* Added support for Bazel's `TEST_RANDOM_SEED` env var (#3021)
|
||||
* Added support for Bazel's `TEST_PREMATURE_EXIT_FILE` env var (#3020)
|
||||
* This creates a file that is deleted if the tests exit normally, but stays around if the process dies unexpectedly.
|
||||
* This functionality is also exposed through CLI as `--premature-exit-guard-file`
|
||||
|
||||
### Miscellaneous
|
||||
* **[Tuple.app](https://tuple.app/catch2) has sponsored Catch2**
|
||||
|
||||
|
||||
## 3.10.0
|
||||
|
||||
### Fixes
|
||||
|
@@ -6,8 +6,8 @@
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
// Catch v3.10.0
|
||||
// Generated: 2025-08-24 16:18:04.775778
|
||||
// Catch v3.11.0
|
||||
// Generated: 2025-09-30 10:49:12.549018
|
||||
// ----------------------------------------------------------
|
||||
// This file is an amalgamation of multiple different files.
|
||||
// You probably shouldn't edit it directly.
|
||||
@@ -825,6 +825,8 @@ namespace Catch {
|
||||
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() ) {
|
||||
readBazelEnvVars();
|
||||
}
|
||||
@@ -889,6 +891,8 @@ namespace Catch {
|
||||
|
||||
bool Config::showHelp() const { return m_data.showHelp; }
|
||||
|
||||
std::string const& Config::getExitGuardFilePath() const { return m_data.prematureExitGuardFilePath; }
|
||||
|
||||
// IConfig interface
|
||||
bool Config::allowThrows() const { return !m_data.noThrow; }
|
||||
StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
|
||||
@@ -950,6 +954,26 @@ namespace Catch {
|
||||
m_data.shardCount = bazelShardOptions->shardCount;
|
||||
}
|
||||
}
|
||||
|
||||
const auto bazelExitGuardFile = Detail::getEnv( "TEST_PREMATURE_EXIT_FILE" );
|
||||
if (bazelExitGuardFile) {
|
||||
m_data.prematureExitGuardFilePath = bazelExitGuardFile;
|
||||
}
|
||||
|
||||
const auto bazelRandomSeed = Detail::getEnv( "TEST_RANDOM_SEED" );
|
||||
if ( bazelRandomSeed ) {
|
||||
auto parsedSeed = parseUInt( bazelRandomSeed, 0 );
|
||||
if ( !parsedSeed ) {
|
||||
// Currently we handle issues with parsing other Bazel Env
|
||||
// options by warning and ignoring the issue. So we do the
|
||||
// same for random seed option.
|
||||
Catch::cerr()
|
||||
<< "Warning: could not parse 'TEST_RANDOM_SEED' ('"
|
||||
<< bazelRandomSeed << "') as proper seed.\n";
|
||||
} else {
|
||||
m_data.rngSeed = *parsedSeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace Catch
|
||||
@@ -975,28 +999,26 @@ namespace Catch {
|
||||
|
||||
|
||||
ScopedMessage::ScopedMessage( MessageBuilder&& builder ):
|
||||
m_info( CATCH_MOVE(builder.m_info) ) {
|
||||
m_info.message = builder.m_stream.str();
|
||||
getResultCapture().pushScopedMessage( m_info );
|
||||
m_messageId( builder.m_info.sequence ) {
|
||||
MessageInfo info( CATCH_MOVE( builder.m_info ) );
|
||||
info.message = builder.m_stream.str();
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
|
||||
m_info( CATCH_MOVE( old.m_info ) ) {
|
||||
m_messageId( old.m_messageId ) {
|
||||
old.m_moved = true;
|
||||
}
|
||||
|
||||
ScopedMessage::~ScopedMessage() {
|
||||
if ( !m_moved ){
|
||||
getResultCapture().popScopedMessage(m_info);
|
||||
}
|
||||
if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
|
||||
}
|
||||
|
||||
|
||||
Capturer::Capturer( StringRef macroName,
|
||||
SourceLineInfo const& lineInfo,
|
||||
ResultWas::OfType resultType,
|
||||
StringRef names ):
|
||||
m_resultCapture( getResultCapture() ) {
|
||||
StringRef names ) {
|
||||
auto trimmed = [&] (size_t start, size_t end) {
|
||||
while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {
|
||||
++start;
|
||||
@@ -1042,8 +1064,8 @@ namespace Catch {
|
||||
case ',':
|
||||
if (start != pos && openings.empty()) {
|
||||
m_messages.emplace_back(macroName, lineInfo, resultType);
|
||||
m_messages.back().message = static_cast<std::string>(trimmed(start, pos));
|
||||
m_messages.back().message += " := ";
|
||||
m_messages.back().message += trimmed(start, pos);
|
||||
m_messages.back().message += " := "_sr;
|
||||
start = pos;
|
||||
}
|
||||
break;
|
||||
@@ -1052,20 +1074,20 @@ namespace Catch {
|
||||
}
|
||||
assert(openings.empty() && "Mismatched openings");
|
||||
m_messages.emplace_back(macroName, lineInfo, resultType);
|
||||
m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));
|
||||
m_messages.back().message += " := ";
|
||||
m_messages.back().message += trimmed(start, names.size() - 1);
|
||||
m_messages.back().message += " := "_sr;
|
||||
}
|
||||
Capturer::~Capturer() {
|
||||
assert( m_captured == m_messages.size() );
|
||||
for ( size_t i = 0; i < m_captured; ++i ) {
|
||||
m_resultCapture.popScopedMessage( m_messages[i] );
|
||||
for (auto const& message : m_messages) {
|
||||
IResultCapture::popScopedMessage( message.sequence );
|
||||
}
|
||||
}
|
||||
|
||||
void Capturer::captureValue( size_t index, std::string const& value ) {
|
||||
assert( index < m_messages.size() );
|
||||
m_messages[index].message += value;
|
||||
m_resultCapture.pushScopedMessage( m_messages[index] );
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
m_captured++;
|
||||
}
|
||||
|
||||
@@ -1149,7 +1171,6 @@ namespace Catch {
|
||||
}
|
||||
void cleanUp() {
|
||||
cleanupSingletons();
|
||||
cleanUpContext();
|
||||
}
|
||||
std::string translateActiveException() {
|
||||
return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
|
||||
@@ -1161,6 +1182,8 @@ namespace Catch {
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <set>
|
||||
@@ -1275,6 +1298,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
|
||||
|
||||
Session::Session() {
|
||||
@@ -1393,6 +1460,7 @@ namespace Catch {
|
||||
static_cast<void>(std::getchar());
|
||||
}
|
||||
int exitCode = runInternal();
|
||||
|
||||
if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
|
||||
Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush;
|
||||
static_cast<void>(std::getchar());
|
||||
@@ -1433,6 +1501,10 @@ namespace Catch {
|
||||
CATCH_TRY {
|
||||
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 );
|
||||
|
||||
if (m_configData.filenamesAsTags) {
|
||||
@@ -1462,9 +1534,12 @@ namespace Catch {
|
||||
TestGroup tests { CATCH_MOVE(reporter), m_config.get() };
|
||||
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()
|
||||
&& m_config->warnAboutUnmatchedTestSpecs() ) {
|
||||
// UnmatchedTestSpecExitCode
|
||||
return UnmatchedTestSpecExitCode;
|
||||
}
|
||||
|
||||
@@ -1974,35 +2049,35 @@ namespace Detail {
|
||||
std::string ret;
|
||||
// This is enough for the "don't escape invisibles" case, and a good
|
||||
// lower bound on the "escape invisibles" case.
|
||||
ret.reserve(string.size() + 2);
|
||||
ret.reserve( string.size() + 2 );
|
||||
|
||||
if (!escapeInvisibles) {
|
||||
if ( !escapeInvisibles ) {
|
||||
ret += '"';
|
||||
ret += string;
|
||||
ret += '"';
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t last_start = 0;
|
||||
auto write_to = [&]( size_t idx ) {
|
||||
if ( last_start < idx ) {
|
||||
ret += string.substr( last_start, idx - last_start );
|
||||
}
|
||||
last_start = idx + 1;
|
||||
};
|
||||
|
||||
ret += '"';
|
||||
for (char c : string) {
|
||||
switch (c) {
|
||||
case '\r':
|
||||
ret.append("\\r");
|
||||
break;
|
||||
case '\n':
|
||||
ret.append("\\n");
|
||||
break;
|
||||
case '\t':
|
||||
ret.append("\\t");
|
||||
break;
|
||||
case '\f':
|
||||
ret.append("\\f");
|
||||
break;
|
||||
default:
|
||||
ret.push_back(c);
|
||||
break;
|
||||
for ( size_t i = 0; i < string.size(); ++i ) {
|
||||
const char c = string[i];
|
||||
if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) {
|
||||
write_to( i );
|
||||
if ( c == '\r' ) { ret.append( "\\r" ); }
|
||||
if ( c == '\n' ) { ret.append( "\\n" ); }
|
||||
if ( c == '\t' ) { ret.append( "\\t" ); }
|
||||
if ( c == '\f' ) { ret.append( "\\f" ); }
|
||||
}
|
||||
}
|
||||
write_to( string.size() );
|
||||
ret += '"';
|
||||
|
||||
return ret;
|
||||
@@ -2279,7 +2354,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 3, 10, 0, "", 0 );
|
||||
static Version version( 3, 11, 0, "", 0 );
|
||||
return version;
|
||||
}
|
||||
|
||||
@@ -2367,8 +2442,14 @@ namespace Catch {
|
||||
|
||||
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
void missingCaptureInstance() {
|
||||
CATCH_INTERNAL_ERROR( "No result capture instance" );
|
||||
}
|
||||
} // namespace Detail
|
||||
|
||||
IResultCapture::~IResultCapture() = default;
|
||||
}
|
||||
} // namespace Catch
|
||||
|
||||
|
||||
|
||||
@@ -3361,6 +3442,9 @@ namespace Catch {
|
||||
| Opt( config.allowZeroTests )
|
||||
["--allow-running-no-tests"]
|
||||
( "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" )
|
||||
( "which test or tests to use" );
|
||||
|
||||
@@ -3515,7 +3599,11 @@ namespace {
|
||||
#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
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
@@ -3639,20 +3727,10 @@ namespace Catch {
|
||||
|
||||
namespace Catch {
|
||||
|
||||
Context* Context::currentContext = nullptr;
|
||||
|
||||
void cleanUpContext() {
|
||||
delete Context::currentContext;
|
||||
Context::currentContext = nullptr;
|
||||
}
|
||||
void Context::createContext() {
|
||||
currentContext = new Context();
|
||||
}
|
||||
Context Context::currentContext;
|
||||
|
||||
Context& getCurrentMutableContext() {
|
||||
if ( !Context::currentContext ) { Context::createContext(); }
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
|
||||
return *Context::currentContext;
|
||||
return Context::currentContext;
|
||||
}
|
||||
|
||||
SimplePcg32& sharedRng() {
|
||||
@@ -3757,7 +3835,7 @@ namespace Catch {
|
||||
#endif
|
||||
} // namespace Catch
|
||||
|
||||
#elif defined(CATCH_PLATFORM_LINUX)
|
||||
#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX)
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
@@ -4091,23 +4169,27 @@ namespace Catch {
|
||||
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
|
||||
};
|
||||
|
||||
// Since we do not support multiple instantiations, we put these
|
||||
// into global variables and rely on cleaning them up in outlined
|
||||
// constructors/destructors
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
|
||||
|
||||
|
||||
static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) {
|
||||
for (auto const& def : signalDefs) {
|
||||
if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
|
||||
reportFatal(def.name);
|
||||
}
|
||||
}
|
||||
// If its not an exception we care about, pass it along.
|
||||
// If a filter was previously registered, invoke it
|
||||
if (previousTopLevelExceptionFilter) {
|
||||
return previousTopLevelExceptionFilter(ExceptionInfo);
|
||||
}
|
||||
// Otherwise, pass along all exceptions.
|
||||
// This stops us from eating debugger breaks etc.
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// Since we do not support multiple instantiations, we put these
|
||||
// into global variables and rely on cleaning them up in outlined
|
||||
// constructors/destructors
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
|
||||
|
||||
|
||||
// For MSVC, we reserve part of the stack memory for handling
|
||||
// memory overflow structured exception.
|
||||
FatalConditionHandler::FatalConditionHandler() {
|
||||
@@ -5565,6 +5647,7 @@ ReporterSpec::ReporterSpec(
|
||||
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace Catch {
|
||||
@@ -5576,16 +5659,16 @@ namespace Catch {
|
||||
std::ostringstream m_referenceStream; // Used for copy state/ flags from
|
||||
Detail::Mutex m_mutex;
|
||||
|
||||
auto add() -> std::size_t {
|
||||
auto add() -> std::pair<std::size_t, std::ostringstream*> {
|
||||
Detail::LockGuard _( m_mutex );
|
||||
if( m_unused.empty() ) {
|
||||
m_streams.push_back( Detail::make_unique<std::ostringstream>() );
|
||||
return m_streams.size()-1;
|
||||
return { m_streams.size()-1, m_streams.back().get() };
|
||||
}
|
||||
else {
|
||||
auto index = m_unused.back();
|
||||
m_unused.pop_back();
|
||||
return index;
|
||||
return { index, m_streams[index].get() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5599,10 +5682,10 @@ namespace Catch {
|
||||
}
|
||||
};
|
||||
|
||||
ReusableStringStream::ReusableStringStream()
|
||||
: m_index( Singleton<StringStreams>::getMutable().add() ),
|
||||
m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() )
|
||||
{}
|
||||
ReusableStringStream::ReusableStringStream() {
|
||||
std::tie( m_index, m_oss ) =
|
||||
Singleton<StringStreams>::getMutable().add();
|
||||
}
|
||||
|
||||
ReusableStringStream::~ReusableStringStream() {
|
||||
static_cast<std::ostringstream*>( m_oss )->str("");
|
||||
@@ -6085,28 +6168,6 @@ namespace Catch {
|
||||
m_reporter->benchmarkFailed( error );
|
||||
}
|
||||
|
||||
void RunContext::pushScopedMessage( MessageInfo const& message ) {
|
||||
Detail::g_messages.push_back( message );
|
||||
}
|
||||
|
||||
void RunContext::popScopedMessage( MessageInfo const& message ) {
|
||||
// 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(),
|
||||
[id = message.sequence]( MessageInfo const& msg ) {
|
||||
return msg.sequence == id;
|
||||
} ) );
|
||||
}
|
||||
|
||||
void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
Detail::g_messageScopes.emplace_back( CATCH_MOVE(builder) );
|
||||
}
|
||||
|
||||
std::string RunContext::getCurrentTestName() const {
|
||||
return m_activeTestCase
|
||||
? m_activeTestCase->getTestCaseInfo().name
|
||||
@@ -6433,11 +6494,26 @@ namespace Catch {
|
||||
}
|
||||
}
|
||||
|
||||
IResultCapture& getResultCapture() {
|
||||
if (auto* capture = getCurrentContext().getResultCapture())
|
||||
return *capture;
|
||||
else
|
||||
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) {
|
||||
|
@@ -6,8 +6,8 @@
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
// Catch v3.10.0
|
||||
// Generated: 2025-08-24 16:18:04.055916
|
||||
// Catch v3.11.0
|
||||
// Generated: 2025-09-30 10:49:11.225746
|
||||
// ----------------------------------------------------------
|
||||
// This file is an amalgamation of multiple different files.
|
||||
// You probably shouldn't edit it directly.
|
||||
@@ -101,6 +101,9 @@
|
||||
#elif defined(linux) || defined(__linux) || defined(__linux__)
|
||||
# define CATCH_PLATFORM_LINUX
|
||||
|
||||
#elif defined(__QNX__)
|
||||
# define CATCH_PLATFORM_QNX
|
||||
|
||||
#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
|
||||
# define CATCH_PLATFORM_WINDOWS
|
||||
|
||||
@@ -308,13 +311,17 @@
|
||||
# endif
|
||||
|
||||
// Universal Windows platform does not support SEH
|
||||
// Or console colours (or console at all...)
|
||||
# if defined(CATCH_PLATFORM_WINDOWS_UWP)
|
||||
# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32
|
||||
# else
|
||||
# if !defined(CATCH_PLATFORM_WINDOWS_UWP)
|
||||
# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
|
||||
# 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 == 0 means new conformant preprocessor
|
||||
// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor
|
||||
@@ -564,11 +571,9 @@ namespace Catch {
|
||||
IConfig const* m_config = nullptr;
|
||||
IResultCapture* m_resultCapture = nullptr;
|
||||
|
||||
CATCH_EXPORT static Context* currentContext;
|
||||
CATCH_EXPORT static Context currentContext;
|
||||
friend Context& getCurrentMutableContext();
|
||||
friend Context const& getCurrentContext();
|
||||
static void createContext();
|
||||
friend void cleanUpContext();
|
||||
|
||||
public:
|
||||
constexpr IResultCapture* getResultCapture() const {
|
||||
@@ -579,21 +584,14 @@ namespace Catch {
|
||||
m_resultCapture = resultCapture;
|
||||
}
|
||||
constexpr void setConfig( IConfig const* config ) { m_config = config; }
|
||||
|
||||
};
|
||||
|
||||
Context& getCurrentMutableContext();
|
||||
|
||||
inline Context const& getCurrentContext() {
|
||||
// We duplicate the logic from `getCurrentMutableContext` here,
|
||||
// to avoid paying the call overhead in debug mode.
|
||||
if ( !Context::currentContext ) { Context::createContext(); }
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
|
||||
return *Context::currentContext;
|
||||
return Context::currentContext;
|
||||
}
|
||||
|
||||
void cleanUpContext();
|
||||
|
||||
class SimplePcg32;
|
||||
SimplePcg32& sharedRng();
|
||||
}
|
||||
@@ -1069,10 +1067,9 @@ namespace Catch {
|
||||
virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0;
|
||||
virtual void benchmarkFailed( StringRef error ) = 0;
|
||||
|
||||
virtual void pushScopedMessage( MessageInfo const& message ) = 0;
|
||||
virtual void popScopedMessage( MessageInfo const& message ) = 0;
|
||||
|
||||
virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0;
|
||||
static void pushScopedMessage( MessageInfo&& message );
|
||||
static void popScopedMessage( unsigned int messageId );
|
||||
static void emplaceUnscopedMessage( MessageBuilder&& builder );
|
||||
|
||||
virtual void handleFatalErrorCondition( StringRef message ) = 0;
|
||||
|
||||
@@ -1108,7 +1105,18 @@ namespace Catch {
|
||||
virtual void exceptionEarlyReported() = 0;
|
||||
};
|
||||
|
||||
IResultCapture& getResultCapture();
|
||||
namespace Detail {
|
||||
[[noreturn]]
|
||||
void missingCaptureInstance();
|
||||
}
|
||||
inline IResultCapture& getResultCapture() {
|
||||
if (auto* capture = getCurrentContext().getResultCapture()) {
|
||||
return *capture;
|
||||
} else {
|
||||
Detail::missingCaptureInstance();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED
|
||||
@@ -3800,6 +3808,8 @@ namespace Catch {
|
||||
|
||||
std::vector<std::string> testsOrTags;
|
||||
std::vector<std::string> sectionsToRun;
|
||||
|
||||
std::string prematureExitGuardFilePath;
|
||||
};
|
||||
|
||||
|
||||
@@ -3827,6 +3837,8 @@ namespace Catch {
|
||||
|
||||
bool showHelp() const;
|
||||
|
||||
std::string const& getExitGuardFilePath() const;
|
||||
|
||||
// IConfig interface
|
||||
bool allowThrows() const override;
|
||||
StringRef name() const override;
|
||||
@@ -3961,6 +3973,7 @@ namespace Catch {
|
||||
std::string message;
|
||||
SourceLineInfo lineInfo;
|
||||
ResultWas::OfType type;
|
||||
// The "ID" of the message, used to know when to remove it from reporter context.
|
||||
unsigned int sequence;
|
||||
|
||||
DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
@@ -4020,13 +4033,12 @@ namespace Catch {
|
||||
ScopedMessage( ScopedMessage&& old ) noexcept;
|
||||
~ScopedMessage();
|
||||
|
||||
MessageInfo m_info;
|
||||
unsigned int m_messageId;
|
||||
bool m_moved = false;
|
||||
};
|
||||
|
||||
class Capturer {
|
||||
std::vector<MessageInfo> m_messages;
|
||||
IResultCapture& m_resultCapture;
|
||||
size_t m_captured = 0;
|
||||
public:
|
||||
Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
|
||||
@@ -4074,7 +4086,7 @@ namespace Catch {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#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)
|
||||
@@ -7466,7 +7478,7 @@ namespace Catch {
|
||||
#define CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
||||
#define CATCH_VERSION_MAJOR 3
|
||||
#define CATCH_VERSION_MINOR 10
|
||||
#define CATCH_VERSION_MINOR 11
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
|
||||
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
@@ -7882,8 +7894,9 @@ namespace Generators {
|
||||
class FilterGenerator final : public IGenerator<T> {
|
||||
GeneratorWrapper<T> m_generator;
|
||||
Predicate m_predicate;
|
||||
static_assert(!std::is_reference<Predicate>::value, "This would most likely result in a dangling reference");
|
||||
public:
|
||||
template <typename P = Predicate>
|
||||
template <typename P>
|
||||
FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator):
|
||||
m_generator(CATCH_MOVE(generator)),
|
||||
m_predicate(CATCH_FORWARD(pred))
|
||||
@@ -7915,7 +7928,7 @@ namespace Generators {
|
||||
|
||||
template <typename T, typename Predicate>
|
||||
GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) {
|
||||
return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, Predicate>>(CATCH_FORWARD(pred), CATCH_MOVE(generator)));
|
||||
return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, typename std::remove_reference<Predicate>::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -9555,7 +9568,7 @@ namespace Catch {
|
||||
#define CATCH_TRAP() __asm__(".inst 0xde01")
|
||||
#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
|
||||
// directly at the location of the failing check instead of breaking inside
|
||||
// raise() called from it, i.e. one stack frame below.
|
||||
@@ -10718,11 +10731,6 @@ namespace Catch {
|
||||
void benchmarkEnded( BenchmarkStats<> const& stats ) override;
|
||||
void benchmarkFailed( StringRef error ) override;
|
||||
|
||||
void pushScopedMessage( MessageInfo const& message ) override;
|
||||
void popScopedMessage( MessageInfo const& message ) override;
|
||||
|
||||
void emplaceUnscopedMessage( MessageBuilder&& builder ) override;
|
||||
|
||||
std::string getCurrentTestName() const override;
|
||||
|
||||
const AssertionResult* getLastResult() const override;
|
||||
|
@@ -8,7 +8,7 @@
|
||||
project(
|
||||
'catch2',
|
||||
'cpp',
|
||||
version: '3.10.0', # CML version placeholder, don't delete
|
||||
version: '3.11.0', # CML version placeholder, don't delete
|
||||
license: 'BSL-1.0',
|
||||
meson_version: '>=0.54.1',
|
||||
)
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -119,6 +119,8 @@ namespace Catch {
|
||||
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() ) {
|
||||
readBazelEnvVars();
|
||||
}
|
||||
@@ -183,6 +185,8 @@ namespace Catch {
|
||||
|
||||
bool Config::showHelp() const { return m_data.showHelp; }
|
||||
|
||||
std::string const& Config::getExitGuardFilePath() const { return m_data.prematureExitGuardFilePath; }
|
||||
|
||||
// IConfig interface
|
||||
bool Config::allowThrows() const { return !m_data.noThrow; }
|
||||
StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
|
||||
@@ -244,6 +248,26 @@ namespace Catch {
|
||||
m_data.shardCount = bazelShardOptions->shardCount;
|
||||
}
|
||||
}
|
||||
|
||||
const auto bazelExitGuardFile = Detail::getEnv( "TEST_PREMATURE_EXIT_FILE" );
|
||||
if (bazelExitGuardFile) {
|
||||
m_data.prematureExitGuardFilePath = bazelExitGuardFile;
|
||||
}
|
||||
|
||||
const auto bazelRandomSeed = Detail::getEnv( "TEST_RANDOM_SEED" );
|
||||
if ( bazelRandomSeed ) {
|
||||
auto parsedSeed = parseUInt( bazelRandomSeed, 0 );
|
||||
if ( !parsedSeed ) {
|
||||
// Currently we handle issues with parsing other Bazel Env
|
||||
// options by warning and ignoring the issue. So we do the
|
||||
// same for random seed option.
|
||||
Catch::cerr()
|
||||
<< "Warning: could not parse 'TEST_RANDOM_SEED' ('"
|
||||
<< bazelRandomSeed << "') as proper seed.\n";
|
||||
} else {
|
||||
m_data.rngSeed = *parsedSeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace Catch
|
||||
|
@@ -87,6 +87,8 @@ namespace Catch {
|
||||
|
||||
std::vector<std::string> testsOrTags;
|
||||
std::vector<std::string> sectionsToRun;
|
||||
|
||||
std::string prematureExitGuardFilePath;
|
||||
};
|
||||
|
||||
|
||||
@@ -114,6 +116,8 @@ namespace Catch {
|
||||
|
||||
bool showHelp() const;
|
||||
|
||||
std::string const& getExitGuardFilePath() const;
|
||||
|
||||
// IConfig interface
|
||||
bool allowThrows() const override;
|
||||
StringRef name() const override;
|
||||
|
@@ -19,28 +19,26 @@ namespace Catch {
|
||||
|
||||
|
||||
ScopedMessage::ScopedMessage( MessageBuilder&& builder ):
|
||||
m_info( CATCH_MOVE(builder.m_info) ) {
|
||||
m_info.message = builder.m_stream.str();
|
||||
getResultCapture().pushScopedMessage( m_info );
|
||||
m_messageId( builder.m_info.sequence ) {
|
||||
MessageInfo info( CATCH_MOVE( builder.m_info ) );
|
||||
info.message = builder.m_stream.str();
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
|
||||
m_info( CATCH_MOVE( old.m_info ) ) {
|
||||
m_messageId( old.m_messageId ) {
|
||||
old.m_moved = true;
|
||||
}
|
||||
|
||||
ScopedMessage::~ScopedMessage() {
|
||||
if ( !m_moved ){
|
||||
getResultCapture().popScopedMessage(m_info);
|
||||
}
|
||||
if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
|
||||
}
|
||||
|
||||
|
||||
Capturer::Capturer( StringRef macroName,
|
||||
SourceLineInfo const& lineInfo,
|
||||
ResultWas::OfType resultType,
|
||||
StringRef names ):
|
||||
m_resultCapture( getResultCapture() ) {
|
||||
StringRef names ) {
|
||||
auto trimmed = [&] (size_t start, size_t end) {
|
||||
while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {
|
||||
++start;
|
||||
@@ -86,8 +84,8 @@ namespace Catch {
|
||||
case ',':
|
||||
if (start != pos && openings.empty()) {
|
||||
m_messages.emplace_back(macroName, lineInfo, resultType);
|
||||
m_messages.back().message = static_cast<std::string>(trimmed(start, pos));
|
||||
m_messages.back().message += " := ";
|
||||
m_messages.back().message += trimmed(start, pos);
|
||||
m_messages.back().message += " := "_sr;
|
||||
start = pos;
|
||||
}
|
||||
break;
|
||||
@@ -96,20 +94,20 @@ namespace Catch {
|
||||
}
|
||||
assert(openings.empty() && "Mismatched openings");
|
||||
m_messages.emplace_back(macroName, lineInfo, resultType);
|
||||
m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));
|
||||
m_messages.back().message += " := ";
|
||||
m_messages.back().message += trimmed(start, names.size() - 1);
|
||||
m_messages.back().message += " := "_sr;
|
||||
}
|
||||
Capturer::~Capturer() {
|
||||
assert( m_captured == m_messages.size() );
|
||||
for ( size_t i = 0; i < m_captured; ++i ) {
|
||||
m_resultCapture.popScopedMessage( m_messages[i] );
|
||||
for (auto const& message : m_messages) {
|
||||
IResultCapture::popScopedMessage( message.sequence );
|
||||
}
|
||||
}
|
||||
|
||||
void Capturer::captureValue( size_t index, std::string const& value ) {
|
||||
assert( index < m_messages.size() );
|
||||
m_messages[index].message += value;
|
||||
m_resultCapture.pushScopedMessage( m_messages[index] );
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
m_captured++;
|
||||
}
|
||||
|
||||
|
@@ -57,13 +57,12 @@ namespace Catch {
|
||||
ScopedMessage( ScopedMessage&& old ) noexcept;
|
||||
~ScopedMessage();
|
||||
|
||||
MessageInfo m_info;
|
||||
unsigned int m_messageId;
|
||||
bool m_moved = false;
|
||||
};
|
||||
|
||||
class Capturer {
|
||||
std::vector<MessageInfo> m_messages;
|
||||
IResultCapture& m_resultCapture;
|
||||
size_t m_captured = 0;
|
||||
public:
|
||||
Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
|
||||
@@ -111,7 +110,7 @@ namespace Catch {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#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)
|
||||
|
@@ -96,7 +96,6 @@ namespace Catch {
|
||||
}
|
||||
void cleanUp() {
|
||||
cleanupSingletons();
|
||||
cleanUpContext();
|
||||
}
|
||||
std::string translateActiveException() {
|
||||
return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
|
||||
|
@@ -26,6 +26,8 @@
|
||||
#include <catch2/internal/catch_istream.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#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
|
||||
|
||||
Session::Session() {
|
||||
@@ -258,6 +304,7 @@ namespace Catch {
|
||||
static_cast<void>(std::getchar());
|
||||
}
|
||||
int exitCode = runInternal();
|
||||
|
||||
if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
|
||||
Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush;
|
||||
static_cast<void>(std::getchar());
|
||||
@@ -298,6 +345,10 @@ namespace Catch {
|
||||
CATCH_TRY {
|
||||
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 );
|
||||
|
||||
if (m_configData.filenamesAsTags) {
|
||||
@@ -327,9 +378,12 @@ namespace Catch {
|
||||
TestGroup tests { CATCH_MOVE(reporter), m_config.get() };
|
||||
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()
|
||||
&& m_config->warnAboutUnmatchedTestSpecs() ) {
|
||||
// UnmatchedTestSpecExitCode
|
||||
return UnmatchedTestSpecExitCode;
|
||||
}
|
||||
|
||||
|
@@ -61,35 +61,35 @@ namespace Detail {
|
||||
std::string ret;
|
||||
// This is enough for the "don't escape invisibles" case, and a good
|
||||
// lower bound on the "escape invisibles" case.
|
||||
ret.reserve(string.size() + 2);
|
||||
ret.reserve( string.size() + 2 );
|
||||
|
||||
if (!escapeInvisibles) {
|
||||
if ( !escapeInvisibles ) {
|
||||
ret += '"';
|
||||
ret += string;
|
||||
ret += '"';
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t last_start = 0;
|
||||
auto write_to = [&]( size_t idx ) {
|
||||
if ( last_start < idx ) {
|
||||
ret += string.substr( last_start, idx - last_start );
|
||||
}
|
||||
last_start = idx + 1;
|
||||
};
|
||||
|
||||
ret += '"';
|
||||
for (char c : string) {
|
||||
switch (c) {
|
||||
case '\r':
|
||||
ret.append("\\r");
|
||||
break;
|
||||
case '\n':
|
||||
ret.append("\\n");
|
||||
break;
|
||||
case '\t':
|
||||
ret.append("\\t");
|
||||
break;
|
||||
case '\f':
|
||||
ret.append("\\f");
|
||||
break;
|
||||
default:
|
||||
ret.push_back(c);
|
||||
break;
|
||||
for ( size_t i = 0; i < string.size(); ++i ) {
|
||||
const char c = string[i];
|
||||
if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) {
|
||||
write_to( i );
|
||||
if ( c == '\r' ) { ret.append( "\\r" ); }
|
||||
if ( c == '\n' ) { ret.append( "\\n" ); }
|
||||
if ( c == '\t' ) { ret.append( "\\t" ); }
|
||||
if ( c == '\f' ) { ret.append( "\\f" ); }
|
||||
}
|
||||
}
|
||||
write_to( string.size() );
|
||||
ret += '"';
|
||||
|
||||
return ret;
|
||||
|
@@ -36,7 +36,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 3, 10, 0, "", 0 );
|
||||
static Version version( 3, 11, 0, "", 0 );
|
||||
return version;
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,7 @@
|
||||
#define CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
||||
#define CATCH_VERSION_MAJOR 3
|
||||
#define CATCH_VERSION_MINOR 10
|
||||
#define CATCH_VERSION_MINOR 11
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
|
||||
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
@@ -7,7 +7,14 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
void missingCaptureInstance() {
|
||||
CATCH_INTERNAL_ERROR( "No result capture instance" );
|
||||
}
|
||||
} // namespace Detail
|
||||
|
||||
IResultCapture::~IResultCapture() = default;
|
||||
}
|
||||
} // namespace Catch
|
||||
|
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <catch2/internal/catch_context.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
#include <catch2/internal/catch_result_type.hpp>
|
||||
#include <catch2/internal/catch_unique_ptr.hpp>
|
||||
@@ -62,10 +63,9 @@ namespace Catch {
|
||||
virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0;
|
||||
virtual void benchmarkFailed( StringRef error ) = 0;
|
||||
|
||||
virtual void pushScopedMessage( MessageInfo const& message ) = 0;
|
||||
virtual void popScopedMessage( MessageInfo const& message ) = 0;
|
||||
|
||||
virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0;
|
||||
static void pushScopedMessage( MessageInfo&& message );
|
||||
static void popScopedMessage( unsigned int messageId );
|
||||
static void emplaceUnscopedMessage( MessageBuilder&& builder );
|
||||
|
||||
virtual void handleFatalErrorCondition( StringRef message ) = 0;
|
||||
|
||||
@@ -101,7 +101,18 @@ namespace Catch {
|
||||
virtual void exceptionEarlyReported() = 0;
|
||||
};
|
||||
|
||||
IResultCapture& getResultCapture();
|
||||
namespace Detail {
|
||||
[[noreturn]]
|
||||
void missingCaptureInstance();
|
||||
}
|
||||
inline IResultCapture& getResultCapture() {
|
||||
if (auto* capture = getCurrentContext().getResultCapture()) {
|
||||
return *capture;
|
||||
} else {
|
||||
Detail::missingCaptureInstance();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED
|
||||
|
@@ -305,6 +305,9 @@ namespace Catch {
|
||||
| Opt( config.allowZeroTests )
|
||||
["--allow-running-no-tests"]
|
||||
( "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" )
|
||||
( "which test or tests to use" );
|
||||
|
||||
|
@@ -220,13 +220,17 @@
|
||||
# endif
|
||||
|
||||
// Universal Windows platform does not support SEH
|
||||
// Or console colours (or console at all...)
|
||||
# if defined(CATCH_PLATFORM_WINDOWS_UWP)
|
||||
# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32
|
||||
# else
|
||||
# if !defined(CATCH_PLATFORM_WINDOWS_UWP)
|
||||
# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
|
||||
# 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 == 0 means new conformant preprocessor
|
||||
// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor
|
||||
|
@@ -161,7 +161,11 @@ namespace {
|
||||
#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
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
@@ -6,25 +6,14 @@
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#include <catch2/internal/catch_context.hpp>
|
||||
#include <catch2/internal/catch_noncopyable.hpp>
|
||||
#include <catch2/internal/catch_random_number_generator.hpp>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
Context* Context::currentContext = nullptr;
|
||||
|
||||
void cleanUpContext() {
|
||||
delete Context::currentContext;
|
||||
Context::currentContext = nullptr;
|
||||
}
|
||||
void Context::createContext() {
|
||||
currentContext = new Context();
|
||||
}
|
||||
Context Context::currentContext;
|
||||
|
||||
Context& getCurrentMutableContext() {
|
||||
if ( !Context::currentContext ) { Context::createContext(); }
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
|
||||
return *Context::currentContext;
|
||||
return Context::currentContext;
|
||||
}
|
||||
|
||||
SimplePcg32& sharedRng() {
|
||||
|
@@ -19,11 +19,9 @@ namespace Catch {
|
||||
IConfig const* m_config = nullptr;
|
||||
IResultCapture* m_resultCapture = nullptr;
|
||||
|
||||
CATCH_EXPORT static Context* currentContext;
|
||||
CATCH_EXPORT static Context currentContext;
|
||||
friend Context& getCurrentMutableContext();
|
||||
friend Context const& getCurrentContext();
|
||||
static void createContext();
|
||||
friend void cleanUpContext();
|
||||
|
||||
public:
|
||||
constexpr IResultCapture* getResultCapture() const {
|
||||
@@ -34,21 +32,14 @@ namespace Catch {
|
||||
m_resultCapture = resultCapture;
|
||||
}
|
||||
constexpr void setConfig( IConfig const* config ) { m_config = config; }
|
||||
|
||||
};
|
||||
|
||||
Context& getCurrentMutableContext();
|
||||
|
||||
inline Context const& getCurrentContext() {
|
||||
// We duplicate the logic from `getCurrentMutableContext` here,
|
||||
// to avoid paying the call overhead in debug mode.
|
||||
if ( !Context::currentContext ) { Context::createContext(); }
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
|
||||
return *Context::currentContext;
|
||||
return Context::currentContext;
|
||||
}
|
||||
|
||||
void cleanUpContext();
|
||||
|
||||
class SimplePcg32;
|
||||
SimplePcg32& sharedRng();
|
||||
}
|
||||
|
@@ -69,7 +69,7 @@
|
||||
#endif
|
||||
} // namespace Catch
|
||||
|
||||
#elif defined(CATCH_PLATFORM_LINUX)
|
||||
#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX)
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
|
@@ -49,7 +49,7 @@ namespace Catch {
|
||||
#define CATCH_TRAP() __asm__(".inst 0xde01")
|
||||
#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
|
||||
// directly at the location of the failing check instead of breaking inside
|
||||
// raise() called from it, i.e. one stack frame below.
|
||||
|
@@ -86,23 +86,27 @@ namespace Catch {
|
||||
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
|
||||
};
|
||||
|
||||
// Since we do not support multiple instantiations, we put these
|
||||
// into global variables and rely on cleaning them up in outlined
|
||||
// constructors/destructors
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
|
||||
|
||||
|
||||
static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) {
|
||||
for (auto const& def : signalDefs) {
|
||||
if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
|
||||
reportFatal(def.name);
|
||||
}
|
||||
}
|
||||
// If its not an exception we care about, pass it along.
|
||||
// If a filter was previously registered, invoke it
|
||||
if (previousTopLevelExceptionFilter) {
|
||||
return previousTopLevelExceptionFilter(ExceptionInfo);
|
||||
}
|
||||
// Otherwise, pass along all exceptions.
|
||||
// This stops us from eating debugger breaks etc.
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// Since we do not support multiple instantiations, we put these
|
||||
// into global variables and rely on cleaning them up in outlined
|
||||
// constructors/destructors
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
|
||||
|
||||
|
||||
// For MSVC, we reserve part of the stack memory for handling
|
||||
// memory overflow structured exception.
|
||||
FatalConditionHandler::FatalConditionHandler() {
|
||||
|
24
src/catch2/internal/catch_lifetimebound.hpp
Normal file
24
src/catch2/internal/catch_lifetimebound.hpp
Normal 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
|
@@ -26,6 +26,7 @@ namespace Catch {
|
||||
std::string message;
|
||||
SourceLineInfo lineInfo;
|
||||
ResultWas::OfType type;
|
||||
// The "ID" of the message, used to know when to remove it from reporter context.
|
||||
unsigned int sequence;
|
||||
|
||||
DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
|
@@ -25,6 +25,9 @@
|
||||
#elif defined(linux) || defined(__linux) || defined(__linux__)
|
||||
# define CATCH_PLATFORM_LINUX
|
||||
|
||||
#elif defined(__QNX__)
|
||||
# define CATCH_PLATFORM_QNX
|
||||
|
||||
#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
|
||||
# define CATCH_PLATFORM_WINDOWS
|
||||
|
||||
|
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace Catch {
|
||||
@@ -23,16 +24,16 @@ namespace Catch {
|
||||
std::ostringstream m_referenceStream; // Used for copy state/ flags from
|
||||
Detail::Mutex m_mutex;
|
||||
|
||||
auto add() -> std::size_t {
|
||||
auto add() -> std::pair<std::size_t, std::ostringstream*> {
|
||||
Detail::LockGuard _( m_mutex );
|
||||
if( m_unused.empty() ) {
|
||||
m_streams.push_back( Detail::make_unique<std::ostringstream>() );
|
||||
return m_streams.size()-1;
|
||||
return { m_streams.size()-1, m_streams.back().get() };
|
||||
}
|
||||
else {
|
||||
auto index = m_unused.back();
|
||||
m_unused.pop_back();
|
||||
return index;
|
||||
return { index, m_streams[index].get() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +47,10 @@ namespace Catch {
|
||||
}
|
||||
};
|
||||
|
||||
ReusableStringStream::ReusableStringStream()
|
||||
: m_index( Singleton<StringStreams>::getMutable().add() ),
|
||||
m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() )
|
||||
{}
|
||||
ReusableStringStream::ReusableStringStream() {
|
||||
std::tie( m_index, m_oss ) =
|
||||
Singleton<StringStreams>::getMutable().add();
|
||||
}
|
||||
|
||||
ReusableStringStream::~ReusableStringStream() {
|
||||
static_cast<std::ostringstream*>( m_oss )->str("");
|
||||
|
@@ -483,28 +483,6 @@ namespace Catch {
|
||||
m_reporter->benchmarkFailed( error );
|
||||
}
|
||||
|
||||
void RunContext::pushScopedMessage( MessageInfo const& message ) {
|
||||
Detail::g_messages.push_back( message );
|
||||
}
|
||||
|
||||
void RunContext::popScopedMessage( MessageInfo const& message ) {
|
||||
// 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(),
|
||||
[id = message.sequence]( MessageInfo const& msg ) {
|
||||
return msg.sequence == id;
|
||||
} ) );
|
||||
}
|
||||
|
||||
void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
Detail::g_messageScopes.emplace_back( CATCH_MOVE(builder) );
|
||||
}
|
||||
|
||||
std::string RunContext::getCurrentTestName() const {
|
||||
return m_activeTestCase
|
||||
? m_activeTestCase->getTestCaseInfo().name
|
||||
@@ -831,11 +809,26 @@ namespace Catch {
|
||||
}
|
||||
}
|
||||
|
||||
IResultCapture& getResultCapture() {
|
||||
if (auto* capture = getCurrentContext().getResultCapture())
|
||||
return *capture;
|
||||
else
|
||||
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) {
|
||||
|
@@ -94,11 +94,6 @@ namespace Catch {
|
||||
void benchmarkEnded( BenchmarkStats<> const& stats ) override;
|
||||
void benchmarkFailed( StringRef error ) override;
|
||||
|
||||
void pushScopedMessage( MessageInfo const& message ) override;
|
||||
void popScopedMessage( MessageInfo const& message ) override;
|
||||
|
||||
void emplaceUnscopedMessage( MessageBuilder&& builder ) override;
|
||||
|
||||
std::string getCurrentTestName() const override;
|
||||
|
||||
const AssertionResult* getLastResult() const override;
|
||||
|
@@ -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)
|
||||
{}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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_ ) {}
|
||||
|
||||
|
@@ -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 };
|
||||
}
|
||||
|
||||
|
@@ -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 };
|
||||
}
|
||||
|
||||
|
@@ -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',
|
||||
|
@@ -662,5 +662,38 @@ foreach(reporterName # "Automake" - the simple .trs format does not support any
|
||||
)
|
||||
endforeach()
|
||||
|
||||
add_test(NAME "Bazel::RngSeedEnvVar::JustEnv"
|
||||
COMMAND
|
||||
$<TARGET_FILE:SelfTest> "Factorials are computed"
|
||||
)
|
||||
set_tests_properties("Bazel::RngSeedEnvVar::JustEnv"
|
||||
PROPERTIES
|
||||
ENVIRONMENT "BAZEL_TEST=1;TEST_RANDOM_SEED=18181818"
|
||||
PASS_REGULAR_EXPRESSION "Randomness seeded to: 18181818"
|
||||
)
|
||||
|
||||
add_test(NAME "Bazel::RngSeedEnvVar::EnvHasPriorityOverCLI"
|
||||
COMMAND
|
||||
$<TARGET_FILE:SelfTest> "Factorials are computed"
|
||||
--rng-seed 17171717
|
||||
)
|
||||
set_tests_properties("Bazel::RngSeedEnvVar::EnvHasPriorityOverCLI"
|
||||
PROPERTIES
|
||||
ENVIRONMENT "BAZEL_TEST=1;TEST_RANDOM_SEED=18181818"
|
||||
PASS_REGULAR_EXPRESSION "Randomness seeded to: 18181818"
|
||||
)
|
||||
|
||||
add_test(NAME "Bazel::RngSeedEnvVar::MalformedValueIsIgnored"
|
||||
COMMAND
|
||||
$<TARGET_FILE:SelfTest> "Factorials are computed"
|
||||
--rng-seed 17171717
|
||||
)
|
||||
set_tests_properties("Bazel::RngSeedEnvVar::MalformedValueIsIgnored"
|
||||
PROPERTIES
|
||||
ENVIRONMENT "BAZEL_TEST=1;TEST_RANDOM_SEED=XOXOXOXO"
|
||||
PASS_REGULAR_EXPRESSION "Randomness seeded to: 17171717"
|
||||
)
|
||||
|
||||
|
||||
list(APPEND CATCH_TEST_TARGETS SelfTest)
|
||||
set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE)
|
||||
|
@@ -95,7 +95,7 @@ foreach(target DisabledExceptions-DefaultHandler DisabledExceptions-CustomHandle
|
||||
target_compile_options(${target}
|
||||
PUBLIC
|
||||
$<$<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)
|
||||
endforeach()
|
||||
@@ -109,18 +109,6 @@ set_tests_properties(CATCH_CONFIG_DISABLE_EXCEPTIONS-1
|
||||
FAIL_REGULAR_EXPRESSION "abort;terminate;fatal"
|
||||
)
|
||||
|
||||
add_executable(BazelReporter ${TESTS_DIR}/X30-BazelReporter.cpp)
|
||||
target_compile_definitions(BazelReporter PRIVATE CATCH_CONFIG_BAZEL_SUPPORT)
|
||||
target_link_libraries(BazelReporter Catch2_buildall_interface)
|
||||
add_test(NAME CATCH_CONFIG_BAZEL_REPORTER-1
|
||||
COMMAND
|
||||
Python3::Interpreter "${CATCH_DIR}/tests/TestScripts/testBazelReporter.py" $<TARGET_FILE:BazelReporter> "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
set_tests_properties(CATCH_CONFIG_BAZEL_REPORTER-1
|
||||
PROPERTIES
|
||||
LABELS "uses-python"
|
||||
)
|
||||
|
||||
# We must now test this works without the build flag.
|
||||
add_executable(BazelReporterNoCatchConfig ${TESTS_DIR}/X30-BazelReporter.cpp)
|
||||
target_link_libraries(BazelReporterNoCatchConfig Catch2WithMain)
|
||||
@@ -429,6 +417,50 @@ set_tests_properties(Reporters::CrashInJunitReporter
|
||||
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
|
||||
X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp
|
||||
)
|
||||
|
28
tests/ExtraTests/X40-QuickExit.cpp
Normal file
28
tests/ExtraTests/X40-QuickExit.cpp
Normal 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") {}
|
@@ -6,11 +6,13 @@
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include <catch2/catch_template_test_macros.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/internal/catch_enum_values_registry.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
#include <catch2/matchers/catch_matchers_vector.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_template_test_macros.hpp>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
@@ -139,3 +141,26 @@ TEST_CASE( "Exception thrown inside stringify does not fail the test", "[toStrin
|
||||
ThrowsOnStringification tos;
|
||||
CHECK( tos == tos );
|
||||
}
|
||||
|
||||
TEST_CASE( "string escaping benchmark", "[toString][!benchmark]" ) {
|
||||
const auto input_length = GENERATE( as<size_t>{}, 10, 100, 10'000, 100'000 );
|
||||
std::string test_input( input_length, 'a' );
|
||||
BENCHMARK( "no-escape string, no-escaping, len=" +
|
||||
std::to_string( input_length ) ) {
|
||||
return Catch::Detail::convertIntoString( test_input, false );
|
||||
};
|
||||
BENCHMARK( "no-escape string, escaping, len=" +
|
||||
std::to_string( input_length ) ) {
|
||||
return Catch::Detail::convertIntoString( test_input, true );
|
||||
};
|
||||
|
||||
std::string escape_input( input_length, '\r' );
|
||||
BENCHMARK( "full escape string, no-escaping, len=" +
|
||||
std::to_string( input_length ) ) {
|
||||
return Catch::Detail::convertIntoString( escape_input, false );
|
||||
};
|
||||
BENCHMARK( "full escape string, escaping, len=" +
|
||||
std::to_string( input_length ) ) {
|
||||
return Catch::Detail::convertIntoString( escape_input, true );
|
||||
};
|
||||
}
|
||||
|
88
tests/TestScripts/testBazelExitGuardFile.py
Executable file
88
tests/TestScripts/testBazelExitGuardFile.py
Executable 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}')
|
||||
|
Reference in New Issue
Block a user