1
0
mirror of https://github.com/catchorg/Catch2.git synced 2025-04-29 04:03:51 +00:00

Compare commits

...

14 Commits

Author SHA1 Message Date
Martin Hořeňovský
313071e8fe
Pick release notes for v2.13.6 2021-05-09 23:45:00 +02:00
Martin Hořeňovský
f9bb2668e4
Pick release notes for v2.13.5 2021-05-09 23:43:05 +02:00
Matteo Beniamino
baf0cd0be4
Fixed [dis]engage_platform declarations mismatch 2021-05-09 23:34:17 +02:00
Martin Hořeňovský
c0d0a50bdb
Significantly refactor fatal error handling
Because new glibc has changed `MINSIGSTKSZ` to be a syscall instead
of being constant, the signal posix handling needed changes, as it
used the value in constexpr context, for deciding size of an array.
It would be simple to fix it by having the handler determine the
signal handling stack size and allocate the memory every time the
handler is being installed, but that would add another allocation
and a syscall every time a test case is entered.

Instead, I split apart the idea of preparing fatal error handlers,
and engaging them, so that the memory can be allocated only once
and still be guarded by RAII.

Also turns out that Catch2's use of `MINSIGSTKSZ` was wrong, and
we should've been using `SIGSTKSZ` the whole time, which we use now.

Closes #2178
2021-05-09 23:34:15 +02:00
Pavel Kamenov
cbcab2dbcd
Add lcc to the list of unwanted compilers that mimic gcc 2021-05-09 18:12:58 +02:00
Scott Hutchinson
ea44e73961
Wrap all std::min and std::max calls in parentheses 2021-05-09 18:10:50 +02:00
Rob Boehne
d61fe3ecc3
[Issue 2154] Correct error when building with IBM's latest XLC (#2155)
* [Issue 2154] Correct error when building with IBM's latest XLC compiler with xlclang++ front-end.

On AIX, the XLC 16.1.0.1 compiler considers the call to `std::abs` ambigious, so it needs help with a static_cast to the type of the template argument.

Co-authored-by: Martin Hořeňovský <martin.horenovsky@gmail.com>
2021-05-09 18:05:39 +02:00
Tom de Geus
b325c6d81e
Making target detection on Mac more robust 2021-05-09 18:05:37 +02:00
Martin Hořeňovský
d4a3cd9992
Make the static library build reproducible with supported compilers
This is based on bed285af07607a9da93892f8b08d36c3fd6af98d from
`v2.x` branch, but done properly for the build in v3 branch,
effectively porting #2141 to devel.
2021-05-09 18:05:34 +02:00
Martin Hořeňovský
342dd3445c
Pick documentation changes for 2.13.4 2021-05-09 18:05:31 +02:00
Martin Hořeňovský
23760327ae
Fix updateVersionPlaceholder when the placeholder starts the line 2021-05-09 18:05:27 +02:00
Reinhold Gschweicher
48f220b68a
Add deprecation warning in ParseAndCatchTests
Parsing C++ with regex in CMake is error prone and regularly leads to silently
dropped (not run) test cases.

Going forward the function `catch_discover_tests` from `contrib/CMake.cmake`
should be used.

For more information see https://github.com/catchorg/Catch2/issues/2092#issuecomment-747342765
2021-05-09 18:05:25 +02:00
Reinhold Gschweicher
031a57e7b7
Fix Catch.cmake helper by setting variable globally
Set `_CATCH_DISCOVER_TESTS_SCRIPT` helper variable globally. Otherwise in a
scoped call (like `add_subdirectory()`) the variable gets lost. This lost
variable results in a post build error with not much information to lead to the
root of the problem.

This enables the usage of the helper script with the following example structure

