Compare commits

..

2 Commits

Author SHA1 Message Date
Martin Hořeňovský
b626e4c7ae Add simple runtime benchmarks
For now we add just two binaries, one with assertions taking the
fast path, one with assertions taking the slow path, and the ability
to run 1 of `REQUIRE(true)`, `REQUIRE_NOTHROW`, `REQUIRE_THROWS`
in a loop.

I also split off a CMake preset which enables more tests than the
basic `simple-tests` preset, but does not enable the most expensive
tests which force recompilation of Catch2 multiple times.
2025-09-23 17:17:33 +02:00
Martin Hořeňovský
434bf55d47 Make message push/pop static
Since the change to make the message macros thread-safe, and thus
thread-local, there is no need to handle messages through instance
of `RunContext`.
2025-09-23 11:46:26 +02:00
11 changed files with 122 additions and 112 deletions

View File

@@ -20,6 +20,7 @@ cmake_dependent_option(CATCH_BUILD_TESTING "Build the SelfTest project" ON "CATC
cmake_dependent_option(CATCH_BUILD_EXAMPLES "Build code examples" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_BUILD_EXTRA_TESTS "Build extra tests" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_BUILD_FUZZERS "Build fuzzers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_BUILD_BENCHMARKS "Build the benchmarks" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_ENABLE_COVERAGE "Generate coverage for codecov.io" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_ENABLE_WERROR "Enables Werror during build" ON "CATCH_DEVELOPMENT_BUILD" OFF)
cmake_dependent_option(CATCH_BUILD_SURROGATES "Enable generating and building surrogate TUs for the main headers" OFF "CATCH_DEVELOPMENT_BUILD" OFF)
@@ -77,6 +78,11 @@ set(SELF_TEST_DIR ${CATCH_DIR}/tests/SelfTest)
# We need to bring-in the variables defined there to this scope
add_subdirectory(src)
if (CATCH_BUILD_BENCHMARKS)
set(CMAKE_FOLDER "benchmarks")
add_subdirectory(benchmarks)
endif()
# Build tests only if requested
if(BUILD_TESTING AND CATCH_BUILD_TESTING AND NOT_SUBPROJECT)
find_package(Python3 REQUIRED COMPONENTS Interpreter)

View File

@@ -15,14 +15,23 @@
}
},
{
"name": "all-tests",
"name": "most-tests",
"inherits": "basic-tests",
"displayName": "Full development build",
"description": "Enables development build with examples and ALL tests",
"description": "Enables development build with extended set of tests (still relatively cheap to build)",
"cacheVariables": {
"CATCH_BUILD_EXAMPLES": "ON",
"CATCH_BUILD_EXTRA_TESTS": "ON",
"CATCH_BUILD_SURROGATES": "ON",
"CATCH_BUILD_BENCHMARKS": "ON"
}
},
{
"name": "all-tests",
"inherits": "most-tests",
"displayName": "Full development build",
"description": "Enables development build with examples and ALL tests",
"cacheVariables": {
"CATCH_ENABLE_CONFIGURE_TESTS": "ON",
"CATCH_ENABLE_CMAKE_HELPER_TESTS": "ON"
}

16
benchmarks/CMakeLists.txt Normal file
View File

@@ -0,0 +1,16 @@
include(CatchMiscFunctions)
add_executable(AssertionsFastPath
runtime_assertion_benches.cpp
)
add_executable(AssertionsSlowPath
runtime_assertion_benches.cpp
assertion_listener.cpp
)
target_link_libraries(AssertionsFastPath PRIVATE Catch2::Catch2WithMain)
target_link_libraries(AssertionsSlowPath PRIVATE Catch2::Catch2WithMain)
list(APPEND CATCH_TEST_TARGETS AssertionsFastPath AssertionsSlowPath)
set(CATCH_TEST_TARGETS ${CATCH_TEST_TARGETS} PARENT_SCOPE)

View File

@@ -0,0 +1,28 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/reporters/catch_reporter_event_listener.hpp>
#include <catch2/reporters/catch_reporter_registrars.hpp>
/**
* Event listener that listens to all assertions, forcing assertion slow path
*/
class AssertionSlowPathListener : public Catch::EventListenerBase {
public:
static std::string getDescription() {
return "Validates ordering of Catch2's listener events";
}
AssertionSlowPathListener(Catch::IConfig const* config) :
EventListenerBase(config) {
m_preferences.shouldReportAllAssertions = true;
m_preferences.shouldReportAllAssertionStarts = true;
}
};
CATCH_REGISTER_LISTENER( AssertionSlowPathListener )

View File

@@ -0,0 +1,27 @@
// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_macros.hpp>
TEST_CASE("Simple REQUIRE - 10M") {
for (size_t i = 0; i < 10'000'000; ++i) {
REQUIRE(true);
}
}
TEST_CASE("Simple NOTHROW - 10M") {
for (size_t i = 0; i < 10'000'000; ++i) {
REQUIRE_NOTHROW([](){}());
}
}
TEST_CASE("Simple THROWS - 10M") {
for (size_t i = 0; i < 10'000'000; ++i) {
REQUIRE_THROWS([]() { throw 1; }());
}
}

View File

@@ -22,7 +22,7 @@ namespace Catch {
m_messageId( builder.m_info.sequence ) {
MessageInfo info( CATCH_MOVE( builder.m_info ) );
info.message = builder.m_stream.str();
getResultCapture().pushScopedMessage( CATCH_MOVE(info) );
IResultCapture::pushScopedMessage( CATCH_MOVE( info ) );
}
ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept:
@@ -31,15 +31,14 @@ namespace Catch {
}
ScopedMessage::~ScopedMessage() {
if ( !m_moved ) { getResultCapture().popScopedMessage( m_messageId ); }
if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); }
}
Capturer::Capturer( StringRef macroName,
SourceLineInfo const& lineInfo,
ResultWas::OfType resultType,
StringRef names ):
m_resultCapture( getResultCapture() ) {
StringRef names ) {
auto trimmed = [&] (size_t start, size_t end) {
while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {
++start;
@@ -101,14 +100,14 @@ namespace Catch {
Capturer::~Capturer() {
assert( m_captured == m_messages.size() );
for (auto const& message : m_messages) {
m_resultCapture.popScopedMessage( message.sequence );
IResultCapture::popScopedMessage( message.sequence );
}
}
void Capturer::captureValue( size_t index, std::string const& value ) {
assert( index < m_messages.size() );
m_messages[index].message += value;
m_resultCapture.pushScopedMessage( CATCH_MOVE(m_messages[index]) );
IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) );
m_captured++;
}

View File

@@ -63,7 +63,6 @@ namespace Catch {
class Capturer {
std::vector<MessageInfo> m_messages;
IResultCapture& m_resultCapture;
size_t m_captured = 0;
public:
Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );
@@ -111,7 +110,7 @@ namespace Catch {
///////////////////////////////////////////////////////////////////////////////
#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \
Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
Catch::IResultCapture::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )
#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE)

View File

@@ -62,10 +62,9 @@ namespace Catch {
virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0;
virtual void benchmarkFailed( StringRef error ) = 0;
virtual void pushScopedMessage( MessageInfo&& message ) = 0;
virtual void popScopedMessage( unsigned int messageId ) = 0;
virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0;
static void pushScopedMessage( MessageInfo&& message );
static void popScopedMessage( unsigned int messageId );
static void emplaceUnscopedMessage( MessageBuilder&& builder );
virtual void handleFatalErrorCondition( StringRef message ) = 0;
@@ -102,6 +101,7 @@ namespace Catch {
};
IResultCapture& getResultCapture();
}
#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED

View File

@@ -483,28 +483,6 @@ namespace Catch {
m_reporter->benchmarkFailed( error );
}
void RunContext::pushScopedMessage( MessageInfo&& message ) {
Detail::g_messages.push_back( CATCH_MOVE(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.
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 ) {
Detail::g_messageScopes.emplace_back( CATCH_MOVE(builder) );
}
std::string RunContext::getCurrentTestName() const {
return m_activeTestCase
? m_activeTestCase->getTestCaseInfo().name
@@ -838,6 +816,28 @@ namespace Catch {
CATCH_INTERNAL_ERROR("No result capture instance");
}
void IResultCapture::pushScopedMessage( MessageInfo&& message ) {
Detail::g_messages.push_back( CATCH_MOVE( message ) );
}
void IResultCapture::popScopedMessage( unsigned int messageId ) {
// Note: On average, it would probably be better to look for the message
// backwards. However, we do not expect to have to deal with more
// messages than low single digits, so the optimization is tiny,
// and we would have to hand-write the loop to avoid terrible
// codegen of reverse iterators in debug mode.
Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(),
Detail::g_messages.end(),
[=]( MessageInfo const& msg ) {
return msg.sequence ==
messageId;
} ) );
}
void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) {
Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) );
}
void seedRng(IConfig const& config) {
sharedRng().seed(config.rngSeed());
}

View File

@@ -94,11 +94,6 @@ namespace Catch {
void benchmarkEnded( BenchmarkStats<> const& stats ) override;
void benchmarkFailed( StringRef error ) override;
void pushScopedMessage( MessageInfo&& message ) override;
void popScopedMessage( unsigned int messageId ) override;
void emplaceUnscopedMessage( MessageBuilder&& builder ) override;
std::string getCurrentTestName() const override;
const AssertionResult* getLastResult() const override;

View File

@@ -6,13 +6,11 @@
// SPDX-License-Identifier: BSL-1.0
#include <catch2/benchmark/catch_benchmark.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/internal/catch_enum_values_registry.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <chrono>
@@ -141,70 +139,3 @@ TEST_CASE( "Exception thrown inside stringify does not fail the test", "[toStrin
ThrowsOnStringification tos;
CHECK( tos == tos );
}
namespace {
[[maybe_unused]]
std::string convertIntoString( Catch::StringRef string,
bool escapeInvisibles ) {
std::string ret;
// This is enough for the "don't escape invisibles" case, and a good
// lower bound on the "escape invisibles" case.
ret.reserve( string.size() + 2 );
if ( !escapeInvisibles ) {
ret += '"';
ret += string;
ret += '"';
return ret;
}
size_t last_start = 0;
auto write_to = [&]( size_t idx ) {
if ( last_start < idx ) {
//ret.append( string.data() + last_start, idx - last_start );
ret += string.substr( last_start, idx - last_start );
}
last_start = idx + 1;
};
ret += '"';
for ( size_t i = 0; i < string.size(); ++i ) {
const char c = string[i];
if (c == '\r' || c == '\n' || c == '\t' || c == '\f') {
write_to( i );
if ( c == '\r' ) { ret.append( "\\r" ); }
if ( c == '\n' ) { ret.append( "\\n" ); }
if ( c == '\t' ) { ret.append( "\\t" ); }
if ( c == '\f' ) { ret.append( "\\f" ); }
}
}
write_to( string.size() );
ret += '"';
return ret;
}
}
TEST_CASE( "string escaping benchmark", "[toString][!benchmark]" ) {
const auto input_length = GENERATE( as<size_t>{}, 10, 100, 10'000, 100'000 );
std::string test_input( input_length, 'a' );
BENCHMARK( "no-escape string, no-escaping, len=" +
std::to_string( input_length ) ) {
return Catch::Detail::convertIntoString( test_input, false );
};
BENCHMARK( "no-escape string, escaping, len=" +
std::to_string( input_length ) ) {
return Catch::Detail::convertIntoString( test_input, true );
};
std::string escape_input( input_length, '\r' );
BENCHMARK( "full escape string, no-escaping, len=" +
std::to_string( input_length ) ) {
return Catch::Detail::convertIntoString( escape_input, false );
};
BENCHMARK( "full escape string, escaping, len=" +
std::to_string( input_length ) ) {
return Catch::Detail::convertIntoString( escape_input, true );
};
}