Compare commits

..

25 Commits

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

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

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

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

leads to use-after-free, as the `combinedMatcher` refers to matcher
temporaries that no longer exists. With this commit, users of Clang,
MSVC or other compiler that understands the `lifetimebound` attribute,
should get a warning.
2025-10-03 22:27:29 +02:00
Martin Hořeňovský
8219ed79f2 Add CATCH_ATTR_LIFETIMEBOUND macro polyfill over lifetimebound attr 2025-10-03 22:15:27 +02:00
Martin Hořeňovský
b3fb4b9fea v3.11.0 2025-09-30 10:54:31 +02:00
Martin Hořeňovský
6500dc8149 Add Tuple sponsorship banner to readme
Until December.
2025-09-30 10:34:21 +02:00
Martin Hořeňovský
4be9de6c54 Disable test for deprecated CATCH_CONFIG_BAZEL_SUPPORT option
Because it requires full rebuild of the base library, it adds about
20% to the build time of the test suite. Between the fact that the
option is deprecated, and that Bazel has added the `BAZEL_TEST`
env var _years_ ago, nobody should be using it, and the chance
of breakage is tiny, the test is not worth its compile-time cost.
2025-09-28 12:57:55 +02:00
Martin Hořeňovský
91b3b3bf40 Support Bazel's TEST_RANDOM_SEED
As with other Bazel env vars, it overrides the corresponding CLI
parameter if both are set.

Closes #3021
Closes #3024
2025-09-28 10:48:08 +02:00
Martin Hořeňovský
a00d654437 Speed up processing mostly visible strings in convertIntoString
This commit causes small (~5%) slowdown when processing strings
that consist mostly of escaped characters, but improves throughput
by about 100% for strings that consist mostly of characters that
do not need escaping.
2025-09-27 22:43:14 +02:00
Martin Hořeňovský
756ae05d30 Inline the getResultCapture helper into header.
We still keep the error check in the function, but hide it in an
outlined function inside a .cpp file, to promote inlining of the
retrieval part.

In the future, we should explore two things

1) Skipping over the context retrieval here, allowing direct access.
   I currently do not see a way to do this while keeping the
   "greppability" of mutable vs immutable accesses that is there now,
   but it would help a lot when inlining is not enabled.
2) Removing the error check, to make the function trivially inlinable,
   and without branches.

**runtime difference**

| --------- | Debug | Release |
|:----------|------:|--------:|
| Slow path |  0.98 |    1.07 |
| Fast path |  1.04 |    1.08 |

We lost bit of performance on the assertion slow path in debug mode,
but together with the previous commit, it comes out at net zero.
For other combinations, we see 5-10% perf improvement across the
two commits.
2025-09-27 13:24:46 +02:00
Martin Hořeňovský
a2e41916f2 Keep the main Context instance as static value, not pointer
This allows us to remove the lazy init checks, improving the inlining
potential when retrieving current context, thus slightly improving
the performance of assertions.

**runtime difference**

| --------- | Debug | Release |
|:----------|------:|--------:|
| Slow path |  1.01 |    0.98 |
| Fast path |  1.02 |    1.02 |

There is small slowdown in case of Release build + assertions taking
the slow path, but

1) going through the slow path is rare
2) Given the code change, I believe this to be artifact of the
   optimizer in the old GCC version I am using locally.
2025-09-27 13:24:44 +02:00
Duncan Horn
0e772cc0d2 Allow composability of unhandled exception filters (#3033)
The change is very simple. If a handler previously existed, Catch2 will invoke it after printing out its output. I've also updated the comment to better reflect that it's returning EXCEPTION_CONTINUE_SEARCH even in scenarios where the exception is one that the library cares about.
2025-09-27 11:09:34 +02:00
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
Martin Hořeňovský
cd93d202e0 Small refactoring in CAPTURE parsing 2025-08-26 12:40:13 +02:00
Martin Hořeňovský
227af796b4 Move MessageInfos into the run context
Together with the previous change, this means that `ScopedMessage`
only needs to keep around a single unsigned int.
2025-08-26 12:37:52 +02:00
Martin Hořeňovský
33adb4c779 Use only the ID of a message when removing it from context 2025-08-26 12:07:41 +02:00
53 changed files with 949 additions and 373 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)
@@ -34,7 +35,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
endif() endif()
project(Catch2 project(Catch2
VERSION 3.10.0 # CML version placeholder, don't delete VERSION 3.11.0 # CML version placeholder, don't delete
LANGUAGES CXX LANGUAGES CXX
HOMEPAGE_URL "https://github.com/catchorg/Catch2" HOMEPAGE_URL "https://github.com/catchorg/Catch2"
DESCRIPTION "A modern, C++-native, unit test framework." 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 # 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"
} }

View File