- CMakeLists.txt (project root with `add_subdirectory(external/catch2)`
- external/catch2
  - CMakeLists.txt (contents listed below)
  - contrib/Catch.cmake
  - contrib/CatchAddTests.cmake
  - catch2/catch.hpp
- tests
  - CMakeLists.txt (add tests with `catch_discover_tests(${PROJECT_NAME})`)

contents of project specific helper `external/catch2/CMakeLists.txt`
```cmake
cmake_minimum_required (VERSION 3.1...${CMAKE_VERSION})
project(Catch2 LANGUAGES CXX VERSION 2.13.3)
add_library(Catch2 INTERFACE)
target_include_directories(Catch2
  INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
 # provide a namespaced alias for clients to 'link' against if catch is included as a sub-project
add_library(Catch2::Catch2 ALIAS Catch2)
include(contrib/Catch.cmake)
```
2021-05-09 18:05:18 +02:00
Sergio Losilla
cdf4748d1c
Modified hash to make it more independent of the choice of test names. 2021-05-09 18:05:14 +02:00
19 changed files with 308 additions and 137 deletions

View File

@ -77,3 +77,13 @@ function(add_warnings_to_targets targets)
endif() endif()
endif() endif()
endfunction() endfunction()
# Adds flags required for reproducible build to the target
# Currently only supports GCC and Clang
function(add_build_reproducibility_settings target)
# Make the build reproducible on versions of g++ and clang that supports -ffile-prefix-map
if(("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 8) OR
("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 10))
target_compile_options(${target} PRIVATE "-ffile-prefix-map=${CATCH_DIR}=.")
endif()
endfunction()

View File

@ -60,7 +60,7 @@ with automatically registering their `TEST_CASE`s with CTest. They
can be found in the `extras` folder, and are can be found in the `extras` folder, and are
1) `Catch.cmake` (and its dependency `CatchAddTests.cmake`) 1) `Catch.cmake` (and its dependency `CatchAddTests.cmake`)
2) `ParseAndAddCatchTests.cmake` 2) `ParseAndAddCatchTests.cmake` (deprecated)
If Catch2 has been installed in system, both of these can be used after If Catch2 has been installed in system, both of these can be used after
doing `find_package(Catch2 REQUIRED)`. Otherwise you need to add them doing `find_package(Catch2 REQUIRED)`. Otherwise you need to add them
@ -179,10 +179,17 @@ the output file name e.g. ".xml".
### `ParseAndAddCatchTests.cmake` ### `ParseAndAddCatchTests.cmake`
⚠ This script is [deprecated](https://github.com/catchorg/Catch2/pull/2120)
in Catch2 2.13.4 and superseded by the above approach using `catch_discover_tests`.
See [#2092](https://github.com/catchorg/Catch2/issues/2092) for details.
`ParseAndAddCatchTests` works by parsing all implementation files `ParseAndAddCatchTests` works by parsing all implementation files
associated with the provided target, and registering them via CTest's associated with the provided target, and registering them via CTest's
`add_test`. This approach has some limitations, such as the fact that `add_test`. This approach has some limitations, such as the fact that
commented-out tests will be registered anyway. commented-out tests will be registered anyway. More serious, only a
subset of the assertion macros currently available in Catch can be
detected by this script and tests with any macros that cannot be
parsed are *silently ignored*.
#### Usage #### Usage

View File

@ -9,6 +9,13 @@ either of these is a breaking change, and thus will not happen until
at least the next major release. at least the next major release.
### `ParseAndAddCatchTests.cmake`
The CMake/CTest integration using `ParseAndAddCatchTests.cmake` is deprecated,
as it can be replaced by `Catch.cmake` that provides the function
`catch_discover_tests` to get tests directly from a CMake target via the
command line interface instead of parsing C++ code with regular expressions.
## Planned changes ## Planned changes

View File

@ -3,6 +3,9 @@
# Release notes # Release notes
**Contents**<br> **Contents**<br>
[3.0.1](#301)<br> [3.0.1](#301)<br>
[2.13.6](#2136)<br>
[2.13.5](#2135)<br>
[2.13.4](#2134)<br>
[2.13.3](#2133)<br> [2.13.3](#2133)<br>
[2.13.2](#2132)<br> [2.13.2](#2132)<br>
[2.13.1](#2131)<br> [2.13.1](#2131)<br>
@ -171,6 +174,48 @@ new design.
* `catch2-with-main` also links in the default main * `catch2-with-main` also links in the default main
## 2.13.6
### Fixes
* Disabling all signal handlers no longer breaks compilation (#2212, #2213)
### Miscellaneous
* `catch_discover_tests` should handle escaped semicolon (`;`) better (#2214, #2215)
## 2.13.5
### Improvements
* Detection of MAC and IPHONE platforms has been improved (#2140, #2157)
* Added workaround for bug in XLC 16.1.0.1 (#2155)
* Add detection for LCC when it is masquerading as GCC (#2199)
* Modified posix signal handling so it supports newer libcs (#2178)
* `MINSIGSTKSZ` was no longer usable in constexpr context.
### Fixes
* Fixed compilation of benchmarking when `min` and `max` macros are defined (#2159)
* Including `windows.h` without `NOMINMAX` remains a really bad idea, don't do it
### Miscellaneous
* The check whether Catch2 is being built as a subproject is now more reliable (#2202, #2204)
* The problem was that if the variable name used internally was defined the project including Catch2 as subproject, it would not be properly overwritten for Catch2's CMake.
## 2.13.4
### Improvements
* Improved the hashing algorithm used for shuffling test cases (#2070)
* `TEST_CASE`s that differ only in the last character should be properly shuffled
* Note that this means that v2.13.4 gives you a different order of test cases than 2.13.3, even given the same seed.
### Miscellaneous
* Deprecated `ParseAndAddCatchTests` CMake integration (#2092)
* It is impossible to implement it properly for all the different test case variants Catch2 provides, and there are better options provided.
* Use `catch_discover_tests` instead, which uses runtime information about available tests.
* Fixed bug in `catch_discover_tests` that would cause it to fail when used in specific project structures (#2119)
* Added Bazel build file
* Added an experimental static library target to CMake
## 2.13.3 ## 2.13.3

View File

@ -202,4 +202,5 @@ endfunction()
set(_CATCH_DISCOVER_TESTS_SCRIPT set(_CATCH_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
) )

View File

@ -241,6 +241,7 @@ endfunction()
# entry point # entry point
function(ParseAndAddCatchTests TestTarget) function(ParseAndAddCatchTests TestTarget)
message(DEPRECATION "ParseAndAddCatchTest: function deprecated because of possibility of missed test cases. Consider using 'catch_discover_tests' from 'Catch.cmake'")
ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}") ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}")
get_target_property(SourceFiles ${TestTarget} SOURCES) get_target_property(SourceFiles ${TestTarget} SOURCES)
ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}") ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}")

View File

@ -240,6 +240,7 @@ add_library(Catch2 STATIC
${BENCHMARK_HEADERS} ${BENCHMARK_HEADERS}
${BENCHMARK_SOURCES} ${BENCHMARK_SOURCES}
) )
add_build_reproducibility_settings(Catch2)
add_library(Catch2::Catch2 ALIAS Catch2) add_library(Catch2::Catch2 ALIAS Catch2)
if (ANDROID) if (ANDROID)
@ -281,6 +282,7 @@ target_include_directories(Catch2
add_library(Catch2WithMain STATIC add_library(Catch2WithMain STATIC
${SOURCES_DIR}/internal/catch_main.cpp ${SOURCES_DIR}/internal/catch_main.cpp
) )
add_build_reproducibility_settings(Catch2WithMain)
add_library(Catch2::Catch2WithMain ALIAS Catch2WithMain) add_library(Catch2::Catch2WithMain ALIAS Catch2WithMain)
target_link_libraries(Catch2WithMain PUBLIC Catch2) target_link_libraries(Catch2WithMain PUBLIC Catch2)
set_target_properties(Catch2WithMain set_target_properties(Catch2WithMain

View File

@ -66,7 +66,9 @@ namespace Catch {
} }
template <typename Clock> template <typename Clock>
EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) { EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) {
auto time_limit = std::min(resolution * clock_cost_estimation_tick_limit, FloatDuration<Clock>(clock_cost_estimation_time_limit)); auto time_limit = (std::min)(
resolution * clock_cost_estimation_tick_limit,
FloatDuration<Clock>(clock_cost_estimation_time_limit));
auto time_clock = [](int k) { auto time_clock = [](int k) {
return Detail::measure<Clock>([k] { return Detail::measure<Clock>([k] {
for (int i = 0; i < k; ++i) { for (int i = 0; i < k; ++i) {

View File

@ -171,7 +171,7 @@ namespace Catch {
double sb = stddev.point; double sb = stddev.point;
double mn = mean.point / n; double mn = mean.point / n;
double mg_min = mn / 2.; double mg_min = mn / 2.;
double sg = std::min(mg_min / 4., sb / std::sqrt(n)); double sg = (std::min)(mg_min / 4., sb / std::sqrt(n));
double sg2 = sg * sg; double sg2 = sg * sg;
double sb2 = sb * sb; double sb2 = sb * sb;
@ -190,7 +190,7 @@ namespace Catch {
return (nc / n) * (sb2 - nc * sg2); return (nc / n) * (sb2 - nc * sg2);
}; };
return std::min(var_out(1), var_out(std::min(c_max(0.), c_max(mg_min)))) / sb2; return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2;
} }

View File

@ -116,8 +116,8 @@ namespace Catch {
double b2 = bias - z1; double b2 = bias - z1;
double a1 = a(b1); double a1 = a(b1);
double a2 = a(b2); double a2 = a(b2);
auto lo = std::max(cumn(a1), 0); auto lo = (std::max)(cumn(a1), 0);
auto hi = std::min(cumn(a2), n - 1); auto hi = (std::min)(cumn(a2), n - 1);
return { point, resample[lo], resample[hi], confidence_level }; return { point, resample[lo], resample[hi], confidence_level };
} }

View File

@ -38,9 +38,9 @@
#endif #endif
// We have to avoid both ICC and Clang, because they try to mask themselves // Only GCC compiler should be used in this block, so other compilers trying to
// as gcc, and we want only GCC in this block // mask themselves as GCC should be ignored.
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) #if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__)
# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" )
# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" )

View File

@ -5,30 +5,73 @@
// https://www.boost.org/LICENSE_1_0.txt) // https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
/** \file
* This file provides platform specific implementations of FatalConditionHandler
*
* This means that there is a lot of conditional compilation, and platform
* specific code. Currently, Catch2 supports a dummy handler (if no
* handler is desired), and 2 platform specific handlers:
* * Windows' SEH
* * POSIX signals
*
* Consequently, various pieces of code below are compiled if either of
* the platform specific handlers is enabled, or if none of them are
* enabled. It is assumed that both cannot be enabled at the same time,
* and doing so should cause a compilation error.
*
* If another platform specific handler is added, the compile guards
* below will need to be updated taking these assumptions into account.
*/
#include <catch2/internal/catch_fatal_condition_handler.hpp> #include <catch2/internal/catch_fatal_condition_handler.hpp>
#include <catch2/internal/catch_context.hpp> #include <catch2/internal/catch_context.hpp>
#include <catch2/internal/catch_enforce.hpp>
#include <catch2/interfaces/catch_interfaces_capture.hpp> #include <catch2/interfaces/catch_interfaces_capture.hpp>
#include <catch2/internal/catch_windows_h_proxy.hpp>
#if defined(__GNUC__) #include <algorithm>
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers" #if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS )
#endif
namespace Catch {
// If neither SEH nor signal handling is required, the handler impls
// do not have to do anything, and can be empty.
void FatalConditionHandler::engage_platform() {}
void FatalConditionHandler::disengage_platform() {}
FatalConditionHandler::FatalConditionHandler() = default;
FatalConditionHandler::~FatalConditionHandler() = default;
} // end namespace Catch
#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS
#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS )
#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time"
#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS
#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )
namespace { namespace {
// Report the error condition //! Signals fatal error message to the run context
void reportFatal( char const * const message ) { void reportFatal( char const * const message ) {
Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );
} }
}
#endif // signals/SEH handling //! Minimal size Catch2 needs for its own fatal error handling.
//! Picked empirically, so it might not be sufficient on all
//! platforms, and for all configurations.
constexpr std::size_t minStackSizeForErrors = 32 * 1024;
} // end unnamed namespace
#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS
#if defined( CATCH_CONFIG_WINDOWS_SEH ) #if defined( CATCH_CONFIG_WINDOWS_SEH )
namespace Catch { namespace Catch {
struct SignalDefs { DWORD id; const char* name; }; struct SignalDefs { DWORD id; const char* name; };
// There is no 1-1 mapping between signals and windows exceptions. // There is no 1-1 mapping between signals and windows exceptions.
@ -41,7 +84,7 @@ namespace Catch {
{ static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, { static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" },
}; };
LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
for (auto const& def : signalDefs) { for (auto const& def : signalDefs) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
reportFatal(def.name); reportFatal(def.name);
@ -52,35 +95,52 @@ namespace Catch {
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
// Since we do not support multiple instantiations, we put these
// into global variables and rely on cleaning them up in outlined
// constructors/destructors
static PVOID exceptionHandlerHandle = nullptr;
// For MSVC, we reserve part of the stack memory for handling
// memory overflow structured exception.
FatalConditionHandler::FatalConditionHandler() { FatalConditionHandler::FatalConditionHandler() {
isSet = true; ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors);
// 32k seems enough for Catch to handle stack overflow, if (!SetThreadStackGuarantee(&guaranteeSize)) {
// but the value was found experimentally, so there is no strong guarantee // We do not want to fully error out, because needing
guaranteeSize = 32 * 1024; // the stack reserve should be rare enough anyway.
exceptionHandlerHandle = nullptr; Catch::cerr()
<< "Failed to reserve piece of stack."
<< " Stack overflows will not be reported successfully.";
}
}
// We do not attempt to unset the stack guarantee, because
// Windows does not support lowering the stack size guarantee.
FatalConditionHandler::~FatalConditionHandler() = default;
void FatalConditionHandler::engage_platform() {
// Register as first handler in current chain // Register as first handler in current chain
exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
// Pass in guarantee size to be filled if (!exceptionHandlerHandle) {
SetThreadStackGuarantee(&guaranteeSize); CATCH_RUNTIME_ERROR("Could not register vectored exception handler");
}
} }
void FatalConditionHandler::reset() { void FatalConditionHandler::disengage_platform() {
if (isSet) { if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) {
RemoveVectoredExceptionHandler(exceptionHandlerHandle); CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler");
SetThreadStackGuarantee(&guaranteeSize); }
exceptionHandlerHandle = nullptr; exceptionHandlerHandle = nullptr;
isSet = false;
}
} }
bool FatalConditionHandler::isSet = false; } // end namespace Catch
ULONG FatalConditionHandler::guaranteeSize = 0;
PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr;
#endif // CATCH_CONFIG_WINDOWS_SEH
} // namespace Catch #if defined( CATCH_CONFIG_POSIX_SIGNALS )
#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) #include <signal.h>
namespace Catch { namespace Catch {
@ -89,10 +149,6 @@ namespace Catch {
const char* name; const char* name;
}; };
// 32kb for the alternate stack seems to be sufficient. However, this value
// is experimentally determined, so that's not guaranteed.
static constexpr std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ;
static SignalDefs signalDefs[] = { static SignalDefs signalDefs[] = {
{ SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGINT, "SIGINT - Terminal interrupt signal" },
{ SIGILL, "SIGILL - Illegal instruction signal" }, { SIGILL, "SIGILL - Illegal instruction signal" },
@ -102,8 +158,32 @@ namespace Catch {
{ SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
}; };
// Older GCCs trigger -Wmissing-field-initializers for T foo = {}
// which is zero initialization, but not explicit. We want to avoid
// that.
#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
void FatalConditionHandler::handleSignal( int sig ) { static char* altStackMem = nullptr;
static std::size_t altStackSize = 0;
static stack_t oldSigStack{};
static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{};
static void restorePreviousSignalHandlers() {
// We set signal handlers back to the previous ones. Hopefully
// nobody overwrote them in the meantime, and doesn't expect
// their signal handlers to live past ours given that they
// installed them after ours..
for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
}
// Return the old stack
sigaltstack(&oldSigStack, nullptr);
}
static void handleSignal( int sig ) {
char const * name = "<unknown signal>"; char const * name = "<unknown signal>";
for (auto const& def : signalDefs) { for (auto const& def : signalDefs) {
if (sig == def.id) { if (sig == def.id) {
@ -111,16 +191,33 @@ namespace Catch {
break; break;
} }
} }
reset(); // We need to restore previous signal handlers and let them do
reportFatal(name); // their thing, so that the users can have the debugger break
// when a signal is raised, and so on.
restorePreviousSignalHandlers();
reportFatal( name );
raise( sig ); raise( sig );
} }
FatalConditionHandler::FatalConditionHandler() { FatalConditionHandler::FatalConditionHandler() {
isSet = true; assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists");
if (altStackSize == 0) {
altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors);
}
altStackMem = new char[altStackSize]();
}
FatalConditionHandler::~FatalConditionHandler() {
delete[] altStackMem;
// We signal that another instance can be constructed by zeroing
// out the pointer.
altStackMem = nullptr;
}
void FatalConditionHandler::engage_platform() {
stack_t sigStack; stack_t sigStack;
sigStack.ss_sp = altStackMem; sigStack.ss_sp = altStackMem;
sigStack.ss_size = sigStackSize; sigStack.ss_size = altStackSize;
sigStack.ss_flags = 0; sigStack.ss_flags = 0;
sigaltstack(&sigStack, &oldSigStack); sigaltstack(&sigStack, &oldSigStack);
struct sigaction sa = { }; struct sigaction sa = { };
@ -132,28 +229,15 @@ namespace Catch {
} }
} }
void FatalConditionHandler::reset() {
if( isSet ) {
// Set signals back to previous values -- hopefully nobody overwrote them in the meantime
for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) {
sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
}
// Return the old stack
sigaltstack(&oldSigStack, nullptr);
isSet = false;
}
}
bool FatalConditionHandler::isSet = false;
struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {};
stack_t FatalConditionHandler::oldSigStack = {};
char FatalConditionHandler::altStackMem[sigStackSize] = {};
} // namespace Catch
#endif // signals/SEH handling
#if defined(__GNUC__) #if defined(__GNUC__)
# pragma GCC diagnostic pop # pragma GCC diagnostic pop
#endif #endif
void FatalConditionHandler::disengage_platform() {
restorePreviousSignalHandlers();
}
} // end namespace Catch
#endif // CATCH_CONFIG_POSIX_SIGNALS

View File

@ -10,57 +10,60 @@
#include <catch2/internal/catch_platform.hpp> #include <catch2/internal/catch_platform.hpp>
#include <catch2/internal/catch_compiler_capabilities.hpp> #include <catch2/internal/catch_compiler_capabilities.hpp>
#include <catch2/internal/catch_windows_h_proxy.hpp>
#include <cassert>
#if defined( CATCH_CONFIG_WINDOWS_SEH )
namespace Catch { namespace Catch {
struct FatalConditionHandler { /**
* Wrapper for platform-specific fatal error (signals/SEH) handlers
*
* Tries to be cooperative with other handlers, and not step over
* other handlers. This means that unknown structured exceptions
* are passed on, previous signal handlers are called, and so on.
*
* Can only be instantiated once, and assumes that once a signal
* is caught, the binary will end up terminating. Thus, there
*/
class FatalConditionHandler {
bool m_started = false;
static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); // Install/disengage implementation for specific platform.
// Should be if-defed to work on current platform, can assume
// engage-disengage 1:1 pairing.
void engage_platform();
void disengage_platform();
public:
// Should also have platform-specific implementations as needed
FatalConditionHandler(); FatalConditionHandler();
static void reset(); ~FatalConditionHandler();
~FatalConditionHandler() { reset(); }
private: void engage() {
static bool isSet; assert(!m_started && "Handler cannot be installed twice.");
static ULONG guaranteeSize; m_started = true;
static PVOID exceptionHandlerHandle; engage_platform();
}
void disengage() {
assert(m_started && "Handler cannot be uninstalled without being installed first");
m_started = false;
disengage_platform();
}
}; };
} // namespace Catch //! Simple RAII guard for (dis)engaging the FatalConditionHandler
class FatalConditionHandlerGuard {
#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) FatalConditionHandler* m_handler;
public:
#include <signal.h> FatalConditionHandlerGuard(FatalConditionHandler* handler):
m_handler(handler) {
namespace Catch { m_handler->engage();
}
struct FatalConditionHandler { ~FatalConditionHandlerGuard() {
m_handler->disengage();
static bool isSet; }
static struct sigaction oldSigActions[];
static stack_t oldSigStack;
static char altStackMem[];
static void handleSignal( int sig );
FatalConditionHandler();
~FatalConditionHandler() { reset(); }
static void reset();
}; };
} // namespace Catch } // end namespace Catch
#else
namespace Catch {
struct FatalConditionHandler {};
}
#endif
#endif // CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED #endif // CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED

View File

@ -8,11 +8,14 @@
#ifndef CATCH_PLATFORM_HPP_INCLUDED #ifndef CATCH_PLATFORM_HPP_INCLUDED
#define CATCH_PLATFORM_HPP_INCLUDED #define CATCH_PLATFORM_HPP_INCLUDED
// See e.g.:
// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html
#ifdef __APPLE__ #ifdef __APPLE__
# include <TargetConditionals.h> # include <TargetConditionals.h>
# if TARGET_OS_OSX == 1 # if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \
(defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1)
# define CATCH_PLATFORM_MAC # define CATCH_PLATFORM_MAC
# elif TARGET_OS_IPHONE == 1 # elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1)
# define CATCH_PLATFORM_IPHONE # define CATCH_PLATFORM_IPHONE
# endif # endif

View File

@ -459,10 +459,10 @@ namespace Catch {
} }
void RunContext::invokeActiveTestCase() { void RunContext::invokeActiveTestCase() {
// We need to register a handler for signals/structured exceptions // We need to engage a handler for signals/structured exceptions
// before running the tests themselves, or the binary can crash // before running the tests themselves, or the binary can crash
// without failed test being reported. // without failed test being reported.
FatalConditionHandler _; FatalConditionHandlerGuard _(&m_fatalConditionhandler);
m_activeTestCase->invoke(); m_activeTestCase->invoke();
} }

View File

@ -11,6 +11,7 @@
#include <catch2/interfaces/catch_interfaces_runner.hpp> #include <catch2/interfaces/catch_interfaces_runner.hpp>
#include <catch2/interfaces/catch_interfaces_reporter.hpp> #include <catch2/interfaces/catch_interfaces_reporter.hpp>
#include <catch2/internal/catch_test_registry.hpp> #include <catch2/internal/catch_test_registry.hpp>
#include <catch2/internal/catch_fatal_condition_handler.hpp>
#include <catch2/catch_test_case_info.hpp> #include <catch2/catch_test_case_info.hpp>
#include <catch2/catch_message.hpp> #include <catch2/catch_message.hpp>
#include <catch2/catch_totals.hpp> #include <catch2/catch_totals.hpp>
@ -139,6 +140,7 @@ namespace Catch {
std::vector<SectionEndInfo> m_unfinishedSections; std::vector<SectionEndInfo> m_unfinishedSections;
std::vector<ITracker*> m_activeSections; std::vector<ITracker*> m_activeSections;
TrackerContext m_trackerContext; TrackerContext m_trackerContext;
FatalConditionHandler m_fatalConditionhandler;
bool m_lastAssertionPassed = false; bool m_lastAssertionPassed = false;
bool m_shouldReportUnexpected = true; bool m_shouldReportUnexpected = true;
bool m_includeSuccessfulResults; bool m_includeSuccessfulResults;

View File

@ -22,24 +22,27 @@
namespace Catch { namespace Catch {
namespace { namespace {
struct HashTest { struct TestHasher {
explicit HashTest(SimplePcg32& rng_inst) { using hash_t = uint64_t;
basis = rng_inst();
basis <<= 32;
basis |= rng_inst();
}
uint64_t basis; explicit TestHasher( hash_t hashSuffix ):
m_hashSuffix( hashSuffix ) {}
uint64_t operator()(TestCaseInfo const& t) const { uint64_t m_hashSuffix;
// Modified FNV-1a hash
static constexpr uint64_t prime = 1099511628211; uint32_t operator()( TestCaseInfo const& t ) const {
uint64_t hash = basis; // FNV-1a hash with multiplication fold.
const hash_t prime = 1099511628211u;
hash_t hash = 14695981039346656037u;
for (const char c : t.name) { for (const char c : t.name) {
hash ^= c; hash ^= c;
hash *= prime; hash *= prime;
} }
return hash; hash ^= m_hashSuffix;
hash *= prime;
const uint32_t low{ static_cast<uint32_t>(hash) };
const uint32_t high{ static_cast<uint32_t>(hash >> 32) };
return low * high;
} }
}; };
} // end anonymous namespace } // end anonymous namespace
@ -56,8 +59,8 @@ namespace {
} }
case TestRunOrder::Randomized: { case TestRunOrder::Randomized: {
seedRng(config); seedRng(config);
HashTest h(rng()); TestHasher h{ config.rngSeed() };
std::vector<std::pair<uint64_t, TestCaseHandle>> indexed_tests; std::vector<std::pair<TestHasher::hash_t, TestCaseHandle>> indexed_tests;
indexed_tests.reserve(unsortedTestCases.size()); indexed_tests.reserve(unsortedTestCases.size());
for (auto const& handle : unsortedTestCases) { for (auto const& handle : unsortedTestCases) {

View File

@ -54,7 +54,8 @@ namespace {
return lhs == rhs; return lhs == rhs;
} }
auto ulpDiff = std::abs(lc - rc); // static cast as a workaround for IBM XLC
auto ulpDiff = std::abs(static_cast<FP>(lc - rc));
return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff; return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff;
} }

View File

@ -127,8 +127,8 @@ def updateVersionDefine(version):
def updateVersionPlaceholder(filename, version): def updateVersionPlaceholder(filename, version):
with open(filename, 'rb') as file: with open(filename, 'rb') as file:
lines = file.readlines() lines = file.readlines()
placeholderRegex = re.compile(b' in Catch[0-9]? X.Y.Z') placeholderRegex = re.compile(b'in Catch[0-9]? X.Y.Z')
replacement = ' in Catch2 {}.{}.{}'.format(version.majorVersion, version.minorVersion, version.patchNumber).encode('ascii') replacement = 'in Catch2 {}.{}.{}'.format(version.majorVersion, version.minorVersion, version.patchNumber).encode('ascii')
with open(filename, 'wb') as file: with open(filename, 'wb') as file:
for line in lines: for line in lines:
file.write(placeholderRegex.sub(replacement, line)) file.write(placeholderRegex.sub(replacement, line))