mirror of
https://github.com/catchorg/Catch2.git
synced 2026-05-20 15:44:54 +02:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29c9844f68 | |||
| 56024c04e4 | |||
| edfed6c04e | |||
| 75bfcc3f30 | |||
| 056e4fe88d | |||
| 3a0cf7e75f | |||
| b6c7b217d4 | |||
| 045ac7acce | |||
| 72671fdbdf | |||
| 120827d4d6 | |||
| daadf42a0e | |||
| d079ee13ab | |||
| 0056cd4efb | |||
| de7e863013 | |||
| 024aec9729 | |||
| 9eca713a1f | |||
| 44c597f074 | |||
| 6aedc79870 | |||
| fcbf006c78 | |||
| 2580eadc42 | |||
| b59f4f3522 | |||
| cd4fc88e2a | |||
| a50ac2f681 | |||
| b81ef2aa2e | |||
| ec4dcbf9cb | |||
| b66b89374e | |||
| b7e31c9ab3 | |||
| 88abf9bf32 | |||
| 970ec144f5 | |||
| eb3811c555 | |||
| 343cc059fe | |||
| 97091636d0 | |||
| f80956a43a | |||
| 32eac2d1bb | |||
| e849735e11 | |||
| d26f763180 | |||
| 5e44382423 | |||
| 985a3f4460 | |||
| a1faad9315 | |||
| 31ee3beb0a | |||
| 3b853aa9fb | |||
| 49d79e9e9c | |||
| 33e6fd217a | |||
| a58df2d7c5 | |||
| a9223b2bb3 | |||
| 363ca5af18 | |||
| cb6d713774 | |||
| 8e4ab5dd8f | |||
| 8219ed79f2 | |||
| b3fb4b9fea | |||
| 6500dc8149 | |||
| 4be9de6c54 | |||
| 91b3b3bf40 | |||
| a00d654437 | |||
| 756ae05d30 | |||
| a2e41916f2 | |||
| 0e772cc0d2 | |||
| 3bcd0a4e74 | |||
| f7e7fa0983 | |||
| b626e4c7ae | |||
| 434bf55d47 | |||
| 9048c24fa7 | |||
| 8ed8c431c6 | |||
| dc3a4ea41a | |||
| cd93d202e0 | |||
| 227af796b4 | |||
| 33adb4c779 | |||
| 25319fd304 | |||
| 85c4bad86b | |||
| 582200a1f8 | |||
| f4e05a67bb | |||
| fb2e4fbe41 | |||
| 78a9518a28 | |||
| 3e82ef9317 | |||
| 7cad6d7539 | |||
| 644821ce28 | |||
| 03c62cdf2e | |||
| 9a3d68315b | |||
| bcd4116df7 | |||
| 9b3f508a1b | |||
| c5e0ef4e67 | |||
| 17fe5eaa5c | |||
| fbfd13501c | |||
| ccabd4de89 | |||
| a1c7ee115f | |||
| d547cae549 |
+1
-1
@@ -78,5 +78,5 @@ WarningsAsErrors: >-
|
||||
readability-duplicate-include,
|
||||
HeaderFilterRegex: '.*\.(c|cxx|cpp)$'
|
||||
FormatStyle: none
|
||||
CheckOptions: {}
|
||||
CheckOptions: []
|
||||
...
|
||||
|
||||
@@ -2,6 +2,10 @@ name: Linux Builds (Bazel)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build_and_test_ubuntu:
|
||||
name: Linux Ubuntu 22.04 Bazel build <GCC 11.2.0>
|
||||
|
||||
@@ -2,6 +2,10 @@ name: Linux Builds (Meson)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: meson ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}}
|
||||
|
||||
@@ -5,6 +5,10 @@ name: Linux Builds (Complex)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{matrix.build_description}}, ${{matrix.cxx}}, C++${{matrix.std}} ${{matrix.build_type}}
|
||||
@@ -90,7 +94,7 @@ jobs:
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build -j --output-on-failure
|
||||
run: ctest --test-dir build -j
|
||||
|
||||
clang-tidy:
|
||||
name: clang-tidy
|
||||
|
||||
@@ -2,6 +2,10 @@ name: Linux Builds (Basic)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}}
|
||||
@@ -102,4 +106,4 @@ jobs:
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build -j --output-on-failure
|
||||
run: ctest --test-dir build -j
|
||||
|
||||
@@ -2,15 +2,17 @@ name: Mac Builds
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# From macos-14 forward, the baseline "macos-X" image is Arm based,
|
||||
# and not Intel based.
|
||||
runs-on: ${{matrix.image}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [macos-13, macos-14, macos-15]
|
||||
image: [macos-14, macos-15, macos-15-intel]
|
||||
build_type: [Debug, Release]
|
||||
std: [14, 17]
|
||||
|
||||
@@ -29,4 +31,4 @@ jobs:
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build -j --output-on-failure
|
||||
run: ctest --test-dir build -j
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Mac Sanitizer Builds
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{matrix.image}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [macos-15, macos-15-intel]
|
||||
build_type: [Debug]
|
||||
std: [17]
|
||||
sanitizer: [thread, address]
|
||||
include:
|
||||
- sanitizer: thread
|
||||
preset: basic-tests
|
||||
filter: -R ThreadSafetyTests
|
||||
- sanitizer: address
|
||||
preset: most-tests
|
||||
filter:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Configure
|
||||
|
||||
run: |
|
||||
CFXXFLAGS=-fsanitize=${{matrix.sanitizer}},undefined
|
||||
cmake --preset ${{matrix.preset}} -GNinja \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
|
||||
-DCATCH_BUILD_EXTRA_TESTS=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
- name: Test
|
||||
run: ctest --test-dir build ${{matrix.filter}}
|
||||
@@ -2,6 +2,10 @@ name: Windows Builds (Basic)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
CTEST_NO_TESTS_ACTION: error
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{matrix.os}}, ${{matrix.std}}, ${{matrix.build_type}}, ${{matrix.platform}}
|
||||
@@ -27,5 +31,5 @@ jobs:
|
||||
shell: cmd
|
||||
|
||||
- name: Run tests
|
||||
run: ctest --test-dir build -C ${{matrix.build_type}} -j %NUMBER_OF_PROCESSORS% --output-on-failure
|
||||
run: ctest --test-dir build -C ${{matrix.build_type}} -j %NUMBER_OF_PROCESSORS%
|
||||
shell: cmd
|
||||
|
||||
+2
-2
@@ -76,8 +76,8 @@ expand_template(
|
||||
"#cmakedefine CATCH_CONFIG_WINDOWS_SEH": "",
|
||||
"#cmakedefine CATCH_CONFIG_USE_BUILTIN_CONSTANT_P": "",
|
||||
"#cmakedefine CATCH_CONFIG_NO_USE_BUILTIN_CONSTANT_P": "",
|
||||
"#cmakedefine CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS": "",
|
||||
"#cmakedefine CATCH_CONFIG_NO_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS": "",
|
||||
"#cmakedefine CATCH_CONFIG_THREAD_SAFE_ASSERTIONS": "",
|
||||
"#cmakedefine CATCH_CONFIG_NO_THREAD_SAFE_ASSERTIONS": "",
|
||||
},
|
||||
template = "src/catch2/catch_user_config.hpp.in",
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ set(_OverridableOptions
|
||||
"EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT"
|
||||
"USE_BUILTIN_CONSTANT_P"
|
||||
"DEPRECATION_ANNOTATIONS"
|
||||
"EXPERIMENTAL_THREAD_SAFE_ASSERTIONS"
|
||||
"THREAD_SAFE_ASSERTIONS"
|
||||
)
|
||||
|
||||
foreach(OptionName ${_OverridableOptions})
|
||||
|
||||
+15
-3
@@ -20,6 +20,7 @@ cmake_dependent_option(CATCH_BUILD_TESTING "Build the SelfTest project" ON "CATC
|
||||
cmake_dependent_option(CATCH_BUILD_EXAMPLES "Build code examples" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
|
||||
cmake_dependent_option(CATCH_BUILD_EXTRA_TESTS "Build extra tests" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
|
||||
cmake_dependent_option(CATCH_BUILD_FUZZERS "Build fuzzers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
|
||||
cmake_dependent_option(CATCH_BUILD_BENCHMARKS "Build the benchmarks" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
|
||||
cmake_dependent_option(CATCH_ENABLE_COVERAGE "Generate coverage for codecov.io" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
|
||||
cmake_dependent_option(CATCH_ENABLE_WERROR "Enables Werror during build" ON "CATCH_DEVELOPMENT_BUILD" OFF)
|
||||
cmake_dependent_option(CATCH_BUILD_SURROGATES "Enable generating and building surrogate TUs for the main headers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
|
||||
@@ -34,7 +35,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
endif()
|
||||
|
||||
project(Catch2
|
||||
VERSION 3.9.0 # CML version placeholder, don't delete
|
||||
VERSION 3.13.0 # CML version placeholder, don't delete
|
||||
LANGUAGES CXX
|
||||
HOMEPAGE_URL "https://github.com/catchorg/Catch2"
|
||||
DESCRIPTION "A modern, C++-native, unit test framework."
|
||||
@@ -77,6 +78,11 @@ set(SELF_TEST_DIR ${CATCH_DIR}/tests/SelfTest)
|
||||
# We need to bring-in the variables defined there to this scope
|
||||
add_subdirectory(src)
|
||||
|
||||
if (CATCH_BUILD_BENCHMARKS)
|
||||
set(CMAKE_FOLDER "benchmarks")
|
||||
add_subdirectory(benchmarks)
|
||||
endif()
|
||||
|
||||
# Build tests only if requested
|
||||
if(BUILD_TESTING AND CATCH_BUILD_TESTING AND NOT_SUBPROJECT)
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
@@ -195,17 +201,23 @@ if(NOT_SUBPROJECT)
|
||||
"set(include_dir \"${CMAKE_INSTALL_INCLUDEDIR}\")"
|
||||
"set(lib_dir \"${CMAKE_INSTALL_LIBDIR}\")"
|
||||
[[
|
||||
message(STATUS "DESTDIR: $ENV{DESTDIR}")
|
||||
set(DESTDIR_PREFIX "")
|
||||
if (DEFINED ENV{DESTDIR})
|
||||
set(DESTDIR_PREFIX "$ENV{DESTDIR}")
|
||||
endif ()
|
||||
message(STATUS "PREFIX: ${DESTDIR_PREFIX}")
|
||||
set(lib_name "$<TARGET_FILE_BASE_NAME:Catch2>")
|
||||
configure_file(
|
||||
"${impl_pc_file}"
|
||||
"${CMAKE_INSTALL_PREFIX}/${install_pkgconfdir}/catch2.pc"
|
||||
"${DESTDIR_PREFIX}${CMAKE_INSTALL_PREFIX}/${install_pkgconfdir}/catch2.pc"
|
||||
@ONLY
|
||||
)
|
||||
|
||||
set(lib_name "$<TARGET_FILE_BASE_NAME:Catch2WithMain>")
|
||||
configure_file(
|
||||
"${main_pc_file}"
|
||||
"${CMAKE_INSTALL_PREFIX}/${install_pkgconfdir}/catch2-with-main.pc"
|
||||
"${DESTDIR_PREFIX}${CMAKE_INSTALL_PREFIX}/${install_pkgconfdir}/catch2-with-main.pc"
|
||||
@ONLY
|
||||
)
|
||||
]]
|
||||
|
||||
+11
-2
@@ -15,14 +15,23 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "all-tests",
|
||||
"name": "most-tests",
|
||||
"inherits": "basic-tests",
|
||||
"displayName": "Full development build",
|
||||
"description": "Enables development build with examples and ALL tests",
|
||||
"description": "Enables development build with extended set of tests (still relatively cheap to build)",
|
||||
"cacheVariables": {
|
||||
"CATCH_BUILD_EXAMPLES": "ON",
|
||||
"CATCH_BUILD_EXTRA_TESTS": "ON",
|
||||
"CATCH_BUILD_SURROGATES": "ON",
|
||||
"CATCH_BUILD_BENCHMARKS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "all-tests",
|
||||
"inherits": "most-tests",
|
||||
"displayName": "Full development build",
|
||||
"description": "Enables development build with examples and ALL tests",
|
||||
"cacheVariables": {
|
||||
"CATCH_ENABLE_CONFIGURE_TESTS": "ON",
|
||||
"CATCH_ENABLE_CMAKE_HELPER_TESTS": "ON"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
// 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
|
||||
*/
|
||||
namespace {
|
||||
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 )
|
||||
}
|
||||
@@ -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; }());
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -24,7 +24,8 @@ Once you're up and running consider the following reference material.
|
||||
* [String Conversions](tostring.md#top)
|
||||
|
||||
**Running:**
|
||||
* [Command line](command-line.md#top)
|
||||
* [Command line reference](command-line.md#top)
|
||||
* [Running specific section/generator](filtering-execution-path.md#top)
|
||||
|
||||
**Odds and ends:**
|
||||
* [Frequently Asked Questions (FAQ)](faq.md#top)
|
||||
|
||||
@@ -66,17 +66,24 @@ test execution. Specifically it understands
|
||||
* JUnit output path via `XML_OUTPUT_FILE`
|
||||
* Test filtering via `TESTBRIDGE_TEST_ONLY`
|
||||
* Test sharding via `TEST_SHARD_INDEX`, `TEST_TOTAL_SHARDS`, and `TEST_SHARD_STATUS_FILE`
|
||||
* Creating a file to signal premature test exit via `TEST_PREMATURE_EXIT_FILE`
|
||||
* Setting the RNG seed via `TEST_RANDOM_SEED`
|
||||
|
||||
> Support for `XML_OUTPUT_FILE` was [introduced](https://github.com/catchorg/Catch2/pull/2399) in Catch2 3.0.1
|
||||
|
||||
> Support for `TESTBRIDGE_TEST_ONLY` and sharding was introduced in Catch2 3.2.0
|
||||
|
||||
> Support for `TEST_PREMATURE_EXIT_FILE` and `TEST_RANDOM_SEED` was introduced in Catch2 3.11.0
|
||||
|
||||
This integration is enabled via either a [compile time configuration
|
||||
option](configuration.md#bazel-support), or via `BAZEL_TEST` environment
|
||||
variable set to "1".
|
||||
|
||||
> Support for `BAZEL_TEST` was [introduced](https://github.com/catchorg/Catch2/pull/2459) in Catch2 3.1.0
|
||||
|
||||
Note that if both the Bazel environment var and command line option for
|
||||
something are used, the environment variable wins.
|
||||
|
||||
|
||||
## Low-level tools
|
||||
|
||||
|
||||
+30
-35
@@ -32,6 +32,7 @@
|
||||
[Test Sharding](#test-sharding)<br>
|
||||
[Allow running the binary without tests](#allow-running-the-binary-without-tests)<br>
|
||||
[Output verbosity](#output-verbosity)<br>
|
||||
[Create file to guard against silent early termination](#create-file-to-guard-against-silent-early-termination)<br>
|
||||
|
||||
Catch works quite nicely without any command line options at all - but for those times when you want greater control the following options are available.
|
||||
Click one of the following links to take you straight to that option - or scroll on to browse the available options.
|
||||
@@ -51,6 +52,8 @@ Click one of the following links to take you straight to that option - or scroll
|
||||
<a href="#reporting-timings"> ` -d, --durations`</a><br />
|
||||
<a href="#input-file"> ` -f, --input-file`</a><br />
|
||||
<a href="#run-section"> ` -c, --section`</a><br />
|
||||
<a href="#path-filtering"> ` -g, --generator-index`</a><br />
|
||||
<a href="#path-filtering"> ` -p, --path-filter`</a><br />
|
||||
<a href="#filenames-as-tags"> ` -#, --filenames-as-tags`</a><br />
|
||||
|
||||
|
||||
@@ -357,10 +360,13 @@ There are currently two warnings implemented:
|
||||
// (e.g. `REQUIRE`) is encountered.
|
||||
UnmatchedTestSpec // Fail test run if any of the CLI test specs did
|
||||
// not match any tests.
|
||||
InfiniteGenerators // Fail if GENERATE would run infinitely
|
||||
```
|
||||
|
||||
> `UnmatchedTestSpec` was introduced in Catch2 3.0.1.
|
||||
|
||||
> `InfiniteGenerators` was introduced in Catch2 3.13.0
|
||||
|
||||
|
||||
<a id="reporting-timings"></a>
|
||||
## Reporting timings
|
||||
@@ -528,45 +534,19 @@ Prints the command line arguments to stdout
|
||||
|
||||
|
||||
<a id="run-section"></a>
|
||||
## Specify the section to run
|
||||
<a id="path-filtering"></a>
|
||||
## Specify the section/generator element to run
|
||||
<pre>-c, --section <section name></pre>
|
||||
<pre>-g, --generator-index <index in generator></pre>
|
||||
<pre>-p, --path-filter <path filter spec></pre>
|
||||
|
||||
To limit execution to a specific section within a test case, use this option one or more times.
|
||||
To narrow to sub-sections use multiple instances, where each subsequent instance specifies a deeper nesting level.
|
||||
> The generator and generic path filtering was added in Catch2 3.13.0
|
||||
|
||||
E.g. if you have:
|
||||
These arguments allow you to run specific section(s) in a test case, or
|
||||
only get specific element from a generator. All the variants form a shared
|
||||
stack of filters.
|
||||
|
||||
<pre>
|
||||
TEST_CASE( "Test" ) {
|
||||
SECTION( "sa" ) {
|
||||
SECTION( "sb" ) {
|
||||
/*...*/
|
||||
}
|
||||
SECTION( "sc" ) {
|
||||
/*...*/
|
||||
}
|
||||
}
|
||||
SECTION( "sd" ) {
|
||||
/*...*/
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
Then you can run `sb` with:
|
||||
<pre>./MyExe Test -c sa -c sb</pre>
|
||||
|
||||
Or run just `sd` with:
|
||||
<pre>./MyExe Test -c sd</pre>
|
||||
|
||||
To run all of `sa`, including `sb` and `sc` use:
|
||||
<pre>./MyExe Test -c sa</pre>
|
||||
|
||||
There are some limitations of this feature to be aware of:
|
||||
- Code outside of sections being skipped will still be executed - e.g. any set-up code in the TEST_CASE before the
|
||||
start of the first section.</br>
|
||||
- At time of writing, wildcards are not supported in section names.
|
||||
- If you specify a section without narrowing to a test case first then all test cases will be executed
|
||||
(but only matching sections within them).
|
||||
[See the full documentation of path filtering for more details](filtering-execution-path.md#top)
|
||||
|
||||
|
||||
<a id="filenames-as-tags"></a>
|
||||
@@ -649,6 +629,21 @@ ignored.
|
||||
Verbosity defaults to _normal_.
|
||||
|
||||
|
||||
## Create file to guard against silent early termination
|
||||
<pre>--premature-exit-guard-file <path></pre>
|
||||
|
||||
> Introduced in Catch2 3.11.0
|
||||
|
||||
Tells Catch2 to create an empty file at specified path before the tests
|
||||
start, and delete it after the tests finish. If the file is present after
|
||||
the process stops, it can be assumed that the testing binary exited
|
||||
prematurely, e.g. due to the OOM killer.
|
||||
|
||||
All directories in the path must already exist. If this option is used
|
||||
and Catch2 cannot create the file (e.g. the location is not writable),
|
||||
the test run will fail.
|
||||
|
||||
|
||||
---
|
||||
|
||||
[Home](Readme.md#top)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
[Disabling deprecation warnings](#disabling-deprecation-warnings)<br>
|
||||
[Overriding Catch's debug break (`-b`)](#overriding-catchs-debug-break--b)<br>
|
||||
[Static analysis support](#static-analysis-support)<br>
|
||||
[Experimental thread safety](#experimental-thread-safety)<br>
|
||||
[Thread safety in assertions (and messages)](#thread-safety-in-assertions-and-messages)<br>
|
||||
|
||||
Catch2 is designed to "just work" as much as possible, and most of the
|
||||
configuration options below are changed automatically during compilation,
|
||||
@@ -29,7 +29,7 @@ with the same name.
|
||||
## Prefixing Catch macros
|
||||
|
||||
CATCH_CONFIG_PREFIX_ALL // Prefix all macros with CATCH_
|
||||
CATCH_CONFIG_PREFIX_MESSAGES // Prefix only INFO, UNSCOPED_INFO, WARN and CAPTURE
|
||||
CATCH_CONFIG_PREFIX_MESSAGES // Prefix only message macros ((UNSCOPED_)INFO, WARN, (UNSCOPED_)CAPTURE)
|
||||
|
||||
To keep test code clean and uncluttered Catch uses short macro names (e.g. ```TEST_CASE``` and ```REQUIRE```). Occasionally these may conflict with identifiers from platform headers or the system under test. In this case the above identifier can be defined. This will cause all the Catch user macros to be prefixed with ```CATCH_``` (e.g. ```CATCH_TEST_CASE``` and ```CATCH_REQUIRE```).
|
||||
|
||||
@@ -316,17 +316,21 @@ no backwards compatibility guarantees._
|
||||
are not meant to be runnable, only "scannable".
|
||||
|
||||
|
||||
## Experimental thread safety
|
||||
|
||||
<a id="experimental-thread-safety"></a>
|
||||
## Thread safety in assertions (and messages)
|
||||
|
||||
> Introduced in Catch2 3.9.0
|
||||
|
||||
> Made non-experimental in Catch2 3.12.0
|
||||
|
||||
Catch2 can optionally support thread-safe assertions, that means, multiple
|
||||
user-spawned threads can use the assertion macros at the same time. Due
|
||||
to the performance cost this imposes even on single-threaded usage, Catch2
|
||||
defaults to non-thread-safe assertions.
|
||||
|
||||
CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS // enables thread safe assertions
|
||||
CATCH_CONFIG_NO_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS // force-disables thread safe assertions
|
||||
CATCH_CONFIG_THREAD_SAFE_ASSERTIONS // enables thread safe assertions
|
||||
CATCH_CONFIG_NO_THREAD_SAFE_ASSERTIONS // force-disables thread safe assertions
|
||||
|
||||
See [the documentation on thread safety in Catch2](thread-safety.md#top)
|
||||
for details on which macros are safe and other notes.
|
||||
|
||||
@@ -48,6 +48,32 @@ If you are mutating the fixture instance from within the test case, and
|
||||
want to keep doing so in the future, mark the mutated members as `mutable`.
|
||||
|
||||
|
||||
### Section-only filtering with `-c/--section`
|
||||
|
||||
> Deprecated in Catch2 3.13.0
|
||||
|
||||
Currently, if you use only `-c/--section` parameters to decide which
|
||||
sections to enter, the filtering ignores generators completely. In the
|
||||
future, using only `-c/--section` will behave the same way as if you
|
||||
specified the filters through the new `-p/--path-filter` parameter, which
|
||||
means that generators are taken into account.
|
||||
|
||||
|
||||
### Generator interfaces
|
||||
|
||||
#### Defaulted `UntypedGeneratorBase::isFinite()`
|
||||
|
||||
> Deprecated in Catch2 3.13.0
|
||||
|
||||
The `UntypedGeneratorBase` currently provides a default implementation
|
||||
for `isFinite` that always returns `true`. This was done to keep backwards
|
||||
compatibility with pre-existing generators, as infinite generators can
|
||||
be diagnosed as errors in some cases.
|
||||
|
||||
In the future, all generators will be expected to override `isFinite`.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
[Home](Readme.md#top)
|
||||
|
||||
@@ -10,7 +10,7 @@ in-memory logs if they are not needed (the test case passed).
|
||||
Unlike reporters, each registered event listener is always active. Event
|
||||
listeners are always notified before reporter(s).
|
||||
|
||||
To write your own event listener, you should derive from `Catch::TestEventListenerBase`,
|
||||
To write your own event listener, you should derive from `Catch::EventListenerBase`,
|
||||
as it provides empty stubs for all reporter events, allowing you to
|
||||
only override events you care for. Afterwards you have to register it
|
||||
with Catch2 using `CATCH_REGISTER_LISTENER` macro, so that Catch2 knows
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
<a id="top"></a>
|
||||
# How to run specific section/generator
|
||||
|
||||
> The generator and generic path filtering was added in Catch2 3.13.0
|
||||
|
||||
Catch2 supports picking specific path through a test case by filtering
|
||||
sections and generator indices to run through. This is done by using one
|
||||
of the three commandline parameters, one or more times.
|
||||
|
||||
```
|
||||
-c, --section <section name>
|
||||
-g, --generator-index <index in generator>
|
||||
-p, --path-filter <path filter spec>
|
||||
```
|
||||
|
||||
All the variants form a shared stack of filters, but if you use only
|
||||
`-c`/`--section` form to specify section filters, you will get the old
|
||||
behaviour, which does not affect generators at all. If you also use either
|
||||
`-g`/`--generator-index`, or `-p`/`--path-filter`, you will get the new
|
||||
behaviour, which can also filter generator elements.
|
||||
|
||||
Both the new and old filter behaviours include some potentially surprising
|
||||
things:
|
||||
* Code outside of sections being skipped will still be executed. E.g.
|
||||
any setup code in the TEST_CASE that lives outside of sections.
|
||||
* Path filters filter the prefix of the path. So if you specify single
|
||||
filter, it affects only the top level sections/generator, with their
|
||||
child sections/generators being unfiltered.
|
||||
* Path filters are independent of test case selection, Catch2 will try
|
||||
to follow the path filters in all selected test cases. This means
|
||||
that if you specify path filters without a test case filter, Catch2
|
||||
will try to apply the path filters inside every registered test case.
|
||||
|
||||
## Old behaviour
|
||||
|
||||
> The old behaviour was deprecated in Catch2 3.13.0
|
||||
|
||||
```
|
||||
-c, --section <section name>
|
||||
```
|
||||
The argument to `-c`/`--section` can be any arbitrary string. When Catch2
|
||||
is deciding whether to enter a section, it will check its trimmed name
|
||||
against the appropriate trimmed section filter. If they are the same,
|
||||
the section can be opened. If not, Catch2 will skip over that section.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
#### Simple section nesting
|
||||
Given
|
||||
```cpp
|
||||
TEST_CASE( "foo" ) {
|
||||
REQUIRE( true );
|
||||
SECTION( "A" ) {
|
||||
SECTION( "A1" ) { REQUIRE( true ); }
|
||||
SECTION( "A2" ) { REQUIRE( true ); }
|
||||
}
|
||||
SECTION( "B" ) {
|
||||
SECTION( "B1" ) { REQUIRE( true ); }
|
||||
SECTION( "B2" ) { REQUIRE( true ); }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `./tests foo -c A` runs section "A" and both of its subsections,
|
||||
resulting in 4 assertions.
|
||||
* `./tests foo -c A -c B` runs section "A", but none of its subsections,
|
||||
resulting in 1 assertion (the one before "A").
|
||||
* `./tests foo -c A -c A1` runs section "A" and only the "A1" subsection,
|
||||
resulting in 2 assertions.
|
||||
|
||||
|
||||
#### Sections with nested generators
|
||||
Note that old behaviour completely _ignores_ generators. This means both
|
||||
that they can't be filtered, but also that they aren't taken into account
|
||||
for the filter depth. In other words, given
|
||||
```cpp
|
||||
TEST_CASE( "bar" ) {
|
||||
REQUIRE( true );
|
||||
SECTION( "A" ) { REQUIRE( true ); }
|
||||
SECTION( "B" ) {
|
||||
auto i = GENERATE( 1, 2, 3 );
|
||||
DYNAMIC_SECTION( "i=" << i ) {
|
||||
REQUIRE( true );
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
* `./tests bar -c A` results in 2 assertions.
|
||||
* `./tests bar -c B -c i=2` results in 4 assertions, because the whole
|
||||
generator in section "B" has to be used up, but the dynamic section is
|
||||
only entered when the generator returns 2 as the value for `i`.
|
||||
* `./tests bar -c B -c i=4` results in 3 assertions, because the assertion
|
||||
outside of section is executed every time the test case is entered, and
|
||||
the generator forces the test case to rerun 3 times before it is used up,
|
||||
even though the dynamic section will never be entered.
|
||||
|
||||
|
||||
#### Section with sibling generators
|
||||
For cases where sections have sibling generators, the filtering can get
|
||||
even more surprising.
|
||||
```cpp
|
||||
TEST_CASE( "qux" ) {
|
||||
REQUIRE( true );
|
||||
SECTION( "A" ) { REQUIRE( true ); }
|
||||
auto i = GENERATE( 1, 2, 3 );
|
||||
DYNAMIC_SECTION( "i=" << i ) {
|
||||
REQUIRE( true );
|
||||
}
|
||||
}
|
||||
```
|
||||
* `./tests qux -c A` results in **4** assertions, because section "A" is
|
||||
entered once, but the sibling generator has to be exhausted, and
|
||||
the first assertion is executed once per generator element.
|
||||
* `./tests qux -c i=2` also results in 4 assertions. Once again,
|
||||
the generator has to be exhausted and the dynamic section is entered
|
||||
once.
|
||||
|
||||
|
||||
## New behaviour
|
||||
|
||||
> The new behaviour was introduced in Catch2 3.13.0
|
||||
|
||||
```
|
||||
-g, --generator-index <index in generator>
|
||||
-p, --path-filter <path filter spec>
|
||||
```
|
||||
|
||||
The argument to `-g`/`--generator-index` must be either a non-negative
|
||||
number, which is interpreted as the index of the desired element from
|
||||
the generator, or "\*", which allows all elements from the generator.
|
||||
|
||||
Providing index outside of the generator is an error.
|
||||
|
||||
The argument to `-p`/`--path-filter` must start with either "c:" for
|
||||
a section filter, or with "g:" for a generator filter. Everything past
|
||||
the colon is then parsed as either a section filter, or a generator filter.
|
||||
|
||||
Note that using `p`/`--path-filter` enables new filtering behaviour, even
|
||||
if it is only used to add section filters.
|
||||
|
||||
There is another important difference between filtering out sections and
|
||||
generators. A section can be left un-entered, but a generator always has
|
||||
to be active. For this reason, if generator fails a filter
|
||||
(e.g. there is a section filter at given depth instead), it has to stop
|
||||
the execution of the test case. Currently, this is done via `SKIP()`
|
||||
equivalent, causing the section to be considered skipped.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
#### Nested generators
|
||||
```cpp
|
||||
TEST_CASE( "waldo" ) {
|
||||
auto i = GENERATE( 1, 10, 100 );
|
||||
auto j = GENERATE( 2, 20, 200 );
|
||||
CAPTURE( i, j );
|
||||
REQUIRE( true );
|
||||
}
|
||||
```
|
||||
* `./tests waldo -g 1` results in 3 assertions, with `i := 10`, because
|
||||
the second nested generator is unfiltered.
|
||||
* `./tests waldo -g 1 -g 2` results in 1 assertion, with `i := 10, j := 200`.
|
||||
* `./tests waldo -g * -g 2` results in 3 assertions, all with `j := 200`.
|
||||
* `./tests waldo -g 1 -g *` results in 3 assertions, all with `i := 10`.
|
||||
* `./tests waldo -g 3` results in 1 **failed** assertion, because the first
|
||||
generator does not have 3rd element.
|
||||
* `./tests waldo -g * -g 3` results in 3 **failed** assertions, as the
|
||||
second generator does not have 3rd element, but we have to exhaust the
|
||||
first generator.
|
||||
|
||||
#### Generator with a nested dynamic section
|
||||
```cpp
|
||||
TEST_CASE( "grault" ) {
|
||||
REQUIRE( true );
|
||||
auto i = GENERATE( 1, 2, 3 );
|
||||
DYNAMIC_SECTION( "i=" << i ) {
|
||||
REQUIRE( true );
|
||||
}
|
||||
}
|
||||
```
|
||||
* `./tests grault -p g:1` results in 2 assertions, as there is no filter
|
||||
on the dynamic section.
|
||||
* `./tests grault -p g:1 -p c:i=2` results in 2 assertions, as the filter
|
||||
on the dynamic section matches the element given from the generator.
|
||||
* `./tests grault -p g:1 -p c:i=3` results in 1 assertion, as the generator
|
||||
is limited to only try `i := 2` and the dynamic section is filtered out.
|
||||
|
||||
#### Section with a sibling generator
|
||||
Because generators have to stop test execution when they don't pass filter,
|
||||
it is impossible to run only a section with sibling generator without
|
||||
triggering a test case skip. Consider this test case from an earlier example:
|
||||
```cpp
|
||||
TEST_CASE( "qux" ) {
|
||||
REQUIRE( true );
|
||||
SECTION( "A" ) { REQUIRE( true ); }
|
||||
auto i = GENERATE( 1, 2, 3 );
|
||||
DYNAMIC_SECTION( "i=" << i ) {
|
||||
REQUIRE( true );
|
||||
}
|
||||
}
|
||||
```
|
||||
* `./tests qux -p g:1` results in 2 assertions, as the dynamic section is
|
||||
entered only once.
|
||||
* `./tests qux -p g:1 -p c:i=1` results in 1 assertion, as the dynamic
|
||||
section filter is incompatible with the generator filter.
|
||||
* `./tests qux -p c:A` results in 2 assertions **and a skipped test case**.
|
||||
This is because the generator is sibling to section "A", and thus reads
|
||||
the same section filter. However, it is not a section and as thus cannot
|
||||
proceed.
|
||||
* `./tests qux -p c:i=2` results in 1 assertion **and a skipped test case**.
|
||||
Once again, the first filter in the filter stack is a section filter,
|
||||
and thus the generator cannot proceed.
|
||||
|
||||
Compare this with the old filter behaviour, where `./tests qux -c i=2`
|
||||
would instead result in 4 assertions, because the generator would go
|
||||
through all elements.
|
||||
|
||||
|
||||
---
|
||||
|
||||
[Home](Readme.md#top)
|
||||
+69
-5
@@ -1,6 +1,12 @@
|
||||
<a id="top"></a>
|
||||
# Data Generators
|
||||
|
||||
**Contents**<br>
|
||||
[Combining `GENERATE` and `SECTION`.](#combining-generate-and-section)<br>
|
||||
[Provided generators](#provided-generators)<br>
|
||||
[Generator interface](#generator-interface)<br>
|
||||
[Other usage examples](#other-usage-examples)<br>
|
||||
|
||||
> Introduced in Catch2 2.6.0.
|
||||
|
||||
Data generators (also known as _data driven/parametrized test cases_)
|
||||
@@ -106,7 +112,7 @@ a test case,
|
||||
* 2 fundamental generators
|
||||
* `SingleValueGenerator<T>` -- contains only single element
|
||||
* `FixedValuesGenerator<T>` -- contains multiple elements
|
||||
* 5 generic generators that modify other generators (defined in `catch2/generators/catch_generators_adapters.hpp`)
|
||||
* 6 generic generators that modify other generators (defined in `catch2/generators/catch_generators_adapters.hpp`)
|
||||
* `FilterGenerator<T, Predicate>` -- filters out elements from a generator
|
||||
for which the predicate returns "false"
|
||||
* `TakeGenerator<T>` -- takes first `n` elements from a generator
|
||||
@@ -114,16 +120,20 @@ a test case,
|
||||
* `MapGenerator<T, U, Func>` -- returns the result of applying `Func`
|
||||
on elements from a different generator
|
||||
* `ChunkGenerator<T>` -- returns chunks (inside `std::vector`) of n elements from a generator
|
||||
* 4 specific purpose generators (defined in `catch2/generators/catch_generators_random.hpp`)
|
||||
* `ConcatGenerator<T>` -- returns elements from multiple generators as if they were one
|
||||
* 2 random generators (defined in `catch2/generators/catch_generators_random.hpp`)
|
||||
* `RandomIntegerGenerator<Integral>` -- generates random Integrals from range
|
||||
* `RandomFloatGenerator<Float>` -- generates random Floats from range
|
||||
* `RangeGenerator<T>(first, last)` -- generates all values inside a `[first, last)` arithmetic range (defined in `catch2/generators/catch_generators_range.hpp`)
|
||||
* `IteratorGenerator<T>` -- copies and returns values from an iterator range (defined in `catch2/generators/catch_generators_range.hpp`)
|
||||
* 2 range generators (defined in `catch2/generators/catch_generators_range.hpp`)
|
||||
* `RangeGenerator<T>(first, last)` -- generates all values inside a `[first, last)` arithmetic range
|
||||
* `IteratorGenerator<T>` -- copies and returns values from an iterator range
|
||||
|
||||
> `ChunkGenerator<T>`, `RandomIntegerGenerator<Integral>`, `RandomFloatGenerator<Float>` and `RangeGenerator<T>` were introduced in Catch2 2.7.0.
|
||||
|
||||
> `IteratorGenerator<T>` was introduced in Catch2 2.10.0.
|
||||
|
||||
> `ConcatGenerator<T>` was introduced in Catch2 3.13.0
|
||||
|
||||
The generators also have associated helper functions that infer their
|
||||
type, making their usage much nicer. These are
|
||||
|
||||
@@ -141,6 +151,7 @@ type, making their usage much nicer. These are
|
||||
* `range(Arithmetic start, Arithmetic end, Arithmetic step)` for `RangeGenerator<Arithmetic>` with a custom step size
|
||||
* `from_range(InputIterator from, InputIterator to)` for `IteratorGenerator<T>`
|
||||
* `from_range(Container const&)` for `IteratorGenerator<T>`
|
||||
* `cat(GeneratorWrapper<T>&&...)` for `ConcatGenerator<T>`
|
||||
|
||||
> `chunk()`, `random()` and both `range()` functions were introduced in Catch2 2.7.0.
|
||||
|
||||
@@ -148,6 +159,8 @@ type, making their usage much nicer. These are
|
||||
|
||||
> `range()` for floating point numbers has been introduced in Catch2 2.11.0
|
||||
|
||||
> `cat` has been introduced in Catch2 3.13.0
|
||||
|
||||
And can be used as shown in the example below to create a generator
|
||||
that returns 100 odd random number:
|
||||
|
||||
@@ -251,9 +264,34 @@ struct IGenerator : GeneratorUntypedBase {
|
||||
// Returns user-friendly string showing the current generator element
|
||||
// Does not have to be overridden, IGenerator provides default implementation
|
||||
virtual std::string stringifyImpl() const;
|
||||
|
||||
/**
|
||||
* Customization point for `skipToNthElement`
|
||||
*
|
||||
* Does not have to be overridden, there is a default implementation.
|
||||
* Can be overridden for better performance.
|
||||
*
|
||||
* If there are not enough elements, shall throw an error.
|
||||
*
|
||||
* Going backwards is not supported.
|
||||
*/
|
||||
virtual void skipToNthElementImpl( std::size_t n );
|
||||
|
||||
/**
|
||||
* Returns true if calls to `next` will eventually return false
|
||||
*
|
||||
* Note that for backwards compatibility this is currently defaulted
|
||||
* to return `true`, but in the future all generators will have to
|
||||
* provide their own implementation.
|
||||
*/
|
||||
virtual bool isFinite() const = 0;
|
||||
};
|
||||
```
|
||||
|
||||
> `skipToNthElementImpl` was added in Catch2 3.13.0
|
||||
|
||||
> `isFinite` was added in Catch2 3.13.0
|
||||
|
||||
However, to be able to use your custom generator inside `GENERATE`, it
|
||||
will need to be wrapped inside a `GeneratorWrapper<T>`.
|
||||
`GeneratorWrapper<T>` is a value wrapper around a
|
||||
@@ -274,9 +312,35 @@ There are two ways to handle this, depending on whether you want this
|
||||
to be an error or not.
|
||||
|
||||
* If empty generator **is** an error, throw an exception in constructor.
|
||||
* If empty generator **is not** an error, use the [`SKIP`](skipping-passing-failing.md#skipping-test-cases-at-runtime) in constructor.
|
||||
* If empty generator **is not** an error, use the [`SKIP` macro](skipping-passing-failing.md#skipping-test-cases-at-runtime) in constructor.
|
||||
|
||||
|
||||
## Other usage examples
|
||||
|
||||
### Adding a reproducer to random tests
|
||||
|
||||
If you use generators to generate random inputs for testing, you might
|
||||
want to combine them with specific inputs, e.g. reproducers for previously
|
||||
found issues.
|
||||
|
||||
Because `GENERATE` accepts multiple values/generators, the basic case is simple:
|
||||
```cpp
|
||||
const int input = GENERATE(1, 2, take(10, random(10, 10'000'000)));
|
||||
```
|
||||
This will set `input` first to "1", then to "2", and then to 10 random
|
||||
integers.
|
||||
|
||||
But if you process the random inputs further (e.g. via `map`), you can't
|
||||
rely on `GENERATE`'s support for multiple generators. In that case, you
|
||||
have to use the `cat` generator combinator.
|
||||
```cpp
|
||||
const auto input = GENERATE(
|
||||
map( foo,
|
||||
cat( value( 4 ), take( 10, random( 10, 10'000'000 ) ) ) ) );
|
||||
```
|
||||
This will set `input` first to `foo(4)`, before transforming the 10 random
|
||||
integers through `foo`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
+22
-11
@@ -26,19 +26,22 @@ started" and "Section B", while the third one will only report "Test case
|
||||
started" as the extra info.
|
||||
|
||||
|
||||
## Logging without local scope
|
||||
## Logging outside of current scope
|
||||
|
||||
> [Introduced](https://github.com/catchorg/Catch2/issues/1522) in Catch2 2.7.0.
|
||||
> `UNSCOPED_INFO` was [introduced](https://github.com/catchorg/Catch2/issues/1522) in Catch2 2.7.0.
|
||||
|
||||
`UNSCOPED_INFO` is similar to `INFO` with two key differences:
|
||||
> `UNSCOPED_CAPTURE` was introduced in Catch2 3.13.0.
|
||||
|
||||
- Lifetime of an unscoped message is not tied to its own scope.
|
||||
The `UNSCOPED_X` macros are similar to their plain `X` macro counterparts,
|
||||
with two key differences:
|
||||
|
||||
- The lifetime of an unscoped message is not tied to its own scope.
|
||||
- An unscoped message can be reported by the first following assertion only, regardless of the result of that assertion.
|
||||
|
||||
In other words, lifetime of `UNSCOPED_INFO` is limited by the following assertion (or by the end of test case/section, whichever comes first) whereas lifetime of `INFO` is limited by its own scope.
|
||||
|
||||
These differences make this macro useful for reporting information from helper functions or inner scopes. An example:
|
||||
In other words, the `UNSCOPED_X` macros are useful to add extra information
|
||||
to the next assertion, e.g. from helper functions or inner scopes.
|
||||
|
||||
An example:
|
||||
```cpp
|
||||
void print_some_info() {
|
||||
UNSCOPED_INFO("Info from helper");
|
||||
@@ -83,9 +86,16 @@ Second info
|
||||
Second unscoped info
|
||||
```
|
||||
|
||||
Note that unscoped messages are not passed between test cases, even if
|
||||
there were no assertions between them.
|
||||
|
||||
|
||||
## Streaming macros
|
||||
|
||||
All these macros allow heterogeneous sequences of values to be streaming using the insertion operator (```<<```) in the same way that std::ostream, std::cout, etc support it.
|
||||
Apart from `CAPTURE` (and its close sibling, `UNSCOPED_CAPTURE`), message
|
||||
macros support gradual streaming of messages and values in the same way
|
||||
that the standard streams do.
|
||||
|
||||
|
||||
E.g.:
|
||||
```c++
|
||||
@@ -99,9 +109,6 @@ These macros come in three forms:
|
||||
|
||||
The message is logged to a buffer, but only reported with next assertions that are logged. This allows you to log contextual information in case of failures which is not shown during a successful test run (for the console reporter, without -s). Messages are removed from the buffer at the end of their scope, so may be used, for example, in loops.
|
||||
|
||||
_Note that in Catch2 2.x.x `INFO` can be used without a trailing semicolon as there is a trailing semicolon inside macro.
|
||||
This semicolon will be removed with next major version. It is highly advised to use a trailing semicolon after `INFO` macro._
|
||||
|
||||
**UNSCOPED_INFO(** _message expression_ **)**
|
||||
|
||||
> [Introduced](https://github.com/catchorg/Catch2/issues/1522) in Catch2 2.7.0.
|
||||
@@ -128,6 +135,10 @@ AS `FAIL`, but does not abort the test
|
||||
|
||||
**CAPTURE(** _expression1_, _expression2_, ... **)**
|
||||
|
||||
**UNSCOPED_CAPTURE(** _expression1_, _expression2_, ... **)**
|
||||
|
||||
> `UNSCOPED_CAPTURE` was introduced in Catch2 3.13.0.
|
||||
|
||||
Sometimes you just want to log a value of variable, or expression. For
|
||||
convenience, we provide the `CAPTURE` macro, that can take a variable,
|
||||
or an expression, and prints out that variable/expression and its value
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ int main( int argc, char* argv[] ) {
|
||||
|
||||
returnCode = session.run();
|
||||
|
||||
// returnCode encodes the type of error that occured. See the
|
||||
// returnCode encodes the type of error that occurred. See the
|
||||
// integer constants in catch_session.hpp for more information
|
||||
// on what each return code means.
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
# Release notes
|
||||
**Contents**<br>
|
||||
[3.13.0](#3130)<br>
|
||||
[3.12.0](#3120)<br>
|
||||
[3.11.0](#3110)<br>
|
||||
[3.10.0](#3100)<br>
|
||||
[3.9.1](#391)<br>
|
||||
[3.9.0](#390)<br>
|
||||
[3.8.1](#381)<br>
|
||||
[3.8.0](#380)<br>
|
||||
@@ -68,6 +73,105 @@
|
||||
[Even Older versions](#even-older-versions)<br>
|
||||
|
||||
|
||||
## 3.13.0
|
||||
|
||||
### Fixes
|
||||
* `--benchmark-samples 0` no longer hard crashes (#3056)
|
||||
* The CLI validation fails instead.
|
||||
* Fixed warning suppression macros being doubly defined when using Clang on Windows (#3060)
|
||||
|
||||
|
||||
### Improvements
|
||||
* Suppressed static analysis 26426 diagnostic for MSVC (#3057)
|
||||
* Renamed the internal deprecation macro from `DEPRECATED` to `CATCH_DEPRECATED` to avoid conflicts (#3058)
|
||||
* Added `UNSCOPED_CAPTURE` macro (#2954)
|
||||
* Added `ConcatGenerator` to combine multiple separate generator into one
|
||||
* The short form is `cat`
|
||||
* Generators can now jump forward to nth element efficiently
|
||||
* Custom generators that can jump forward efficiently should override `skipToNthElementImpl`
|
||||
* Generators can declare themselves infinite
|
||||
* The generator base defaults to declaring itself finite for backwards compatibility
|
||||
* Custom generators should override `isFinite()` to return the proper value
|
||||
* Added `--warn InfiniteGenerators` to error out on `GENERATE` being given an infinite generator
|
||||
* Extended options for section filtering from CLI to include generators
|
||||
* The user can specify which element from the generator to use in the test case
|
||||
* See documentation for how the new filters work and how they can be specified
|
||||
* `MapGenerator` only calls the mapping function if the output will be used
|
||||
|
||||
|
||||
## 3.12.0
|
||||
|
||||
### Fixes
|
||||
* Fixed unscoped messages after a passing fast-pathed assertion being lost.
|
||||
* Fixed the help string for `--order` to mention random order as the default. (#3045)
|
||||
* Fixed small documentation typos. (#3039)
|
||||
* Fixed compilation with `CATCH_CONFIG_THREAD_SAFE_ASSERTIONS` for older C++ standards.
|
||||
* Fixed a thread-safety issue with message macros being used too early after the process starts.
|
||||
* Fixed automatic configuration to properly handle PlayStation platform. (#3054)
|
||||
* **Fixed the _weird_ behaviour of section filtering when specifying multiple filters.** (#3038)
|
||||
* See #3038 for more details.
|
||||
|
||||
### Improvements
|
||||
* Added `lifetimebound` attribute to various places.
|
||||
* As an example, compiler that supports lifetime analysis will now diagnose invalid use of Matcher combinators.
|
||||
* Minor compile-time improvements to stringification. (#3028)
|
||||
* `std::tuple` printer does not recurse.
|
||||
* Some implementation details were outlined into the cpp file.
|
||||
* Global variables will only be marked with `thread_local` in thread-safe builds. (#3044)
|
||||
|
||||
### Miscellaneous
|
||||
* The thread safety support is no longer experimental.
|
||||
* The new CMake option and C++ define is now `CATCH_CONFIG_THREAD_SAFE_ASSERTIONS`.
|
||||
|
||||
|
||||
## 3.11.0
|
||||
|
||||
### Fixes
|
||||
* Fixed building on non-desktop GDK platforms (#3029)
|
||||
* Fixed message macros being susceptible to race in specific scenario (#3031)
|
||||
* Catch2's SEH filter will call the previously installed filter after reporting the error (#3033)
|
||||
|
||||
### Improvements
|
||||
* Handling of scoped messages (e.g. `CAPTURE`) is a bit faster.
|
||||
* Better out-of-the-box support for QNX (#2953)
|
||||
* Improved performance of assertions by up-to 10%
|
||||
* Release mode assertion fast-path sees the biggest improvement.
|
||||
* Faster processing of non-escaped strings in `--invisibles` mode.
|
||||
* Added support for Bazel's `TEST_RANDOM_SEED` env var (#3021)
|
||||
* Added support for Bazel's `TEST_PREMATURE_EXIT_FILE` env var (#3020)
|
||||
* This creates a file that is deleted if the tests exit normally, but stays around if the process dies unexpectedly.
|
||||
* This functionality is also exposed through CLI as `--premature-exit-guard-file`
|
||||
|
||||
### Miscellaneous
|
||||
* **[Tuple.app](https://tuple.app/catch2) has sponsored Catch2**
|
||||
|
||||
|
||||
## 3.10.0
|
||||
|
||||
### Fixes
|
||||
* pkg-config files will take `DESTDIR` env var into account when selecting install destination (#3006, #3019)
|
||||
* Changed `filter` to store the provided predicate by value (#3002, #3005)
|
||||
* This is done to avoid dangling-by-default behaviour when `filter` is used inside `GENERATE_COPY`/`GENERATE_REF`.
|
||||
|
||||
### Improvements
|
||||
* Escaping XML and JSON output is faster when the strings do not need escaping.
|
||||
* The improvement starts at about 3x throughput, up to 10x for long strings.
|
||||
* Message macros (`INFO`, `CAPTURE`, `WARN`, `SUCCEED`, etc) are now thread safe.
|
||||
|
||||
|
||||
## 3.9.1
|
||||
|
||||
### Fixes
|
||||
* Fixed bad error reporting for multiple nested assertions (#1292)
|
||||
* Fixed W4702 (unreachable code) in the polyfill for std::unreachable (#3007)
|
||||
* Fixed decomposition of assertions comparing enum-backed bitfields (#3001)
|
||||
* Fixed StringMaker specialization for `time_point<system_clock>` with non-default duration type (#2685)
|
||||
|
||||
### Improvements
|
||||
* Exceptions thrown during stringification of decomposed expression no longer fail the assertion (#2980)
|
||||
* The selection logic for `CATCH_TRAP` prefers `__builtin_debugtrap` on all platforms when Catch2 is compiled with Clang
|
||||
|
||||
|
||||
## 3.9.0
|
||||
|
||||
### Improvements
|
||||
|
||||
@@ -52,6 +52,20 @@ TEST_CASE("failing test") {
|
||||
}
|
||||
```
|
||||
|
||||
Same applies for a `SKIP` nested inside an assertion:
|
||||
|
||||
```cpp
|
||||
static bool do_skip() {
|
||||
SKIP();
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CASE("Another failing test") {
|
||||
CHECK(do_skip());
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Interaction with Sections and Generators
|
||||
|
||||
Sections, nested sections as well as specific outputs from [generators](generators.md#top)
|
||||
@@ -73,7 +87,7 @@ TEST_CASE("complex test case") {
|
||||
```
|
||||
|
||||
This test case will report 5 passing assertions; one for each of the three
|
||||
values in section `a1`, and then two in section `a2`, from values 2 and 4.
|
||||
values in section `a1`, and then two in section `a2`, from values 2 and 6.
|
||||
|
||||
Note that as soon as one section is skipped, the entire test case will
|
||||
be reported as _skipped_ (unless there is a failing assertion, in which
|
||||
|
||||
+118
-19
@@ -2,7 +2,9 @@
|
||||
# Thread safety in Catch2
|
||||
|
||||
**Contents**<br>
|
||||
[Using assertion macros from multiple threads](#using-assertion-macros-from-multiple-threads)<br>
|
||||
[Using assertion macros from spawned threads](#using-assertion-macros-from-spawned-threads)<br>
|
||||
[Assertion-like message macros and spawned threads](#assertion-like-message-macros-and-spawned-threads)<br>
|
||||
[Message macros and spawned threads](#message-macros-and-spawned-threads)<br>
|
||||
[examples](#examples)<br>
|
||||
[`STATIC_REQUIRE` and `STATIC_CHECK`](#static_require-and-static_check)<br>
|
||||
[Fatal errors and multiple threads](#fatal-errors-and-multiple-threads)<br>
|
||||
@@ -10,17 +12,18 @@
|
||||
|
||||
> Thread safe assertions were introduced in Catch2 3.9.0
|
||||
|
||||
Thread safety in Catch2 is currently limited to all the assertion macros.
|
||||
Interacting with benchmark macros, message macros (e.g. `INFO` or `CAPTURE`),
|
||||
sections macros, generator macros, or test case macros is not thread-safe.
|
||||
The message macros are likely to be made thread-safe in the future, but
|
||||
the way sections define test runs is incompatible with user being able
|
||||
to spawn threads arbitrarily, thus that limitation is here to stay.
|
||||
Thread safety in Catch2 is currently limited to all the assertion macros,
|
||||
and to message or message-adjacent macros (e.g. `INFO` or `WARN`).
|
||||
|
||||
**Important: thread safety in Catch2 is [opt-in](configuration.md#experimental-thread-safety)**
|
||||
Interacting with benchmark macros, sections macros, generator macros, or
|
||||
test case macros is not thread-safe. The way sections define paths through
|
||||
the test is incompatible with user spawning threads arbitrarily, so this
|
||||
limitation is here to stay.
|
||||
|
||||
**Important: thread safety in Catch2 is [opt-in](configuration.md#thread-safety)**
|
||||
|
||||
|
||||
## Using assertion macros from multiple threads
|
||||
## Using assertion macros from spawned threads
|
||||
|
||||
The full set of Catch2's runtime assertion macros is thread-safe. However,
|
||||
it is important to keep in mind that their semantics might not support
|
||||
@@ -30,7 +33,7 @@ Specifically, the `REQUIRE` family of assertion macros have semantics
|
||||
of stopping the test execution on failure. This is done by throwing
|
||||
an exception, but since the user-spawned thread will not have the test-level
|
||||
try-catch block ready to catch the test failure exception, failing a
|
||||
`REQUIRE` assertion inside this thread will terminate the process.
|
||||
`REQUIRE` assertion inside user-spawned thread will terminate the process.
|
||||
|
||||
The `CHECK` family of assertions does not have this issue, because it
|
||||
does not try to stop the test execution.
|
||||
@@ -38,16 +41,36 @@ does not try to stop the test execution.
|
||||
Note that `CHECKED_IF` and `CHECKED_ELSE` are also thread safe (internally
|
||||
they are assertion macro + an if).
|
||||
|
||||
**`SKIP()`, `FAIL()`, `SUCCEED()` are not assertion macros, and are not
|
||||
thread-safe.**
|
||||
|
||||
## Assertion-like message macros and spawned threads
|
||||
|
||||
> Assertion-like messages were made thread safe in Catch2 3.10.0
|
||||
|
||||
Similarly to assertion macros, not all assertion-like message macros can
|
||||
be used from spawned thread.
|
||||
|
||||
`SKIP` and `FAIL` macros stop the test execution. Just like with `REQUIRE`,
|
||||
this means that they cannot be used inside user-spawned threads. `SUCCEED`,
|
||||
`FAIL_CHECK` and `WARN` do not attempt to stop the test execution and
|
||||
thus can be used from any thread.
|
||||
|
||||
|
||||
## Message macros and spawned threads
|
||||
|
||||
> Message macros were made thread safe in Catch2 3.10.0
|
||||
|
||||
Macros that add extra messages to following assertion, such as `INFO`
|
||||
or `CAPTURE`, are all thread safe and can be used in any thread. Note
|
||||
that these messages are per-thread, and thus `INFO` inside a user-spawned
|
||||
thread will not be seen by the main thread, and vice versa.
|
||||
|
||||
|
||||
## examples
|
||||
|
||||
### `REQUIRE` from main thread, `CHECK` from spawned threads
|
||||
### `REQUIRE` from the main thread, `CHECK` from spawned threads
|
||||
|
||||
```cpp
|
||||
TEST_CASE( "Failed REQUIRE in main thread is fine" ) {
|
||||
TEST_CASE( "Failed REQUIRE in the main thread is fine" ) {
|
||||
std::vector<std::jthread> threads;
|
||||
for ( size_t t = 0; t < 16; ++t) {
|
||||
threads.emplace_back( []() {
|
||||
@@ -85,7 +108,7 @@ TEST_CASE( "Successful REQUIRE in spawned thread is fine" ) {
|
||||
This will also work as expected, because the `REQUIRE` is successful.
|
||||
|
||||
```cpp
|
||||
TEST_CASE( "Failed REQUIRE in spawned thread is fine" ) {
|
||||
TEST_CASE( "Failed REQUIRE in spawned thread kills the process" ) {
|
||||
std::vector<std::jthread> threads;
|
||||
for ( size_t t = 0; t < 16; ++t) {
|
||||
threads.emplace_back( []() {
|
||||
@@ -99,12 +122,88 @@ TEST_CASE( "Failed REQUIRE in spawned thread is fine" ) {
|
||||
This will fail catastrophically and terminate the process.
|
||||
|
||||
|
||||
### INFO across threads
|
||||
|
||||
```cpp
|
||||
TEST_CASE( "messages don't cross threads" ) {
|
||||
std::jthread t1( [&]() {
|
||||
for ( size_t i = 0; i < 100; ++i ) {
|
||||
INFO( "spawned thread #1" );
|
||||
CHECK( 1 == 1 );
|
||||
}
|
||||
} );
|
||||
|
||||
std::thread t2( [&]() {
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
UNSCOPED_INFO( "spawned thread #2" );
|
||||
}
|
||||
} );
|
||||
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
CHECK( 1 == 2 );
|
||||
}
|
||||
}
|
||||
```
|
||||
None of the failed checks will show the "spawned thread #1" message, as
|
||||
that message is for the `t1` thread. If the reporter shows passing
|
||||
assertions (e.g. due to the tests being run with `-s`), you will see the
|
||||
"spawned thread #1" message alongside the passing `CHECK( 1 == 1 )` assertion.
|
||||
|
||||
The message "spawned thread #2" will never be shown, because there are no
|
||||
assertions in `t2`.
|
||||
|
||||
|
||||
### FAIL/SKIP from the main thread
|
||||
|
||||
```cpp
|
||||
TEST_CASE( "FAIL in the main thread is fine" ) {
|
||||
std::vector<std::jthread> threads;
|
||||
for ( size_t t = 0; t < 16; ++t) {
|
||||
threads.emplace_back( []() {
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
CHECK( true );
|
||||
CHECK( false );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
FAIL();
|
||||
}
|
||||
```
|
||||
|
||||
This will work as expected, that is, the process will finish running
|
||||
normally, the test case will fail and there will be 321 total assertions,
|
||||
160 passing and 161 failing (`FAIL` counts as failed assertion).
|
||||
|
||||
However, when the main thread hits `FAIL`, it will wait for the other
|
||||
threads to finish due to `std::jthread`'s destructor joining the spawned
|
||||
thread. Due to this, using `SKIP` is not recommended once more threads
|
||||
are spawned; while the main thread will bail from the test execution,
|
||||
the spawned threads will keep running and may fail the test case.
|
||||
|
||||
|
||||
### FAIL/SKIP from spawned threads
|
||||
|
||||
```cpp
|
||||
TEST_CASE( "FAIL/SKIP in spawned thread kills the process" ) {
|
||||
std::vector<std::jthread> threads;
|
||||
for ( size_t t = 0; t < 16; ++t) {
|
||||
threads.emplace_back( []() {
|
||||
for (size_t i = 0; i < 10'000; ++i) {
|
||||
FAIL();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
```
|
||||
As with failing `REQUIRE`, both `FAIL` and `SKIP` in spawned threads
|
||||
terminate the process.
|
||||
|
||||
|
||||
## `STATIC_REQUIRE` and `STATIC_CHECK`
|
||||
|
||||
None of `STATIC_REQUIRE`, `STATIC_REQUIRE_FALSE`, `STATIC_CHECK`, and
|
||||
`STATIC_CHECK_FALSE` are currently thread safe. This might be surprising
|
||||
given that they are a compile-time checks, but they also rely on the
|
||||
message macros to register the result with reporter at runtime.
|
||||
All of `STATIC_REQUIRE`, `STATIC_REQUIRE_FALSE`, `STATIC_CHECK`, and
|
||||
`STATIC_CHECK_FALSE` are thread safe in the delayed evaluation configuration.
|
||||
|
||||
|
||||
## Fatal errors and multiple threads
|
||||
|
||||
@@ -39,6 +39,24 @@ public:
|
||||
current_number = m_dist(m_rand);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return false; }
|
||||
|
||||
// Note: this improves the performance only a bit, but it is here
|
||||
// to show how you can override the skip functionality.
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
auto current_index = currentElementIndex();
|
||||
assert(current_index <= n);
|
||||
// We cannot jump forward the underlying generator directly,
|
||||
// because we do not know how many bits each distributed number
|
||||
// would consume to be generated.
|
||||
for (; current_index < n; ++current_index) {
|
||||
(void)m_dist(m_rand);
|
||||
}
|
||||
|
||||
// We do not have to touch the current element index; it is handled
|
||||
// by the base class.
|
||||
}
|
||||
};
|
||||
|
||||
// Avoids -Wweak-vtables
|
||||
|
||||
@@ -40,6 +40,8 @@ public:
|
||||
bool next() override {
|
||||
return !!std::getline(m_stream, m_line);
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
std::string const& LineGenerator::get() const {
|
||||
|
||||
+667
-273
File diff suppressed because it is too large
Load Diff
+494
-229
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -8,7 +8,7 @@
|
||||
project(
|
||||
'catch2',
|
||||
'cpp',
|
||||
version: '3.9.0', # CML version placeholder, don't delete
|
||||
version: '3.13.0', # CML version placeholder, don't delete
|
||||
license: 'BSL-1.0',
|
||||
meson_version: '>=0.54.1',
|
||||
)
|
||||
|
||||
@@ -98,6 +98,7 @@ set(IMPL_HEADERS
|
||||
${SOURCES_DIR}/internal/catch_jsonwriter.hpp
|
||||
${SOURCES_DIR}/internal/catch_lazy_expr.hpp
|
||||
${SOURCES_DIR}/internal/catch_leak_detector.hpp
|
||||
${SOURCES_DIR}/internal/catch_lifetimebound.hpp
|
||||
${SOURCES_DIR}/internal/catch_list.hpp
|
||||
${SOURCES_DIR}/internal/catch_logical_traits.hpp
|
||||
${SOURCES_DIR}/internal/catch_message_info.hpp
|
||||
@@ -107,6 +108,7 @@ set(IMPL_HEADERS
|
||||
${SOURCES_DIR}/internal/catch_optional.hpp
|
||||
${SOURCES_DIR}/internal/catch_output_redirect.hpp
|
||||
${SOURCES_DIR}/internal/catch_parse_numbers.hpp
|
||||
${SOURCES_DIR}/internal/catch_path_filter.hpp
|
||||
${SOURCES_DIR}/internal/catch_platform.hpp
|
||||
${SOURCES_DIR}/internal/catch_polyfills.hpp
|
||||
${SOURCES_DIR}/internal/catch_preprocessor.hpp
|
||||
@@ -139,6 +141,7 @@ set(IMPL_HEADERS
|
||||
${SOURCES_DIR}/internal/catch_test_registry.hpp
|
||||
${SOURCES_DIR}/internal/catch_test_spec_parser.hpp
|
||||
${SOURCES_DIR}/internal/catch_textflow.hpp
|
||||
${SOURCES_DIR}/internal/catch_thread_local.hpp
|
||||
${SOURCES_DIR}/internal/catch_thread_support.hpp
|
||||
${SOURCES_DIR}/internal/catch_to_string.hpp
|
||||
${SOURCES_DIR}/internal/catch_uncaught_exceptions.hpp
|
||||
@@ -253,11 +256,13 @@ set(GENERATOR_HEADERS
|
||||
${SOURCES_DIR}/generators/catch_generators_all.hpp
|
||||
${SOURCES_DIR}/generators/catch_generators_random.hpp
|
||||
${SOURCES_DIR}/generators/catch_generators_range.hpp
|
||||
${SOURCES_DIR}/generators/catch_generators_throw.hpp
|
||||
)
|
||||
set(GENERATOR_SOURCES
|
||||
${SOURCES_DIR}/generators/catch_generator_exception.cpp
|
||||
${SOURCES_DIR}/generators/catch_generators.cpp
|
||||
${SOURCES_DIR}/generators/catch_generators_random.cpp
|
||||
${SOURCES_DIR}/generators/catch_generators_throw.cpp
|
||||
)
|
||||
set(GENERATOR_FILES ${GENERATOR_HEADERS} ${GENERATOR_SOURCES})
|
||||
|
||||
|
||||
@@ -86,9 +86,7 @@ namespace Catch {
|
||||
auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size());
|
||||
BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance };
|
||||
getResultCapture().benchmarkEnded(stats);
|
||||
} CATCH_CATCH_ANON (TestFailureException const&) {
|
||||
getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr);
|
||||
} CATCH_CATCH_ALL{
|
||||
} CATCH_CATCH_ALL {
|
||||
getResultCapture().benchmarkFailed(translateActiveException());
|
||||
// We let the exception go further up so that the
|
||||
// test case is marked as failed.
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Catch {
|
||||
|
||||
// We cannot forward declare the type with default template argument
|
||||
// multiple times, so it is split out into a separate header so that
|
||||
// we can prevent multiple declarations in dependees
|
||||
// we can prevent multiple declarations in dependencies
|
||||
template <typename Duration = Benchmark::FDuration>
|
||||
struct BenchmarkStats;
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
#include <catch2/internal/catch_jsonwriter.hpp>
|
||||
#include <catch2/internal/catch_lazy_expr.hpp>
|
||||
#include <catch2/internal/catch_leak_detector.hpp>
|
||||
#include <catch2/internal/catch_lifetimebound.hpp>
|
||||
#include <catch2/internal/catch_list.hpp>
|
||||
#include <catch2/internal/catch_logical_traits.hpp>
|
||||
#include <catch2/internal/catch_message_info.hpp>
|
||||
@@ -88,6 +89,7 @@
|
||||
#include <catch2/internal/catch_optional.hpp>
|
||||
#include <catch2/internal/catch_output_redirect.hpp>
|
||||
#include <catch2/internal/catch_parse_numbers.hpp>
|
||||
#include <catch2/internal/catch_path_filter.hpp>
|
||||
#include <catch2/internal/catch_platform.hpp>
|
||||
#include <catch2/internal/catch_polyfills.hpp>
|
||||
#include <catch2/internal/catch_preprocessor.hpp>
|
||||
@@ -121,6 +123,7 @@
|
||||
#include <catch2/internal/catch_test_registry.hpp>
|
||||
#include <catch2/internal/catch_test_spec_parser.hpp>
|
||||
#include <catch2/internal/catch_textflow.hpp>
|
||||
#include <catch2/internal/catch_thread_local.hpp>
|
||||
#include <catch2/internal/catch_thread_support.hpp>
|
||||
#include <catch2/internal/catch_to_string.hpp>
|
||||
#include <catch2/internal/catch_uncaught_exceptions.hpp>
|
||||
|
||||
@@ -92,6 +92,10 @@ namespace Catch {
|
||||
lhs.customOptions == rhs.customOptions;
|
||||
}
|
||||
|
||||
bool operator==( PathFilter const& lhs, PathFilter const& rhs ) {
|
||||
return lhs.type == rhs.type && lhs.filter == rhs.filter;
|
||||
}
|
||||
|
||||
Config::Config( ConfigData const& data ):
|
||||
m_data( data ) {
|
||||
// We need to trim filter specs to avoid trouble with superfluous
|
||||
@@ -101,9 +105,6 @@ namespace Catch {
|
||||
for (auto& elem : m_data.testsOrTags) {
|
||||
elem = trim(elem);
|
||||
}
|
||||
for (auto& elem : m_data.sectionsToRun) {
|
||||
elem = trim(elem);
|
||||
}
|
||||
|
||||
// Insert the default reporter if user hasn't asked for a specific one
|
||||
if ( m_data.reporterSpecifications.empty() ) {
|
||||
@@ -119,6 +120,8 @@ namespace Catch {
|
||||
m_data.reporterSpecifications.push_back( std::move( *parsed ) );
|
||||
}
|
||||
|
||||
// Reading bazel env vars can change some parts of the config data,
|
||||
// so we have to process the bazel env before acting on the config.
|
||||
if ( enableBazelEnvSupport() ) {
|
||||
readBazelEnvVars();
|
||||
}
|
||||
@@ -167,7 +170,8 @@ namespace Catch {
|
||||
bool Config::listListeners() const { return m_data.listListeners; }
|
||||
|
||||
std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }
|
||||
std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }
|
||||
std::vector<PathFilter> const& Config::getPathFilters() const { return m_data.pathFilters; }
|
||||
bool Config::useNewFilterBehaviour() const { return m_data.useNewPathFilteringBehaviour; }
|
||||
|
||||
std::vector<ReporterSpec> const& Config::getReporterSpecs() const {
|
||||
return m_data.reporterSpecifications;
|
||||
@@ -183,6 +187,8 @@ namespace Catch {
|
||||
|
||||
bool Config::showHelp() const { return m_data.showHelp; }
|
||||
|
||||
std::string const& Config::getExitGuardFilePath() const { return m_data.prematureExitGuardFilePath; }
|
||||
|
||||
// IConfig interface
|
||||
bool Config::allowThrows() const { return !m_data.noThrow; }
|
||||
StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
|
||||
@@ -193,6 +199,9 @@ namespace Catch {
|
||||
bool Config::warnAboutUnmatchedTestSpecs() const {
|
||||
return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec );
|
||||
}
|
||||
bool Config::warnAboutInfiniteGenerators() const {
|
||||
return !!( m_data.warnings & WarnAbout::InfiniteGenerator );
|
||||
}
|
||||
bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; }
|
||||
ShowDurations Config::showDurations() const { return m_data.showDurations; }
|
||||
double Config::minDuration() const { return m_data.minDuration; }
|
||||
@@ -244,6 +253,26 @@ namespace Catch {
|
||||
m_data.shardCount = bazelShardOptions->shardCount;
|
||||
}
|
||||
}
|
||||
|
||||
const auto bazelExitGuardFile = Detail::getEnv( "TEST_PREMATURE_EXIT_FILE" );
|
||||
if (bazelExitGuardFile) {
|
||||
m_data.prematureExitGuardFilePath = bazelExitGuardFile;
|
||||
}
|
||||
|
||||
const auto bazelRandomSeed = Detail::getEnv( "TEST_RANDOM_SEED" );
|
||||
if ( bazelRandomSeed ) {
|
||||
auto parsedSeed = parseUInt( bazelRandomSeed, 0 );
|
||||
if ( !parsedSeed ) {
|
||||
// Currently we handle issues with parsing other Bazel Env
|
||||
// options by warning and ignoring the issue. So we do the
|
||||
// same for random seed option.
|
||||
Catch::cerr()
|
||||
<< "Warning: could not parse 'TEST_RANDOM_SEED' ('"
|
||||
<< bazelRandomSeed << "') as proper seed.\n";
|
||||
} else {
|
||||
m_data.rngSeed = *parsedSeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <catch2/interfaces/catch_interfaces_config.hpp>
|
||||
#include <catch2/internal/catch_unique_ptr.hpp>
|
||||
#include <catch2/internal/catch_optional.hpp>
|
||||
#include <catch2/internal/catch_path_filter.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
#include <catch2/internal/catch_random_seed_generation.hpp>
|
||||
#include <catch2/internal/catch_reporter_spec_parser.hpp>
|
||||
@@ -86,7 +87,10 @@ namespace Catch {
|
||||
std::vector<ReporterSpec> reporterSpecifications;
|
||||
|
||||
std::vector<std::string> testsOrTags;
|
||||
std::vector<std::string> sectionsToRun;
|
||||
std::vector<PathFilter> pathFilters;
|
||||
bool useNewPathFilteringBehaviour = false;
|
||||
|
||||
std::string prematureExitGuardFilePath;
|
||||
};
|
||||
|
||||
|
||||
@@ -107,19 +111,23 @@ namespace Catch {
|
||||
getProcessedReporterSpecs() const;
|
||||
|
||||
std::vector<std::string> const& getTestsOrTags() const override;
|
||||
std::vector<std::string> const& getSectionsToRun() const override;
|
||||
std::vector<PathFilter> const& getPathFilters() const override;
|
||||
bool useNewFilterBehaviour() const override;
|
||||
|
||||
TestSpec const& testSpec() const override;
|
||||
bool hasTestFilters() const override;
|
||||
|
||||
bool showHelp() const;
|
||||
|
||||
std::string const& getExitGuardFilePath() const;
|
||||
|
||||
// IConfig interface
|
||||
bool allowThrows() const override;
|
||||
StringRef name() const override;
|
||||
bool includeSuccessfulResults() const override;
|
||||
bool warnAboutMissingAssertions() const override;
|
||||
bool warnAboutUnmatchedTestSpecs() const override;
|
||||
bool warnAboutInfiniteGenerators() const override;
|
||||
bool zeroTestsCountAsSuccess() const override;
|
||||
ShowDurations showDurations() const override;
|
||||
double minDuration() const override;
|
||||
|
||||
@@ -19,28 +19,28 @@ namespace Catch {
|
||||
|
||||
|
||||
ScopedMessage::ScopedMessage( MessageBuilder&& builder ):
|
||||
m_info( CATCH_MOVE(builder.m_info) ) {
|
||||
m_info.message = builder.m_stream.str();
|
||||
getResultCapture().pushScopedMessage( m_info );
|
||||
m_messageId( builder.m_info.sequence ) {
|
||||
MessageInfo info( CATCH_MOVE( builder.m_info ) );
|
||||
info.message = builder.m_stream.str();
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
|
||||
m_info( CATCH_MOVE( old.m_info ) ) {
|
||||
m_messageId( old.m_messageId ) {
|
||||
old.m_moved = true;
|
||||
}
|
||||
|
||||
ScopedMessage::~ScopedMessage() {
|
||||
if ( !m_moved ){
|
||||
getResultCapture().popScopedMessage(m_info);
|
||||
}
|
||||
if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
|
||||
}
|
||||
|
||||
|
||||
Capturer::Capturer( StringRef macroName,
|
||||
SourceLineInfo const& lineInfo,
|
||||
ResultWas::OfType resultType,
|
||||
StringRef names ):
|
||||
m_resultCapture( getResultCapture() ) {
|
||||
StringRef names,
|
||||
bool isScoped):
|
||||
m_isScoped(isScoped) {
|
||||
auto trimmed = [&] (size_t start, size_t end) {
|
||||
while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {
|
||||
++start;
|
||||
@@ -86,8 +86,8 @@ namespace Catch {
|
||||
case ',':
|
||||
if (start != pos && openings.empty()) {
|
||||
m_messages.emplace_back(macroName, lineInfo, resultType);
|
||||
m_messages.back().message = static_cast<std::string>(trimmed(start, pos));
|
||||
m_messages.back().message += " := ";
|
||||
m_messages.back().message += trimmed(start, pos);
|
||||
m_messages.back().message += " := "_sr;
|
||||
start = pos;
|
||||
}
|
||||
break;
|
||||
@@ -96,19 +96,26 @@ namespace Catch {
|
||||
}
|
||||
assert(openings.empty() && "Mismatched openings");
|
||||
m_messages.emplace_back(macroName, lineInfo, resultType);
|
||||
m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));
|
||||
m_messages.back().message += " := ";
|
||||
m_messages.back().message += trimmed(start, names.size() - 1);
|
||||
m_messages.back().message += " := "_sr;
|
||||
}
|
||||
Capturer::~Capturer() {
|
||||
assert( m_captured == m_messages.size() );
|
||||
for ( size_t i = 0; i < m_captured; ++i )
|
||||
m_resultCapture.popScopedMessage( m_messages[i] );
|
||||
if ( m_isScoped ) {
|
||||
for ( auto const& message : m_messages ) {
|
||||
IResultCapture::popScopedMessage( message.sequence );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Capturer::captureValue( size_t index, std::string const& value ) {
|
||||
assert( index < m_messages.size() );
|
||||
m_messages[index].message += value;
|
||||
m_resultCapture.pushScopedMessage( m_messages[index] );
|
||||
if ( m_isScoped ) {
|
||||
IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
} else {
|
||||
IResultCapture::addUnscopedMessage( CATCH_MOVE( m_messages[index] ) );
|
||||
}
|
||||
m_captured++;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,16 +57,16 @@ namespace Catch {
|
||||
ScopedMessage( ScopedMessage&& old ) noexcept;
|
||||
~ScopedMessage();
|
||||
|
||||
MessageInfo m_info;
|
||||
unsigned int m_messageId;
|
||||
bool m_moved = false;
|
||||
};
|
||||
|
||||
class Capturer {
|
||||
std::vector<MessageInfo> m_messages;
|
||||
IResultCapture& m_resultCapture;
|
||||
size_t m_captured = 0;
|
||||
bool m_isScoped = false;
|
||||
public:
|
||||
Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
|
||||
Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names, bool isScoped );
|
||||
|
||||
Capturer(Capturer const&) = delete;
|
||||
Capturer& operator=(Capturer const&) = delete;
|
||||
@@ -98,11 +98,12 @@ namespace Catch {
|
||||
} while( false )
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \
|
||||
Catch::Capturer varName( macroName##_catch_sr, \
|
||||
CATCH_INTERNAL_LINEINFO, \
|
||||
Catch::ResultWas::Info, \
|
||||
#__VA_ARGS__##_catch_sr ); \
|
||||
#define INTERNAL_CATCH_CAPTURE( varName, macroName, scopedCapture, ... ) \
|
||||
Catch::Capturer varName( macroName##_catch_sr, \
|
||||
CATCH_INTERNAL_LINEINFO, \
|
||||
Catch::ResultWas::Info, \
|
||||
#__VA_ARGS__##_catch_sr, \
|
||||
scopedCapture ); \
|
||||
varName.captureValues( 0, __VA_ARGS__ )
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -111,7 +112,7 @@ namespace Catch {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \
|
||||
Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
|
||||
Catch::IResultCapture::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
|
||||
|
||||
|
||||
#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)
|
||||
@@ -119,28 +120,32 @@ namespace Catch {
|
||||
#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg )
|
||||
#define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg )
|
||||
#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
|
||||
#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ )
|
||||
#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", true, __VA_ARGS__ )
|
||||
#define CATCH_UNSCOPED_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_UNSCOPED_CAPTURE", false, __VA_ARGS__ )
|
||||
|
||||
#elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
#define CATCH_INFO( msg ) (void)(0)
|
||||
#define CATCH_UNSCOPED_INFO( msg ) (void)(0)
|
||||
#define CATCH_WARN( msg ) (void)(0)
|
||||
#define CATCH_CAPTURE( ... ) (void)(0)
|
||||
#define CATCH_INFO( msg ) (void)(0)
|
||||
#define CATCH_UNSCOPED_INFO( msg ) (void)(0)
|
||||
#define CATCH_WARN( msg ) (void)(0)
|
||||
#define CATCH_CAPTURE( ... ) (void)(0)
|
||||
#define CATCH_UNSCOPED_CAPTURE( ... ) (void)(0)
|
||||
|
||||
#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg )
|
||||
#define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg )
|
||||
#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )
|
||||
#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ )
|
||||
#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", true, __VA_ARGS__ )
|
||||
#define UNSCOPED_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "UNSCOPED_CAPTURE", false, __VA_ARGS__ )
|
||||
|
||||
#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE)
|
||||
|
||||
#define INFO( msg ) (void)(0)
|
||||
#define UNSCOPED_INFO( msg ) (void)(0)
|
||||
#define WARN( msg ) (void)(0)
|
||||
#define CAPTURE( ... ) (void)(0)
|
||||
#define INFO( msg ) (void)(0)
|
||||
#define UNSCOPED_INFO( msg ) (void)(0)
|
||||
#define WARN( msg ) (void)(0)
|
||||
#define CAPTURE( ... ) (void)(0)
|
||||
#define UNSCOPED_CAPTURE( ... ) (void)(0)
|
||||
|
||||
#endif // end of user facing macro declarations
|
||||
|
||||
|
||||
@@ -96,7 +96,6 @@ namespace Catch {
|
||||
}
|
||||
void cleanUp() {
|
||||
cleanupSingletons();
|
||||
cleanUpContext();
|
||||
}
|
||||
std::string translateActiveException() {
|
||||
return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <catch2/internal/catch_istream.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <set>
|
||||
@@ -140,6 +142,50 @@ namespace Catch {
|
||||
}
|
||||
}
|
||||
|
||||
// Creates empty file at path. The path must be writable, we do not
|
||||
// try to create directories in path because that's hard in C++14.
|
||||
void setUpGuardFile( std::string const& guardFilePath ) {
|
||||
if ( !guardFilePath.empty() ) {
|
||||
#if defined( _MSC_VER )
|
||||
std::FILE* file = nullptr;
|
||||
if ( fopen_s( &file, guardFilePath.c_str(), "w" ) ) {
|
||||
char msgBuffer[100];
|
||||
const auto err = errno;
|
||||
std::string errMsg;
|
||||
if ( !strerror_s( msgBuffer, err ) ) {
|
||||
errMsg = msgBuffer;
|
||||
} else {
|
||||
errMsg = "Could not translate errno to a string";
|
||||
}
|
||||
|
||||
#else
|
||||
std::FILE* file = std::fopen( guardFilePath.c_str(), "w" );
|
||||
if ( !file ) {
|
||||
const auto err = errno;
|
||||
const char* errMsg = std::strerror( err );
|
||||
#endif
|
||||
|
||||
CATCH_RUNTIME_ERROR( "Could not open the exit guard file '"
|
||||
<< guardFilePath << "' because '"
|
||||
<< errMsg << "' (" << err << ')' );
|
||||
}
|
||||
const int ret = std::fclose( file );
|
||||
CATCH_ENFORCE(
|
||||
ret == 0,
|
||||
"Error when closing the exit guard file: " << ret );
|
||||
}
|
||||
}
|
||||
|
||||
// Removes file at path. Assuming we created it in setUpGuardFile.
|
||||
void tearDownGuardFile( std::string const& guardFilePath ) {
|
||||
if ( !guardFilePath.empty() ) {
|
||||
const int ret = std::remove( guardFilePath.c_str() );
|
||||
CATCH_ENFORCE(
|
||||
ret == 0,
|
||||
"Error when removing the exit guard file: " << ret );
|
||||
}
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
Session::Session() {
|
||||
@@ -258,6 +304,7 @@ namespace Catch {
|
||||
static_cast<void>(std::getchar());
|
||||
}
|
||||
int exitCode = runInternal();
|
||||
|
||||
if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {
|
||||
Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush;
|
||||
static_cast<void>(std::getchar());
|
||||
@@ -298,6 +345,10 @@ namespace Catch {
|
||||
CATCH_TRY {
|
||||
config(); // Force config to be constructed
|
||||
|
||||
// We need to retrieve potential Bazel config with the full Config
|
||||
// constructor, so we have to create the guard file after it is created.
|
||||
setUpGuardFile( m_config->getExitGuardFilePath() );
|
||||
|
||||
seedRng( *m_config );
|
||||
|
||||
if (m_configData.filenamesAsTags) {
|
||||
@@ -327,9 +378,12 @@ namespace Catch {
|
||||
TestGroup tests { CATCH_MOVE(reporter), m_config.get() };
|
||||
auto const totals = tests.execute();
|
||||
|
||||
// If we got here, running the tests finished normally-enough.
|
||||
// They might've failed, but that would've been reported elsewhere.
|
||||
tearDownGuardFile( m_config->getExitGuardFilePath() );
|
||||
|
||||
if ( tests.hadUnmatchedTestSpecs()
|
||||
&& m_config->warnAboutUnmatchedTestSpecs() ) {
|
||||
// UnmatchedTestSpecExitCode
|
||||
return UnmatchedTestSpecExitCode;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <catch2/catch_tostring.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_config.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_registry_hub.hpp>
|
||||
#include <catch2/internal/catch_context.hpp>
|
||||
#include <catch2/internal/catch_polyfills.hpp>
|
||||
|
||||
@@ -56,39 +57,69 @@ namespace Detail {
|
||||
}
|
||||
} // end unnamed namespace
|
||||
|
||||
std::size_t catch_strnlen( const char* str, std::size_t n ) {
|
||||
auto ret = std::char_traits<char>::find( str, n, '\0' );
|
||||
if ( ret != nullptr ) { return static_cast<std::size_t>( ret - str ); }
|
||||
return n;
|
||||
}
|
||||
|
||||
std::string formatTimeT(std::time_t time) {
|
||||
#ifdef _MSC_VER
|
||||
std::tm timeInfo = {};
|
||||
const auto err = gmtime_s( &timeInfo, &time );
|
||||
if ( err ) {
|
||||
return "gmtime from provided timepoint has failed. This "
|
||||
"happens e.g. with pre-1970 dates using Microsoft libc";
|
||||
}
|
||||
#else
|
||||
std::tm* timeInfo = std::gmtime( &time );
|
||||
#endif
|
||||
|
||||
auto const timeStampSize = sizeof( "2017-01-16T17:06:45Z" );
|
||||
char timeStamp[timeStampSize];
|
||||
const char* const fmt = "%Y-%m-%dT%H:%M:%SZ";
|
||||
|
||||
#ifdef _MSC_VER
|
||||
std::strftime( timeStamp, timeStampSize, fmt, &timeInfo );
|
||||
#else
|
||||
std::strftime( timeStamp, timeStampSize, fmt, timeInfo );
|
||||
#endif
|
||||
return std::string( timeStamp, timeStampSize - 1 );
|
||||
}
|
||||
|
||||
std::string convertIntoString(StringRef string, bool escapeInvisibles) {
|
||||
std::string ret;
|
||||
// This is enough for the "don't escape invisibles" case, and a good
|
||||
// lower bound on the "escape invisibles" case.
|
||||
ret.reserve(string.size() + 2);
|
||||
ret.reserve( string.size() + 2 );
|
||||
|
||||
if (!escapeInvisibles) {
|
||||
if ( !escapeInvisibles ) {
|
||||
ret += '"';
|
||||
ret += string;
|
||||
ret += '"';
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t last_start = 0;
|
||||
auto write_to = [&]( size_t idx ) {
|
||||
if ( last_start < idx ) {
|
||||
ret += string.substr( last_start, idx - last_start );
|
||||
}
|
||||
last_start = idx + 1;
|
||||
};
|
||||
|
||||
ret += '"';
|
||||
for (char c : string) {
|
||||
switch (c) {
|
||||
case '\r':
|
||||
ret.append("\\r");
|
||||
break;
|
||||
case '\n':
|
||||
ret.append("\\n");
|
||||
break;
|
||||
case '\t':
|
||||
ret.append("\\t");
|
||||
break;
|
||||
case '\f':
|
||||
ret.append("\\f");
|
||||
break;
|
||||
default:
|
||||
ret.push_back(c);
|
||||
break;
|
||||
for ( size_t i = 0; i < string.size(); ++i ) {
|
||||
const char c = string[i];
|
||||
if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) {
|
||||
write_to( i );
|
||||
if ( c == '\r' ) { ret.append( "\\r" ); }
|
||||
if ( c == '\n' ) { ret.append( "\\n" ); }
|
||||
if ( c == '\t' ) { ret.append( "\\t" ); }
|
||||
if ( c == '\f' ) { ret.append( "\\f" ); }
|
||||
}
|
||||
}
|
||||
write_to( string.size() );
|
||||
ret += '"';
|
||||
|
||||
return ret;
|
||||
@@ -113,6 +144,13 @@ namespace Detail {
|
||||
rss << std::setw(2) << static_cast<unsigned>(bytes[i]);
|
||||
return rss.str();
|
||||
}
|
||||
|
||||
std::string makeExceptionHappenedString() {
|
||||
return "{ stringification failed with an exception: \"" +
|
||||
translateActiveException() + "\" }";
|
||||
|
||||
}
|
||||
|
||||
} // end Detail namespace
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CATCH_TOSTRING_HPP_INCLUDED
|
||||
#define CATCH_TOSTRING_HPP_INCLUDED
|
||||
|
||||
|
||||
#include <ctime>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
@@ -40,13 +40,9 @@ namespace Catch {
|
||||
|
||||
namespace Detail {
|
||||
|
||||
inline std::size_t catch_strnlen(const char *str, std::size_t n) {
|
||||
auto ret = std::char_traits<char>::find(str, n, '\0');
|
||||
if (ret != nullptr) {
|
||||
return static_cast<std::size_t>(ret - str);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
std::size_t catch_strnlen(const char *str, std::size_t n);
|
||||
|
||||
std::string formatTimeT( std::time_t time );
|
||||
|
||||
constexpr StringRef unprintableString = "{?}"_sr;
|
||||
|
||||
@@ -139,11 +135,17 @@ namespace Catch {
|
||||
|
||||
namespace Detail {
|
||||
|
||||
std::string makeExceptionHappenedString();
|
||||
|
||||
// This function dispatches all stringification requests inside of Catch.
|
||||
// Should be preferably called fully qualified, like ::Catch::Detail::stringify
|
||||
template <typename T>
|
||||
std::string stringify(const T& e) {
|
||||
return ::Catch::StringMaker<std::remove_cv_t<std::remove_reference_t<T>>>::convert(e);
|
||||
std::string stringify( const T& e ) {
|
||||
CATCH_TRY {
|
||||
return ::Catch::StringMaker<
|
||||
std::remove_cv_t<std::remove_reference_t<T>>>::convert( e );
|
||||
}
|
||||
CATCH_CATCH_ALL { return makeExceptionHappenedString(); }
|
||||
}
|
||||
|
||||
template<typename E>
|
||||
@@ -405,44 +407,38 @@ namespace Catch {
|
||||
|
||||
// Separate std::tuple specialization
|
||||
#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)
|
||||
#include <tuple>
|
||||
# include <tuple>
|
||||
# include <utility>
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
template<
|
||||
typename Tuple,
|
||||
std::size_t N = 0,
|
||||
bool = (N < std::tuple_size<Tuple>::value)
|
||||
>
|
||||
struct TupleElementPrinter {
|
||||
static void print(const Tuple& tuple, std::ostream& os) {
|
||||
os << (N ? ", " : " ")
|
||||
<< ::Catch::Detail::stringify(std::get<N>(tuple));
|
||||
TupleElementPrinter<Tuple, N + 1>::print(tuple, os);
|
||||
}
|
||||
};
|
||||
template <typename Tuple, std::size_t... Is>
|
||||
void PrintTuple( const Tuple& tuple,
|
||||
std::ostream& os,
|
||||
std::index_sequence<Is...> ) {
|
||||
// 1 + Account for when the tuple is empty
|
||||
char a[1 + sizeof...( Is )] = {
|
||||
( ( os << ( Is ? ", " : " " )
|
||||
<< ::Catch::Detail::stringify( std::get<Is>( tuple ) ) ),
|
||||
'\0' )... };
|
||||
(void)a;
|
||||
}
|
||||
|
||||
template<
|
||||
typename Tuple,
|
||||
std::size_t N
|
||||
>
|
||||
struct TupleElementPrinter<Tuple, N, false> {
|
||||
static void print(const Tuple&, std::ostream&) {}
|
||||
};
|
||||
} // namespace Detail
|
||||
|
||||
}
|
||||
|
||||
|
||||
template<typename ...Types>
|
||||
template <typename... Types>
|
||||
struct StringMaker<std::tuple<Types...>> {
|
||||
static std::string convert(const std::tuple<Types...>& tuple) {
|
||||
static std::string convert( const std::tuple<Types...>& tuple ) {
|
||||
ReusableStringStream rss;
|
||||
rss << '{';
|
||||
Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get());
|
||||
Detail::PrintTuple(
|
||||
tuple,
|
||||
rss.get(),
|
||||
std::make_index_sequence<sizeof...( Types )>{} );
|
||||
rss << " }";
|
||||
return rss.str();
|
||||
}
|
||||
};
|
||||
}
|
||||
} // namespace Catch
|
||||
#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
|
||||
|
||||
#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT)
|
||||
@@ -626,29 +622,10 @@ struct ratio_string<std::milli> {
|
||||
template<typename Duration>
|
||||
struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {
|
||||
static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) {
|
||||
auto converted = std::chrono::system_clock::to_time_t(time_point);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
std::tm timeInfo = {};
|
||||
const auto err = gmtime_s(&timeInfo, &converted);
|
||||
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(&converted);
|
||||
#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);
|
||||
const auto systemish = std::chrono::time_point_cast<
|
||||
std::chrono::system_clock::duration>( time_point );
|
||||
const auto as_time_t = std::chrono::system_clock::to_time_t( systemish );
|
||||
return ::Catch::Detail::formatTimeT( as_time_t );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -196,12 +196,12 @@
|
||||
#endif
|
||||
|
||||
|
||||
#cmakedefine CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS
|
||||
#cmakedefine CATCH_CONFIG_NO_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS
|
||||
#cmakedefine CATCH_CONFIG_THREAD_SAFE_ASSERTIONS
|
||||
#cmakedefine CATCH_CONFIG_NO_THREAD_SAFE_ASSERTIONS
|
||||
|
||||
#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS ) && \
|
||||
defined( CATCH_CONFIG_NO_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )
|
||||
# error Cannot force EXPERIMENTAL_THREAD_SAFE_ASSERTIONS to both ON and OFF
|
||||
#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS ) && \
|
||||
defined( CATCH_CONFIG_NO_THREAD_SAFE_ASSERTIONS )
|
||||
# error Cannot force THREAD_SAFE_ASSERTIONS to both ON and OFF
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 3, 9, 0, "", 0 );
|
||||
static Version version( 3, 13, 0, "", 0 );
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#define CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
||||
#define CATCH_VERSION_MAJOR 3
|
||||
#define CATCH_VERSION_MINOR 9
|
||||
#define CATCH_VERSION_MINOR 13
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
|
||||
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
#include <catch2/generators/catch_generator_exception.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
|
||||
namespace Catch {
|
||||
@@ -17,14 +15,6 @@ namespace Catch {
|
||||
|
||||
namespace Generators {
|
||||
|
||||
namespace Detail {
|
||||
|
||||
[[noreturn]]
|
||||
void throw_generator_exception(char const* msg) {
|
||||
Catch::throw_exception(GeneratorException{ msg });
|
||||
}
|
||||
} // end namespace Detail
|
||||
|
||||
GeneratorUntypedBase::~GeneratorUntypedBase() = default;
|
||||
|
||||
IGeneratorTracker* acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const& lineInfo ) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#define CATCH_GENERATORS_HPP_INCLUDED
|
||||
|
||||
#include <catch2/catch_tostring.hpp>
|
||||
#include <catch2/generators/catch_generators_throw.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_generatortracker.hpp>
|
||||
#include <catch2/internal/catch_source_line_info.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
@@ -22,14 +23,6 @@ namespace Catch {
|
||||
|
||||
namespace Generators {
|
||||
|
||||
namespace Detail {
|
||||
|
||||
//! Throws GeneratorException with the provided message
|
||||
[[noreturn]]
|
||||
void throw_generator_exception(char const * msg);
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
template<typename T>
|
||||
class IGenerator : public GeneratorUntypedBase {
|
||||
std::string stringifyImpl() const override {
|
||||
@@ -64,6 +57,9 @@ namespace Detail {
|
||||
bool next() {
|
||||
return m_generator->countedNext();
|
||||
}
|
||||
|
||||
bool isFinite() const { return m_generator->isFinite(); }
|
||||
void skipToNthElement( size_t n ) { m_generator->skipToNthElement(n); }
|
||||
};
|
||||
|
||||
|
||||
@@ -84,6 +80,8 @@ namespace Detail {
|
||||
bool next() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -93,6 +91,15 @@ namespace Detail {
|
||||
"specialization, use SingleValue Generator instead.");
|
||||
std::vector<T> m_values;
|
||||
size_t m_idx = 0;
|
||||
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
if ( n >= m_values.size() ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Coud not jump to Nth element: not enough elements" );
|
||||
}
|
||||
m_idx = n;
|
||||
}
|
||||
|
||||
public:
|
||||
FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {}
|
||||
|
||||
@@ -103,6 +110,8 @@ namespace Detail {
|
||||
++m_idx;
|
||||
return m_idx < m_values.size();
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template <typename T, typename DecayedT = std::decay_t<T>>
|
||||
@@ -167,6 +176,14 @@ namespace Detail {
|
||||
}
|
||||
return m_current < m_generators.size();
|
||||
}
|
||||
|
||||
bool isFinite() const override {
|
||||
for (auto const& gen : m_generators) {
|
||||
if (!gen.isFinite()) { return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/internal/catch_meta.hpp>
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
#include <catch2/internal/catch_optional.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -22,6 +23,17 @@ namespace Generators {
|
||||
GeneratorWrapper<T> m_generator;
|
||||
size_t m_returned = 0;
|
||||
size_t m_target;
|
||||
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
if ( n >= m_target ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Coud not jump to Nth element: not enough elements" );
|
||||
}
|
||||
|
||||
m_generator.skipToNthElement( n );
|
||||
m_returned = n;
|
||||
}
|
||||
|
||||
public:
|
||||
TakeGenerator(size_t target, GeneratorWrapper<T>&& generator):
|
||||
m_generator(CATCH_MOVE(generator)),
|
||||
@@ -46,6 +58,8 @@ namespace Generators {
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -58,8 +72,9 @@ namespace Generators {
|
||||
class FilterGenerator final : public IGenerator<T> {
|
||||
GeneratorWrapper<T> m_generator;
|
||||
Predicate m_predicate;
|
||||
static_assert(!std::is_reference<Predicate>::value, "This would most likely result in a dangling reference");
|
||||
public:
|
||||
template <typename P = Predicate>
|
||||
template <typename P>
|
||||
FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator):
|
||||
m_generator(CATCH_MOVE(generator)),
|
||||
m_predicate(CATCH_FORWARD(pred))
|
||||
@@ -86,12 +101,14 @@ namespace Generators {
|
||||
while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return m_generator.isFinite(); }
|
||||
};
|
||||
|
||||
|
||||
template <typename T, typename Predicate>
|
||||
GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) {
|
||||
return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, Predicate>>(CATCH_FORWARD(pred), CATCH_MOVE(generator)));
|
||||
return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, typename std::remove_reference<Predicate>::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -110,6 +127,9 @@ namespace Generators {
|
||||
m_target_repeats(repeats)
|
||||
{
|
||||
assert(m_target_repeats > 0 && "Repeat generator must repeat at least once");
|
||||
if (!m_generator.isFinite()) {
|
||||
Detail::throw_generator_exception( "Cannot repeat infinite generator" );
|
||||
}
|
||||
}
|
||||
|
||||
T const& get() const override {
|
||||
@@ -143,6 +163,8 @@ namespace Generators {
|
||||
}
|
||||
return m_current_repeat < m_target_repeats;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return m_generator.isFinite(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -156,25 +178,30 @@ namespace Generators {
|
||||
GeneratorWrapper<U> m_generator;
|
||||
Func m_function;
|
||||
// To avoid returning dangling reference, we have to save the values
|
||||
T m_cache;
|
||||
mutable Optional<T> m_cache;
|
||||
|
||||
void skipToNthElementImpl( std::size_t n ) override {
|
||||
m_generator.skipToNthElement( n );
|
||||
m_cache.reset();
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename F2 = Func>
|
||||
MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) :
|
||||
m_generator(CATCH_MOVE(generator)),
|
||||
m_function(CATCH_FORWARD(function)),
|
||||
m_cache(m_function(m_generator.get()))
|
||||
m_function(CATCH_FORWARD(function))
|
||||
{}
|
||||
|
||||
T const& get() const override {
|
||||
return m_cache;
|
||||
if ( !m_cache ) { m_cache = m_function( m_generator.get() ); }
|
||||
return *m_cache;
|
||||
}
|
||||
bool next() override {
|
||||
const auto success = m_generator.next();
|
||||
if (success) {
|
||||
m_cache = m_function(m_generator.get());
|
||||
}
|
||||
return success;
|
||||
m_cache.reset();
|
||||
return m_generator.next();
|
||||
}
|
||||
|
||||
bool isFinite() const override { return m_generator.isFinite(); }
|
||||
};
|
||||
|
||||
template <typename Func, typename U, typename T = FunctionReturnType<Func, U>>
|
||||
@@ -196,7 +223,6 @@ namespace Generators {
|
||||
std::vector<T> m_chunk;
|
||||
size_t m_chunk_size;
|
||||
GeneratorWrapper<T> m_generator;
|
||||
bool m_used_up = false;
|
||||
public:
|
||||
ChunkGenerator(size_t size, GeneratorWrapper<T> generator) :
|
||||
m_chunk_size(size), m_generator(CATCH_MOVE(generator))
|
||||
@@ -225,6 +251,8 @@ namespace Generators {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isFinite() const override { return m_generator.isFinite(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -234,6 +262,56 @@ namespace Generators {
|
||||
);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class ConcatGenerator final : public IGenerator<T> {
|
||||
std::vector<GeneratorWrapper<T>> m_generators;
|
||||
size_t m_current_generator = 0;
|
||||
|
||||
void InsertGenerators( GeneratorWrapper<T>&& gen ) {
|
||||
m_generators.push_back( CATCH_MOVE( gen ) );
|
||||
}
|
||||
|
||||
template <typename... Generators>
|
||||
void InsertGenerators( GeneratorWrapper<T>&& gen, Generators&&... gens ) {
|
||||
m_generators.push_back( CATCH_MOVE( gen ) );
|
||||
InsertGenerators( CATCH_MOVE( gens )... );
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename... Generators>
|
||||
ConcatGenerator( Generators&&... generators ) {
|
||||
InsertGenerators( CATCH_MOVE( generators )... );
|
||||
}
|
||||
|
||||
T const& get() const override {
|
||||
return m_generators[m_current_generator].get();
|
||||
}
|
||||
bool next() override {
|
||||
const bool success = m_generators[m_current_generator].next();
|
||||
if ( success ) { return true; }
|
||||
|
||||
// If current generator is used up, we have to move to the next one
|
||||
++m_current_generator;
|
||||
return m_current_generator < m_generators.size();
|
||||
}
|
||||
|
||||
bool isFinite() const override {
|
||||
for ( auto const& gen : m_generators ) {
|
||||
if ( !gen.isFinite() ) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... Generators>
|
||||
GeneratorWrapper<T> cat( GeneratorWrapper<T>&& generator,
|
||||
Generators&&... generators ) {
|
||||
return GeneratorWrapper<T>(
|
||||
Catch::Detail::make_unique<ConcatGenerator<T>>(
|
||||
CATCH_MOVE( generator ), CATCH_MOVE( generators )... ) );
|
||||
}
|
||||
|
||||
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
|
||||
|
||||
@@ -26,5 +26,6 @@
|
||||
#include <catch2/generators/catch_generators_adapters.hpp>
|
||||
#include <catch2/generators/catch_generators_random.hpp>
|
||||
#include <catch2/generators/catch_generators_range.hpp>
|
||||
#include <catch2/generators/catch_generators_throw.hpp>
|
||||
|
||||
#endif // CATCH_GENERATORS_ALL_HPP_INCLUDED
|
||||
|
||||
@@ -37,5 +37,10 @@ namespace Catch {
|
||||
m_current_number = m_pimpl->dist( m_pimpl->rng );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RandomFloatingGenerator<long double>::isFinite() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
m_current_number = m_dist(m_rng);
|
||||
return true;
|
||||
}
|
||||
bool isFinite() const override { return false; }
|
||||
};
|
||||
|
||||
template <>
|
||||
@@ -59,6 +60,7 @@ public:
|
||||
bool next() override;
|
||||
|
||||
~RandomFloatingGenerator() override; // = default
|
||||
bool isFinite() const override;
|
||||
};
|
||||
|
||||
template <typename Integer>
|
||||
@@ -80,6 +82,7 @@ public:
|
||||
m_current_number = m_dist(m_rng);
|
||||
return true;
|
||||
}
|
||||
bool isFinite() const override { return false; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
m_current += m_step;
|
||||
return (m_positive) ? (m_current < m_end) : (m_current > m_end);
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -87,6 +89,8 @@ public:
|
||||
++m_current;
|
||||
return m_current != m_elems.size();
|
||||
}
|
||||
|
||||
bool isFinite() const override { return true; }
|
||||
};
|
||||
|
||||
template <typename InputIterator,
|
||||
|
||||
@@ -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
|
||||
|
||||
#include <catch2/generators/catch_generator_exception.hpp>
|
||||
#include <catch2/generators/catch_generators_throw.hpp>
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
|
||||
namespace Catch {
|
||||
namespace Generators {
|
||||
namespace Detail {
|
||||
|
||||
[[noreturn]]
|
||||
void throw_generator_exception( char const* msg ) {
|
||||
Catch::throw_exception( GeneratorException{ msg } );
|
||||
}
|
||||
|
||||
} // namespace Detail
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
// 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_GENERATORS_THROW_HPP_INCLUDED
|
||||
#define CATCH_GENERATORS_THROW_HPP_INCLUDED
|
||||
|
||||
namespace Catch {
|
||||
namespace Generators {
|
||||
namespace Detail {
|
||||
|
||||
//! Throws GeneratorException with the provided message
|
||||
[[noreturn]]
|
||||
void throw_generator_exception( char const* msg );
|
||||
|
||||
} // namespace Detail
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
|
||||
#endif // CATCH_GENERATORS_THROW_HPP_INCLUDED
|
||||
@@ -7,7 +7,14 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/interfaces/catch_interfaces_capture.hpp>
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
void missingCaptureInstance() {
|
||||
CATCH_INTERNAL_ERROR( "No result capture instance" );
|
||||
}
|
||||
} // namespace Detail
|
||||
|
||||
IResultCapture::~IResultCapture() = default;
|
||||
}
|
||||
} // namespace Catch
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <catch2/internal/catch_context.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
#include <catch2/internal/catch_result_type.hpp>
|
||||
#include <catch2/internal/catch_unique_ptr.hpp>
|
||||
@@ -62,10 +63,10 @@ namespace Catch {
|
||||
virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0;
|
||||
virtual void benchmarkFailed( StringRef error ) = 0;
|
||||
|
||||
virtual void pushScopedMessage( MessageInfo const& message ) = 0;
|
||||
virtual void popScopedMessage( MessageInfo const& message ) = 0;
|
||||
|
||||
virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0;
|
||||
static void pushScopedMessage( MessageInfo&& message );
|
||||
static void popScopedMessage( unsigned int messageId );
|
||||
static void addUnscopedMessage( MessageInfo&& message );
|
||||
static void emplaceUnscopedMessage( MessageBuilder&& builder );
|
||||
|
||||
virtual void handleFatalErrorCondition( StringRef message ) = 0;
|
||||
|
||||
@@ -101,7 +102,18 @@ namespace Catch {
|
||||
virtual void exceptionEarlyReported() = 0;
|
||||
};
|
||||
|
||||
IResultCapture& getResultCapture();
|
||||
namespace Detail {
|
||||
[[noreturn]]
|
||||
void missingCaptureInstance();
|
||||
}
|
||||
inline IResultCapture& getResultCapture() {
|
||||
if (auto* capture = getCurrentContext().getResultCapture()) {
|
||||
return *capture;
|
||||
} else {
|
||||
Detail::missingCaptureInstance();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Catch {
|
||||
NoAssertions = 0x01,
|
||||
//! A command line test spec matched no test cases
|
||||
UnmatchedTestSpec = 0x02,
|
||||
//! The resulting generator in GENERATE is infinite
|
||||
InfiniteGenerator = 0x04,
|
||||
}; };
|
||||
|
||||
enum class ShowDurations {
|
||||
@@ -60,6 +62,7 @@ namespace Catch {
|
||||
|
||||
class TestSpec;
|
||||
class IStream;
|
||||
struct PathFilter;
|
||||
|
||||
class IConfig : public Detail::NonCopyable {
|
||||
public:
|
||||
@@ -71,6 +74,7 @@ namespace Catch {
|
||||
virtual bool shouldDebugBreak() const = 0;
|
||||
virtual bool warnAboutMissingAssertions() const = 0;
|
||||
virtual bool warnAboutUnmatchedTestSpecs() const = 0;
|
||||
virtual bool warnAboutInfiniteGenerators() const = 0;
|
||||
virtual bool zeroTestsCountAsSuccess() const = 0;
|
||||
virtual int abortAfter() const = 0;
|
||||
virtual bool showInvisibles() const = 0;
|
||||
@@ -84,7 +88,9 @@ namespace Catch {
|
||||
virtual unsigned int shardCount() const = 0;
|
||||
virtual unsigned int shardIndex() const = 0;
|
||||
virtual ColourMode defaultColourMode() const = 0;
|
||||
virtual std::vector<std::string> const& getSectionsToRun() const = 0;
|
||||
virtual std::vector<PathFilter> const& getPathFilters() const = 0;
|
||||
virtual bool useNewFilterBehaviour() const = 0;
|
||||
|
||||
virtual Verbosity verbosity() const = 0;
|
||||
|
||||
virtual bool skipBenchmarks() const = 0;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/interfaces/catch_interfaces_generatortracker.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Catch {
|
||||
@@ -21,6 +23,31 @@ namespace Catch {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GeneratorUntypedBase::skipToNthElementImpl( std::size_t n ) {
|
||||
for ( size_t i = m_currentElementIndex; i < n; ++i ) {
|
||||
bool isValid = next();
|
||||
if ( !isValid ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Coud not jump to Nth element: not enough elements" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeneratorUntypedBase::skipToNthElement( std::size_t n ) {
|
||||
if ( n < m_currentElementIndex ) {
|
||||
Detail::throw_generator_exception(
|
||||
"Tried to jump generator backwards" );
|
||||
}
|
||||
if ( n == m_currentElementIndex ) { return; }
|
||||
|
||||
skipToNthElementImpl(n);
|
||||
// Fixup tracking after moving the generator forward
|
||||
// * Ensure that the correct element index is set after skipping
|
||||
// * Invalidate cache
|
||||
m_currentElementIndex = n;
|
||||
m_stringReprCache.clear();
|
||||
}
|
||||
|
||||
StringRef GeneratorUntypedBase::currentElementAsString() const {
|
||||
if ( m_stringReprCache.empty() ) {
|
||||
m_stringReprCache = stringifyImpl();
|
||||
@@ -28,5 +55,7 @@ namespace Catch {
|
||||
return m_stringReprCache;
|
||||
}
|
||||
|
||||
bool GeneratorUntypedBase::isFinite() const { return true; }
|
||||
|
||||
} // namespace Generators
|
||||
} // namespace Catch
|
||||
|
||||
@@ -35,6 +35,15 @@ namespace Catch {
|
||||
//! Customization point for `currentElementAsString`
|
||||
virtual std::string stringifyImpl() const = 0;
|
||||
|
||||
/**
|
||||
* Customization point for skipping to the n-th element
|
||||
*
|
||||
* Defaults to successively calling `countedNext`. If there
|
||||
* are not enough elements to reach the nth one, will throw
|
||||
* an error.
|
||||
*/
|
||||
virtual void skipToNthElementImpl( std::size_t n );
|
||||
|
||||
public:
|
||||
GeneratorUntypedBase() = default;
|
||||
// Generation of copy ops is deprecated (and Clang will complain)
|
||||
@@ -58,6 +67,13 @@ namespace Catch {
|
||||
|
||||
std::size_t currentElementIndex() const { return m_currentElementIndex; }
|
||||
|
||||
/**
|
||||
* Moves the generator forward **to** the n-th element
|
||||
*
|
||||
* Cannot move backwards. Can stay in place.
|
||||
*/
|
||||
void skipToNthElement( std::size_t n );
|
||||
|
||||
/**
|
||||
* Returns generator's current element as user-friendly string.
|
||||
*
|
||||
@@ -72,6 +88,15 @@ namespace Catch {
|
||||
* comes first.
|
||||
*/
|
||||
StringRef currentElementAsString() const;
|
||||
|
||||
/**
|
||||
* Returns true if calls to `next` will eventually return false
|
||||
*
|
||||
* Note that for backwards compatibility this is currently defaulted
|
||||
* to return `true`, but in the future all generators will have to
|
||||
* provide their own implementation.
|
||||
*/
|
||||
virtual bool isFinite() const;
|
||||
};
|
||||
using GeneratorBasePtr = Catch::Detail::unique_ptr<GeneratorUntypedBase>;
|
||||
|
||||
@@ -80,9 +105,7 @@ namespace Catch {
|
||||
class IGeneratorTracker {
|
||||
public:
|
||||
virtual ~IGeneratorTracker(); // = default;
|
||||
virtual auto hasGenerator() const -> bool = 0;
|
||||
virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0;
|
||||
virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0;
|
||||
};
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
@@ -32,6 +32,9 @@ namespace Catch {
|
||||
} else if ( warning == "UnmatchedTestSpec" ) {
|
||||
config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::UnmatchedTestSpec);
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
} else if ( warning == "InfiniteGenerators" ) {
|
||||
config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::InfiniteGenerator);
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
}
|
||||
|
||||
return ParserResult ::runtimeError(
|
||||
@@ -189,6 +192,19 @@ namespace Catch {
|
||||
config.shardCount = *parsedCount;
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
auto const setBenchmarkSamples = [&]( std::string const& samples ) {
|
||||
auto parsedSamples = parseUInt( samples );
|
||||
if ( !parsedSamples ) {
|
||||
return ParserResult::runtimeError(
|
||||
"Could not parse '" + samples + "' as benchmark samples" );
|
||||
}
|
||||
if ( *parsedSamples == 0 ) {
|
||||
return ParserResult::runtimeError(
|
||||
"Benchmark samples must be greater than 0" );
|
||||
}
|
||||
config.benchmarkSamples = *parsedSamples;
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
|
||||
auto const setShardIndex = [&](std::string const& shardIndex) {
|
||||
auto parsedIndex = parseUInt( shardIndex );
|
||||
@@ -200,6 +216,43 @@ namespace Catch {
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
|
||||
auto const setSectionFilter = [&]( std::string const& sectionFilter ) {
|
||||
config.pathFilters.emplace_back( PathFilter::For::Section, trim(sectionFilter) );
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
auto const setGeneratorFilter = [&]( std::string const& generatorFilter ) {
|
||||
if (generatorFilter != "*") {
|
||||
// TODO: avoid re-parsing the index?
|
||||
auto parsedIndex = parseUInt( generatorFilter );
|
||||
if ( !parsedIndex ) {
|
||||
return ParserResult::runtimeError( "Could not parse '" +
|
||||
generatorFilter +
|
||||
"' as generator index" );
|
||||
}
|
||||
}
|
||||
config.useNewPathFilteringBehaviour = true;
|
||||
config.pathFilters.emplace_back( PathFilter::For::Generator, trim(generatorFilter) );
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
};
|
||||
// Copy-capturing other `setFoo` functions enables calling them later,
|
||||
// as the config ref remains valid, but the local lambda vars won't.
|
||||
auto const setPathFilter = [=, &config]( std::string const& pathFilter ) {
|
||||
config.useNewPathFilteringBehaviour = true;
|
||||
if ( pathFilter.size() < 3 ) {
|
||||
return ParserResult::runtimeError(
|
||||
"Path filter '" + pathFilter + "' is too short" );
|
||||
}
|
||||
if ( startsWith( pathFilter, "g:" ) ) {
|
||||
return setGeneratorFilter( pathFilter.substr( 2 ) );
|
||||
}
|
||||
if ( startsWith( pathFilter, "c:" ) ) {
|
||||
return setSectionFilter( pathFilter.substr( 2 ) );
|
||||
}
|
||||
return ParserResult::runtimeError( "Path filter '" + pathFilter +
|
||||
"' has unknown type prefix" );
|
||||
};
|
||||
|
||||
|
||||
auto cli
|
||||
= ExeName( config.processName )
|
||||
| Help( config.showHelp )
|
||||
@@ -245,9 +298,15 @@ namespace Catch {
|
||||
| Opt( config.filenamesAsTags )
|
||||
["-#"]["--filenames-as-tags"]
|
||||
( "adds a tag for the filename" )
|
||||
| Opt( config.sectionsToRun, "section name" )
|
||||
| Opt( accept_many, setSectionFilter, "section name" )
|
||||
["-c"]["--section"]
|
||||
( "specify section to run" )
|
||||
| Opt( accept_many, setGeneratorFilter, "index spec" )
|
||||
["-g"]["--generator-index"]
|
||||
( "specify generator elements to try" )
|
||||
| Opt( accept_many, setPathFilter, "path filter spec" )
|
||||
["-p"]["--path-filter"]
|
||||
( "qualified path filter" )
|
||||
| Opt( setVerbosity, "quiet|normal|high" )
|
||||
["-v"]["--verbosity"]
|
||||
( "set output verbosity" )
|
||||
@@ -265,7 +324,7 @@ namespace Catch {
|
||||
( "list all listeners" )
|
||||
| Opt( setTestOrder, "decl|lex|rand" )
|
||||
["--order"]
|
||||
( "test case order (defaults to decl)" )
|
||||
( "test case order (defaults to rand)" )
|
||||
| Opt( setRngSeed, "'time'|'random-device'|number" )
|
||||
["--rng-seed"]
|
||||
( "set a specific seed for random numbers" )
|
||||
@@ -281,7 +340,7 @@ namespace Catch {
|
||||
| Opt( config.skipBenchmarks)
|
||||
["--skip-benchmarks"]
|
||||
( "disable running benchmarks")
|
||||
| Opt( config.benchmarkSamples, "samples" )
|
||||
| Opt( setBenchmarkSamples, "samples" )
|
||||
["--benchmark-samples"]
|
||||
( "number of samples to collect (default: 100)" )
|
||||
| Opt( config.benchmarkResamples, "resamples" )
|
||||
@@ -305,6 +364,9 @@ namespace Catch {
|
||||
| Opt( config.allowZeroTests )
|
||||
["--allow-running-no-tests"]
|
||||
( "Treat 'No tests run' as a success" )
|
||||
| Opt( config.prematureExitGuardFilePath, "path" )
|
||||
["--premature-exit-guard-file"]
|
||||
( "create a file before running tests and delete it during clean exit" )
|
||||
| Arg( config.testsOrTags, "test name|pattern|tags" )
|
||||
( "which test or tests to use" );
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Visual C++
|
||||
#if defined(_MSC_VER)
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
|
||||
// We want to defer to nvcc-specific warning suppression if we are compiled
|
||||
// with nvcc masquerading for MSVC.
|
||||
@@ -219,14 +219,23 @@
|
||||
__pragma( warning( pop ) )
|
||||
# endif
|
||||
|
||||
// Suppress MSVC C++ Core Guidelines checker warning 26426:
|
||||
// "Global initializer calls a non-constexpr function (i.22)"
|
||||
# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
|
||||
__pragma( warning( disable : 26426 ) )
|
||||
|
||||
// Universal Windows platform does not support SEH
|
||||
// Or console colours (or console at all...)
|
||||
# if defined(CATCH_PLATFORM_WINDOWS_UWP)
|
||||
# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32
|
||||
# else
|
||||
# if !defined(CATCH_PLATFORM_WINDOWS_UWP)
|
||||
# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
|
||||
# endif
|
||||
|
||||
// Only some Windows platform families support the console
|
||||
# if defined(WINAPI_FAMILY_PARTITION)
|
||||
# if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
|
||||
# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32
|
||||
# endif
|
||||
# endif
|
||||
|
||||
// MSVC traditional preprocessor needs some workaround for __VA_ARGS__
|
||||
// _MSVC_TRADITIONAL == 0 means new conformant preprocessor
|
||||
// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor
|
||||
|
||||
@@ -161,7 +161,13 @@ namespace {
|
||||
#endif // Windows/ ANSI/ None
|
||||
|
||||
|
||||
#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) || defined( __GLIBC__ )
|
||||
#if defined( CATCH_PLATFORM_LINUX ) \
|
||||
|| defined( CATCH_PLATFORM_MAC ) \
|
||||
|| defined( __GLIBC__ ) \
|
||||
|| (defined( __FreeBSD__ ) \
|
||||
/* PlayStation platform does not have `isatty()` */ \
|
||||
&& !defined(CATCH_PLATFORM_PLAYSTATION)) \
|
||||
|| defined( CATCH_PLATFORM_QNX )
|
||||
# define CATCH_INTERNAL_HAS_ISATTY
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -6,25 +6,14 @@
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#include <catch2/internal/catch_context.hpp>
|
||||
#include <catch2/internal/catch_noncopyable.hpp>
|
||||
#include <catch2/internal/catch_random_number_generator.hpp>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
Context* Context::currentContext = nullptr;
|
||||
|
||||
void cleanUpContext() {
|
||||
delete Context::currentContext;
|
||||
Context::currentContext = nullptr;
|
||||
}
|
||||
void Context::createContext() {
|
||||
currentContext = new Context();
|
||||
}
|
||||
Context Context::currentContext;
|
||||
|
||||
Context& getCurrentMutableContext() {
|
||||
if ( !Context::currentContext ) { Context::createContext(); }
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
|
||||
return *Context::currentContext;
|
||||
return Context::currentContext;
|
||||
}
|
||||
|
||||
SimplePcg32& sharedRng() {
|
||||
|
||||
@@ -19,11 +19,9 @@ namespace Catch {
|
||||
IConfig const* m_config = nullptr;
|
||||
IResultCapture* m_resultCapture = nullptr;
|
||||
|
||||
CATCH_EXPORT static Context* currentContext;
|
||||
CATCH_EXPORT static Context currentContext;
|
||||
friend Context& getCurrentMutableContext();
|
||||
friend Context const& getCurrentContext();
|
||||
static void createContext();
|
||||
friend void cleanUpContext();
|
||||
|
||||
public:
|
||||
constexpr IResultCapture* getResultCapture() const {
|
||||
@@ -34,21 +32,14 @@ namespace Catch {
|
||||
m_resultCapture = resultCapture;
|
||||
}
|
||||
constexpr void setConfig( IConfig const* config ) { m_config = config; }
|
||||
|
||||
};
|
||||
|
||||
Context& getCurrentMutableContext();
|
||||
|
||||
inline Context const& getCurrentContext() {
|
||||
// We duplicate the logic from `getCurrentMutableContext` here,
|
||||
// to avoid paying the call overhead in debug mode.
|
||||
if ( !Context::currentContext ) { Context::createContext(); }
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
|
||||
return *Context::currentContext;
|
||||
return Context::currentContext;
|
||||
}
|
||||
|
||||
void cleanUpContext();
|
||||
|
||||
class SimplePcg32;
|
||||
SimplePcg32& sharedRng();
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
#endif
|
||||
} // namespace Catch
|
||||
|
||||
#elif defined(CATCH_PLATFORM_LINUX)
|
||||
#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX)
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
|
||||
@@ -14,6 +14,17 @@ namespace Catch {
|
||||
bool isDebuggerActive();
|
||||
}
|
||||
|
||||
#if !defined( CATCH_TRAP ) && defined( __clang__ ) && defined( __has_builtin )
|
||||
# if __has_builtin( __builtin_debugtrap )
|
||||
# define CATCH_TRAP() __builtin_debugtrap()
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined( CATCH_TRAP ) && defined( _MSC_VER )
|
||||
# define CATCH_TRAP() __debugbreak()
|
||||
#endif
|
||||
|
||||
#if !defined(CATCH_TRAP) // If we couldn't use compiler-specific impl from above, we get into platform-specific options
|
||||
#ifdef CATCH_PLATFORM_MAC
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
@@ -38,7 +49,7 @@ namespace Catch {
|
||||
#define CATCH_TRAP() __asm__(".inst 0xde01")
|
||||
#endif
|
||||
|
||||
#elif defined(CATCH_PLATFORM_LINUX)
|
||||
#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX)
|
||||
// If we can use inline assembler, do it because this allows us to break
|
||||
// directly at the location of the failing check instead of breaking inside
|
||||
// raise() called from it, i.e. one stack frame below.
|
||||
@@ -49,15 +60,15 @@ namespace Catch {
|
||||
|
||||
#define CATCH_TRAP() raise(SIGTRAP)
|
||||
#endif
|
||||
#elif defined(_MSC_VER)
|
||||
#define CATCH_TRAP() __debugbreak()
|
||||
#elif defined(__MINGW32__)
|
||||
extern "C" __declspec(dllimport) void __stdcall DebugBreak();
|
||||
#define CATCH_TRAP() DebugBreak()
|
||||
#endif
|
||||
#endif // ^^ CATCH_TRAP is not defined yet, so we define it
|
||||
|
||||
#ifndef CATCH_BREAK_INTO_DEBUGGER
|
||||
#ifdef CATCH_TRAP
|
||||
|
||||
#if !defined(CATCH_BREAK_INTO_DEBUGGER)
|
||||
#if defined(CATCH_TRAP)
|
||||
#define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }()
|
||||
#else
|
||||
#define CATCH_BREAK_INTO_DEBUGGER() []{}()
|
||||
|
||||
@@ -78,10 +78,11 @@
|
||||
*
|
||||
* 3) If a type has no linkage, we also cannot capture it by reference.
|
||||
* The solution is once again to capture them by value. We handle
|
||||
* the common cases by using `std::is_arithmetic` as the default
|
||||
* for `Catch::capture_by_value`, but that is only a some-effort
|
||||
* heuristic. But as with 2), users can specialize `capture_by_value`
|
||||
* for their own types as needed.
|
||||
* the common cases by using `std::is_arithmetic` and `std::is_enum`
|
||||
* as the default for `Catch::capture_by_value`, but that is only a
|
||||
* some-effort heuristic. These combine to capture all possible bitfield
|
||||
* bases, and also some trait-like types. As with 2), users can
|
||||
* specialize `capture_by_value` for their own types as needed.
|
||||
*
|
||||
* 4) To support C++20 and make the SFINAE on our decomposing operators
|
||||
* work, the SFINAE has to happen in return type, rather than in
|
||||
@@ -133,13 +134,22 @@ namespace Catch {
|
||||
using RemoveCVRef_t = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||
}
|
||||
|
||||
// Note: There is nothing that stops us from extending this,
|
||||
// e.g. to `std::is_scalar`, but the more encompassing
|
||||
// traits are usually also more expensive. For now we
|
||||
// keep this as it used to be and it can be changed later.
|
||||
// Note: This is about as much as we can currently reasonably support.
|
||||
// In an ideal world, we could capture by value small trivially
|
||||
// copyable types, but the actual `std::is_trivially_copyable`
|
||||
// trait is a huge mess with standard-violating results on
|
||||
// GCC and Clang, which are unlikely to be fixed soon due to ABI
|
||||
// concerns.
|
||||
// `std::is_scalar` also causes issues due to the `is_pointer`
|
||||
// component, which causes ambiguity issues with (references-to)
|
||||
// function pointer. If those are resolved, we still need to
|
||||
// disambiguate the overload set for arrays, through explicit
|
||||
// overload for references to sized arrays.
|
||||
template <typename T>
|
||||
struct capture_by_value
|
||||
: std::integral_constant<bool, std::is_arithmetic<T>{}> {};
|
||||
: std::integral_constant<bool,
|
||||
std::is_arithmetic<T>::value ||
|
||||
std::is_enum<T>::value> {};
|
||||
|
||||
#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS )
|
||||
template <>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
#include <catch2/catch_user_config.hpp>
|
||||
|
||||
#if !defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS )
|
||||
# define DEPRECATED( msg ) [[deprecated( msg )]]
|
||||
# define CATCH_DEPRECATED( msg ) [[deprecated( msg )]]
|
||||
#else
|
||||
# define DEPRECATED( msg )
|
||||
# define CATCH_DEPRECATED( msg )
|
||||
#endif
|
||||
|
||||
#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED
|
||||
|
||||
@@ -59,10 +59,10 @@ namespace Catch {
|
||||
// To avoid having to handle TFE explicitly everywhere, we just
|
||||
// rethrow it so that it goes back up the caller.
|
||||
catch( TestFailureException& ) {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
return "{ nested assertion failed }";
|
||||
}
|
||||
catch( TestSkipException& ) {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
return "{ nested SKIP() called }";
|
||||
}
|
||||
catch( std::exception const& ex ) {
|
||||
return ex.what();
|
||||
|
||||
@@ -86,23 +86,27 @@ namespace Catch {
|
||||
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
|
||||
};
|
||||
|
||||
// Since we do not support multiple instantiations, we put these
|
||||
// into global variables and rely on cleaning them up in outlined
|
||||
// constructors/destructors
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
|
||||
|
||||
|
||||
static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) {
|
||||
for (auto const& def : signalDefs) {
|
||||
if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
|
||||
reportFatal(def.name);
|
||||
}
|
||||
}
|
||||
// If its not an exception we care about, pass it along.
|
||||
// If a filter was previously registered, invoke it
|
||||
if (previousTopLevelExceptionFilter) {
|
||||
return previousTopLevelExceptionFilter(ExceptionInfo);
|
||||
}
|
||||
// Otherwise, pass along all exceptions.
|
||||
// This stops us from eating debugger breaks etc.
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// Since we do not support multiple instantiations, we put these
|
||||
// into global variables and rely on cleaning them up in outlined
|
||||
// constructors/destructors
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
|
||||
|
||||
|
||||
// For MSVC, we reserve part of the stack memory for handling
|
||||
// memory overflow structured exception.
|
||||
FatalConditionHandler::FatalConditionHandler() {
|
||||
|
||||
@@ -7,8 +7,36 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
#include <catch2/internal/catch_jsonwriter.hpp>
|
||||
#include <catch2/internal/catch_unreachable.hpp>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
namespace {
|
||||
static bool needsEscape( char c ) {
|
||||
return c == '"' || c == '\\' || c == '\b' || c == '\f' ||
|
||||
c == '\n' || c == '\r' || c == '\t';
|
||||
}
|
||||
|
||||
static Catch::StringRef makeEscapeStringRef( char c ) {
|
||||
if ( c == '"' ) {
|
||||
return "\\\""_sr;
|
||||
} else if ( c == '\\' ) {
|
||||
return "\\\\"_sr;
|
||||
} else if ( c == '\b' ) {
|
||||
return "\\b"_sr;
|
||||
} else if ( c == '\f' ) {
|
||||
return "\\f"_sr;
|
||||
} else if ( c == '\n' ) {
|
||||
return "\\n"_sr;
|
||||
} else if ( c == '\r' ) {
|
||||
return "\\r"_sr;
|
||||
} else if ( c == '\t' ) {
|
||||
return "\\t"_sr;
|
||||
}
|
||||
Catch::Detail::Unreachable();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void JsonUtils::indent( std::ostream& os, std::uint64_t level ) {
|
||||
for ( std::uint64_t i = 0; i < level; ++i ) {
|
||||
os << " ";
|
||||
@@ -118,30 +146,19 @@ namespace Catch {
|
||||
|
||||
void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) {
|
||||
if ( quote ) { m_os << '"'; }
|
||||
for (char c : value) {
|
||||
// Escape list taken from https://www.json.org/json-en.html,
|
||||
// string definition.
|
||||
// Note that while forward slash _can_ be escaped, it does
|
||||
// not have to be, if JSON is not further embedded somewhere
|
||||
// where forward slash is meaningful.
|
||||
if ( c == '"' ) {
|
||||
m_os << "\\\"";
|
||||
} else if ( c == '\\' ) {
|
||||
m_os << "\\\\";
|
||||
} else if ( c == '\b' ) {
|
||||
m_os << "\\b";
|
||||
} else if ( c == '\f' ) {
|
||||
m_os << "\\f";
|
||||
} else if ( c == '\n' ) {
|
||||
m_os << "\\n";
|
||||
} else if ( c == '\r' ) {
|
||||
m_os << "\\r";
|
||||
} else if ( c == '\t' ) {
|
||||
m_os << "\\t";
|
||||
} else {
|
||||
m_os << c;
|
||||
size_t current_start = 0;
|
||||
for ( size_t i = 0; i < value.size(); ++i ) {
|
||||
if ( needsEscape( value[i] ) ) {
|
||||
if ( current_start < i ) {
|
||||
m_os << value.substr( current_start, i - current_start );
|
||||
}
|
||||
m_os << makeEscapeStringRef( value[i] );
|
||||
current_start = i + 1;
|
||||
}
|
||||
}
|
||||
if ( current_start < value.size() ) {
|
||||
m_os << value.substr( current_start, value.size() - current_start );
|
||||
}
|
||||
if ( quote ) { m_os << '"'; }
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#ifndef CATCH_JSONWRITER_HPP_INCLUDED
|
||||
#define CATCH_JSONWRITER_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_lifetimebound.hpp>
|
||||
#include <catch2/internal/catch_reusable_string_stream.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
|
||||
@@ -27,8 +28,8 @@ namespace Catch {
|
||||
|
||||
class JsonValueWriter {
|
||||
public:
|
||||
JsonValueWriter( std::ostream& os );
|
||||
JsonValueWriter( std::ostream& os, std::uint64_t indent_level );
|
||||
JsonValueWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
|
||||
JsonValueWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level );
|
||||
|
||||
JsonObjectWriter writeObject() &&;
|
||||
JsonArrayWriter writeArray() &&;
|
||||
@@ -62,8 +63,8 @@ namespace Catch {
|
||||
|
||||
class JsonObjectWriter {
|
||||
public:
|
||||
JsonObjectWriter( std::ostream& os );
|
||||
JsonObjectWriter( std::ostream& os, std::uint64_t indent_level );
|
||||
JsonObjectWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
|
||||
JsonObjectWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level );
|
||||
|
||||
JsonObjectWriter( JsonObjectWriter&& source ) noexcept;
|
||||
JsonObjectWriter& operator=( JsonObjectWriter&& source ) = delete;
|
||||
@@ -81,8 +82,8 @@ namespace Catch {
|
||||
|
||||
class JsonArrayWriter {
|
||||
public:
|
||||
JsonArrayWriter( std::ostream& os );
|
||||
JsonArrayWriter( std::ostream& os, std::uint64_t indent_level );
|
||||
JsonArrayWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
|
||||
JsonArrayWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level );
|
||||
|
||||
JsonArrayWriter( JsonArrayWriter&& source ) noexcept;
|
||||
JsonArrayWriter& operator=( JsonArrayWriter&& source ) = delete;
|
||||
|
||||
@@ -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
|
||||
@@ -7,19 +7,24 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/internal/catch_message_info.hpp>
|
||||
#include <catch2/internal/catch_thread_local.hpp>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
MessageInfo::MessageInfo( StringRef _macroName,
|
||||
SourceLineInfo const& _lineInfo,
|
||||
ResultWas::OfType _type )
|
||||
namespace {
|
||||
// Messages are owned by their individual threads, so the counter should
|
||||
// be thread-local as well. Alternative consideration: atomic counter,
|
||||
// so threads don't share IDs and things are easier to debug.
|
||||
static CATCH_INTERNAL_THREAD_LOCAL unsigned int messageIDCounter = 0;
|
||||
}
|
||||
|
||||
MessageInfo::MessageInfo( StringRef _macroName,
|
||||
SourceLineInfo const& _lineInfo,
|
||||
ResultWas::OfType _type )
|
||||
: macroName( _macroName ),
|
||||
lineInfo( _lineInfo ),
|
||||
type( _type ),
|
||||
sequence( ++globalCount )
|
||||
sequence( ++messageIDCounter )
|
||||
{}
|
||||
|
||||
// This may need protecting if threading support is added
|
||||
unsigned int MessageInfo::globalCount = 0;
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
@@ -26,18 +26,17 @@ namespace Catch {
|
||||
std::string message;
|
||||
SourceLineInfo lineInfo;
|
||||
ResultWas::OfType type;
|
||||
// The "ID" of the message, used to know when to remove it from reporter context.
|
||||
unsigned int sequence;
|
||||
|
||||
DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
CATCH_DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
bool operator == (MessageInfo const& other) const {
|
||||
return sequence == other.sequence;
|
||||
}
|
||||
DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
CATCH_DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
bool operator < (MessageInfo const& other) const {
|
||||
return sequence < other.sequence;
|
||||
}
|
||||
private:
|
||||
static unsigned int globalCount;
|
||||
};
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
// 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_PATH_FILTER_HPP_INCLUDED
|
||||
#define CATCH_PATH_FILTER_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
struct PathFilter {
|
||||
enum class For {
|
||||
Section,
|
||||
Generator,
|
||||
};
|
||||
PathFilter( For type_, std::string filter_ ):
|
||||
type( type_ ), filter( CATCH_MOVE( filter_ ) ) {}
|
||||
|
||||
For type;
|
||||
std::string filter;
|
||||
|
||||
friend bool operator==( PathFilter const& lhs, PathFilter const& rhs );
|
||||
};
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#endif // CATCH_PATH_FILTER_HPP_INCLUDED
|
||||
@@ -25,6 +25,9 @@
|
||||
#elif defined(linux) || defined(__linux) || defined(__linux__)
|
||||
# define CATCH_PLATFORM_LINUX
|
||||
|
||||
#elif defined(__QNX__)
|
||||
# define CATCH_PLATFORM_QNX
|
||||
|
||||
#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
|
||||
# define CATCH_PLATFORM_WINDOWS
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#include <catch2/internal/catch_reusable_string_stream.hpp>
|
||||
#include <catch2/internal/catch_singletons.hpp>
|
||||
#include <catch2/internal/catch_thread_support.hpp>
|
||||
#include <catch2/internal/catch_unique_ptr.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace Catch {
|
||||
@@ -20,34 +22,40 @@ namespace Catch {
|
||||
std::vector<Detail::unique_ptr<std::ostringstream>> m_streams;
|
||||
std::vector<std::size_t> m_unused;
|
||||
std::ostringstream m_referenceStream; // Used for copy state/ flags from
|
||||
Detail::Mutex m_mutex;
|
||||
|
||||
auto add() -> std::size_t {
|
||||
auto add() -> std::pair<std::size_t, std::ostringstream*> {
|
||||
Detail::LockGuard _( m_mutex );
|
||||
if( m_unused.empty() ) {
|
||||
m_streams.push_back( Detail::make_unique<std::ostringstream>() );
|
||||
return m_streams.size()-1;
|
||||
return { m_streams.size()-1, m_streams.back().get() };
|
||||
}
|
||||
else {
|
||||
auto index = m_unused.back();
|
||||
m_unused.pop_back();
|
||||
return index;
|
||||
return { index, m_streams[index].get() };
|
||||
}
|
||||
}
|
||||
|
||||
void release( std::size_t index ) {
|
||||
m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state
|
||||
m_unused.push_back(index);
|
||||
void release( std::size_t index, std::ostream* originalPtr ) {
|
||||
assert( originalPtr );
|
||||
originalPtr->copyfmt( m_referenceStream ); // Restore initial flags and other state
|
||||
|
||||
Detail::LockGuard _( m_mutex );
|
||||
assert( originalPtr == m_streams[index].get() && "Mismatch between release index and stream ptr" );
|
||||
m_unused.push_back( index );
|
||||
}
|
||||
};
|
||||
|
||||
ReusableStringStream::ReusableStringStream()
|
||||
: m_index( Singleton<StringStreams>::getMutable().add() ),
|
||||
m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() )
|
||||
{}
|
||||
ReusableStringStream::ReusableStringStream() {
|
||||
std::tie( m_index, m_oss ) =
|
||||
Singleton<StringStreams>::getMutable().add();
|
||||
}
|
||||
|
||||
ReusableStringStream::~ReusableStringStream() {
|
||||
static_cast<std::ostringstream*>( m_oss )->str("");
|
||||
m_oss->clear();
|
||||
Singleton<StringStreams>::getMutable().release( m_index );
|
||||
Singleton<StringStreams>::getMutable().release( m_index, m_oss );
|
||||
}
|
||||
|
||||
std::string ReusableStringStream::str() const {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <catch2/internal/catch_run_context.hpp>
|
||||
|
||||
#include <catch2/catch_user_config.hpp>
|
||||
#include <catch2/generators/catch_generators_throw.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_config.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_generatortracker.hpp>
|
||||
#include <catch2/interfaces/catch_interfaces_reporter.hpp>
|
||||
@@ -19,8 +20,12 @@
|
||||
#include <catch2/catch_timer.hpp>
|
||||
#include <catch2/internal/catch_output_redirect.hpp>
|
||||
#include <catch2/internal/catch_assertion_handler.hpp>
|
||||
#include <catch2/internal/catch_path_filter.hpp>
|
||||
#include <catch2/internal/catch_test_failure_exception.hpp>
|
||||
#include <catch2/internal/catch_thread_local.hpp>
|
||||
#include <catch2/internal/catch_unreachable.hpp>
|
||||
#include <catch2/internal/catch_result_type.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
@@ -32,12 +37,46 @@ namespace Catch {
|
||||
struct GeneratorTracker final : TestCaseTracking::TrackerBase,
|
||||
IGeneratorTracker {
|
||||
GeneratorBasePtr m_generator;
|
||||
// Filtered generator has moved to specific index due to
|
||||
// a filter, it needs special handling of `countedNext()`
|
||||
bool m_isFiltered = false;
|
||||
|
||||
GeneratorTracker(
|
||||
TestCaseTracking::NameAndLocation&& nameAndLocation,
|
||||
TrackerContext& ctx,
|
||||
ITracker* parent ):
|
||||
TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ) {}
|
||||
ITracker* parent,
|
||||
GeneratorBasePtr&& generator ):
|
||||
TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ),
|
||||
m_generator( CATCH_MOVE( generator ) ) {
|
||||
assert( m_generator &&
|
||||
"Cannot create tracker without generator" );
|
||||
|
||||
// Handle potential filter and move forward here...
|
||||
// Old style filters do not affect generators at all
|
||||
if (m_newStyleFilters && m_allTrackerDepth < m_filterRef->size()) {
|
||||
auto const& filter =
|
||||
( *m_filterRef )[m_allTrackerDepth];
|
||||
// Generator cannot be un-entered the way a section
|
||||
// can be, so the tracker has to throw for a wrong
|
||||
// filter to stop the execution flow.
|
||||
if (filter.type == PathFilter::For::Section) {
|
||||
// TBD: Explicit SKIP, or new exception that says
|
||||
// "don't continue", but doesn't show in totals?
|
||||
SKIP();
|
||||
}
|
||||
// '*' is the wildcard for "all elements in generator"
|
||||
// used for filtering sections below the generator, but
|
||||
// not the generator itself.
|
||||
if ( filter.filter != "*" ) {
|
||||
m_isFiltered = true;
|
||||
// TBD: We assume that the filter was validated as
|
||||
// number during parsing. We should pass it
|
||||
// as number from the CLI parser.
|
||||
size_t targetIndex = std::stoul( filter.filter );
|
||||
m_generator->skipToNthElement( targetIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GeneratorTracker*
|
||||
acquire( TrackerContext& ctx,
|
||||
@@ -81,9 +120,6 @@ namespace Catch {
|
||||
|
||||
// TrackerBase interface
|
||||
bool isGeneratorTracker() const override { return true; }
|
||||
auto hasGenerator() const -> bool override {
|
||||
return !!m_generator;
|
||||
}
|
||||
void close() override {
|
||||
TrackerBase::close();
|
||||
// If a generator has a child (it is followed by a section)
|
||||
@@ -112,29 +148,24 @@ namespace Catch {
|
||||
// _can_ start, and thus we should wait for them, or
|
||||
// they cannot start (due to filters), and we shouldn't
|
||||
// wait for them
|
||||
ITracker* parent = m_parent;
|
||||
// This is safe: there is always at least one section
|
||||
// tracker in a test case tracking tree
|
||||
while ( !parent->isSectionTracker() ) {
|
||||
parent = parent->parent();
|
||||
|
||||
// No filters left -> no restrictions on running sections
|
||||
size_t childDepth = 1 + (m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth);
|
||||
if ( childDepth >= m_filterRef->size() ) {
|
||||
return true;
|
||||
}
|
||||
assert( parent &&
|
||||
"Missing root (test case) level section" );
|
||||
|
||||
auto const& parentSection =
|
||||
static_cast<SectionTracker const&>( *parent );
|
||||
auto const& filters = parentSection.getFilters();
|
||||
// No filters -> no restrictions on running sections
|
||||
if ( filters.empty() ) { return true; }
|
||||
|
||||
// If we are using the new style filters, we need to check
|
||||
// whether the successive filter is for section or a generator.
|
||||
if ( m_newStyleFilters
|
||||
&& (*m_filterRef)[childDepth].type != PathFilter::For::Section ) {
|
||||
return false;
|
||||
}
|
||||
// Look for any child section that could match the remaining filters
|
||||
for ( auto const& child : m_children ) {
|
||||
if ( child->isSectionTracker() &&
|
||||
std::find( filters.begin(),
|
||||
filters.end(),
|
||||
static_cast<SectionTracker const&>(
|
||||
*child )
|
||||
.trimmedName() ) !=
|
||||
filters.end() ) {
|
||||
static_cast<SectionTracker const&>( *child )
|
||||
.trimmedName() == StringRef((*m_filterRef)[childDepth].filter) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -146,9 +177,10 @@ namespace Catch {
|
||||
// value, but we do not want to invoke the side-effect if
|
||||
// this generator is still waiting for any child to start.
|
||||
assert( m_generator && "Tracker without generator" );
|
||||
if ( should_wait_for_child ||
|
||||
( m_runState == CompletedSuccessfully &&
|
||||
m_generator->countedNext() ) ) {
|
||||
if ( should_wait_for_child
|
||||
|| ( m_runState == CompletedSuccessfully
|
||||
&& !m_isFiltered // filtered generators cannot meaningfully move forward, as they would get past the filter
|
||||
&& m_generator->countedNext() ) ) {
|
||||
m_children.clear();
|
||||
m_runState = Executing;
|
||||
}
|
||||
@@ -158,9 +190,6 @@ namespace Catch {
|
||||
auto getGenerator() const -> GeneratorBasePtr const& override {
|
||||
return m_generator;
|
||||
}
|
||||
void setGenerator( GeneratorBasePtr&& generator ) override {
|
||||
m_generator = CATCH_MOVE( generator );
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
}
|
||||
@@ -172,26 +201,109 @@ namespace Catch {
|
||||
// This also implies that messages are owned by their respective
|
||||
// threads, and should not be shared across different threads.
|
||||
//
|
||||
// For simplicity, we disallow messages in multi-threaded contexts,
|
||||
// but in the future we can enable them under this logic.
|
||||
//
|
||||
// This implies that various pieces of metadata referring to last
|
||||
// assertion result/source location/message handling, etc
|
||||
// should also be thread local. For now we just use naked globals
|
||||
// below, in the future we will want to allocate piece of memory
|
||||
// from heap, to avoid consuming too much thread-local storage.
|
||||
//
|
||||
// Note that we also don't want non-trivial the thread-local variables
|
||||
// below be initialized for every thread, only for those that touch
|
||||
// Catch2. To make this work with both GCC/Clang and MSVC, we have to
|
||||
// make them thread-local magic statics. (Class-level statics have the
|
||||
// desired semantics on GCC, but not on MSVC).
|
||||
|
||||
// This is used for the "if" part of CHECKED_IF/CHECKED_ELSE
|
||||
static thread_local bool g_lastAssertionPassed = false;
|
||||
// Should we clear message scopes before sending off the messages to
|
||||
// reporter? Set in `assertionPassedFastPath` to avoid doing the full
|
||||
// clear there for performance reasons.
|
||||
static thread_local bool g_clearMessageScopes = false;
|
||||
static CATCH_INTERNAL_THREAD_LOCAL bool g_lastAssertionPassed = false;
|
||||
|
||||
// This is the source location for last encountered macro. It is
|
||||
// used to provide the users with more precise location of error
|
||||
// when an unexpected exception/fatal error happens.
|
||||
static thread_local SourceLineInfo g_lastKnownLineInfo("DummyLocation", static_cast<size_t>(-1));
|
||||
}
|
||||
static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo
|
||||
g_lastKnownLineInfo( "DummyLocation", static_cast<size_t>( -1 ) );
|
||||
|
||||
// Should we clear message scopes before sending off the messages to
|
||||
// reporter? Set in `assertionPassedFastPath` to avoid doing the full
|
||||
// clear there for performance reasons.
|
||||
static CATCH_INTERNAL_THREAD_LOCAL bool g_clearMessageScopes = false;
|
||||
|
||||
|
||||
// Holds the data for both scoped and unscoped messages together,
|
||||
// to avoid issues where their lifetimes start in wrong order,
|
||||
// and then are destroyed in wrong order.
|
||||
class MessageHolder {
|
||||
// The actual message vector passed to the reporters
|
||||
std::vector<MessageInfo> messages;
|
||||
// IDs of messages from UNSCOPED_X macros, which we have to
|
||||
// remove manually.
|
||||
std::vector<unsigned int> unscoped_ids;
|
||||
|
||||
public:
|
||||
// We do not need to special-case the unscoped messages when
|
||||
// we only keep around the raw msg ids.
|
||||
~MessageHolder() = default;
|
||||
|
||||
void addUnscopedMessage( MessageInfo&& info ) {
|
||||
repairUnscopedMessageInvariant();
|
||||
unscoped_ids.push_back( info.sequence );
|
||||
messages.push_back( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
void addUnscopedMessage(MessageBuilder&& builder) {
|
||||
MessageInfo info( CATCH_MOVE( builder.m_info ) );
|
||||
info.message = builder.m_stream.str();
|
||||
addUnscopedMessage( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
void addScopedMessage(MessageInfo&& info) {
|
||||
messages.push_back( CATCH_MOVE( info ) );
|
||||
}
|
||||
|
||||
std::vector<MessageInfo> const& getMessages() const {
|
||||
return messages;
|
||||
}
|
||||
|
||||
void removeMessage( 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 improvement is tiny, and we would have to hand-write
|
||||
// the loop to avoid terrible codegen of reverse iterators
|
||||
// in debug mode.
|
||||
auto iter =
|
||||
std::find_if( messages.begin(),
|
||||
messages.end(),
|
||||
[messageId]( MessageInfo const& msg ) {
|
||||
return msg.sequence == messageId;
|
||||
} );
|
||||
assert( iter != messages.end() &&
|
||||
"Trying to remove non-existent message." );
|
||||
messages.erase( iter );
|
||||
}
|
||||
|
||||
void removeUnscopedMessages() {
|
||||
for ( const auto messageId : unscoped_ids ) {
|
||||
removeMessage( messageId );
|
||||
}
|
||||
unscoped_ids.clear();
|
||||
g_clearMessageScopes = false;
|
||||
}
|
||||
|
||||
void repairUnscopedMessageInvariant() {
|
||||
if ( g_clearMessageScopes ) { removeUnscopedMessages(); }
|
||||
g_clearMessageScopes = false;
|
||||
}
|
||||
};
|
||||
|
||||
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
|
||||
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
|
||||
static MessageHolder& g_messageHolder() {
|
||||
static CATCH_INTERNAL_THREAD_LOCAL MessageHolder value;
|
||||
return value;
|
||||
}
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)
|
||||
: m_runInfo(_config->name()),
|
||||
@@ -205,6 +317,13 @@ namespace Catch {
|
||||
{
|
||||
getCurrentMutableContext().setResultCapture( this );
|
||||
m_reporter->testRunStarting(m_runInfo);
|
||||
|
||||
// TODO: HACK!
|
||||
// We need to make sure the underlying cache is initialized
|
||||
// while we are guaranteed to be running in a single thread,
|
||||
// because the initialization is not thread-safe.
|
||||
ReusableStringStream rss;
|
||||
(void)rss;
|
||||
}
|
||||
|
||||
RunContext::~RunContext() {
|
||||
@@ -224,7 +343,8 @@ namespace Catch {
|
||||
|
||||
ITracker& rootTracker = m_trackerContext.startRun();
|
||||
assert(rootTracker.isSectionTracker());
|
||||
static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun());
|
||||
rootTracker.setFilters( &m_config->getPathFilters(),
|
||||
m_config->useNewFilterBehaviour() );
|
||||
|
||||
// We intentionally only seed the internal RNG once per test case,
|
||||
// before it is first invoked. The reason for that is a complex
|
||||
@@ -327,20 +447,19 @@ namespace Catch {
|
||||
Detail::g_lastAssertionPassed = true;
|
||||
}
|
||||
|
||||
auto& msgHolder = Detail::g_messageHolder();
|
||||
msgHolder.repairUnscopedMessageInvariant();
|
||||
|
||||
// From here, we are touching shared state and need mutex.
|
||||
Detail::LockGuard lock( m_assertionMutex );
|
||||
{
|
||||
if ( Detail::g_clearMessageScopes ) {
|
||||
m_messageScopes.clear();
|
||||
Detail::g_clearMessageScopes = false;
|
||||
}
|
||||
auto _ = scopedDeactivate( *m_outputRedirect );
|
||||
updateTotalsFromAtomics();
|
||||
m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) );
|
||||
m_reporter->assertionEnded( AssertionStats( result, msgHolder.getMessages(), m_totals ) );
|
||||
}
|
||||
|
||||
if ( result.getResultType() != ResultWas::Warning ) {
|
||||
m_messageScopes.clear();
|
||||
msgHolder.removeUnscopedMessages();
|
||||
}
|
||||
|
||||
// Reset working state. assertion info will be reset after
|
||||
@@ -397,18 +516,28 @@ namespace Catch {
|
||||
SourceLineInfo lineInfo,
|
||||
Generators::GeneratorBasePtr&& generator ) {
|
||||
|
||||
// TBD: Do we want to avoid the warning if the generator is filtered?
|
||||
if ( m_config->warnAboutInfiniteGenerators() &&
|
||||
!generator->isFinite() ) {
|
||||
// TBD: Would it be better to expand this macro inline?
|
||||
FAIL( "GENERATE() would run infinitely" );
|
||||
}
|
||||
|
||||
auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast<std::string>( generatorName ), lineInfo );
|
||||
auto& currentTracker = m_trackerContext.currentTracker();
|
||||
assert(
|
||||
currentTracker.nameAndLocation() != nameAndLoc &&
|
||||
"Trying to create tracker for a generator that already has one" );
|
||||
|
||||
auto newTracker = Catch::Detail::make_unique<Generators::GeneratorTracker>(
|
||||
CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker );
|
||||
auto newTracker =
|
||||
Catch::Detail::make_unique<Generators::GeneratorTracker>(
|
||||
CATCH_MOVE( nameAndLoc ),
|
||||
m_trackerContext,
|
||||
¤tTracker,
|
||||
CATCH_MOVE( generator ) );
|
||||
auto ret = newTracker.get();
|
||||
currentTracker.addChild( CATCH_MOVE( newTracker ) );
|
||||
|
||||
ret->setGenerator( CATCH_MOVE( generator ) );
|
||||
ret->open();
|
||||
return ret;
|
||||
}
|
||||
@@ -473,28 +602,6 @@ namespace Catch {
|
||||
m_reporter->benchmarkFailed( error );
|
||||
}
|
||||
|
||||
void RunContext::pushScopedMessage(MessageInfo const & message) {
|
||||
m_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.
|
||||
m_messages.erase(
|
||||
std::find_if( m_messages.begin(),
|
||||
m_messages.end(),
|
||||
[id = message.sequence]( MessageInfo const& msg ) {
|
||||
return msg.sequence == id;
|
||||
} ) );
|
||||
}
|
||||
|
||||
void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
m_messageScopes.emplace_back( CATCH_MOVE(builder) );
|
||||
}
|
||||
|
||||
std::string RunContext::getCurrentTestName() const {
|
||||
return m_activeTestCase
|
||||
? m_activeTestCase->getTestCaseInfo().name
|
||||
@@ -651,10 +758,10 @@ namespace Catch {
|
||||
|
||||
m_testCaseTracker->close();
|
||||
handleUnfinishedSections();
|
||||
m_messageScopes.clear();
|
||||
// TBD: At this point, m_messages should be empty. Do we want to
|
||||
// assert that this is true, or keep the defensive clear call?
|
||||
m_messages.clear();
|
||||
auto& msgHolder = Detail::g_messageHolder();
|
||||
msgHolder.removeUnscopedMessages();
|
||||
assert( msgHolder.getMessages().empty() &&
|
||||
"There should be no leftover messages after the test ends" );
|
||||
|
||||
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
|
||||
m_reporter->sectionEnded(testCaseSectionStats);
|
||||
@@ -821,11 +928,20 @@ namespace Catch {
|
||||
}
|
||||
}
|
||||
|
||||
IResultCapture& getResultCapture() {
|
||||
if (auto* capture = getCurrentContext().getResultCapture())
|
||||
return *capture;
|
||||
else
|
||||
CATCH_INTERNAL_ERROR("No result capture instance");
|
||||
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
void IResultCapture::popScopedMessage( unsigned int messageId ) {
|
||||
Detail::g_messageHolder().removeMessage( messageId );
|
||||
}
|
||||
|
||||
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) );
|
||||
}
|
||||
|
||||
void IResultCapture::addUnscopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( message ) );
|
||||
}
|
||||
|
||||
void seedRng(IConfig const& config) {
|
||||
|
||||
@@ -94,11 +94,6 @@ namespace Catch {
|
||||
void benchmarkEnded( BenchmarkStats<> const& stats ) override;
|
||||
void benchmarkFailed( StringRef error ) override;
|
||||
|
||||
void pushScopedMessage( MessageInfo const& message ) override;
|
||||
void popScopedMessage( MessageInfo const& message ) override;
|
||||
|
||||
void emplaceUnscopedMessage( MessageBuilder&& builder ) override;
|
||||
|
||||
std::string getCurrentTestName() const override;
|
||||
|
||||
const AssertionResult* getLastResult() const override;
|
||||
@@ -149,9 +144,6 @@ namespace Catch {
|
||||
Totals m_totals;
|
||||
Detail::AtomicCounts m_atomicAssertionCount;
|
||||
IEventListenerPtr m_reporter;
|
||||
std::vector<MessageInfo> m_messages;
|
||||
// Owners for the UNSCOPED_X information macro
|
||||
std::vector<ScopedMessage> m_messageScopes;
|
||||
std::vector<SectionEndInfo> m_unfinishedSections;
|
||||
std::vector<ITracker*> m_activeSections;
|
||||
TrackerContext m_trackerContext;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#ifndef CATCH_STRING_MANIP_HPP_INCLUDED
|
||||
#define CATCH_STRING_MANIP_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_lifetimebound.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -28,10 +29,10 @@ namespace Catch {
|
||||
//! Returns a new string without whitespace at the start/end
|
||||
std::string trim( std::string const& str );
|
||||
//! Returns a substring of the original ref without whitespace. Beware lifetimes!
|
||||
StringRef trim(StringRef ref);
|
||||
StringRef trim( StringRef ref CATCH_ATTR_LIFETIMEBOUND );
|
||||
|
||||
// !!! Be aware, returns refs into original string - make sure original string outlives them
|
||||
std::vector<StringRef> splitStringRef( StringRef str, char delimiter );
|
||||
std::vector<StringRef> splitStringRef( StringRef str CATCH_ATTR_LIFETIMEBOUND, char delimiter );
|
||||
bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
|
||||
|
||||
/**
|
||||
@@ -49,7 +50,7 @@ namespace Catch {
|
||||
StringRef m_label;
|
||||
|
||||
public:
|
||||
constexpr pluralise(std::uint64_t count, StringRef label):
|
||||
constexpr pluralise(std::uint64_t count, StringRef label CATCH_ATTR_LIFETIMEBOUND):
|
||||
m_count(count),
|
||||
m_label(label)
|
||||
{}
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
#ifndef CATCH_STRINGREF_HPP_INCLUDED
|
||||
#define CATCH_STRINGREF_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_lifetimebound.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <iosfwd>
|
||||
#include <cassert>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace Catch {
|
||||
@@ -36,14 +37,16 @@ namespace Catch {
|
||||
public: // construction
|
||||
constexpr StringRef() noexcept = default;
|
||||
|
||||
StringRef( char const* rawChars ) noexcept;
|
||||
StringRef( char const* rawChars CATCH_ATTR_LIFETIMEBOUND ) noexcept;
|
||||
|
||||
constexpr StringRef( char const* rawChars, size_type size ) noexcept
|
||||
constexpr StringRef( char const* rawChars CATCH_ATTR_LIFETIMEBOUND,
|
||||
size_type size ) noexcept
|
||||
: m_start( rawChars ),
|
||||
m_size( size )
|
||||
{}
|
||||
|
||||
StringRef( std::string const& stdString ) noexcept
|
||||
StringRef(
|
||||
std::string const& stdString CATCH_ATTR_LIFETIMEBOUND ) noexcept
|
||||
: m_start( stdString.c_str() ),
|
||||
m_size( stdString.size() )
|
||||
{}
|
||||
@@ -89,7 +92,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
// Returns the current start pointer. May not be null-terminated.
|
||||
constexpr char const* data() const noexcept {
|
||||
constexpr char const* data() const noexcept CATCH_ATTR_LIFETIMEBOUND {
|
||||
return m_start;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
#include <catch2/internal/catch_test_case_tracker.hpp>
|
||||
|
||||
#include <catch2/internal/catch_enforce.hpp>
|
||||
#include <catch2/internal/catch_string_manip.hpp>
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
#include <catch2/internal/catch_path_filter.hpp>
|
||||
#include <catch2/internal/catch_string_manip.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
@@ -27,6 +28,17 @@ namespace TestCaseTracking {
|
||||
location( _location )
|
||||
{}
|
||||
|
||||
ITracker::ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ):
|
||||
m_nameAndLocation( CATCH_MOVE( nameAndLoc ) ), m_parent( parent ) {
|
||||
if ( m_parent ) {
|
||||
m_allTrackerDepth = m_parent->m_allTrackerDepth + 1;
|
||||
// We leave section trackers to bump themselves up, as
|
||||
// we cannot use `isSectionTracker` in constructor
|
||||
m_sectionOnlyDepth = m_parent->m_sectionOnlyDepth;
|
||||
m_filterRef = m_parent->m_filterRef;
|
||||
m_newStyleFilters = m_parent->m_newStyleFilters;
|
||||
}
|
||||
}
|
||||
|
||||
ITracker::~ITracker() = default;
|
||||
|
||||
@@ -159,25 +171,32 @@ namespace TestCaseTracking {
|
||||
: TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ),
|
||||
m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name)))
|
||||
{
|
||||
if( parent ) {
|
||||
while ( !parent->isSectionTracker() ) {
|
||||
parent = parent->parent();
|
||||
}
|
||||
|
||||
SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );
|
||||
addNextFilters( parentSection.m_filters );
|
||||
if( m_parent ) {
|
||||
++m_sectionOnlyDepth;
|
||||
}
|
||||
}
|
||||
|
||||
bool SectionTracker::isComplete() const {
|
||||
bool complete = true;
|
||||
|
||||
if (m_filters.empty()
|
||||
|| m_filters[0].empty()
|
||||
|| std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) {
|
||||
complete = TrackerBase::isComplete();
|
||||
// If there are active filters AND we do not pass them,
|
||||
// the section is always "completed"
|
||||
const size_t filterIndex =
|
||||
m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth;
|
||||
if ( filterIndex < m_filterRef->size() ) {
|
||||
// There is active filter, check it
|
||||
// 1) New style filter must explicitly target section
|
||||
if ( m_newStyleFilters && ( *m_filterRef )[filterIndex].type !=
|
||||
PathFilter::For::Section ) {
|
||||
return true;
|
||||
}
|
||||
// 2) Both style filters must match the trimmed name exactly
|
||||
if ( m_trimmed_name !=
|
||||
StringRef( ( *m_filterRef )[filterIndex].filter ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return complete;
|
||||
|
||||
// Otherwise we delegate to the generic processing
|
||||
return TrackerBase::isComplete();
|
||||
}
|
||||
|
||||
bool SectionTracker::isSectionTracker() const { return true; }
|
||||
@@ -213,19 +232,6 @@ namespace TestCaseTracking {
|
||||
open();
|
||||
}
|
||||
|
||||
void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) {
|
||||
if( !filters.empty() ) {
|
||||
m_filters.reserve( m_filters.size() + filters.size() + 2 );
|
||||
m_filters.emplace_back(StringRef{}); // Root - should never be consulted
|
||||
m_filters.emplace_back(StringRef{}); // Test Case - not a section filter
|
||||
m_filters.insert( m_filters.end(), filters.begin(), filters.end() );
|
||||
}
|
||||
}
|
||||
void SectionTracker::addNextFilters( std::vector<StringRef> const& filters ) {
|
||||
if( filters.size() > 1 )
|
||||
m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() );
|
||||
}
|
||||
|
||||
StringRef SectionTracker::trimmedName() const {
|
||||
return m_trimmed_name;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#ifndef CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
|
||||
#define CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_lifetimebound.hpp>
|
||||
#include <catch2/internal/catch_source_line_info.hpp>
|
||||
#include <catch2/internal/catch_unique_ptr.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
@@ -16,6 +17,9 @@
|
||||
#include <vector>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
struct PathFilter;
|
||||
|
||||
namespace TestCaseTracking {
|
||||
|
||||
struct NameAndLocation {
|
||||
@@ -48,7 +52,7 @@ namespace TestCaseTracking {
|
||||
StringRef name;
|
||||
SourceLineInfo location;
|
||||
|
||||
constexpr NameAndLocationRef( StringRef name_,
|
||||
constexpr NameAndLocationRef( StringRef name_ CATCH_ATTR_LIFETIMEBOUND,
|
||||
SourceLineInfo location_ ):
|
||||
name( name_ ), location( location_ ) {}
|
||||
|
||||
@@ -91,12 +95,23 @@ namespace TestCaseTracking {
|
||||
Children m_children;
|
||||
CycleState m_runState = NotStarted;
|
||||
|
||||
public:
|
||||
ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ):
|
||||
m_nameAndLocation( CATCH_MOVE(nameAndLoc) ),
|
||||
m_parent( parent )
|
||||
{}
|
||||
// Members for path filtering
|
||||
std::vector<PathFilter> const* m_filterRef = nullptr;
|
||||
|
||||
// Note: There are 2 dummy section trackers (root, test-case) before
|
||||
// the first "real" section tracker can be encountered. We start
|
||||
// the default tracker at -2, so that the first "real" section
|
||||
// tracker overflows to index 0.
|
||||
// Nesting depth of this tracker, used to decide which new-style filter applies.
|
||||
size_t m_allTrackerDepth = static_cast<size_t>( -2 );
|
||||
// Nesting depth of sections (inc. this tracker), used for old-style filters.
|
||||
// Must be updated by the section tracker on its own.
|
||||
size_t m_sectionOnlyDepth = static_cast<size_t>( -2 );
|
||||
// Transitory: Remove once we remove backwards compatibility with old-style (v3.x) filters
|
||||
bool m_newStyleFilters = false;
|
||||
|
||||
public:
|
||||
ITracker( NameAndLocation&& nameAndLoc, ITracker* parent );
|
||||
|
||||
// static queries
|
||||
NameAndLocation const& nameAndLocation() const {
|
||||
@@ -122,6 +137,11 @@ namespace TestCaseTracking {
|
||||
//! Returns true iff tracker has started
|
||||
bool hasStarted() const;
|
||||
|
||||
void setFilters( std::vector<PathFilter> const* filters, bool newStyleFilters ) {
|
||||
m_filterRef = filters;
|
||||
m_newStyleFilters = newStyleFilters;
|
||||
}
|
||||
|
||||
// actions
|
||||
virtual void close() = 0; // Successfully complete
|
||||
virtual void fail() = 0;
|
||||
@@ -207,8 +227,7 @@ namespace TestCaseTracking {
|
||||
void moveToThis();
|
||||
};
|
||||
|
||||
class SectionTracker : public TrackerBase {
|
||||
std::vector<StringRef> m_filters;
|
||||
class SectionTracker final : public TrackerBase {
|
||||
// Note that lifetime-wise we piggy back off the name stored in the `ITracker` parent`.
|
||||
// Currently it allocates owns the name, so this is safe. If it is later refactored
|
||||
// to not own the name, the name still has to outlive the `ITracker` parent, so
|
||||
@@ -225,10 +244,6 @@ namespace TestCaseTracking {
|
||||
|
||||
void tryOpen();
|
||||
|
||||
void addInitialFilters( std::vector<std::string> const& filters );
|
||||
void addNextFilters( std::vector<StringRef> const& filters );
|
||||
//! Returns filters active in this tracker
|
||||
std::vector<StringRef> const& getFilters() const { return m_filters; }
|
||||
//! Returns whitespace-trimmed name of the tracked section
|
||||
StringRef trimmedName() const;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
// Copyright Catch2 Authors
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE.txt or copy at
|
||||
// https://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
#ifndef CATCH_THREAD_LOCAL_HPP_INCLUDED
|
||||
#define CATCH_THREAD_LOCAL_HPP_INCLUDED
|
||||
|
||||
#include <catch2/catch_user_config.hpp>
|
||||
|
||||
#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS )
|
||||
#define CATCH_INTERNAL_THREAD_LOCAL thread_local
|
||||
#else
|
||||
#define CATCH_INTERNAL_THREAD_LOCAL
|
||||
#endif
|
||||
|
||||
#endif // CATCH_THREAD_LOCAL_HPP_INCLUDED
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include <catch2/catch_user_config.hpp>
|
||||
|
||||
#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )
|
||||
#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS )
|
||||
# include <atomic>
|
||||
# include <mutex>
|
||||
#endif
|
||||
@@ -19,14 +19,14 @@
|
||||
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS )
|
||||
#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS )
|
||||
using Mutex = std::mutex;
|
||||
using LockGuard = std::lock_guard<std::mutex>;
|
||||
struct AtomicCounts {
|
||||
std::atomic<std::uint64_t> passed = 0;
|
||||
std::atomic<std::uint64_t> failed = 0;
|
||||
std::atomic<std::uint64_t> failedButOk = 0;
|
||||
std::atomic<std::uint64_t> skipped = 0;
|
||||
std::atomic<std::uint64_t> passed{ 0 };
|
||||
std::atomic<std::uint64_t> failed{ 0 };
|
||||
std::atomic<std::uint64_t> failedButOk{ 0 };
|
||||
std::atomic<std::uint64_t> skipped{ 0 };
|
||||
};
|
||||
#else // ^^ Use actual mutex, lock and atomics
|
||||
// vv Dummy implementations for single-thread performance
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Detail {
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return m_ptr;
|
||||
return m_ptr != nullptr;
|
||||
}
|
||||
|
||||
friend void swap(unique_ptr& lhs, unique_ptr& rhs) {
|
||||
|
||||
@@ -39,9 +39,13 @@ namespace Catch {
|
||||
__assume( false );
|
||||
# elif defined( __GNUC__ )
|
||||
__builtin_unreachable();
|
||||
# endif
|
||||
# endif // ^^ NDEBUG
|
||||
# else // vv platform without known optimization hint
|
||||
std::terminate();
|
||||
# endif
|
||||
# else // ^^ NDEBUG
|
||||
// For non-release builds, we prefer termination on bug over UB
|
||||
std::terminate();
|
||||
# endif //
|
||||
}
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace {
|
||||
|
||||
void hexEscapeChar(std::ostream& os, unsigned char c) {
|
||||
std::ios_base::fmtflags f(os.flags());
|
||||
os << "\\x"
|
||||
os << "\\x"_sr
|
||||
<< std::uppercase << std::hex << std::setfill('0') << std::setw(2)
|
||||
<< static_cast<int>(c);
|
||||
os.flags(f);
|
||||
@@ -66,95 +66,111 @@ namespace {
|
||||
void XmlEncode::encodeTo( std::ostream& os ) const {
|
||||
// Apostrophe escaping not necessary if we always use " to write attributes
|
||||
// (see: http://www.w3.org/TR/xml/#syntax)
|
||||
size_t last_start = 0;
|
||||
auto write_to = [&]( size_t idx ) {
|
||||
if ( last_start < idx ) {
|
||||
os << m_str.substr( last_start, idx - last_start );
|
||||
}
|
||||
last_start = idx + 1;
|
||||
};
|
||||
|
||||
for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
|
||||
unsigned char c = static_cast<unsigned char>(m_str[idx]);
|
||||
switch (c) {
|
||||
case '<': os << "<"; break;
|
||||
case '&': os << "&"; break;
|
||||
for ( std::size_t idx = 0; idx < m_str.size(); ++idx ) {
|
||||
unsigned char c = static_cast<unsigned char>( m_str[idx] );
|
||||
switch ( c ) {
|
||||
case '<':
|
||||
write_to( idx );
|
||||
os << "<"_sr;
|
||||
break;
|
||||
case '&':
|
||||
write_to( idx );
|
||||
os << "&"_sr;
|
||||
break;
|
||||
|
||||
case '>':
|
||||
// See: http://www.w3.org/TR/xml/#syntax
|
||||
if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
|
||||
os << ">";
|
||||
else
|
||||
os << c;
|
||||
if ( idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']' ) {
|
||||
write_to( idx );
|
||||
os << ">"_sr;
|
||||
}
|
||||
break;
|
||||
|
||||
case '\"':
|
||||
if (m_forWhat == ForAttributes)
|
||||
os << """;
|
||||
else
|
||||
os << c;
|
||||
if ( m_forWhat == ForAttributes ) {
|
||||
write_to( idx );
|
||||
os << """_sr;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Check for control characters and invalid utf-8
|
||||
|
||||
// Escape control characters in standard ascii
|
||||
// see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
|
||||
if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
|
||||
hexEscapeChar(os, c);
|
||||
// see
|
||||
// http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
|
||||
if ( c < 0x09 || ( c > 0x0D && c < 0x20 ) || c == 0x7F ) {
|
||||
write_to( idx );
|
||||
hexEscapeChar( os, c );
|
||||
break;
|
||||
}
|
||||
|
||||
// Plain ASCII: Write it to stream
|
||||
if (c < 0x7F) {
|
||||
os << c;
|
||||
if ( c < 0x7F ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// UTF-8 territory
|
||||
// Check if the encoding is valid and if it is not, hex escape bytes.
|
||||
// Important: We do not check the exact decoded values for validity, only the encoding format
|
||||
// First check that this bytes is a valid lead byte:
|
||||
// This means that it is not encoded as 1111 1XXX
|
||||
// Check if the encoding is valid and if it is not, hex escape
|
||||
// bytes. Important: We do not check the exact decoded values for
|
||||
// validity, only the encoding format First check that this bytes is
|
||||
// a valid lead byte: This means that it is not encoded as 1111 1XXX
|
||||
// Or as 10XX XXXX
|
||||
if (c < 0xC0 ||
|
||||
c >= 0xF8) {
|
||||
hexEscapeChar(os, c);
|
||||
if ( c < 0xC0 || c >= 0xF8 ) {
|
||||
write_to( idx );
|
||||
hexEscapeChar( os, c );
|
||||
break;
|
||||
}
|
||||
|
||||
auto encBytes = trailingBytes(c);
|
||||
// Are there enough bytes left to avoid accessing out-of-bounds memory?
|
||||
if (idx + encBytes - 1 >= m_str.size()) {
|
||||
hexEscapeChar(os, c);
|
||||
auto encBytes = trailingBytes( c );
|
||||
// Are there enough bytes left to avoid accessing out-of-bounds
|
||||
// memory?
|
||||
if ( idx + encBytes - 1 >= m_str.size() ) {
|
||||
write_to( idx );
|
||||
hexEscapeChar( os, c );
|
||||
break;
|
||||
}
|
||||
// The header is valid, check data
|
||||
// The next encBytes bytes must together be a valid utf-8
|
||||
// This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
|
||||
// This means: bitpattern 10XX XXXX and the extracted value is sane
|
||||
// (ish)
|
||||
bool valid = true;
|
||||
uint32_t value = headerValue(c);
|
||||
for (std::size_t n = 1; n < encBytes; ++n) {
|
||||
unsigned char nc = static_cast<unsigned char>(m_str[idx + n]);
|
||||
valid &= ((nc & 0xC0) == 0x80);
|
||||
value = (value << 6) | (nc & 0x3F);
|
||||
uint32_t value = headerValue( c );
|
||||
for ( std::size_t n = 1; n < encBytes; ++n ) {
|
||||
unsigned char nc = static_cast<unsigned char>( m_str[idx + n] );
|
||||
valid &= ( ( nc & 0xC0 ) == 0x80 );
|
||||
value = ( value << 6 ) | ( nc & 0x3F );
|
||||
}
|
||||
|
||||
if (
|
||||
// Wrong bit pattern of following bytes
|
||||
(!valid) ||
|
||||
( !valid ) ||
|
||||
// Overlong encodings
|
||||
(value < 0x80) ||
|
||||
(0x80 <= value && value < 0x800 && encBytes > 2) ||
|
||||
(0x800 < value && value < 0x10000 && encBytes > 3) ||
|
||||
( value < 0x80 ) ||
|
||||
( 0x80 <= value && value < 0x800 && encBytes > 2 ) ||
|
||||
( 0x800 < value && value < 0x10000 && encBytes > 3 ) ||
|
||||
// Encoded value out of range
|
||||
(value >= 0x110000)
|
||||
) {
|
||||
hexEscapeChar(os, c);
|
||||
( value >= 0x110000 ) ) {
|
||||
write_to( idx );
|
||||
hexEscapeChar( os, c );
|
||||
break;
|
||||
}
|
||||
|
||||
// If we got here, this is in fact a valid(ish) utf-8 sequence
|
||||
for (std::size_t n = 0; n < encBytes; ++n) {
|
||||
os << m_str[idx + n];
|
||||
}
|
||||
idx += encBytes - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
write_to( m_str.size() );
|
||||
}
|
||||
|
||||
std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#ifndef CATCH_XMLWRITER_HPP_INCLUDED
|
||||
#define CATCH_XMLWRITER_HPP_INCLUDED
|
||||
|
||||
#include <catch2/internal/catch_lifetimebound.hpp>
|
||||
#include <catch2/internal/catch_reusable_string_stream.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
|
||||
@@ -43,7 +44,7 @@ namespace Catch {
|
||||
public:
|
||||
enum ForWhat { ForTextNodes, ForAttributes };
|
||||
|
||||
constexpr XmlEncode( StringRef str, ForWhat forWhat = ForTextNodes ):
|
||||
constexpr XmlEncode( StringRef str CATCH_ATTR_LIFETIMEBOUND, ForWhat forWhat = ForTextNodes ):
|
||||
m_str( str ), m_forWhat( forWhat ) {}
|
||||
|
||||
|
||||
@@ -61,7 +62,7 @@ namespace Catch {
|
||||
|
||||
class ScopedElement {
|
||||
public:
|
||||
ScopedElement( XmlWriter* writer, XmlFormatting fmt );
|
||||
ScopedElement( XmlWriter* writer CATCH_ATTR_LIFETIMEBOUND, XmlFormatting fmt );
|
||||
|
||||
ScopedElement( ScopedElement&& other ) noexcept;
|
||||
ScopedElement& operator=( ScopedElement&& other ) noexcept;
|
||||
@@ -93,7 +94,7 @@ namespace Catch {
|
||||
XmlFormatting m_fmt;
|
||||
};
|
||||
|
||||
XmlWriter( std::ostream& os );
|
||||
XmlWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND );
|
||||
~XmlWriter();
|
||||
|
||||
XmlWriter( XmlWriter const& ) = delete;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <catch2/matchers/internal/catch_matchers_impl.hpp>
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
#include <catch2/internal/catch_lifetimebound.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -79,11 +80,15 @@ namespace Matchers {
|
||||
return description;
|
||||
}
|
||||
|
||||
friend MatchAllOf operator&& (MatchAllOf&& lhs, MatcherBase<ArgT> const& rhs) {
|
||||
friend MatchAllOf operator&&( MatchAllOf&& lhs,
|
||||
MatcherBase<ArgT> const& rhs
|
||||
CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
lhs.m_matchers.push_back(&rhs);
|
||||
return CATCH_MOVE(lhs);
|
||||
}
|
||||
friend MatchAllOf operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf&& rhs) {
|
||||
friend MatchAllOf
|
||||
operator&&( MatcherBase<ArgT> const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAllOf&& rhs ) {
|
||||
rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs);
|
||||
return CATCH_MOVE(rhs);
|
||||
}
|
||||
@@ -131,11 +136,15 @@ namespace Matchers {
|
||||
return description;
|
||||
}
|
||||
|
||||
friend MatchAnyOf operator|| (MatchAnyOf&& lhs, MatcherBase<ArgT> const& rhs) {
|
||||
friend MatchAnyOf operator||( MatchAnyOf&& lhs,
|
||||
MatcherBase<ArgT> const& rhs
|
||||
CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
lhs.m_matchers.push_back(&rhs);
|
||||
return CATCH_MOVE(lhs);
|
||||
}
|
||||
friend MatchAnyOf operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf&& rhs) {
|
||||
friend MatchAnyOf
|
||||
operator||( MatcherBase<ArgT> const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAnyOf&& rhs ) {
|
||||
rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs);
|
||||
return CATCH_MOVE(rhs);
|
||||
}
|
||||
@@ -155,7 +164,8 @@ namespace Matchers {
|
||||
MatcherBase<ArgT> const& m_underlyingMatcher;
|
||||
|
||||
public:
|
||||
explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ):
|
||||
explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher
|
||||
CATCH_ATTR_LIFETIMEBOUND ):
|
||||
m_underlyingMatcher( underlyingMatcher )
|
||||
{}
|
||||
|
||||
@@ -171,16 +181,22 @@ namespace Matchers {
|
||||
} // namespace Detail
|
||||
|
||||
template <typename T>
|
||||
Detail::MatchAllOf<T> operator&& (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) {
|
||||
Detail::MatchAllOf<T>
|
||||
operator&&( MatcherBase<T> const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherBase<T> const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return Detail::MatchAllOf<T>{} && lhs && rhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Detail::MatchAnyOf<T> operator|| (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) {
|
||||
Detail::MatchAnyOf<T>
|
||||
operator||( MatcherBase<T> const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherBase<T> const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return Detail::MatchAnyOf<T>{} || lhs || rhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Detail::MatchNotOf<T> operator! (MatcherBase<T> const& matcher) {
|
||||
Detail::MatchNotOf<T>
|
||||
operator!( MatcherBase<T> const& matcher CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return Detail::MatchNotOf<T>{ matcher };
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <catch2/matchers/catch_matchers.hpp>
|
||||
#include <catch2/internal/catch_stringref.hpp>
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
#include <catch2/internal/catch_lifetimebound.hpp>
|
||||
#include <catch2/internal/catch_logical_traits.hpp>
|
||||
|
||||
#include <array>
|
||||
@@ -114,7 +115,8 @@ namespace Matchers {
|
||||
MatchAllOfGeneric(MatchAllOfGeneric&&) = default;
|
||||
MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default;
|
||||
|
||||
MatchAllOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {}
|
||||
MatchAllOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND)
|
||||
: m_matchers{ {std::addressof(matchers)...} } {}
|
||||
explicit MatchAllOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
|
||||
|
||||
template<typename Arg>
|
||||
@@ -136,8 +138,8 @@ namespace Matchers {
|
||||
template<typename... MatchersRHS>
|
||||
friend
|
||||
MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && (
|
||||
MatchAllOfGeneric<MatcherTs...>&& lhs,
|
||||
MatchAllOfGeneric<MatchersRHS...>&& rhs) {
|
||||
MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAllOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return MatchAllOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};
|
||||
}
|
||||
|
||||
@@ -145,8 +147,8 @@ namespace Matchers {
|
||||
template<typename MatcherRHS>
|
||||
friend std::enable_if_t<is_matcher_v<MatcherRHS>,
|
||||
MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && (
|
||||
MatchAllOfGeneric<MatcherTs...>&& lhs,
|
||||
MatcherRHS const& rhs) {
|
||||
MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return MatchAllOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(&rhs))};
|
||||
}
|
||||
|
||||
@@ -154,8 +156,8 @@ namespace Matchers {
|
||||
template<typename MatcherLHS>
|
||||
friend std::enable_if_t<is_matcher_v<MatcherLHS>,
|
||||
MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && (
|
||||
MatcherLHS const& lhs,
|
||||
MatchAllOfGeneric<MatcherTs...>&& rhs) {
|
||||
MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAllOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return MatchAllOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))};
|
||||
}
|
||||
};
|
||||
@@ -169,7 +171,8 @@ namespace Matchers {
|
||||
MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default;
|
||||
MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default;
|
||||
|
||||
MatchAnyOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {}
|
||||
MatchAnyOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND)
|
||||
: m_matchers{ {std::addressof(matchers)...} } {}
|
||||
explicit MatchAnyOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
|
||||
|
||||
template<typename Arg>
|
||||
@@ -190,8 +193,8 @@ namespace Matchers {
|
||||
//! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case
|
||||
template<typename... MatchersRHS>
|
||||
friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || (
|
||||
MatchAnyOfGeneric<MatcherTs...>&& lhs,
|
||||
MatchAnyOfGeneric<MatchersRHS...>&& rhs) {
|
||||
MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAnyOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};
|
||||
}
|
||||
|
||||
@@ -199,8 +202,8 @@ namespace Matchers {
|
||||
template<typename MatcherRHS>
|
||||
friend std::enable_if_t<is_matcher_v<MatcherRHS>,
|
||||
MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || (
|
||||
MatchAnyOfGeneric<MatcherTs...>&& lhs,
|
||||
MatcherRHS const& rhs) {
|
||||
MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return MatchAnyOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))};
|
||||
}
|
||||
|
||||
@@ -208,8 +211,8 @@ namespace Matchers {
|
||||
template<typename MatcherLHS>
|
||||
friend std::enable_if_t<is_matcher_v<MatcherLHS>,
|
||||
MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || (
|
||||
MatcherLHS const& lhs,
|
||||
MatchAnyOfGeneric<MatcherTs...>&& rhs) {
|
||||
MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatchAnyOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND) {
|
||||
return MatchAnyOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))};
|
||||
}
|
||||
};
|
||||
@@ -225,7 +228,8 @@ namespace Matchers {
|
||||
MatchNotOfGeneric(MatchNotOfGeneric&&) = default;
|
||||
MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default;
|
||||
|
||||
explicit MatchNotOfGeneric(MatcherT const& matcher) : m_matcher{matcher} {}
|
||||
explicit MatchNotOfGeneric(MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND)
|
||||
: m_matcher{matcher} {}
|
||||
|
||||
template<typename Arg>
|
||||
bool match(Arg&& arg) const {
|
||||
@@ -237,7 +241,9 @@ namespace Matchers {
|
||||
}
|
||||
|
||||
//! Negating negation can just unwrap and return underlying matcher
|
||||
friend MatcherT const& operator ! (MatchNotOfGeneric<MatcherT> const& matcher) {
|
||||
friend MatcherT const&
|
||||
operator!( MatchNotOfGeneric<MatcherT> const& matcher
|
||||
CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return matcher.m_matcher;
|
||||
}
|
||||
};
|
||||
@@ -247,20 +253,22 @@ namespace Matchers {
|
||||
// compose only generic matchers
|
||||
template<typename MatcherLHS, typename MatcherRHS>
|
||||
std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>>
|
||||
operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) {
|
||||
operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return { lhs, rhs };
|
||||
}
|
||||
|
||||
template<typename MatcherLHS, typename MatcherRHS>
|
||||
std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>
|
||||
operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) {
|
||||
operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return { lhs, rhs };
|
||||
}
|
||||
|
||||
//! Wrap provided generic matcher in generic negator
|
||||
template<typename MatcherT>
|
||||
std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>, Detail::MatchNotOfGeneric<MatcherT>>
|
||||
operator ! (MatcherT const& matcher) {
|
||||
operator!( MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return Detail::MatchNotOfGeneric<MatcherT>{matcher};
|
||||
}
|
||||
|
||||
@@ -268,25 +276,29 @@ namespace Matchers {
|
||||
// compose mixed generic and non-generic matchers
|
||||
template<typename MatcherLHS, typename ArgRHS>
|
||||
std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>
|
||||
operator && (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {
|
||||
operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherBase<ArgRHS> const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return { lhs, rhs };
|
||||
}
|
||||
|
||||
template<typename ArgLHS, typename MatcherRHS>
|
||||
std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAllOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>
|
||||
operator && (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {
|
||||
operator&&( MatcherBase<ArgLHS> const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return { lhs, rhs };
|
||||
}
|
||||
|
||||
template<typename MatcherLHS, typename ArgRHS>
|
||||
std::enable_if_t<Detail::is_generic_matcher_v<MatcherLHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>
|
||||
operator || (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {
|
||||
operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherBase<ArgRHS> const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return { lhs, rhs };
|
||||
}
|
||||
|
||||
template<typename ArgLHS, typename MatcherRHS>
|
||||
std::enable_if_t<Detail::is_generic_matcher_v<MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>
|
||||
operator || (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {
|
||||
operator||( MatcherBase<ArgLHS> const& lhs CATCH_ATTR_LIFETIMEBOUND,
|
||||
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
|
||||
return { lhs, rhs };
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ internal_headers = [
|
||||
'generators/catch_generators_all.hpp',
|
||||
'generators/catch_generators_random.hpp',
|
||||
'generators/catch_generators_range.hpp',
|
||||
'generators/catch_generators_throw.hpp',
|
||||
'interfaces/catch_interfaces_all.hpp',
|
||||
'interfaces/catch_interfaces_capture.hpp',
|
||||
'interfaces/catch_interfaces_config.hpp',
|
||||
@@ -105,6 +106,7 @@ internal_headers = [
|
||||
'internal/catch_jsonwriter.hpp',
|
||||
'internal/catch_lazy_expr.hpp',
|
||||
'internal/catch_leak_detector.hpp',
|
||||
'internal/catch_lifetimebound.hpp',
|
||||
'internal/catch_list.hpp',
|
||||
'internal/catch_logical_traits.hpp',
|
||||
'internal/catch_message_info.hpp',
|
||||
@@ -114,6 +116,7 @@ internal_headers = [
|
||||
'internal/catch_optional.hpp',
|
||||
'internal/catch_output_redirect.hpp',
|
||||
'internal/catch_parse_numbers.hpp',
|
||||
'internal/catch_path_filter.hpp',
|
||||
'internal/catch_platform.hpp',
|
||||
'internal/catch_polyfills.hpp',
|
||||
'internal/catch_preprocessor.hpp',
|
||||
@@ -147,6 +150,7 @@ internal_headers = [
|
||||
'internal/catch_test_registry.hpp',
|
||||
'internal/catch_test_spec_parser.hpp',
|
||||
'internal/catch_textflow.hpp',
|
||||
'internal/catch_thread_local.hpp',
|
||||
'internal/catch_thread_support.hpp',
|
||||
'internal/catch_to_string.hpp',
|
||||
'internal/catch_uncaught_exceptions.hpp',
|
||||
@@ -201,6 +205,7 @@ internal_sources = files(
|
||||
'generators/catch_generator_exception.cpp',
|
||||
'generators/catch_generators.cpp',
|
||||
'generators/catch_generators_random.cpp',
|
||||
'generators/catch_generators_throw.cpp',
|
||||
'interfaces/catch_interfaces_capture.cpp',
|
||||
'interfaces/catch_interfaces_config.cpp',
|
||||
'interfaces/catch_interfaces_exception.cpp',
|
||||
|
||||
+53
-18
@@ -169,7 +169,7 @@ if(CATCH_ENABLE_COVERAGE)
|
||||
endif()
|
||||
|
||||
# configure unit tests via CTest
|
||||
add_test(NAME RunTests COMMAND $<TARGET_FILE:SelfTest> --order rand --rng-seed time)
|
||||
add_test(NAME RunTests COMMAND $<TARGET_FILE:SelfTest> --order rand --rng-seed time --warn InfiniteGenerators)
|
||||
set_tests_properties(RunTests PROPERTIES
|
||||
FAIL_REGULAR_EXPRESSION "Filters:"
|
||||
COST 15
|
||||
@@ -310,36 +310,39 @@ set_tests_properties(UnmatchedOutputFilter
|
||||
PASS_REGULAR_EXPRESSION "No test cases matched '\\[this-tag-does-not-exist\\]'"
|
||||
)
|
||||
|
||||
add_test(NAME FilteredSection-1 COMMAND $<TARGET_FILE:SelfTest> \#1394 -c RunSection)
|
||||
set_tests_properties(FilteredSection-1 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
|
||||
add_test(NAME FilteredSection-2 COMMAND $<TARGET_FILE:SelfTest> \#1394\ nested -c NestedRunSection -c s1)
|
||||
set_tests_properties(FilteredSection-2 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
|
||||
add_test(NAME FilteredSections::SimpleExample::1 COMMAND $<TARGET_FILE:SelfTest> \#1394 -c RunSection)
|
||||
set_tests_properties(FilteredSections::SimpleExample::1 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
|
||||
add_test(NAME FilteredSections::SimpleExample::2 COMMAND $<TARGET_FILE:SelfTest> \#1394\ nested -c NestedRunSection -c s1)
|
||||
set_tests_properties(FilteredSections::SimpleExample::2 PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran")
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
FilteredSection::GeneratorsDontCauseInfiniteLoop-1
|
||||
FilteredSections::GeneratorsDontCauseInfiniteLoop
|
||||
COMMAND
|
||||
$<TARGET_FILE:SelfTest> "#2025: original repro" -c "fov_0"
|
||||
)
|
||||
set_tests_properties(FilteredSection::GeneratorsDontCauseInfiniteLoop-1
|
||||
set_tests_properties(FilteredSections::GeneratorsDontCauseInfiniteLoop
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "inside with fov: 0" # This should happen
|
||||
FAIL_REGULAR_EXPRESSION "inside with fov: 1" # This would mean there was no filtering
|
||||
)
|
||||
|
||||
# GENERATE between filtered sections (both are selected)
|
||||
add_test(
|
||||
NAME
|
||||
FilteredSection::GeneratorsDontCauseInfiniteLoop-2
|
||||
add_test(NAME "FilteredSections::DifferentSimpleFilters"
|
||||
COMMAND
|
||||
$<TARGET_FILE:SelfTest> "#2025: same-level sections"
|
||||
-c "A"
|
||||
-c "B"
|
||||
--colour-mode none
|
||||
Python3::Interpreter "${CMAKE_CURRENT_LIST_DIR}/TestScripts/testSectionFiltering.py" $<TARGET_FILE:SelfTest>
|
||||
)
|
||||
set_tests_properties(FilteredSection::GeneratorsDontCauseInfiniteLoop-2
|
||||
set_tests_properties("FilteredSections::DifferentSimpleFilters"
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "All tests passed \\(4 assertions in 1 test case\\)"
|
||||
LABELS "uses-python"
|
||||
)
|
||||
|
||||
add_test(NAME "FilteredSections::HittingGeneratorsWithSectionFilterCausesSkip"
|
||||
COMMAND
|
||||
$<TARGET_FILE:SelfTest> "baz" -p "c:A" -r "compact"
|
||||
)
|
||||
set_tests_properties("FilteredSections::HittingGeneratorsWithSectionFilterCausesSkip"
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "test cases: 1 \\| 1 skipped[\r\n\t ]*assertions: 1 \\| 1 passed"
|
||||
)
|
||||
|
||||
add_test(NAME ApprovalTests
|
||||
@@ -498,7 +501,7 @@ set_tests_properties("ErrorHandling::InvalidTestSpecExitsEarly"
|
||||
FAIL_REGULAR_EXPRESSION "No tests ran"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
if(WIN32)
|
||||
set(_NullFile "NUL")
|
||||
else()
|
||||
set(_NullFile "/dev/null")
|
||||
@@ -662,5 +665,37 @@ foreach(reporterName # "Automake" - the simple .trs format does not support any
|
||||
)
|
||||
endforeach()
|
||||
|
||||
add_test(NAME "Bazel::RngSeedEnvVar::JustEnv"
|
||||
COMMAND
|
||||
$<TARGET_FILE:SelfTest> "Factorials are computed"
|
||||
)
|
||||
set_tests_properties("Bazel::RngSeedEnvVar::JustEnv"
|
||||
PROPERTIES
|
||||
ENVIRONMENT "BAZEL_TEST=1;TEST_RANDOM_SEED=18181818"
|
||||
PASS_REGULAR_EXPRESSION "Randomness seeded to: 18181818"
|
||||
)
|
||||
|
||||
add_test(NAME "Bazel::RngSeedEnvVar::EnvHasPriorityOverCLI"
|
||||
COMMAND
|
||||
$<TARGET_FILE:SelfTest> "Factorials are computed"
|
||||
--rng-seed 17171717
|
||||
)
|
||||
set_tests_properties("Bazel::RngSeedEnvVar::EnvHasPriorityOverCLI"
|
||||
PROPERTIES
|
||||
ENVIRONMENT "BAZEL_TEST=1;TEST_RANDOM_SEED=18181818"
|
||||
PASS_REGULAR_EXPRESSION "Randomness seeded to: 18181818"
|
||||
)
|
||||
|
||||
add_test(NAME "Bazel::RngSeedEnvVar::MalformedValueIsIgnored"
|
||||
COMMAND
|
||||
$<TARGET_FILE:SelfTest> "Factorials are computed"
|
||||
--rng-seed 17171717
|
||||
)
|
||||
set_tests_properties("Bazel::RngSeedEnvVar::MalformedValueIsIgnored"
|
||||
PROPERTIES
|
||||
ENVIRONMENT "BAZEL_TEST=1;TEST_RANDOM_SEED=XOXOXOXO"
|
||||
PASS_REGULAR_EXPRESSION "Randomness seeded to: 17171717"
|
||||
)
|
||||
|
||||
list(APPEND CATCH_TEST_TARGETS SelfTest)
|
||||
set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE)
|
||||
|
||||
@@ -95,7 +95,7 @@ foreach(target DisabledExceptions-DefaultHandler DisabledExceptions-CustomHandle
|
||||
target_compile_options(${target}
|
||||
PUBLIC
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/EHs-c-;/D_HAS_EXCEPTIONS=0>
|
||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:AppleClang>>:-fno-exceptions>
|
||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:QCC>,$<CXX_COMPILER_ID:AppleClang>>:-fno-exceptions>
|
||||
)
|
||||
target_link_libraries(${target} Catch2_buildall_interface)
|
||||
endforeach()
|
||||
@@ -109,18 +109,6 @@ set_tests_properties(CATCH_CONFIG_DISABLE_EXCEPTIONS-1
|
||||
FAIL_REGULAR_EXPRESSION "abort;terminate;fatal"
|
||||
)
|
||||
|
||||
add_executable(BazelReporter ${TESTS_DIR}/X30-BazelReporter.cpp)
|
||||
target_compile_definitions(BazelReporter PRIVATE CATCH_CONFIG_BAZEL_SUPPORT)
|
||||
target_link_libraries(BazelReporter Catch2_buildall_interface)
|
||||
add_test(NAME CATCH_CONFIG_BAZEL_REPORTER-1
|
||||
COMMAND
|
||||
Python3::Interpreter "${CATCH_DIR}/tests/TestScripts/testBazelReporter.py" $<TARGET_FILE:BazelReporter> "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
set_tests_properties(CATCH_CONFIG_BAZEL_REPORTER-1
|
||||
PROPERTIES
|
||||
LABELS "uses-python"
|
||||
)
|
||||
|
||||
# We must now test this works without the build flag.
|
||||
add_executable(BazelReporterNoCatchConfig ${TESTS_DIR}/X30-BazelReporter.cpp)
|
||||
target_link_libraries(BazelReporterNoCatchConfig Catch2WithMain)
|
||||
@@ -189,6 +177,18 @@ set_tests_properties(DeferredStaticChecks
|
||||
PASS_REGULAR_EXPRESSION "test cases: 1 \\| 1 failed\nassertions: 3 \\| 3 failed"
|
||||
)
|
||||
|
||||
add_executable(MixingClearedAndUnclearedMessages ${TESTS_DIR}/X06-MixingClearedAndUnclearedMessages.cpp)
|
||||
target_link_libraries(MixingClearedAndUnclearedMessages PRIVATE Catch2WithMain)
|
||||
|
||||
add_test(NAME MixingClearedAndUnclearedMessages
|
||||
COMMAND
|
||||
MixingClearedAndUnclearedMessages
|
||||
-r compact)
|
||||
set_tests_properties(MixingClearedAndUnclearedMessages
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION ": false with 1 message: 'b'"
|
||||
)
|
||||
|
||||
add_executable(FallbackStringifier ${TESTS_DIR}/X10-FallbackStringifier.cpp)
|
||||
target_compile_definitions(FallbackStringifier PRIVATE CATCH_CONFIG_FALLBACK_STRINGIFIER=fallbackStringifier)
|
||||
target_link_libraries(FallbackStringifier Catch2WithMain)
|
||||
@@ -273,7 +273,7 @@ set_tests_properties(Reporters::CapturedStdOutInEvents
|
||||
FAIL_REGULAR_EXPRESSION "X27 ERROR"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
if(WIN32)
|
||||
set(_NullFile "NUL")
|
||||
else()
|
||||
set(_NullFile "/dev/null")
|
||||
@@ -429,6 +429,50 @@ set_tests_properties(Reporters::CrashInJunitReporter
|
||||
LABELS "uses-signals"
|
||||
)
|
||||
|
||||
|
||||
add_executable(QuickExitInTest ${TESTS_DIR}/X40-QuickExit.cpp)
|
||||
target_link_libraries(QuickExitInTest PRIVATE Catch2::Catch2WithMain)
|
||||
add_test(
|
||||
NAME BazelEnv::TEST_PREMATURE_EXIT_FILE
|
||||
COMMAND
|
||||
Python3::Interpreter
|
||||
"${CATCH_DIR}/tests/TestScripts/testBazelExitGuardFile.py"
|
||||
$<TARGET_FILE:QuickExitInTest>
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"bazel"
|
||||
)
|
||||
set_tests_properties(BazelEnv::TEST_PREMATURE_EXIT_FILE
|
||||
PROPERTIES
|
||||
LABELS "uses-python"
|
||||
)
|
||||
add_test(
|
||||
NAME PrematureExitGuardFileCanBeUsedFromCLI::CheckAfterCrash
|
||||
COMMAND
|
||||
Python3::Interpreter
|
||||
"${CATCH_DIR}/tests/TestScripts/testBazelExitGuardFile.py"
|
||||
$<TARGET_FILE:QuickExitInTest>
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"cli"
|
||||
)
|
||||
set_tests_properties(PrematureExitGuardFileCanBeUsedFromCLI::CheckAfterCrash
|
||||
PROPERTIES
|
||||
LABELS "uses-python"
|
||||
)
|
||||
add_test(
|
||||
NAME PrematureExitGuardFileCanBeUsedFromCLI::CheckWithoutCrash
|
||||
COMMAND
|
||||
Python3::Interpreter
|
||||
"${CATCH_DIR}/tests/TestScripts/testBazelExitGuardFile.py"
|
||||
$<TARGET_FILE:QuickExitInTest>
|
||||
"${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"no-crash"
|
||||
)
|
||||
set_tests_properties(PrematureExitGuardFileCanBeUsedFromCLI::CheckWithoutCrash
|
||||
PROPERTIES
|
||||
LABELS "uses-python"
|
||||
)
|
||||
|
||||
|
||||
add_executable(AssertionStartingEventGoesBeforeAssertionIsEvaluated
|
||||
X20-AssertionStartingEventGoesBeforeAssertionIsEvaluated.cpp
|
||||
)
|
||||
@@ -496,6 +540,7 @@ set(EXTRA_TEST_BINARIES
|
||||
DuplicatedTestCases-DuplicatedTestCaseMethods
|
||||
NoTests
|
||||
ListenersGetEventsBeforeReporters
|
||||
MixingClearedAndUnclearedMessages
|
||||
# DebugBreakMacros
|
||||
)
|
||||
|
||||
@@ -521,3 +566,40 @@ set_tests_properties(AmalgamatedFileTest
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "All tests passed \\(14 assertions in 3 test cases\\)"
|
||||
)
|
||||
|
||||
add_executable(ThreadSafetyTests
|
||||
${TESTS_DIR}/X94-ThreadSafetyTests.cpp
|
||||
)
|
||||
target_link_libraries(ThreadSafetyTests Catch2_buildall_interface)
|
||||
target_compile_definitions(ThreadSafetyTests PUBLIC CATCH_CONFIG_THREAD_SAFE_ASSERTIONS)
|
||||
|
||||
add_test(NAME ThreadSafetyTests::ScopedMessagesAndAssertions
|
||||
COMMAND ThreadSafetyTests -r compact "Failed REQUIRE in the main thread is fine"
|
||||
)
|
||||
set_tests_properties(ThreadSafetyTests::ScopedMessagesAndAssertions
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "assertions: 801 \\| 400 passed \\| 401 failed as expected"
|
||||
RUN_SERIAL ON
|
||||
)
|
||||
add_test(NAME ThreadSafetyTests::UnscopedMessagesAndAssertions
|
||||
COMMAND ThreadSafetyTests -r compact "Using unscoped messages in sibling threads"
|
||||
)
|
||||
set_tests_properties(ThreadSafetyTests::UnscopedMessagesAndAssertions
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "assertions: 401 \\| 401 failed as expected"
|
||||
RUN_SERIAL ON
|
||||
)
|
||||
|
||||
add_executable(InfiniteGenerators ${TESTS_DIR}/X95-InfiniteGenerators.cpp)
|
||||
target_link_libraries(InfiniteGenerators PRIVATE Catch2::Catch2WithMain)
|
||||
|
||||
add_test(
|
||||
NAME Warnings::InfiniteGenerators
|
||||
COMMAND $<TARGET_FILE:InfiniteGenerators> --warn InfiniteGenerators
|
||||
)
|
||||
set_tests_properties(Warnings::InfiniteGenerators
|
||||
PROPERTIES
|
||||
# One test case fails with infinite generator, but the other one runs
|
||||
PASS_REGULAR_EXPRESSION "test cases: 2 \\| 1 passed \\| 1 failed"
|
||||
TIMEOUT 5
|
||||
)
|
||||
|
||||
@@ -67,6 +67,7 @@ CATCH_TEST_CASE("PrefixedMacros") {
|
||||
int i = 1;
|
||||
CATCH_CAPTURE( i );
|
||||
CATCH_CAPTURE( i, i + 1 );
|
||||
CATCH_UNSCOPED_CAPTURE( i + 2, i + 3 );
|
||||
CATCH_DYNAMIC_SECTION("Dynamic section: " << i) {
|
||||
CATCH_FAIL_CHECK( "failure" );
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ TEST_CASE( "Disabled Macros" ) {
|
||||
|
||||
CAPTURE( 1 );
|
||||
CAPTURE( 1, "captured" );
|
||||
UNSCOPED_CAPTURE( 3 );
|
||||
|
||||
REQUIRE_THAT( 1,
|
||||
Catch::Matchers::Predicate( []( int ) { return false; } ) );
|
||||
@@ -68,6 +69,7 @@ TEST_CASE_PERSISTENT_FIXTURE( DisabledFixture, "Disabled Persistent Fixture" ) {
|
||||
|
||||
CAPTURE( 1 );
|
||||
CAPTURE( 1, "captured" );
|
||||
UNSCOPED_CAPTURE( 3 );
|
||||
|
||||
REQUIRE_THAT( 1,
|
||||
Catch::Matchers::Predicate( []( int ) { return false; } ) );
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user