mirror of
https://github.com/catchorg/Catch2.git
synced 2025-09-10 15:35:39 +02:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cd93d202e0 | ||
![]() |
227af796b4 | ||
![]() |
33adb4c779 | ||
![]() |
25319fd304 | ||
![]() |
85c4bad86b | ||
![]() |
582200a1f8 | ||
![]() |
f4e05a67bb | ||
![]() |
fb2e4fbe41 | ||
![]() |
78a9518a28 | ||
![]() |
3e82ef9317 | ||
![]() |
7cad6d7539 |
@@ -34,7 +34,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
endif()
|
||||
|
||||
project(Catch2
|
||||
VERSION 3.9.1 # CML version placeholder, don't delete
|
||||
VERSION 3.10.0 # CML version placeholder, don't delete
|
||||
LANGUAGES CXX
|
||||
HOMEPAGE_URL "https://github.com/catchorg/Catch2"
|
||||
DESCRIPTION "A modern, C++-native, unit test framework."
|
||||
@@ -195,17 +195,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
|
||||
)
|
||||
]]
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
# Release notes
|
||||
**Contents**<br>
|
||||
[3.10.0](#3100)<br>
|
||||
[3.9.1](#391)<br>
|
||||
[3.9.0](#390)<br>
|
||||
[3.8.1](#381)<br>
|
||||
@@ -69,6 +70,19 @@
|
||||
[Even Older versions](#even-older-versions)<br>
|
||||
|
||||
|
||||
## 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
|
||||
|
@@ -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`).
|
||||
|
||||
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#experimental-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
|
||||
|
@@ -6,8 +6,8 @@
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
// Catch v3.9.1
|
||||
// Generated: 2025-08-09 00:29:21.552225
|
||||
// Catch v3.10.0
|
||||
// Generated: 2025-08-24 16:18:04.775778
|
||||
// ----------------------------------------------------------
|
||||
// This file is an amalgamation of multiple different files.
|
||||
// You probably shouldn't edit it directly.
|
||||
@@ -1057,8 +1057,9 @@ namespace Catch {
|
||||
}
|
||||
Capturer::~Capturer() {
|
||||
assert( m_captured == m_messages.size() );
|
||||
for ( size_t i = 0; i < m_captured; ++i )
|
||||
for ( size_t i = 0; i < m_captured; ++i ) {
|
||||
m_resultCapture.popScopedMessage( m_messages[i] );
|
||||
}
|
||||
}
|
||||
|
||||
void Capturer::captureValue( size_t index, std::string const& value ) {
|
||||
@@ -2278,7 +2279,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 3, 9, 1, "", 0 );
|
||||
static Version version( 3, 10, 0, "", 0 );
|
||||
return version;
|
||||
}
|
||||
|
||||
@@ -3514,7 +3515,7 @@ 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__)
|
||||
# define CATCH_INTERNAL_HAS_ISATTY
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
@@ -4458,6 +4459,33 @@ namespace Detail {
|
||||
|
||||
|
||||
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 << " ";
|
||||
@@ -4567,30 +4595,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 << '"'; }
|
||||
}
|
||||
|
||||
@@ -4787,17 +4804,18 @@ int main (int argc, char * argv[]) {
|
||||
|
||||
namespace Catch {
|
||||
|
||||
MessageInfo::MessageInfo( StringRef _macroName,
|
||||
SourceLineInfo const& _lineInfo,
|
||||
ResultWas::OfType _type )
|
||||
MessageInfo::MessageInfo( StringRef _macroName,
|
||||
SourceLineInfo const& _lineInfo,
|
||||
ResultWas::OfType _type )
|
||||
: macroName( _macroName ),
|
||||
lineInfo( _lineInfo ),
|
||||
type( _type ),
|
||||
sequence( ++globalCount )
|
||||
{}
|
||||
|
||||
// This may need protecting if threading support is added
|
||||
unsigned int MessageInfo::globalCount = 0;
|
||||
// Messages are owned by their individual threads, so the counter should be thread-local as well.
|
||||
// Alternative consideration: atomic, so threads don't share IDs and things are easier to debug.
|
||||
thread_local unsigned int MessageInfo::globalCount = 0;
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
@@ -5556,8 +5574,10 @@ 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 {
|
||||
Detail::LockGuard _( m_mutex );
|
||||
if( m_unused.empty() ) {
|
||||
m_streams.push_back( Detail::make_unique<std::ostringstream>() );
|
||||
return m_streams.size()-1;
|
||||
@@ -5569,9 +5589,13 @@ namespace Catch {
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5583,7 +5607,7 @@ namespace Catch {
|
||||
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 {
|
||||
@@ -5750,9 +5774,6 @@ 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
|
||||
@@ -5761,15 +5782,27 @@ namespace Catch {
|
||||
|
||||
// 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;
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
|
||||
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
|
||||
// Actual messages to be provided to the reporter
|
||||
static thread_local std::vector<MessageInfo> g_messages;
|
||||
|
||||
// Owners for the UNSCOPED_X information macro
|
||||
static thread_local std::vector<ScopedMessage> g_messageScopes;
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)
|
||||
: m_runInfo(_config->name()),
|
||||
@@ -5905,20 +5938,21 @@ namespace Catch {
|
||||
Detail::g_lastAssertionPassed = true;
|
||||
}
|
||||
|
||||
if ( Detail::g_clearMessageScopes ) {
|
||||
Detail::g_messageScopes.clear();
|
||||
Detail::g_clearMessageScopes = false;
|
||||
}
|
||||
|
||||
// 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, Detail::g_messages, m_totals ) );
|
||||
}
|
||||
|
||||
if ( result.getResultType() != ResultWas::Warning ) {
|
||||
m_messageScopes.clear();
|
||||
Detail::g_messageScopes.clear();
|
||||
}
|
||||
|
||||
// Reset working state. assertion info will be reset after
|
||||
@@ -6051,8 +6085,8 @@ namespace Catch {
|
||||
m_reporter->benchmarkFailed( error );
|
||||
}
|
||||
|
||||
void RunContext::pushScopedMessage(MessageInfo const & message) {
|
||||
m_messages.push_back(message);
|
||||
void RunContext::pushScopedMessage( MessageInfo const& message ) {
|
||||
Detail::g_messages.push_back( message );
|
||||
}
|
||||
|
||||
void RunContext::popScopedMessage( MessageInfo const& message ) {
|
||||
@@ -6061,16 +6095,16 @@ namespace Catch {
|
||||
// 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(),
|
||||
Detail::g_messages.erase(
|
||||
std::find_if( Detail::g_messages.begin(),
|
||||
Detail::g_messages.end(),
|
||||
[id = message.sequence]( MessageInfo const& msg ) {
|
||||
return msg.sequence == id;
|
||||
} ) );
|
||||
}
|
||||
|
||||
void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
m_messageScopes.emplace_back( CATCH_MOVE(builder) );
|
||||
Detail::g_messageScopes.emplace_back( CATCH_MOVE(builder) );
|
||||
}
|
||||
|
||||
std::string RunContext::getCurrentTestName() const {
|
||||
@@ -6229,10 +6263,10 @@ namespace Catch {
|
||||
|
||||
m_testCaseTracker->close();
|
||||
handleUnfinishedSections();
|
||||
m_messageScopes.clear();
|
||||
Detail::g_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();
|
||||
Detail::g_messages.clear();
|
||||
|
||||
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
|
||||
m_reporter->sectionEnded(testCaseSectionStats);
|
||||
@@ -7971,7 +8005,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);
|
||||
@@ -7990,95 +8024,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 ) {
|
||||
|
@@ -6,8 +6,8 @@
|
||||
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
// Catch v3.9.1
|
||||
// Generated: 2025-08-09 00:29:20.303175
|
||||
// Catch v3.10.0
|
||||
// Generated: 2025-08-24 16:18:04.055916
|
||||
// ----------------------------------------------------------
|
||||
// This file is an amalgamation of multiple different files.
|
||||
// You probably shouldn't edit it directly.
|
||||
@@ -3972,7 +3972,7 @@ namespace Catch {
|
||||
return sequence < other.sequence;
|
||||
}
|
||||
private:
|
||||
static unsigned int globalCount;
|
||||
static thread_local unsigned int globalCount;
|
||||
};
|
||||
|
||||
} // end namespace Catch
|
||||
@@ -7466,8 +7466,8 @@ namespace Catch {
|
||||
#define CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
||||
#define CATCH_VERSION_MAJOR 3
|
||||
#define CATCH_VERSION_MINOR 9
|
||||
#define CATCH_VERSION_PATCH 1
|
||||
#define CATCH_VERSION_MINOR 10
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
|
||||
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
||||
@@ -10773,9 +10773,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,7 +8,7 @@
|
||||
project(
|
||||
'catch2',
|
||||
'cpp',
|
||||
version: '3.9.1', # CML version placeholder, don't delete
|
||||
version: '3.10.0', # CML version placeholder, don't delete
|
||||
license: 'BSL-1.0',
|
||||
meson_version: '>=0.54.1',
|
||||
)
|
||||
|
@@ -19,20 +19,19 @@ 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();
|
||||
getResultCapture().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 ) { getResultCapture().popScopedMessage( m_messageId ); }
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +85,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 +95,20 @@ namespace Catch {
|
||||
}
|
||||
assert(openings.empty() && "Mismatched openings");
|
||||
m_messages.emplace_back(macroName, lineInfo, resultType);
|
||||
m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));
|
||||
m_messages.back().message += " := ";
|
||||
m_messages.back().message += trimmed(start, names.size() - 1);
|
||||
m_messages.back().message += " := "_sr;
|
||||
}
|
||||
Capturer::~Capturer() {
|
||||
assert( m_captured == m_messages.size() );
|
||||
for ( size_t i = 0; i < m_captured; ++i )
|
||||
m_resultCapture.popScopedMessage( m_messages[i] );
|
||||
for (auto const& message : m_messages) {
|
||||
m_resultCapture.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] );
|
||||
m_resultCapture.pushScopedMessage( CATCH_MOVE(m_messages[index]) );
|
||||
m_captured++;
|
||||
}
|
||||
|
||||
|
@@ -57,7 +57,7 @@ namespace Catch {
|
||||
ScopedMessage( ScopedMessage&& old ) noexcept;
|
||||
~ScopedMessage();
|
||||
|
||||
MessageInfo m_info;
|
||||
unsigned int m_messageId;
|
||||
bool m_moved = false;
|
||||
};
|
||||
|
||||
|
@@ -36,7 +36,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 3, 9, 1, "", 0 );
|
||||
static Version version( 3, 10, 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_PATCH 1
|
||||
#define CATCH_VERSION_MINOR 10
|
||||
#define CATCH_VERSION_PATCH 0
|
||||
|
||||
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
|
||||
|
@@ -58,8 +58,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))
|
||||
@@ -91,7 +92,7 @@ namespace Generators {
|
||||
|
||||
template <typename T, typename Predicate>
|
||||
GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) {
|
||||
return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, Predicate>>(CATCH_FORWARD(pred), CATCH_MOVE(generator)));
|
||||
return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, typename std::remove_reference<Predicate>::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@@ -62,8 +62,8 @@ 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 pushScopedMessage( MessageInfo&& message ) = 0;
|
||||
virtual void popScopedMessage( unsigned int messageId ) = 0;
|
||||
|
||||
virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0;
|
||||
|
||||
|
@@ -161,7 +161,7 @@ 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__)
|
||||
# define CATCH_INTERNAL_HAS_ISATTY
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
@@ -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 << '"'; }
|
||||
}
|
||||
|
||||
|
@@ -10,16 +10,17 @@
|
||||
|
||||
namespace Catch {
|
||||
|
||||
MessageInfo::MessageInfo( StringRef _macroName,
|
||||
SourceLineInfo const& _lineInfo,
|
||||
ResultWas::OfType _type )
|
||||
MessageInfo::MessageInfo( StringRef _macroName,
|
||||
SourceLineInfo const& _lineInfo,
|
||||
ResultWas::OfType _type )
|
||||
: macroName( _macroName ),
|
||||
lineInfo( _lineInfo ),
|
||||
type( _type ),
|
||||
sequence( ++globalCount )
|
||||
{}
|
||||
|
||||
// This may need protecting if threading support is added
|
||||
unsigned int MessageInfo::globalCount = 0;
|
||||
// Messages are owned by their individual threads, so the counter should be thread-local as well.
|
||||
// Alternative consideration: atomic, so threads don't share IDs and things are easier to debug.
|
||||
thread_local unsigned int MessageInfo::globalCount = 0;
|
||||
|
||||
} // end namespace Catch
|
||||
|
@@ -26,6 +26,7 @@ namespace Catch {
|
||||
std::string message;
|
||||
SourceLineInfo lineInfo;
|
||||
ResultWas::OfType type;
|
||||
// The "ID" of the message, used to know when to remove it from reporter context.
|
||||
unsigned int sequence;
|
||||
|
||||
DEPRECATED( "Explicitly use the 'sequence' member instead" )
|
||||
@@ -37,7 +38,7 @@ namespace Catch {
|
||||
return sequence < other.sequence;
|
||||
}
|
||||
private:
|
||||
static unsigned int globalCount;
|
||||
static thread_local unsigned int globalCount;
|
||||
};
|
||||
|
||||
} // end namespace Catch
|
||||
|
@@ -7,6 +7,7 @@
|
||||
// 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>
|
||||
@@ -20,8 +21,10 @@ 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 {
|
||||
Detail::LockGuard _( m_mutex );
|
||||
if( m_unused.empty() ) {
|
||||
m_streams.push_back( Detail::make_unique<std::ostringstream>() );
|
||||
return m_streams.size()-1;
|
||||
@@ -33,9 +36,13 @@ namespace Catch {
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,7 +54,7 @@ namespace Catch {
|
||||
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 {
|
||||
|
@@ -172,9 +172,6 @@ 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
|
||||
@@ -183,15 +180,27 @@ namespace Catch {
|
||||
|
||||
// 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;
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
|
||||
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
|
||||
// Actual messages to be provided to the reporter
|
||||
static thread_local std::vector<MessageInfo> g_messages;
|
||||
|
||||
// Owners for the UNSCOPED_X information macro
|
||||
static thread_local std::vector<ScopedMessage> g_messageScopes;
|
||||
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)
|
||||
: m_runInfo(_config->name()),
|
||||
@@ -327,20 +336,21 @@ namespace Catch {
|
||||
Detail::g_lastAssertionPassed = true;
|
||||
}
|
||||
|
||||
if ( Detail::g_clearMessageScopes ) {
|
||||
Detail::g_messageScopes.clear();
|
||||
Detail::g_clearMessageScopes = false;
|
||||
}
|
||||
|
||||
// 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, Detail::g_messages, m_totals ) );
|
||||
}
|
||||
|
||||
if ( result.getResultType() != ResultWas::Warning ) {
|
||||
m_messageScopes.clear();
|
||||
Detail::g_messageScopes.clear();
|
||||
}
|
||||
|
||||
// Reset working state. assertion info will be reset after
|
||||
@@ -473,26 +483,26 @@ namespace Catch {
|
||||
m_reporter->benchmarkFailed( error );
|
||||
}
|
||||
|
||||
void RunContext::pushScopedMessage(MessageInfo const & message) {
|
||||
m_messages.push_back(message);
|
||||
void RunContext::pushScopedMessage( MessageInfo&& message ) {
|
||||
Detail::g_messages.push_back( CATCH_MOVE(message) );
|
||||
}
|
||||
|
||||
void RunContext::popScopedMessage( MessageInfo const& message ) {
|
||||
void RunContext::popScopedMessage( unsigned int messageId ) {
|
||||
// Note: On average, it would probably be better to look for the message
|
||||
// backwards. However, we do not expect to have to deal with more
|
||||
// messages than low single digits, so the optimization is tiny,
|
||||
// and we would have to hand-write the loop to avoid terrible
|
||||
// codegen of reverse iterators in debug mode.
|
||||
m_messages.erase(
|
||||
std::find_if( m_messages.begin(),
|
||||
m_messages.end(),
|
||||
[id = message.sequence]( MessageInfo const& msg ) {
|
||||
return msg.sequence == id;
|
||||
Detail::g_messages.erase(
|
||||
std::find_if( Detail::g_messages.begin(),
|
||||
Detail::g_messages.end(),
|
||||
[=]( MessageInfo const& msg ) {
|
||||
return msg.sequence == messageId;
|
||||
} ) );
|
||||
}
|
||||
|
||||
void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {
|
||||
m_messageScopes.emplace_back( CATCH_MOVE(builder) );
|
||||
Detail::g_messageScopes.emplace_back( CATCH_MOVE(builder) );
|
||||
}
|
||||
|
||||
std::string RunContext::getCurrentTestName() const {
|
||||
@@ -651,10 +661,10 @@ namespace Catch {
|
||||
|
||||
m_testCaseTracker->close();
|
||||
handleUnfinishedSections();
|
||||
m_messageScopes.clear();
|
||||
Detail::g_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();
|
||||
Detail::g_messages.clear();
|
||||
|
||||
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
|
||||
m_reporter->sectionEnded(testCaseSectionStats);
|
||||
|
@@ -94,8 +94,8 @@ 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 pushScopedMessage( MessageInfo&& message ) override;
|
||||
void popScopedMessage( unsigned int messageId ) override;
|
||||
|
||||
void emplaceUnscopedMessage( MessageBuilder&& builder ) override;
|
||||
|
||||
@@ -149,9 +149,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;
|
||||
|
@@ -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 ) {
|
||||
|
@@ -824,6 +824,10 @@ GeneratorsImpl.tests.cpp:<line number>: passed: filter([](int) { return false; }
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: filter([](int) { return false; }, values({ 1, 2, 3 })), Catch::GeneratorException
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 1 for: 1 == 1
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.next() for: true
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 3 for: 3 == 3
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: !(gen.next()) for: !false
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 1 for: 1 == 1
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.next() for: true
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 2 for: 2 == 2
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: !(gen.next()) for: !false
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 1 for: 1 == 1
|
||||
@@ -2885,6 +2889,6 @@ InternalBenchmark.tests.cpp:<line number>: passed: q3 == 23. for: 23.0 == 23.0
|
||||
Misc.tests.cpp:<line number>: passed:
|
||||
Misc.tests.cpp:<line number>: passed:
|
||||
test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected
|
||||
assertions: 2299 | 2101 passed | 157 failed | 41 failed as expected
|
||||
assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected
|
||||
|
||||
|
||||
|
@@ -822,6 +822,10 @@ GeneratorsImpl.tests.cpp:<line number>: passed: filter([](int) { return false; }
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: filter([](int) { return false; }, values({ 1, 2, 3 })), Catch::GeneratorException
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 1 for: 1 == 1
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.next() for: true
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 3 for: 3 == 3
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: !(gen.next()) for: !false
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 1 for: 1 == 1
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.next() for: true
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 2 for: 2 == 2
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: !(gen.next()) for: !false
|
||||
GeneratorsImpl.tests.cpp:<line number>: passed: gen.get() == 1 for: 1 == 1
|
||||
@@ -2874,6 +2878,6 @@ InternalBenchmark.tests.cpp:<line number>: passed: q3 == 23. for: 23.0 == 23.0
|
||||
Misc.tests.cpp:<line number>: passed:
|
||||
Misc.tests.cpp:<line number>: passed:
|
||||
test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected
|
||||
assertions: 2299 | 2101 passed | 157 failed | 41 failed as expected
|
||||
assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected
|
||||
|
||||
|
||||
|
@@ -1720,5 +1720,5 @@ due to unexpected exception with message:
|
||||
|
||||
===============================================================================
|
||||
test cases: 435 | 335 passed | 76 failed | 7 skipped | 17 failed as expected
|
||||
assertions: 2278 | 2101 passed | 136 failed | 41 failed as expected
|
||||
assertions: 2282 | 2105 passed | 136 failed | 41 failed as expected
|
||||
|
||||
|
@@ -6054,6 +6054,34 @@ GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE_THROWS_AS( filter([](int) { return false; }, values({ 1, 2, 3 })), Catch::GeneratorException )
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Generators internals
|
||||
Filter generator
|
||||
Out-of-line predicates are copied into the generator
|
||||
-------------------------------------------------------------------------------
|
||||
GeneratorsImpl.tests.cpp:<line number>
|
||||
...............................................................................
|
||||
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE( gen.get() == 1 )
|
||||
with expansion:
|
||||
1 == 1
|
||||
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE( gen.next() )
|
||||
with expansion:
|
||||
true
|
||||
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE( gen.get() == 3 )
|
||||
with expansion:
|
||||
3 == 3
|
||||
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE_FALSE( gen.next() )
|
||||
with expansion:
|
||||
!false
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Generators internals
|
||||
Take generator
|
||||
@@ -19268,5 +19296,5 @@ Misc.tests.cpp:<line number>: PASSED:
|
||||
|
||||
===============================================================================
|
||||
test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected
|
||||
assertions: 2299 | 2101 passed | 157 failed | 41 failed as expected
|
||||
assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected
|
||||
|
||||
|
@@ -6052,6 +6052,34 @@ GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE_THROWS_AS( filter([](int) { return false; }, values({ 1, 2, 3 })), Catch::GeneratorException )
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Generators internals
|
||||
Filter generator
|
||||
Out-of-line predicates are copied into the generator
|
||||
-------------------------------------------------------------------------------
|
||||
GeneratorsImpl.tests.cpp:<line number>
|
||||
...............................................................................
|
||||
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE( gen.get() == 1 )
|
||||
with expansion:
|
||||
1 == 1
|
||||
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE( gen.next() )
|
||||
with expansion:
|
||||
true
|
||||
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE( gen.get() == 3 )
|
||||
with expansion:
|
||||
3 == 3
|
||||
|
||||
GeneratorsImpl.tests.cpp:<line number>: PASSED:
|
||||
REQUIRE_FALSE( gen.next() )
|
||||
with expansion:
|
||||
!false
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Generators internals
|
||||
Take generator
|
||||
@@ -19257,5 +19285,5 @@ Misc.tests.cpp:<line number>: PASSED:
|
||||
|
||||
===============================================================================
|
||||
test cases: 435 | 317 passed | 95 failed | 6 skipped | 17 failed as expected
|
||||
assertions: 2299 | 2101 passed | 157 failed | 41 failed as expected
|
||||
assertions: 2303 | 2105 passed | 157 failed | 41 failed as expected
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuitesloose text artifact
|
||||
>
|
||||
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2311" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
|
||||
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2315" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
|
||||
<properties>
|
||||
<property name="random-seed" value="1"/>
|
||||
<property name="filters" value=""*" ~[!nonportable] ~[!benchmark] ~[approvals]"/>
|
||||
@@ -846,6 +846,7 @@ at Message.tests.cpp:<line number>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Filter generator/Simple filtering" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Filter generator/Filter out multiple elements at the start and end" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Filter generator/Throws on construction if it can't get initial element" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Filter generator/Out-of-line predicates are copied into the generator" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Take generator" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Take generator/Take less" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Take generator/Take more" time="{duration}" status="run"/>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites>
|
||||
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2311" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
|
||||
<testsuite name="<exe-name>" errors="17" failures="140" skipped="12" tests="2315" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
|
||||
<properties>
|
||||
<property name="random-seed" value="1"/>
|
||||
<property name="filters" value=""*" ~[!nonportable] ~[!benchmark] ~[approvals]"/>
|
||||
@@ -845,6 +845,7 @@ at Message.tests.cpp:<line number>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Filter generator/Simple filtering" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Filter generator/Filter out multiple elements at the start and end" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Filter generator/Throws on construction if it can't get initial element" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Filter generator/Out-of-line predicates are copied into the generator" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Take generator" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Take generator/Take less" time="{duration}" status="run"/>
|
||||
<testcase classname="<exe-name>.global" name="Generators internals/Take generator/Take more" time="{duration}" status="run"/>
|
||||
|
@@ -153,6 +153,7 @@ at AssertionHandler.tests.cpp:<line number>
|
||||
<testCase name="Generators internals/Filter generator/Simple filtering" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Filter generator/Filter out multiple elements at the start and end" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Filter generator/Throws on construction if it can't get initial element" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Filter generator/Out-of-line predicates are copied into the generator" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Take generator" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Take generator/Take less" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Take generator/Take more" duration="{duration}"/>
|
||||
|
@@ -152,6 +152,7 @@ at AssertionHandler.tests.cpp:<line number>
|
||||
<testCase name="Generators internals/Filter generator/Simple filtering" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Filter generator/Filter out multiple elements at the start and end" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Filter generator/Throws on construction if it can't get initial element" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Filter generator/Out-of-line predicates are copied into the generator" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Take generator" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Take generator/Take less" duration="{duration}"/>
|
||||
<testCase name="Generators internals/Take generator/Take more" duration="{duration}"/>
|
||||
|
@@ -1505,6 +1505,14 @@ ok {test-number} - gen.get() == 1 for: 1 == 1
|
||||
# Generators internals
|
||||
ok {test-number} - gen.next() for: true
|
||||
# Generators internals
|
||||
ok {test-number} - gen.get() == 3 for: 3 == 3
|
||||
# Generators internals
|
||||
ok {test-number} - !(gen.next()) for: !false
|
||||
# Generators internals
|
||||
ok {test-number} - gen.get() == 1 for: 1 == 1
|
||||
# Generators internals
|
||||
ok {test-number} - gen.next() for: true
|
||||
# Generators internals
|
||||
ok {test-number} - gen.get() == 2 for: 2 == 2
|
||||
# Generators internals
|
||||
ok {test-number} - !(gen.next()) for: !false
|
||||
@@ -4619,5 +4627,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0
|
||||
ok {test-number} -
|
||||
# xmlentitycheck
|
||||
ok {test-number} -
|
||||
1..2311
|
||||
1..2315
|
||||
|
||||
|
@@ -1503,6 +1503,14 @@ ok {test-number} - gen.get() == 1 for: 1 == 1
|
||||
# Generators internals
|
||||
ok {test-number} - gen.next() for: true
|
||||
# Generators internals
|
||||
ok {test-number} - gen.get() == 3 for: 3 == 3
|
||||
# Generators internals
|
||||
ok {test-number} - !(gen.next()) for: !false
|
||||
# Generators internals
|
||||
ok {test-number} - gen.get() == 1 for: 1 == 1
|
||||
# Generators internals
|
||||
ok {test-number} - gen.next() for: true
|
||||
# Generators internals
|
||||
ok {test-number} - gen.get() == 2 for: 2 == 2
|
||||
# Generators internals
|
||||
ok {test-number} - !(gen.next()) for: !false
|
||||
@@ -4608,5 +4616,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0
|
||||
ok {test-number} -
|
||||
# xmlentitycheck
|
||||
ok {test-number} -
|
||||
1..2311
|
||||
1..2315
|
||||
|
||||
|
@@ -6916,6 +6916,44 @@ Approx( 1.30000000000000004 )
|
||||
</Section>
|
||||
<OverallResults successes="2" failures="0" expectedFailures="0" skipped="false"/>
|
||||
</Section>
|
||||
<Section name="Filter generator" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Section name="Out-of-line predicates are copied into the generator" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Original>
|
||||
gen.get() == 1
|
||||
</Original>
|
||||
<Expanded>
|
||||
1 == 1
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Original>
|
||||
gen.next()
|
||||
</Original>
|
||||
<Expanded>
|
||||
true
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Original>
|
||||
gen.get() == 3
|
||||
</Original>
|
||||
<Expanded>
|
||||
3 == 3
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_FALSE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Original>
|
||||
!(gen.next())
|
||||
</Original>
|
||||
<Expanded>
|
||||
!false
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResults successes="4" failures="0" expectedFailures="0" skipped="false"/>
|
||||
</Section>
|
||||
<OverallResults successes="4" failures="0" expectedFailures="0" skipped="false"/>
|
||||
</Section>
|
||||
<Section name="Take generator" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Section name="Take less" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
@@ -22286,6 +22324,6 @@ Approx( -1.95996398454005449 )
|
||||
</Section>
|
||||
<OverallResult success="true" skips="0"/>
|
||||
</TestCase>
|
||||
<OverallResults successes="2101" failures="157" expectedFailures="41" skips="12"/>
|
||||
<OverallResults successes="2105" failures="157" expectedFailures="41" skips="12"/>
|
||||
<OverallResultsCases successes="317" failures="95" expectedFailures="17" skips="6"/>
|
||||
</Catch2TestRun>
|
||||
|
@@ -6916,6 +6916,44 @@ Approx( 1.30000000000000004 )
|
||||
</Section>
|
||||
<OverallResults successes="2" failures="0" expectedFailures="0" skipped="false"/>
|
||||
</Section>
|
||||
<Section name="Filter generator" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Section name="Out-of-line predicates are copied into the generator" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Original>
|
||||
gen.get() == 1
|
||||
</Original>
|
||||
<Expanded>
|
||||
1 == 1
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Original>
|
||||
gen.next()
|
||||
</Original>
|
||||
<Expanded>
|
||||
true
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Original>
|
||||
gen.get() == 3
|
||||
</Original>
|
||||
<Expanded>
|
||||
3 == 3
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_FALSE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Original>
|
||||
!(gen.next())
|
||||
</Original>
|
||||
<Expanded>
|
||||
!false
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResults successes="4" failures="0" expectedFailures="0" skipped="false"/>
|
||||
</Section>
|
||||
<OverallResults successes="4" failures="0" expectedFailures="0" skipped="false"/>
|
||||
</Section>
|
||||
<Section name="Take generator" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Section name="Take less" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
<Expression success="true" type="REQUIRE" filename="tests/<exe-name>/IntrospectiveTests/GeneratorsImpl.tests.cpp" >
|
||||
@@ -22285,6 +22323,6 @@ Approx( -1.95996398454005449 )
|
||||
</Section>
|
||||
<OverallResult success="true" skips="0"/>
|
||||
</TestCase>
|
||||
<OverallResults successes="2101" failures="157" expectedFailures="41" skips="12"/>
|
||||
<OverallResults successes="2105" failures="157" expectedFailures="41" skips="12"/>
|
||||
<OverallResultsCases successes="317" failures="95" expectedFailures="17" skips="6"/>
|
||||
</Catch2TestRun>
|
||||
|
@@ -85,6 +85,19 @@ TEST_CASE("Generators internals", "[generators][internals]") {
|
||||
filter([](int) { return false; }, values({ 1, 2, 3 })),
|
||||
Catch::GeneratorException);
|
||||
}
|
||||
|
||||
// Non-trivial usage
|
||||
SECTION("Out-of-line predicates are copied into the generator") {
|
||||
auto evilNumber = Catch::Detail::make_unique<int>(2);
|
||||
auto gen = [&]{
|
||||
const auto predicate = [&](int i) { return i != *evilNumber; };
|
||||
return filter(predicate, values({ 2, 1, 2, 3, 2, 2 }));
|
||||
}();
|
||||
REQUIRE(gen.get() == 1);
|
||||
REQUIRE(gen.next());
|
||||
REQUIRE(gen.get() == 3);
|
||||
REQUIRE_FALSE(gen.next());
|
||||
}
|
||||
}
|
||||
SECTION("Take generator") {
|
||||
SECTION("Take less") {
|
||||
|
@@ -7,6 +7,8 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/internal/catch_jsonwriter.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
|
||||
@@ -17,7 +19,6 @@ namespace {
|
||||
static std::ostream& operator<<( std::ostream& os, Custom const& ) {
|
||||
return os << "custom";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE( "JsonWriter", "[JSON][JsonWriter]" ) {
|
||||
|
||||
@@ -150,3 +151,28 @@ TEST_CASE( "JsonWriter escapes characters in strings properly", "[JsonWriter]" )
|
||||
REQUIRE( sstream.str() == "\"\\\\/\\t\\r\\n\"" );
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE( "JsonWriter benchmarks", "[JsonWriter][!benchmark]" ) {
|
||||
const auto input_length = GENERATE( as<size_t>{}, 10, 100, 10'000 );
|
||||
std::string test_input( input_length, 'a' );
|
||||
BENCHMARK_ADVANCED( "write string, no-escaping, len=" +
|
||||
std::to_string( input_length ) )(
|
||||
Catch::Benchmark::Chronometer meter ) {
|
||||
std::stringstream sstream;
|
||||
meter.measure( [&]( int ) {
|
||||
Catch::JsonValueWriter( sstream ).write( test_input );
|
||||
} );
|
||||
};
|
||||
|
||||
std::string escape_input( input_length, '\b' );
|
||||
BENCHMARK_ADVANCED( "write string, all-escaped, len=" +
|
||||
std::to_string( input_length ) )(
|
||||
Catch::Benchmark::Chronometer meter ) {
|
||||
std::stringstream sstream;
|
||||
meter.measure( [&]( int ) {
|
||||
Catch::JsonValueWriter( sstream ).write( escape_input );
|
||||
} );
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@@ -7,13 +7,16 @@
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/internal/catch_xmlwriter.hpp>
|
||||
|
||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <catch2/internal/catch_reusable_string_stream.hpp>
|
||||
#include <catch2/internal/catch_xmlwriter.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
|
||||
static std::string encode( std::string const& str, Catch::XmlEncode::ForWhat forWhat = Catch::XmlEncode::ForTextNodes ) {
|
||||
Catch::ReusableStringStream oss;
|
||||
oss << Catch::XmlEncode( str, forWhat );
|
||||
@@ -181,3 +184,20 @@ TEST_CASE("XmlWriter escapes attributes properly", "[XML][XmlWriter][approvals]"
|
||||
REQUIRE_THAT(stream.str(),
|
||||
ContainsSubstring(R"(some-attribute="Special chars need escaping: < > ' " &")"));
|
||||
}
|
||||
|
||||
TEST_CASE( "XmlWriter benchmarks", "[XML][XmlWriter][!benchmark]" ) {
|
||||
const auto input_length = GENERATE( as<size_t>{}, 10, 100, 10'000 );
|
||||
std::string test_input( input_length, 'a' );
|
||||
BENCHMARK_ADVANCED( "write string, no-escaping, len=" +
|
||||
std::to_string( input_length ) ) {
|
||||
return encode( test_input );
|
||||
};
|
||||
|
||||
std::string escape_input( input_length, '\b' );
|
||||
BENCHMARK_ADVANCED( "write string, all-escaped, len=" +
|
||||
std::to_string( input_length ) ) {
|
||||
return encode( escape_input );
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
Reference in New Issue
Block a user