@@ -1,5 +1,16 @@
<a id="top"></a> <a id="top"></a>
![Catch2 logo](data/artwork/catch2-logo-full-with-background.svg)
<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>
[![Github Releases](https://img.shields.io/github/release/catchorg/catch2.svg)](https://github.com/catchorg/catch2/releases) [![Github Releases](https://img.shields.io/github/release/catchorg/catch2.svg)](https://github.com/catchorg/catch2/releases)
[![Linux build status](https://github.com/catchorg/Catch2/actions/workflows/linux-simple-builds.yml/badge.svg)](https://github.com/catchorg/Catch2/actions/workflows/linux-simple-builds.yml) [![Linux build status](https://github.com/catchorg/Catch2/actions/workflows/linux-simple-builds.yml/badge.svg)](https://github.com/catchorg/Catch2/actions/workflows/linux-simple-builds.yml)

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; }());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

View File

@@ -66,17 +66,24 @@ 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`
* 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 `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` and `TEST_RANDOM_SEED` was introduced in Catch2 3.11.0
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".
> Support for `BAZEL_TEST` was [introduced](https://github.com/catchorg/Catch2/pull/2459) in Catch2 3.1.0 > 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 ## Low-level tools

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 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) [Home](Readme.md#top)

View File

@@ -2,6 +2,7 @@
# Release notes # Release notes
**Contents**<br> **Contents**<br>
[3.11.0](#3110)<br>
[3.10.0](#3100)<br> [3.10.0](#3100)<br>
[3.9.1](#391)<br> [3.9.1](#391)<br>
[3.9.0](#390)<br> [3.9.0](#390)<br>
@@ -70,6 +71,28 @@
[Even Older versions](#even-older-versions)<br> [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 ## 3.10.0
### Fixes ### Fixes

View File

@@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
// Catch v3.10.0 // Catch v3.11.0
// Generated: 2025-08-24 16:18:04.775778 // Generated: 2025-09-30 10:49:12.549018
// ---------------------------------------------------------- // ----------------------------------------------------------
// This file is an amalgamation of multiple different files. // This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly. // You probably shouldn't edit it directly.
@@ -825,6 +825,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();
} }
@@ -889,6 +891,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; }
@@ -950,6 +954,26 @@ 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;
}
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 } // end namespace Catch
@@ -975,28 +999,26 @@ namespace Catch {
ScopedMessage::ScopedMessage( MessageBuilder&& builder ): ScopedMessage::ScopedMessage( MessageBuilder&& builder ):
m_info( CATCH_MOVE(builder.m_info) ) { m_messageId( builder.m_info.sequence ) {
m_info.message = builder.m_stream.str(); MessageInfo info( CATCH_MOVE( builder.m_info ) );
getResultCapture().pushScopedMessage( m_info ); info.message = builder.m_stream.str();
IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
} }
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
m_info( CATCH_MOVE( old.m_info ) ) { m_messageId( old.m_messageId ) {
old.m_moved = true; old.m_moved = true;
} }
ScopedMessage::~ScopedMessage() { ScopedMessage::~ScopedMessage() {
if ( !m_moved ){ if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
getResultCapture().popScopedMessage(m_info);
}
} }
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;
@@ -1042,8 +1064,8 @@ namespace Catch {
case ',': case ',':
if (start != pos && openings.empty()) { if (start != pos && openings.empty()) {
m_messages.emplace_back(macroName, lineInfo, resultType); m_messages.emplace_back(macroName, lineInfo, resultType);
m_messages.back().message = static_cast<std::string>(trimmed(start, pos)); m_messages.back().message += trimmed(start, pos);
m_messages.back().message += " := "; m_messages.back().message += " := "_sr;
start = pos; start = pos;
} }
break; break;
@@ -1052,20 +1074,20 @@ namespace Catch {
} }
assert(openings.empty() && "Mismatched openings"); assert(openings.empty() && "Mismatched openings");
m_messages.emplace_back(macroName, lineInfo, resultType); m_messages.emplace_back(macroName, lineInfo, resultType);
m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1)); m_messages.back().message += trimmed(start, names.size() - 1);
m_messages.back().message += " := "; m_messages.back().message += " := "_sr;
} }
Capturer::~Capturer() { Capturer::~Capturer() {
assert( m_captured == m_messages.size() ); assert( m_captured == m_messages.size() );
for ( size_t i = 0; i < m_captured; ++i ) { for (auto const& message : m_messages) {
m_resultCapture.popScopedMessage( m_messages[i] ); 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( m_messages[index] ); IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
m_captured++; m_captured++;
} }
@@ -1149,7 +1171,6 @@ namespace Catch {
} }
void cleanUp() { void cleanUp() {
cleanupSingletons(); cleanupSingletons();
cleanUpContext();
} }
std::string translateActiveException() { std::string translateActiveException() {
return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
@@ -1161,6 +1182,8 @@ namespace Catch {
#include <cassert> #include <cassert>
#include <cstdio>
#include <cstdlib>
#include <exception> #include <exception>
#include <iomanip> #include <iomanip>
#include <set> #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 } // anon namespace
Session::Session() { Session::Session() {
@@ -1393,6 +1460,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());
@@ -1433,6 +1501,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) {
@@ -1462,9 +1534,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;
} }
@@ -1974,35 +2049,35 @@ namespace Detail {
std::string ret; std::string ret;
// This is enough for the "don't escape invisibles" case, and a good // This is enough for the "don't escape invisibles" case, and a good
// lower bound on the "escape invisibles" case. // lower bound on the "escape invisibles" case.
ret.reserve(string.size() + 2); ret.reserve( string.size() + 2 );
if (!escapeInvisibles) { if ( !escapeInvisibles ) {
ret += '"'; ret += '"';
ret += string; ret += string;
ret += '"'; ret += '"';
return 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 += '"'; ret += '"';
for (char c : string) { for ( size_t i = 0; i < string.size(); ++i ) {
switch (c) { const char c = string[i];
case '\r': if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) {
ret.append("\\r"); write_to( i );
break; if ( c == '\r' ) { ret.append( "\\r" ); }
case '\n': if ( c == '\n' ) { ret.append( "\\n" ); }
ret.append("\\n"); if ( c == '\t' ) { ret.append( "\\t" ); }
break; if ( c == '\f' ) { ret.append( "\\f" ); }
case '\t':
ret.append("\\t");
break;
case '\f':
ret.append("\\f");
break;
default:
ret.push_back(c);
break;
} }
} }
write_to( string.size() );
ret += '"'; ret += '"';
return ret; return ret;
@@ -2279,7 +2354,7 @@ namespace Catch {
} }
Version const& libraryVersion() { Version const& libraryVersion() {
static Version version( 3, 10, 0, "", 0 ); static Version version( 3, 11, 0, "", 0 );
return version; return version;
} }
@@ -2367,8 +2442,14 @@ namespace Catch {
namespace Catch { namespace Catch {
namespace Detail {
void missingCaptureInstance() {
CATCH_INTERNAL_ERROR( "No result capture instance" );
}
} // namespace Detail
IResultCapture::~IResultCapture() = default; IResultCapture::~IResultCapture() = default;
} } // namespace Catch
@@ -3361,6 +3442,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" );
@@ -3515,7 +3599,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
@@ -3639,20 +3727,10 @@ namespace Catch {
namespace Catch { namespace Catch {
Context* Context::currentContext = nullptr; Context Context::currentContext;
void cleanUpContext() {
delete Context::currentContext;
Context::currentContext = nullptr;
}
void Context::createContext() {
currentContext = new Context();
}
Context& getCurrentMutableContext() { Context& getCurrentMutableContext() {
if ( !Context::currentContext ) { Context::createContext(); } return Context::currentContext;
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
return *Context::currentContext;
} }
SimplePcg32& sharedRng() { SimplePcg32& sharedRng() {
@@ -3757,7 +3835,7 @@ namespace Catch {
#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>
@@ -4091,23 +4169,27 @@ namespace Catch {
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, { 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) { static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) {
for (auto const& def : signalDefs) { for (auto const& def : signalDefs) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
reportFatal(def.name); 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. // This stops us from eating debugger breaks etc.
return EXCEPTION_CONTINUE_SEARCH; 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 // For MSVC, we reserve part of the stack memory for handling
// memory overflow structured exception. // memory overflow structured exception.
FatalConditionHandler::FatalConditionHandler() { FatalConditionHandler::FatalConditionHandler() {
@@ -5565,6 +5647,7 @@ ReporterSpec::ReporterSpec(
#include <cstdio> #include <cstdio>
#include <sstream> #include <sstream>
#include <tuple>
#include <vector> #include <vector>
namespace Catch { namespace Catch {
@@ -5576,16 +5659,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() };
} }
} }
@@ -5599,10 +5682,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("");
@@ -6085,28 +6168,6 @@ namespace Catch {
m_reporter->benchmarkFailed( error ); 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 { std::string RunContext::getCurrentTestName() const {
return m_activeTestCase return m_activeTestCase
? m_activeTestCase->getTestCaseInfo().name ? m_activeTestCase->getTestCaseInfo().name
@@ -6433,11 +6494,26 @@ namespace Catch {
} }
} }
IResultCapture& getResultCapture() { void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
if (auto* capture = getCurrentContext().getResultCapture()) Detail::g_messages.push_back( CATCH_MOVE( message ) );
return *capture; }
else
CATCH_INTERNAL_ERROR("No result capture instance"); 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) {

View File

@@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
// Catch v3.10.0 // Catch v3.11.0
// Generated: 2025-08-24 16:18:04.055916 // Generated: 2025-09-30 10:49:11.225746
// ---------------------------------------------------------- // ----------------------------------------------------------
// This file is an amalgamation of multiple different files. // This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly. // You probably shouldn't edit it directly.
@@ -101,6 +101,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
@@ -308,13 +311,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
@@ -564,11 +571,9 @@ namespace Catch {
IConfig const* m_config = nullptr; IConfig const* m_config = nullptr;
IResultCapture* m_resultCapture = nullptr; IResultCapture* m_resultCapture = nullptr;
CATCH_EXPORT static Context* currentContext; CATCH_EXPORT static Context currentContext;
friend Context& getCurrentMutableContext(); friend Context& getCurrentMutableContext();
friend Context const& getCurrentContext(); friend Context const& getCurrentContext();
static void createContext();
friend void cleanUpContext();
public: public:
constexpr IResultCapture* getResultCapture() const { constexpr IResultCapture* getResultCapture() const {
@@ -579,21 +584,14 @@ namespace Catch {
m_resultCapture = resultCapture; m_resultCapture = resultCapture;
} }
constexpr void setConfig( IConfig const* config ) { m_config = config; } constexpr void setConfig( IConfig const* config ) { m_config = config; }
}; };
Context& getCurrentMutableContext(); Context& getCurrentMutableContext();
inline Context const& getCurrentContext() { inline Context const& getCurrentContext() {
// We duplicate the logic from `getCurrentMutableContext` here, return Context::currentContext;
// to avoid paying the call overhead in debug mode.
if ( !Context::currentContext ) { Context::createContext(); }
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
return *Context::currentContext;
} }
void cleanUpContext();
class SimplePcg32; class SimplePcg32;
SimplePcg32& sharedRng(); SimplePcg32& sharedRng();
} }
@@ -1069,10 +1067,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 const& message ) = 0; static void pushScopedMessage( MessageInfo&& message );
virtual void popScopedMessage( MessageInfo const& message ) = 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;
@@ -1108,7 +1105,18 @@ namespace Catch {
virtual void exceptionEarlyReported() = 0; 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 #endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED
@@ -3800,6 +3808,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;
}; };
@@ -3827,6 +3837,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;
@@ -3961,6 +3973,7 @@ namespace Catch {
std::string message; std::string message;
SourceLineInfo lineInfo; SourceLineInfo lineInfo;
ResultWas::OfType type; ResultWas::OfType type;
// The "ID" of the message, used to know when to remove it from reporter context.
unsigned int sequence; unsigned int sequence;
DEPRECATED( "Explicitly use the 'sequence' member instead" ) DEPRECATED( "Explicitly use the 'sequence' member instead" )
@@ -4020,13 +4033,12 @@ namespace Catch {
ScopedMessage( ScopedMessage&& old ) noexcept; ScopedMessage( ScopedMessage&& old ) noexcept;
~ScopedMessage(); ~ScopedMessage();
MessageInfo m_info; unsigned int m_messageId;
bool m_moved = false; bool m_moved = false;
}; };
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 );
@@ -4074,7 +4086,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)
@@ -7466,7 +7478,7 @@ namespace Catch {
#define CATCH_VERSION_MACROS_HPP_INCLUDED #define CATCH_VERSION_MACROS_HPP_INCLUDED
#define CATCH_VERSION_MAJOR 3 #define CATCH_VERSION_MAJOR 3
#define CATCH_VERSION_MINOR 10 #define CATCH_VERSION_MINOR 11
#define CATCH_VERSION_PATCH 0 #define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED #endif // CATCH_VERSION_MACROS_HPP_INCLUDED
@@ -7882,8 +7894,9 @@ namespace Generators {
class FilterGenerator final : public IGenerator<T> { class FilterGenerator final : public IGenerator<T> {
GeneratorWrapper<T> m_generator; GeneratorWrapper<T> m_generator;
Predicate m_predicate; Predicate m_predicate;
static_assert(!std::is_reference<Predicate>::value, "This would most likely result in a dangling reference");
public: public:
template <typename P = Predicate> template <typename P>
FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator): FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator):
m_generator(CATCH_MOVE(generator)), m_generator(CATCH_MOVE(generator)),
m_predicate(CATCH_FORWARD(pred)) m_predicate(CATCH_FORWARD(pred))
@@ -7915,7 +7928,7 @@ namespace Generators {
template <typename T, typename Predicate> template <typename T, typename Predicate>
GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) { 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> template <typename T>
@@ -9555,7 +9568,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.
@@ -10718,11 +10731,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 const& message ) override;
void popScopedMessage( MessageInfo const& message ) 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

@@ -8,7 +8,7 @@
project( project(
'catch2', 'catch2',
'cpp', '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', license: 'BSL-1.0',
meson_version: '>=0.54.1', meson_version: '>=0.54.1',
) )

View File

@@ -98,6 +98,7 @@ set(IMPL_HEADERS
${SOURCES_DIR}/internal/catch_jsonwriter.hpp ${SOURCES_DIR}/internal/catch_jsonwriter.hpp
${SOURCES_DIR}/internal/catch_lazy_expr.hpp ${SOURCES_DIR}/internal/catch_lazy_expr.hpp
${SOURCES_DIR}/internal/catch_leak_detector.hpp ${SOURCES_DIR}/internal/catch_leak_detector.hpp
${SOURCES_DIR}/internal/catch_lifetimebound.hpp
${SOURCES_DIR}/internal/catch_list.hpp ${SOURCES_DIR}/internal/catch_list.hpp
${SOURCES_DIR}/internal/catch_logical_traits.hpp ${SOURCES_DIR}/internal/catch_logical_traits.hpp
${SOURCES_DIR}/internal/catch_message_info.hpp ${SOURCES_DIR}/internal/catch_message_info.hpp

View File

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

View File

@@ -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,26 @@ 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;
}
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 } // 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

@@ -19,28 +19,26 @@ namespace Catch {
ScopedMessage::ScopedMessage( MessageBuilder&& builder ): ScopedMessage::ScopedMessage( MessageBuilder&& builder ):
m_info( CATCH_MOVE(builder.m_info) ) { m_messageId( builder.m_info.sequence ) {
m_info.message = builder.m_stream.str(); MessageInfo info( CATCH_MOVE( builder.m_info ) );
getResultCapture().pushScopedMessage( m_info ); info.message = builder.m_stream.str();
IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
} }
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
m_info( CATCH_MOVE( old.m_info ) ) { m_messageId( old.m_messageId ) {
old.m_moved = true; old.m_moved = true;
} }
ScopedMessage::~ScopedMessage() { ScopedMessage::~ScopedMessage() {
if ( !m_moved ){ if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
getResultCapture().popScopedMessage(m_info);
}
} }
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;
@@ -86,8 +84,8 @@ namespace Catch {
case ',': case ',':
if (start != pos && openings.empty()) { if (start != pos && openings.empty()) {
m_messages.emplace_back(macroName, lineInfo, resultType); m_messages.emplace_back(macroName, lineInfo, resultType);
m_messages.back().message = static_cast<std::string>(trimmed(start, pos)); m_messages.back().message += trimmed(start, pos);
m_messages.back().message += " := "; m_messages.back().message += " := "_sr;
start = pos; start = pos;
} }
break; break;
@@ -96,20 +94,20 @@ namespace Catch {
} }
assert(openings.empty() && "Mismatched openings"); assert(openings.empty() && "Mismatched openings");
m_messages.emplace_back(macroName, lineInfo, resultType); m_messages.emplace_back(macroName, lineInfo, resultType);
m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1)); m_messages.back().message += trimmed(start, names.size() - 1);
m_messages.back().message += " := "; m_messages.back().message += " := "_sr;
} }
Capturer::~Capturer() { Capturer::~Capturer() {
assert( m_captured == m_messages.size() ); assert( m_captured == m_messages.size() );
for ( size_t i = 0; i < m_captured; ++i ) { for (auto const& message : m_messages) {
m_resultCapture.popScopedMessage( m_messages[i] ); 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( m_messages[index] ); IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
m_captured++; m_captured++;
} }

View File

@@ -57,13 +57,12 @@ namespace Catch {
ScopedMessage( ScopedMessage&& old ) noexcept; ScopedMessage( ScopedMessage&& old ) noexcept;
~ScopedMessage(); ~ScopedMessage();
MessageInfo m_info; unsigned int m_messageId;
bool m_moved = false; bool m_moved = false;
}; };
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

@@ -96,7 +96,6 @@ namespace Catch {
} }
void cleanUp() { void cleanUp() {
cleanupSingletons(); cleanupSingletons();
cleanUpContext();
} }
std::string translateActiveException() { std::string translateActiveException() {
return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();

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

@@ -57,39 +57,69 @@ namespace Detail {
} }
} // end unnamed namespace } // end unnamed namespace
std::size_t catch_strnlen( const char* str, std::size_t n ) {
auto ret = std::char_traits<char>::find( str, n, '\0' );
if ( ret != nullptr ) { return static_cast<std::size_t>( ret - str ); }
return n;
}
std::string formatTimeT(std::time_t time) {
#ifdef _MSC_VER
std::tm timeInfo = {};
const auto err = gmtime_s( &timeInfo, &time );
if ( err ) {
return "gmtime from provided timepoint has failed. This "
"happens e.g. with pre-1970 dates using Microsoft libc";
}
#else
std::tm* timeInfo = std::gmtime( &time );
#endif
auto const timeStampSize = sizeof( "2017-01-16T17:06:45Z" );
char timeStamp[timeStampSize];
const char* const fmt = "%Y-%m-%dT%H:%M:%SZ";
#ifdef _MSC_VER
std::strftime( timeStamp, timeStampSize, fmt, &timeInfo );
#else
std::strftime( timeStamp, timeStampSize, fmt, timeInfo );
#endif
return std::string( timeStamp, timeStampSize - 1 );
}
std::string convertIntoString(StringRef string, bool escapeInvisibles) { std::string convertIntoString(StringRef string, bool escapeInvisibles) {
std::string ret; std::string ret;
// This is enough for the "don't escape invisibles" case, and a good // This is enough for the "don't escape invisibles" case, and a good
// lower bound on the "escape invisibles" case. // lower bound on the "escape invisibles" case.
ret.reserve(string.size() + 2); ret.reserve( string.size() + 2 );
if (!escapeInvisibles) { if ( !escapeInvisibles ) {
ret += '"'; ret += '"';
ret += string; ret += string;
ret += '"'; ret += '"';
return 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 += '"'; ret += '"';
for (char c : string) { for ( size_t i = 0; i < string.size(); ++i ) {
switch (c) { const char c = string[i];
case '\r': if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) {
ret.append("\\r"); write_to( i );
break; if ( c == '\r' ) { ret.append( "\\r" ); }
case '\n': if ( c == '\n' ) { ret.append( "\\n" ); }
ret.append("\\n"); if ( c == '\t' ) { ret.append( "\\t" ); }
break; if ( c == '\f' ) { ret.append( "\\f" ); }
case '\t':
ret.append("\\t");
break;
case '\f':
ret.append("\\f");
break;
default:
ret.push_back(c);
break;
} }
} }
write_to( string.size() );
ret += '"'; ret += '"';
return ret; return ret;

View File

@@ -8,7 +8,7 @@
#ifndef CATCH_TOSTRING_HPP_INCLUDED #ifndef CATCH_TOSTRING_HPP_INCLUDED
#define CATCH_TOSTRING_HPP_INCLUDED #define CATCH_TOSTRING_HPP_INCLUDED
#include <ctime>
#include <vector> #include <vector>
#include <cstddef> #include <cstddef>
#include <type_traits> #include <type_traits>
@@ -40,13 +40,9 @@ namespace Catch {
namespace Detail { namespace Detail {
inline std::size_t catch_strnlen(const char *str, std::size_t n) { std::size_t catch_strnlen(const char *str, std::size_t n);
auto ret = std::char_traits<char>::find(str, n, '\0');
if (ret != nullptr) { std::string formatTimeT( std::time_t time );
return static_cast<std::size_t>(ret - str);
}
return n;
}
constexpr StringRef unprintableString = "{?}"_sr; constexpr StringRef unprintableString = "{?}"_sr;
@@ -411,44 +407,38 @@ namespace Catch {
// Separate std::tuple specialization // Separate std::tuple specialization
#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) #if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)
#include <tuple> # include <tuple>
# include <utility>
namespace Catch { namespace Catch {
namespace Detail { namespace Detail {
template< template <typename Tuple, std::size_t... Is>
typename Tuple, void PrintTuple( const Tuple& tuple,
std::size_t N = 0, std::ostream& os,
bool = (N < std::tuple_size<Tuple>::value) std::index_sequence<Is...> ) {
> // 1 + Account for when the tuple is empty
struct TupleElementPrinter { char a[1 + sizeof...( Is )] = {
static void print(const Tuple& tuple, std::ostream& os) { ( ( os << ( Is ? ", " : " " )
os << (N ? ", " : " ") << ::Catch::Detail::stringify( std::get<Is>( tuple ) ) ),
<< ::Catch::Detail::stringify(std::get<N>(tuple)); '\0' )... };
TupleElementPrinter<Tuple, N + 1>::print(tuple, os); (void)a;
} }
};
template< } // namespace Detail
typename Tuple,
std::size_t N
>
struct TupleElementPrinter<Tuple, N, false> {
static void print(const Tuple&, std::ostream&) {}
};
} template <typename... Types>
template<typename ...Types>
struct StringMaker<std::tuple<Types...>> { struct StringMaker<std::tuple<Types...>> {
static std::string convert(const std::tuple<Types...>& tuple) { static std::string convert( const std::tuple<Types...>& tuple ) {
ReusableStringStream rss; ReusableStringStream rss;
rss << '{'; rss << '{';
Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get()); Detail::PrintTuple(
tuple,
rss.get(),
std::make_index_sequence<sizeof...( Types )>{} );
rss << " }"; rss << " }";
return rss.str(); return rss.str();
} }
}; };
} } // namespace Catch
#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER #endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) #if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT)
@@ -635,28 +625,7 @@ struct ratio_string<std::milli> {
const auto systemish = std::chrono::time_point_cast< const auto systemish = std::chrono::time_point_cast<
std::chrono::system_clock::duration>( time_point ); std::chrono::system_clock::duration>( time_point );
const auto as_time_t = std::chrono::system_clock::to_time_t( systemish ); const auto as_time_t = std::chrono::system_clock::to_time_t( systemish );
return ::Catch::Detail::formatTimeT( as_time_t );
#ifdef _MSC_VER
std::tm timeInfo = {};
const auto err = gmtime_s( &timeInfo, &as_time_t );
if ( err ) {
return "gmtime from provided timepoint has failed. This "
"happens e.g. with pre-1970 dates using Microsoft libc";
}
#else
std::tm* timeInfo = std::gmtime( &as_time_t );
#endif
auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
char timeStamp[timeStampSize];
const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
#ifdef _MSC_VER
std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
#else
std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
#endif
return std::string(timeStamp, timeStampSize - 1);
} }
}; };
} }

View File

@@ -36,7 +36,7 @@ namespace Catch {
} }
Version const& libraryVersion() { Version const& libraryVersion() {
static Version version( 3, 10, 0, "", 0 ); static Version version( 3, 11, 0, "", 0 );
return version; return version;
} }

View File

@@ -9,7 +9,7 @@
#define CATCH_VERSION_MACROS_HPP_INCLUDED #define CATCH_VERSION_MACROS_HPP_INCLUDED
#define CATCH_VERSION_MAJOR 3 #define CATCH_VERSION_MAJOR 3
#define CATCH_VERSION_MINOR 10 #define CATCH_VERSION_MINOR 11
#define CATCH_VERSION_PATCH 0 #define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED #endif // CATCH_VERSION_MACROS_HPP_INCLUDED

View File

@@ -7,7 +7,14 @@
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
#include <catch2/interfaces/catch_interfaces_capture.hpp> #include <catch2/interfaces/catch_interfaces_capture.hpp>
#include <catch2/internal/catch_enforce.hpp>
namespace Catch { namespace Catch {
namespace Detail {
void missingCaptureInstance() {
CATCH_INTERNAL_ERROR( "No result capture instance" );
}
} // namespace Detail
IResultCapture::~IResultCapture() = default; IResultCapture::~IResultCapture() = default;
} } // namespace Catch

View File

@@ -10,6 +10,7 @@
#include <string> #include <string>
#include <catch2/internal/catch_context.hpp>
#include <catch2/internal/catch_stringref.hpp> #include <catch2/internal/catch_stringref.hpp>
#include <catch2/internal/catch_result_type.hpp> #include <catch2/internal/catch_result_type.hpp>
#include <catch2/internal/catch_unique_ptr.hpp> #include <catch2/internal/catch_unique_ptr.hpp>
@@ -62,10 +63,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 const& message ) = 0; static void pushScopedMessage( MessageInfo&& message );
virtual void popScopedMessage( MessageInfo const& message ) = 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;
@@ -101,7 +101,18 @@ namespace Catch {
virtual void exceptionEarlyReported() = 0; 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 #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

@@ -6,25 +6,14 @@
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
#include <catch2/internal/catch_context.hpp> #include <catch2/internal/catch_context.hpp>
#include <catch2/internal/catch_noncopyable.hpp>
#include <catch2/internal/catch_random_number_generator.hpp> #include <catch2/internal/catch_random_number_generator.hpp>
namespace Catch { namespace Catch {
Context* Context::currentContext = nullptr; Context Context::currentContext;
void cleanUpContext() {
delete Context::currentContext;
Context::currentContext = nullptr;
}
void Context::createContext() {
currentContext = new Context();
}
Context& getCurrentMutableContext() { Context& getCurrentMutableContext() {
if ( !Context::currentContext ) { Context::createContext(); } return Context::currentContext;
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
return *Context::currentContext;
} }
SimplePcg32& sharedRng() { SimplePcg32& sharedRng() {

View File

@@ -19,11 +19,9 @@ namespace Catch {
IConfig const* m_config = nullptr; IConfig const* m_config = nullptr;
IResultCapture* m_resultCapture = nullptr; IResultCapture* m_resultCapture = nullptr;
CATCH_EXPORT static Context* currentContext; CATCH_EXPORT static Context currentContext;
friend Context& getCurrentMutableContext(); friend Context& getCurrentMutableContext();
friend Context const& getCurrentContext(); friend Context const& getCurrentContext();
static void createContext();
friend void cleanUpContext();
public: public:
constexpr IResultCapture* getResultCapture() const { constexpr IResultCapture* getResultCapture() const {
@@ -34,21 +32,14 @@ namespace Catch {
m_resultCapture = resultCapture; m_resultCapture = resultCapture;
} }
constexpr void setConfig( IConfig const* config ) { m_config = config; } constexpr void setConfig( IConfig const* config ) { m_config = config; }
}; };
Context& getCurrentMutableContext(); Context& getCurrentMutableContext();
inline Context const& getCurrentContext() { inline Context const& getCurrentContext() {
// We duplicate the logic from `getCurrentMutableContext` here, return Context::currentContext;
// to avoid paying the call overhead in debug mode.
if ( !Context::currentContext ) { Context::createContext(); }
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
return *Context::currentContext;
} }
void cleanUpContext();
class SimplePcg32; class SimplePcg32;
SimplePcg32& sharedRng(); SimplePcg32& sharedRng();
} }

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

@@ -86,23 +86,27 @@ namespace Catch {
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, { 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) { static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) {
for (auto const& def : signalDefs) { for (auto const& def : signalDefs) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
reportFatal(def.name); 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. // This stops us from eating debugger breaks etc.
return EXCEPTION_CONTINUE_SEARCH; 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 // For MSVC, we reserve part of the stack memory for handling
// memory overflow structured exception. // memory overflow structured exception.
FatalConditionHandler::FatalConditionHandler() { FatalConditionHandler::FatalConditionHandler() {

View File

@@ -0,0 +1,24 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_LIFETIMEBOUND_HPP_INCLUDED
#define CATCH_LIFETIMEBOUND_HPP_INCLUDED
#if !defined( __has_cpp_attribute )
# define CATCH_ATTR_LIFETIMEBOUND
#elif __has_cpp_attribute( msvc::lifetimebound )
# define CATCH_ATTR_LIFETIMEBOUND [[msvc::lifetimebound]]
#elif __has_cpp_attribute( clang::lifetimebound )
# define CATCH_ATTR_LIFETIMEBOUND [[clang::lifetimebound]]
#elif __has_cpp_attribute( lifetimebound )
# define CATCH_ATTR_LIFETIMEBOUND [[lifetimebound]]
#else
# define CATCH_ATTR_LIFETIMEBOUND
#endif
#endif // CATCH_LIFETIMEBOUND_HPP_INCLUDED

View File

@@ -26,6 +26,7 @@ namespace Catch {
std::string message; std::string message;
SourceLineInfo lineInfo; SourceLineInfo lineInfo;
ResultWas::OfType type; ResultWas::OfType type;
// The "ID" of the message, used to know when to remove it from reporter context.
unsigned int sequence; unsigned int sequence;
DEPRECATED( "Explicitly use the 'sequence' member instead" ) DEPRECATED( "Explicitly use the 'sequence' member instead" )

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 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 { std::string RunContext::getCurrentTestName() const {
return m_activeTestCase return m_activeTestCase
? m_activeTestCase->getTestCaseInfo().name ? m_activeTestCase->getTestCaseInfo().name
@@ -831,11 +809,26 @@ namespace Catch {
} }
} }
IResultCapture& getResultCapture() { void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
if (auto* capture = getCurrentContext().getResultCapture()) Detail::g_messages.push_back( CATCH_MOVE( message ) );
return *capture; }
else
CATCH_INTERNAL_ERROR("No result capture instance"); 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) {

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 const& message ) override;
void popScopedMessage( MessageInfo const& message ) 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -662,5 +662,38 @@ foreach(reporterName # "Automake" - the simple .trs format does not support any
) )
endforeach() 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) list(APPEND CATCH_TEST_TARGETS SelfTest)
set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE) set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE)

View File

@@ -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()
@@ -109,18 +109,6 @@ set_tests_properties(CATCH_CONFIG_DISABLE_EXCEPTIONS-1
FAIL_REGULAR_EXPRESSION "abort;terminate;fatal" 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. # We must now test this works without the build flag.
add_executable(BazelReporterNoCatchConfig ${TESTS_DIR}/X30-BazelReporter.cpp) add_executable(BazelReporterNoCatchConfig ${TESTS_DIR}/X30-BazelReporter.cpp)
target_link_libraries(BazelReporterNoCatchConfig Catch2WithMain) target_link_libraries(BazelReporterNoCatchConfig Catch2WithMain)
@@ -429,6 +417,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

@@ -6,11 +6,13 @@
// SPDX-License-Identifier: BSL-1.0 // 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/internal/catch_enum_values_registry.hpp>
#include <catch2/matchers/catch_matchers_string.hpp> #include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp> #include <catch2/matchers/catch_matchers_vector.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <chrono> #include <chrono>
@@ -139,3 +141,26 @@ TEST_CASE( "Exception thrown inside stringify does not fail the test", "[toStrin
ThrowsOnStringification tos; ThrowsOnStringification tos;
CHECK( tos == 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 );
};
}

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}')