Let reporters opt into fast path in RunContext::assertionStarting

The fast path allows `RunContext` to skip disabling output redirect,
and notifying the reporters, turning `RunContext::notifyAssertionStarted`
into a no-op. This improves the overall performance of assertion handling
significantly, and also prepares ground for future changes around
assertion handling and thread safety.

For simple 10M assertion run, this improves the running time by ~30%
in Debug build and ~40% in Release build.

For backwards-compatibility reasons, the fast path is disabled
by default. However, none of the first party reporters use the
`assertionStarting` event, so all first party reporters are opted-in.
This commit is contained in:
Martin Hořeňovský 2025-07-17 13:13:08 +02:00
parent ae33e5b99a
commit c22096846c
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
15 changed files with 41 additions and 7 deletions

View File

@ -115,6 +115,11 @@ namespace Catch {
//! Catch2 should call `Reporter::assertionEnded` even for passing
//! assertions
bool shouldReportAllAssertions = false;
//! Catch2 should call `Reporter::assertionStarting` for all assertions
// Defaults to true for backwards compatibility, but none of our current
// reporters actually want this, and it enables a fast path in assertion
// handling.
bool shouldReportAllAssertionStarts = true;
};
/**

View File

@ -172,6 +172,7 @@ namespace Catch {
m_lastKnownLineInfo("DummyLocation", static_cast<size_t>(-1)),
m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ),
m_abortAfterXFailedAssertions( m_config->abortAfter() ),
m_reportAssertionStarting( m_reporter->getPreferences().shouldReportAllAssertionStarts ),
m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ),
m_shouldDebugBreak( m_config->shouldDebugBreak() )
{
@ -309,9 +310,11 @@ namespace Catch {
}
void RunContext::notifyAssertionStarted( AssertionInfo const& info ) {
if (m_reportAssertionStarting) {
auto _ = scopedDeactivate( *m_outputRedirect );
m_reporter->assertionStarting( info );
}
}
bool RunContext::sectionStarted( StringRef sectionName,
SourceLineInfo const& sectionLineInfo,

View File

@ -159,6 +159,9 @@ namespace Catch {
size_t m_abortAfterXFailedAssertions;
bool m_lastAssertionPassed = false;
bool m_shouldReportUnexpected = true;
// Caches whether `assertionStarting` events should be sent to the reporter.
bool m_reportAssertionStarting;
// Caches whether `assertionEnded` events for successful assertions should be sent to the reporter
bool m_includeSuccessfulResults;
// Caches m_config->shouldDebugBreak() to avoid vptr calls/allow inlining
bool m_shouldDebugBreak;

View File

@ -20,8 +20,10 @@ namespace Catch {
// GCC5 compat: we cannot use inherited constructor, because it
// doesn't implement backport of P0136
AutomakeReporter( ReporterConfig&& _config ):
StreamingReporterBase(CATCH_MOVE(_config))
{}
StreamingReporterBase( CATCH_MOVE( _config ) ) {
m_preferences.shouldReportAllAssertionStarts = false;
}
~AutomakeReporter() override;
static std::string getDescription() {

View File

@ -16,7 +16,10 @@ namespace Catch {
class CompactReporter final : public StreamingReporterBase {
public:
using StreamingReporterBase::StreamingReporterBase;
CompactReporter( ReporterConfig&& _config ):
StreamingReporterBase( CATCH_MOVE( _config ) ) {
m_preferences.shouldReportAllAssertionStarts = false;
}
~CompactReporter() override;

View File

@ -401,7 +401,10 @@ ConsoleReporter::ConsoleReporter(ReporterConfig&& config):
{ "est run time high mean high std dev", 14, Justification::Right }
};
}
}())) {}
}())) {
m_preferences.shouldReportAllAssertionStarts = false;
}
ConsoleReporter::~ConsoleReporter() = default;
std::string ConsoleReporter::getDescription() {

View File

@ -51,6 +51,8 @@ namespace Catch {
// not, but for machine-parseable reporters I think the answer
// should be yes.
m_preferences.shouldReportAllAssertions = true;
// We only handle assertions when they end
m_preferences.shouldReportAllAssertionStarts = false;
m_objectWriters.emplace( m_stream );
m_writers.emplace( Writer::Object );

View File

@ -89,6 +89,7 @@ namespace Catch {
{
m_preferences.shouldRedirectStdOut = true;
m_preferences.shouldReportAllAssertions = false;
m_preferences.shouldReportAllAssertionStarts = false;
m_shouldStoreSuccesfulAssertions = false;
}

View File

@ -19,6 +19,8 @@ namespace Catch {
reporterish.getPreferences().shouldRedirectStdOut;
m_preferences.shouldReportAllAssertions |=
reporterish.getPreferences().shouldReportAllAssertions;
m_preferences.shouldReportAllAssertionStarts |=
reporterish.getPreferences().shouldReportAllAssertionStarts;
}
void MultiReporter::addListener( IEventListenerPtr&& listener ) {

View File

@ -29,6 +29,11 @@ namespace Catch {
void updatePreferences(IEventListener const& reporterish);
public:
MultiReporter( IConfig const* config ):
IEventListener( config ) {
m_preferences.shouldReportAllAssertionStarts = false;
}
using IEventListener::IEventListener;
void addListener( IEventListenerPtr&& listener );

View File

@ -22,6 +22,7 @@ namespace Catch {
, xml(m_stream) {
m_preferences.shouldRedirectStdOut = true;
m_preferences.shouldReportAllAssertions = false;
m_preferences.shouldReportAllAssertionStarts = false;
m_shouldStoreSuccesfulAssertions = false;
}

View File

@ -18,6 +18,7 @@ namespace Catch {
TAPReporter( ReporterConfig&& config ):
StreamingReporterBase( CATCH_MOVE(config) ) {
m_preferences.shouldReportAllAssertions = true;
m_preferences.shouldReportAllAssertionStarts = false;
}
static std::string getDescription() {

View File

@ -26,6 +26,7 @@ namespace Catch {
: StreamingReporterBase( CATCH_MOVE(_config) )
{
m_preferences.shouldRedirectStdOut = true;
m_preferences.shouldReportAllAssertionStarts = false;
}
~TeamCityReporter() override;

View File

@ -30,6 +30,7 @@ namespace Catch {
{
m_preferences.shouldRedirectStdOut = true;
m_preferences.shouldReportAllAssertions = true;
m_preferences.shouldReportAllAssertionStarts = false;
}
XmlReporter::~XmlReporter() = default;

View File

@ -54,6 +54,7 @@ public:
ValidatingTestListener(Catch::IConfig const* config) :
EventListenerBase(config) {
m_preferences.shouldReportAllAssertions = true;
m_preferences.shouldReportAllAssertionStarts = true;
}
void testRunStarting( Catch::TestRunInfo const& ) override {