Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMartin Hořeňovský2025-07-24 22:01:46 +0200
committerMartin Hořeňovský2025-07-24 22:01:46 +0200
commitfee81626d2a4811095c3a39d20fb355eeb954101 (patch)
tree47204f930a0669fa89bb5dcd06b88839abdf1918
parentb6c5a635bb67d31c6feda3e79c7139dba2f35b89 (diff)
downloadcatch3-3.9.0.tar.gz
v3.9.0v3.9.0
-rw-r--r--CMakeLists.txt2
-rw-r--r--docs/command-line.md2
-rw-r--r--docs/configuration.md4
-rw-r--r--docs/release-notes.md40
-rw-r--r--docs/reporters.md2
-rw-r--r--docs/thread-safety.md2
-rw-r--r--extras/catch_amalgamated.cpp347
-rw-r--r--extras/catch_amalgamated.hpp437
-rw-r--r--meson.build2
-rw-r--r--src/catch2/catch_version.cpp2
-rw-r--r--src/catch2/catch_version_macros.hpp4
11 files changed, 580 insertions, 264 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bdc6c30c..1676ee79 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -34,7 +34,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
endif()
project(Catch2
- VERSION 3.8.1 # CML version placeholder, don't delete
+ VERSION 3.9.0 # CML version placeholder, don't delete
LANGUAGES CXX
HOMEPAGE_URL "https://github.com/catchorg/Catch2"
DESCRIPTION "A modern, C++-native, unit test framework."
diff --git a/docs/command-line.md b/docs/command-line.md
index 6096802e..73b94f6c 100644
--- a/docs/command-line.md
+++ b/docs/command-line.md
@@ -413,7 +413,7 @@ Tests are sorted by their name, their tags are ignored.
### rand
Randomized order. The default order.
-> Randomized order has been made default in Catch2 X.Y.Z
+> Randomized order has been made default in Catch2 3.9.0
The order is dependent on Catch2's random seed (see
[`--rng-seed`](#rng-seed)), and is subset invariant. What this means
diff --git a/docs/configuration.md b/docs/configuration.md
index c54776ad..f63a6d41 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -268,7 +268,7 @@ namespace Catch {
## Disabling deprecation warnings
-> Introduced in Catch2 X.Y.Z
+> Introduced in Catch2 3.9.0
Catch2 has started using the C++ macro `[[deprecated]]` to mark things
that are deprecated and should not be used any more. If you need to
@@ -318,7 +318,7 @@ are not meant to be runnable, only "scannable".
## Experimental thread safety
-> Introduced in Catch2 X.Y.Z
+> Introduced in Catch2 3.9.0
Catch2 can optionally support thread-safe assertions, that means, multiple
user-spawned threads can use the assertion macros at the same time. Due
diff --git a/docs/release-notes.md b/docs/release-notes.md
index fd0559bd..b8b973c9 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -2,6 +2,7 @@
# Release notes
**Contents**<br>
+[3.9.0](#390)<br>
[3.8.1](#381)<br>
[3.8.0](#380)<br>
[3.7.1](#371)<br>
@@ -67,6 +68,45 @@
[Even Older versions](#even-older-versions)<br>
+## 3.9.0
+
+### Improvements
+* **Added experimental opt-in support for thread safe assertions**
+ * Read the documentation for full details
+* **The default test run order has been changed to random**
+* Passing assertions are significantly faster when the reporter does not ask for `assertionEnded` events on passing assertions.
+ * This is the default behaviour of e.g. Console or Compact reporter
+ * Simple `REQUIRE(true)` is 60% faster in Release and 80% faster in Debug build configuration
+ * Simple `REQUIRE_NOTHROW` is 230% faster in Release and 430% faster in Debug build configuration
+ * Simple `REQUIRE_THROWS` is ~3% faster in Release and 20% faster in Debug build configuration (throwing introduces enough overhead that the optimizations inside Catch2 are mostly irrelevant)
+* Small (2-5%) improvement if the reporter asks for `assertionEnded` events for passing assertions.
+* The exit code constants are part of the Session API. (#2955, #2976)
+* Suppressed unsigned integer overflow checking in locations with intended overflow (#2965)
+* Reporters flush output after writing metadata, e.g. rng seed (#2964)
+* Added unreachable after `FAIL` and `SKIP` macros (#2941)
+ * This allows the compiler to understand that the execution does not continue past the macro, and avoids warnings.
+* Added fast path for `assertionStarting` event when no reporter requires it
+ * For backwards compatibility, this fast path is opt-in
+ * A reporter can opt in by changing its `ReporterPreferences::shouldReportAllAssertionStarts`
+* Improved last seen source location tracking to be more precise
+ * This is used when reporting unexpected exceptions from tests
+
+### Fixes
+* Fixed formatting of tags with more than 100 tests in the default `--list-tags` output (#2963)
+* Fixed Clang-Tidy's `readability-static-accessed-through-instance` in tests
+* Fixed most of Clang-Tidy's `cppcoreguidelines-avoid-non-const-global-variables` (#2582)
+* The lifetime of scoped messages now strictly obeys their scope (#1759, #2019, #2959)
+ * Previously Catch2 would try to keep them around during unexpected exception, to provide helpful context.
+ * The amount of surprises the irregularities caused was not worth the occasional utility provided.
+* `TEMPLATE_TEST_CASE_SIG` can handle signatures consisting of only types (#2680, #2995)
+* Moved `catch_test_run_info.hpp` up from `internal/` subfolder into the main one (#2972)
+
+### Miscellaneous
+* pkg-config files are now generated at install time (#2979)
+ * This fixes missing debug suffix in library names
+ * This fixes install prefix mismatch between build config and actuall installation
+
+
## 3.8.1
### Fixes
diff --git a/docs/reporters.md b/docs/reporters.md
index c600130c..d42648cf 100644
--- a/docs/reporters.md
+++ b/docs/reporters.md
@@ -169,7 +169,7 @@ Currently there are three customization options:
`assertionStarting` events. Most reporters do not, and opting out
explicitly enables a fast-path in Catch2's handling of assertions.
-> `shouldReportAllAssertionStarts` was introduced in Catch2 X.Y.Z
+> `shouldReportAllAssertionStarts` was introduced in Catch2 3.9.0
### Per-reporter configuration
diff --git a/docs/thread-safety.md b/docs/thread-safety.md
index 218246e6..c3f610f0 100644
--- a/docs/thread-safety.md
+++ b/docs/thread-safety.md
@@ -8,7 +8,7 @@
[Fatal errors and multiple threads](#fatal-errors-and-multiple-threads)<br>
[Performance overhead](#performance-overhead)<br>
-> Thread safe assertions were introduced in Catch2 X.Y.Z
+> 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`),
diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp
index b979eb24..3a9cd757 100644
--- a/extras/catch_amalgamated.cpp
+++ b/extras/catch_amalgamated.cpp
@@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0
-// Catch v3.8.1
-// Generated: 2025-04-08 12:33:19.863332
+// Catch v3.9.0
+// Generated: 2025-07-24 22:00:25.173359
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
@@ -986,7 +986,7 @@ namespace Catch {
}
ScopedMessage::~ScopedMessage() {
- if ( !uncaught_exceptions() && !m_moved ){
+ if ( !m_moved ){
getResultCapture().popScopedMessage(m_info);
}
}
@@ -1056,11 +1056,9 @@ namespace Catch {
m_messages.back().message += " := ";
}
Capturer::~Capturer() {
- if ( !uncaught_exceptions() ){
- assert( m_captured == m_messages.size() );
- for( size_t i = 0; i < m_captured; ++i )
- m_resultCapture.popScopedMessage( m_messages[i] );
- }
+ assert( m_captured == m_messages.size() );
+ 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 ) {
@@ -1161,7 +1159,6 @@ namespace Catch {
-#include <algorithm>
#include <cassert>
#include <exception>
#include <iomanip>
@@ -1170,14 +1167,6 @@ namespace Catch {
namespace Catch {
namespace {
- static constexpr int TestFailureExitCode = 42;
- static constexpr int UnspecifiedErrorExitCode = 1;
- static constexpr int AllTestsSkippedExitCode = 4;
- static constexpr int NoTestsRunExitCode = 2;
- static constexpr int UnmatchedTestSpecExitCode = 3;
- static constexpr int InvalidTestSpecExitCode = 5;
-
-
IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) {
auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config));
CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\'');
@@ -1935,7 +1924,6 @@ namespace Catch {
-#include <cmath>
#include <iomanip>
namespace Catch {
@@ -2283,7 +2271,7 @@ namespace Catch {
}
Version const& libraryVersion() {
- static Version version( 3, 8, 1, "", 0 );
+ static Version version( 3, 9, 0, "", 0 );
return version;
}
@@ -4088,7 +4076,7 @@ namespace Catch {
// There is no 1-1 mapping between signals and windows exceptions.
// Windows can easily distinguish between SO and SigSegV,
// but SigInt, SigTerm, etc are handled differently.
- static SignalDefs signalDefs[] = {
+ static constexpr SignalDefs signalDefs[] = {
{ EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" },
{ EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" },
{ EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" },
@@ -4159,7 +4147,7 @@ namespace Catch {
const char* name;
};
- static SignalDefs signalDefs[] = {
+ static constexpr SignalDefs signalDefs[] = {
{ SIGINT, "SIGINT - Terminal interrupt signal" },
{ SIGILL, "SIGILL - Illegal instruction signal" },
{ SIGFPE, "SIGFPE - Floating point error signal" },
@@ -4323,8 +4311,6 @@ namespace Catch {
#include <cstdio>
#include <fstream>
-#include <sstream>
-#include <vector>
namespace Catch {
@@ -4765,7 +4751,7 @@ namespace Catch {
namespace Catch {
CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
- static LeakDetector leakDetector;
+ static const LeakDetector leakDetector;
CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
}
@@ -5216,6 +5202,12 @@ namespace Catch {
+#if defined( __clang__ )
+# define CATCH2_CLANG_NO_SANITIZE_INTEGER \
+ __attribute__( ( no_sanitize( "unsigned-integer-overflow" ) ) )
+#else
+# define CATCH2_CLANG_NO_SANITIZE_INTEGER
+#endif
namespace Catch {
namespace {
@@ -5225,6 +5217,7 @@ namespace {
#pragma warning(disable:4146) // we negate uint32 during the rotate
#endif
// Safe rotr implementation thanks to John Regehr
+ CATCH2_CLANG_NO_SANITIZE_INTEGER
uint32_t rotate_right(uint32_t val, uint32_t count) {
const uint32_t mask = 31;
count &= mask;
@@ -5258,6 +5251,7 @@ namespace {
}
}
+ CATCH2_CLANG_NO_SANITIZE_INTEGER
SimplePcg32::result_type SimplePcg32::operator()() {
// prepare the output value
const uint32_t xorshifted = static_cast<uint32_t>(((m_state >> 18u) ^ m_state) >> 27u);
@@ -5742,23 +5736,55 @@ namespace Catch {
} // namespace
}
+ namespace Detail {
+ // Assertions are owned by the thread that is executing them.
+ // This allows for lock-free progress in common cases where we
+ // do not need to send the assertion events to the reporter.
+ // This also implies that messages are owned by their respective
+ // threads, and should not be shared across different threads.
+ //
+ // For simplicity, we disallow messages in multi-threaded contexts,
+ // but in the future we can enable them under this logic.
+ //
+ // This implies that various pieces of metadata referring to last
+ // assertion result/source location/message handling, etc
+ // should also be thread local. For now we just use naked globals
+ // below, in the future we will want to allocate piece of memory
+ // from heap, to avoid consuming too much thread-local storage.
+
+ // 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));
+ }
+
RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter)
: m_runInfo(_config->name()),
m_config(_config),
m_reporter(CATCH_MOVE(reporter)),
- m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal },
m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ),
- m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions )
+ 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() )
{
getCurrentMutableContext().setResultCapture( this );
m_reporter->testRunStarting(m_runInfo);
}
RunContext::~RunContext() {
+ updateTotalsFromAtomics();
m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));
}
Totals RunContext::runTest(TestCaseHandle const& testCase) {
+ updateTotalsFromAtomics();
const Totals prevTotals = m_totals;
auto const& testInfo = testCase.getTestCaseInfo();
@@ -5813,6 +5839,7 @@ namespace Catch {
m_reporter->testCasePartialStarting(testInfo, testRuns);
+ updateTotalsFromAtomics();
const auto beforeRunTotals = m_totals;
runCurrentTest();
std::string oneRunCout = m_outputRedirect->getStdout();
@@ -5821,6 +5848,7 @@ namespace Catch {
redirectedCout += oneRunCout;
redirectedCerr += oneRunCerr;
+ updateTotalsFromAtomics();
const auto singleRunTotals = m_totals.delta(beforeRunTotals);
auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting());
m_reporter->testCasePartialEnded(statsForOneRun, testRuns);
@@ -5850,27 +5878,35 @@ namespace Catch {
void RunContext::assertionEnded(AssertionResult&& result) {
+ Detail::g_lastKnownLineInfo = result.m_info.lineInfo;
if (result.getResultType() == ResultWas::Ok) {
- m_totals.assertions.passed++;
- m_lastAssertionPassed = true;
+ m_atomicAssertionCount.passed++;
+ Detail::g_lastAssertionPassed = true;
} else if (result.getResultType() == ResultWas::ExplicitSkip) {
- m_totals.assertions.skipped++;
- m_lastAssertionPassed = true;
+ m_atomicAssertionCount.skipped++;
+ Detail::g_lastAssertionPassed = true;
} else if (!result.succeeded()) {
- m_lastAssertionPassed = false;
+ Detail::g_lastAssertionPassed = false;
if (result.isOk()) {
}
- else if( m_activeTestCase->getTestCaseInfo().okToFail() )
- m_totals.assertions.failedButOk++;
+ else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine
+ m_atomicAssertionCount.failedButOk++;
else
- m_totals.assertions.failed++;
+ m_atomicAssertionCount.failed++;
}
else {
- m_lastAssertionPassed = true;
+ Detail::g_lastAssertionPassed = true;
}
+ // 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 ) );
}
@@ -5882,15 +5918,13 @@ namespace Catch {
// populateReaction is run if it is needed
m_lastResult = CATCH_MOVE( result );
}
- void RunContext::resetAssertionInfo() {
- m_lastAssertionInfo.macroName = StringRef();
- m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr;
- m_lastAssertionInfo.resultDisposition = ResultDisposition::Normal;
- }
void RunContext::notifyAssertionStarted( AssertionInfo const& info ) {
- auto _ = scopedDeactivate( *m_outputRedirect );
- m_reporter->assertionStarting( info );
+ if (m_reportAssertionStarting) {
+ Detail::LockGuard lock( m_assertionMutex );
+ auto _ = scopedDeactivate( *m_outputRedirect );
+ m_reporter->assertionStarting( info );
+ }
}
bool RunContext::sectionStarted( StringRef sectionName,
@@ -5906,13 +5940,14 @@ namespace Catch {
m_activeSections.push_back(&sectionTracker);
SectionInfo sectionInfo( sectionLineInfo, static_cast<std::string>(sectionName) );
- m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;
+ Detail::g_lastKnownLineInfo = sectionLineInfo;
{
auto _ = scopedDeactivate( *m_outputRedirect );
m_reporter->sectionStarting( sectionInfo );
}
+ updateTotalsFromAtomics();
assertions = m_totals.assertions;
return true;
@@ -5920,12 +5955,11 @@ namespace Catch {
IGeneratorTracker*
RunContext::acquireGeneratorTracker( StringRef generatorName,
SourceLineInfo const& lineInfo ) {
- using namespace Generators;
- GeneratorTracker* tracker = GeneratorTracker::acquire(
+ auto* tracker = Generators::GeneratorTracker::acquire(
m_trackerContext,
TestCaseTracking::NameAndLocationRef(
generatorName, lineInfo ) );
- m_lastAssertionInfo.lineInfo = lineInfo;
+ Detail::g_lastKnownLineInfo = lineInfo;
return tracker;
}
@@ -5938,7 +5972,7 @@ namespace Catch {
auto& currentTracker = m_trackerContext.currentTracker();
assert(
currentTracker.nameAndLocation() != nameAndLoc &&
- "Trying to create tracker for a genreator that already has one" );
+ "Trying to create tracker for a generator that already has one" );
auto newTracker = Catch::Detail::make_unique<Generators::GeneratorTracker>(
CATCH_MOVE(nameAndLoc), m_trackerContext, &currentTracker );
@@ -5957,12 +5991,13 @@ namespace Catch {
return false;
if (m_trackerContext.currentTracker().hasChildren())
return false;
- m_totals.assertions.failed++;
+ m_atomicAssertionCount.failed++;
assertions.failed++;
return true;
}
void RunContext::sectionEnded(SectionEndInfo&& endInfo) {
+ updateTotalsFromAtomics();
Counts assertions = m_totals.assertions - endInfo.prevAssertions;
bool missingAssertions = testForMissingAssertions(assertions);
@@ -5979,9 +6014,6 @@ namespace Catch {
endInfo.durationInSeconds,
missingAssertions ) );
}
-
- m_messages.clear();
- m_messageScopes.clear();
}
void RunContext::sectionEndedEarly(SectionEndInfo&& endInfo) {
@@ -6016,8 +6048,18 @@ namespace Catch {
m_messages.push_back(message);
}
- void RunContext::popScopedMessage(MessageInfo const & message) {
- m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end());
+ void RunContext::popScopedMessage( MessageInfo const& message ) {
+ // Note: On average, it would probably be better to look for the message
+ // backwards. However, we do not expect to have to deal with more
+ // messages than low single digits, so the optimization is tiny,
+ // and we would have to hand-write the loop to avoid terrible
+ // codegen of reverse iterators in debug mode.
+ m_messages.erase(
+ std::find_if( m_messages.begin(),
+ m_messages.end(),
+ [id = message.sequence]( MessageInfo const& msg ) {
+ return msg.sequence == id;
+ } ) );
}
void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) {
@@ -6031,6 +6073,18 @@ namespace Catch {
}
const AssertionResult * RunContext::getLastResult() const {
+ // m_lastResult is updated inside the assertion slow-path, under
+ // a mutex, so the read needs to happen under mutex as well.
+
+ // TBD: The last result only makes sense if it is a thread-local
+ // thing, because the answer is different per thread, like
+ // last line info, whether last assertion passed, and so on.
+ //
+ // However, the last result was also never updated in the
+ // assertion fast path, so it was always somewhat broken,
+ // and since IResultCapture::getLastResult is deprecated,
+ // we will leave it as is, until it is finally removed.
+ Detail::LockGuard _( m_assertionMutex );
return &(*m_lastResult);
}
@@ -6039,28 +6093,44 @@ namespace Catch {
}
void RunContext::handleFatalErrorCondition( StringRef message ) {
- // TODO: scoped deactivate here? Just give up and do best effort?
- // the deactivation can break things further, OTOH so can the
- // capture
- auto _ = scopedDeactivate( *m_outputRedirect );
+ // We lock only when touching the reporters directly, to avoid
+ // deadlocks when we call into other functions that also want
+ // to lock the mutex before touching reporters.
+ //
+ // This does mean that we allow other threads to run while handling
+ // a fatal error, but this is all a best effort attempt anyway.
+ {
+ Detail::LockGuard lock( m_assertionMutex );
+ // TODO: scoped deactivate here? Just give up and do best effort?
+ // the deactivation can break things further, OTOH so can the
+ // capture
+ auto _ = scopedDeactivate( *m_outputRedirect );
- // First notify reporter that bad things happened
- m_reporter->fatalErrorEncountered( message );
+ // First notify reporter that bad things happened
+ m_reporter->fatalErrorEncountered( message );
+ }
// Don't rebuild the result -- the stringification itself can cause more fatal errors
// Instead, fake a result data.
AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } );
tempResult.message = static_cast<std::string>(message);
- AssertionResult result(m_lastAssertionInfo, CATCH_MOVE(tempResult));
+ AssertionResult result( makeDummyAssertionInfo(),
+ CATCH_MOVE( tempResult ) );
assertionEnded(CATCH_MOVE(result) );
- resetAssertionInfo();
+
+
+ // At this point we touch sections/test cases from this thread
+ // to try and end them. Technically that is not supported when
+ // using multiple threads, but the worst thing that can happen
+ // is that the process aborts harder :-D
+ Detail::LockGuard lock( m_assertionMutex );
// Best effort cleanup for sections that have not been destructed yet
// Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly
while (!m_activeSections.empty()) {
- auto nl = m_activeSections.back()->nameAndLocation();
- SectionEndInfo endInfo{ SectionInfo(CATCH_MOVE(nl.location), CATCH_MOVE(nl.name)), {}, 0.0 };
+ auto const& nl = m_activeSections.back()->nameAndLocation();
+ SectionEndInfo endInfo{ SectionInfo(nl.location, nl.name), {}, 0.0 };
sectionEndedEarly(CATCH_MOVE(endInfo));
}
handleUnfinishedSections();
@@ -6085,32 +6155,44 @@ namespace Catch {
std::string(),
false));
m_totals.testCases.failed++;
+ updateTotalsFromAtomics();
m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));
}
bool RunContext::lastAssertionPassed() {
- return m_lastAssertionPassed;
+ return Detail::g_lastAssertionPassed;
}
- void RunContext::assertionPassed() {
- m_lastAssertionPassed = true;
- ++m_totals.assertions.passed;
- resetAssertionInfo();
- m_messageScopes.clear();
+ void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) {
+ // We want to save the line info for better experience with unexpected assertions
+ Detail::g_lastKnownLineInfo = lineInfo;
+ ++m_atomicAssertionCount.passed;
+ Detail::g_lastAssertionPassed = true;
+ Detail::g_clearMessageScopes = true;
+ }
+
+ void RunContext::updateTotalsFromAtomics() {
+ m_totals.assertions = Counts{
+ m_atomicAssertionCount.passed,
+ m_atomicAssertionCount.failed,
+ m_atomicAssertionCount.failedButOk,
+ m_atomicAssertionCount.skipped,
+ };
}
bool RunContext::aborting() const {
- return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter());
+ return m_atomicAssertionCount.failed >= m_abortAfterXFailedAssertions;
}
void RunContext::runCurrentTest() {
auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);
m_reporter->sectionStarting(testCaseSection);
+ updateTotalsFromAtomics();
Counts prevAssertions = m_totals.assertions;
double duration = 0;
m_shouldReportUnexpected = true;
- m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal };
+ Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo;
Timer timer;
CATCH_TRY {
@@ -6127,18 +6209,23 @@ namespace Catch {
} CATCH_CATCH_ALL {
// Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions
// are reported without translation at the point of origin.
- if( m_shouldReportUnexpected ) {
+ if ( m_shouldReportUnexpected ) {
AssertionReaction dummyReaction;
- handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction );
+ handleUnexpectedInflightException( makeDummyAssertionInfo(),
+ translateActiveException(),
+ dummyReaction );
}
}
+ updateTotalsFromAtomics();
Counts assertions = m_totals.assertions - prevAssertions;
bool missingAssertions = testForMissingAssertions(assertions);
m_testCaseTracker->close();
handleUnfinishedSections();
- m_messages.clear();
m_messageScopes.clear();
+ // TBD: At this point, m_messages should be empty. Do we want to
+ // assert that this is true, or keep the defensive clear call?
+ m_messages.clear();
SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions);
m_reporter->sectionEnded(testCaseSectionStats);
@@ -6179,7 +6266,7 @@ namespace Catch {
if( result ) {
if (!m_includeSuccessfulResults) {
- assertionPassed();
+ assertionPassedFastPath(info.lineInfo);
}
else {
reportExpr(info, ResultWas::Ok, &expr, negated);
@@ -6187,9 +6274,9 @@ namespace Catch {
}
else {
reportExpr(info, ResultWas::ExpressionFailed, &expr, negated );
- populateReaction( reaction );
+ populateReaction(
+ reaction, info.resultDisposition & ResultDisposition::Normal );
}
- resetAssertionInfo();
}
void RunContext::reportExpr(
AssertionInfo const &info,
@@ -6197,7 +6284,7 @@ namespace Catch {
ITransientExpression const *expr,
bool negated ) {
- m_lastAssertionInfo = info;
+ Detail::g_lastKnownLineInfo = info.lineInfo;
AssertionResultData data( resultType, LazyExpression( negated ) );
AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
@@ -6212,24 +6299,25 @@ namespace Catch {
std::string&& message,
AssertionReaction& reaction
) {
- m_lastAssertionInfo = info;
+ Detail::g_lastKnownLineInfo = info.lineInfo;
AssertionResultData data( resultType, LazyExpression( false ) );
data.message = CATCH_MOVE( message );
- AssertionResult assertionResult{ m_lastAssertionInfo,
+ AssertionResult assertionResult{ info,
CATCH_MOVE( data ) };
const auto isOk = assertionResult.isOk();
assertionEnded( CATCH_MOVE(assertionResult) );
if ( !isOk ) {
- populateReaction( reaction );
+ populateReaction(
+ reaction, info.resultDisposition & ResultDisposition::Normal );
} else if ( resultType == ResultWas::ExplicitSkip ) {
// TODO: Need to handle this explicitly, as ExplicitSkip is
// considered "OK"
reaction.shouldSkip = true;
}
- resetAssertionInfo();
}
+
void RunContext::handleUnexpectedExceptionNotThrown(
AssertionInfo const& info,
AssertionReaction& reaction
@@ -6242,50 +6330,68 @@ namespace Catch {
std::string&& message,
AssertionReaction& reaction
) {
- m_lastAssertionInfo = info;
+ Detail::g_lastKnownLineInfo = info.lineInfo;
AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
data.message = CATCH_MOVE(message);
AssertionResult assertionResult{ info, CATCH_MOVE(data) };
assertionEnded( CATCH_MOVE(assertionResult) );
- populateReaction( reaction );
- resetAssertionInfo();
+ populateReaction( reaction,
+ info.resultDisposition & ResultDisposition::Normal );
}
- void RunContext::populateReaction( AssertionReaction& reaction ) {
- reaction.shouldDebugBreak = m_config->shouldDebugBreak();
- reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal);
+ void RunContext::populateReaction( AssertionReaction& reaction,
+ bool has_normal_disposition ) {
+ reaction.shouldDebugBreak = m_shouldDebugBreak;
+ reaction.shouldThrow = aborting() || has_normal_disposition;
+ }
+
+ AssertionInfo RunContext::makeDummyAssertionInfo() {
+ const bool testCaseJustStarted =
+ Detail::g_lastKnownLineInfo ==
+ m_activeTestCase->getTestCaseInfo().lineInfo;
+
+ return AssertionInfo{
+ testCaseJustStarted ? "TEST_CASE"_sr : StringRef(),
+ Detail::g_lastKnownLineInfo,
+ testCaseJustStarted ? StringRef() : "{Unknown expression after the reported line}"_sr,
+ ResultDisposition::Normal
+ };
}
void RunContext::handleIncomplete(
AssertionInfo const& info
) {
using namespace std::string_literals;
- m_lastAssertionInfo = info;
+ Detail::g_lastKnownLineInfo = info.lineInfo;
AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );
data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s;
AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
assertionEnded( CATCH_MOVE(assertionResult) );
- resetAssertionInfo();
}
+
void RunContext::handleNonExpr(
AssertionInfo const &info,
ResultWas::OfType resultType,
AssertionReaction &reaction
) {
- m_lastAssertionInfo = info;
-
AssertionResultData data( resultType, LazyExpression( false ) );
AssertionResult assertionResult{ info, CATCH_MOVE( data ) };
const auto isOk = assertionResult.isOk();
+ if ( isOk && !m_includeSuccessfulResults ) {
+ assertionPassedFastPath( info.lineInfo );
+ return;
+ }
+
assertionEnded( CATCH_MOVE(assertionResult) );
- if ( !isOk ) { populateReaction( reaction ); }
- resetAssertionInfo();
+ if ( !isOk ) {
+ populateReaction(
+ reaction, info.resultDisposition & ResultDisposition::Normal );
+ }
}
-
IResultCapture& getResultCapture() {
if (auto* capture = getCurrentContext().getResultCapture())
return *capture;
@@ -6516,7 +6622,7 @@ namespace Catch {
std::string origStr = CATCH_MOVE(str);
str.clear();
// There is at least one replacement, so