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**
+[3.9.0](#390)
[3.8.1](#381)
[3.8.0](#380)
[3.7.1](#371)
@@ -67,6 +68,45 @@ [Even Older versions](#even-older-versions)
+## 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)
[Performance overhead](#performance-overhead)
-> 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 #include #include #include @@ -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 #include 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 #include -#include -#include 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(((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(-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(§ionTracker); SectionInfo sectionInfo( sectionLineInfo, static_cast(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( CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker ); @@ -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(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(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,49 +6330,67 @@ 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(); - assertionEnded( CATCH_MOVE(assertionResult) ); - if ( !isOk ) { populateReaction( reaction ); } - resetAssertionInfo(); - } + if ( isOk && !m_includeSuccessfulResults ) { + assertionPassedFastPath( info.lineInfo ); + return; + } + assertionEnded( CATCH_MOVE(assertionResult) ); + if ( !isOk ) { + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); + } + } IResultCapture& getResultCapture() { if (auto* capture = getCurrentContext().getResultCapture()) @@ -6516,7 +6622,7 @@ namespace Catch { std::string origStr = CATCH_MOVE(str); str.clear(); // There is at least one replacement, so reserve with the best guess - // we can make without actually counting the number of occurences. + // we can make without actually counting the number of occurrences. str.reserve(origStr.size() - replaceThis.size() + withThis.size()); do { str.append(origStr, copyBegin, i-copyBegin ); @@ -6562,7 +6668,6 @@ namespace Catch { #include #include #include -#include namespace Catch { StringRef::StringRef( char const* rawChars ) noexcept @@ -8877,7 +8982,8 @@ private: << m_config->testSpec() << '\n'; } - m_stream << "RNG seed: " << getSeed() << '\n'; + m_stream << "RNG seed: " << getSeed() << '\n' + << std::flush; } void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { @@ -9298,7 +9404,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() { @@ -9313,8 +9422,6 @@ void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) { m_stream << "Invalid Filter: " << arg << '\n'; } -void ConsoleReporter::assertionStarting(AssertionInfo const&) {} - void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { AssertionResult const& result = _assertionStats.assertionResult; @@ -9426,7 +9533,8 @@ void ConsoleReporter::testRunStarting(TestRunInfo const& _testRunInfo) { m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: " << m_config->testSpec() << '\n'; } - m_stream << "Randomness seeded to: " << getSeed() << '\n'; + m_stream << "Randomness seeded to: " << getSeed() << '\n' + << std::flush; } void ConsoleReporter::lazyPrint() { @@ -9754,6 +9862,7 @@ namespace Catch { #include #include +#include #include #include #include @@ -9917,9 +10026,29 @@ namespace Catch { out << "All available tags:\n"; } + // minimum whitespace to pad tag counts, possibly overwritten below + size_t maxTagCountLen = 2; + + // determine necessary padding for tag count column + if ( ! tags.empty() ) { + const auto maxTagCount = + std::max_element( tags.begin(), + tags.end(), + []( auto const& lhs, auto const& rhs ) { + return lhs.count < rhs.count; + } ) + ->count; + + // more padding necessary for 3+ digits + if (maxTagCount >= 100) { + auto numDigits = 1 + std::floor( std::log10( maxTagCount ) ); + maxTagCountLen = static_cast( numDigits ); + } + } + for ( auto const& tagCount : tags ) { ReusableStringStream rss; - rss << " " << std::setw( 2 ) << tagCount.count << " "; + rss << " " << std::setw( maxTagCountLen ) << tagCount.count << " "; auto str = rss.str(); auto wrapper = TextFlow::Column( tagCount.all() ) .initialIndent( 0 ) @@ -10117,6 +10246,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 ); @@ -10346,7 +10477,6 @@ namespace Catch { endObject(); } - void JsonReporter::assertionStarting( AssertionInfo const& /*assertionInfo*/ ) {} void JsonReporter::assertionEnded( AssertionStats const& assertionStats ) { // TODO: There is lot of different things to handle here, but // we can fill it in later, after we show that the basic @@ -10513,6 +10643,7 @@ namespace Catch { { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; m_shouldStoreSuccesfulAssertions = false; } @@ -10743,6 +10874,8 @@ namespace Catch { reporterish.getPreferences().shouldRedirectStdOut; m_preferences.shouldReportAllAssertions |= reporterish.getPreferences().shouldReportAllAssertions; + m_preferences.shouldReportAllAssertionStarts |= + reporterish.getPreferences().shouldReportAllAssertionStarts; } void MultiReporter::addListener( IEventListenerPtr&& listener ) { @@ -11300,7 +11433,8 @@ namespace Catch { if ( m_config->testSpec().hasFilters() ) { m_stream << "# filters: " << m_config->testSpec() << '\n'; } - m_stream << "# rng-seed: " << m_config->rngSeed() << '\n'; + m_stream << "# rng-seed: " << m_config->rngSeed() << '\n' + << std::flush; } void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) { @@ -11514,6 +11648,7 @@ namespace Catch { { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; } XmlReporter::~XmlReporter() = default; @@ -11570,8 +11705,6 @@ namespace Catch { } } - void XmlReporter::assertionStarting( AssertionInfo const& ) { } - void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { AssertionResult const& result = assertionStats.assertionResult; diff --git a/extras/catch_amalgamated.hpp b/extras/catch_amalgamated.hpp index 7703e957..608a184b 100644 --- a/extras/catch_amalgamated.hpp +++ b/extras/catch_amalgamated.hpp @@ -6,8 +6,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.8.1 -// Generated: 2025-04-08 12:33:19.851017 +// Catch v3.9.0 +// Generated: 2025-07-24 22:00:24.654688 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -1100,9 +1100,7 @@ namespace Catch { AssertionReaction &reaction ) = 0; - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; // Deprecated, do not use: virtual std::string getCurrentTestName() const = 0; @@ -1129,6 +1127,7 @@ namespace Catch { //! Deriving classes become noncopyable and nonmovable class NonCopyable { + public: NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable&& ) = delete; NonCopyable& operator=( NonCopyable const& ) = delete; @@ -1144,7 +1143,6 @@ namespace Catch { #endif // CATCH_NONCOPYABLE_HPP_INCLUDED #include -#include #include #include @@ -1599,8 +1597,7 @@ namespace Catch { namespace Benchmark { namespace Detail { template - struct is_related - : std::is_same, std::decay_t> {}; + static constexpr bool is_related_v = std::is_same, std::decay_t>::value; /// We need to reinvent std::function because every piece of code that might add overhead /// in a measurement context needs to have consistent performance characteristics so that we @@ -1641,7 +1638,7 @@ namespace Catch { BenchmarkFunction(); template ::value, int> = 0> + std::enable_if_t, int> = 0> BenchmarkFunction(Fun&& fun) : f(new model>(CATCH_FORWARD(fun))) {} @@ -1767,8 +1764,6 @@ namespace Catch { #define CATCH_TIMING_HPP_INCLUDED -#include - namespace Catch { namespace Benchmark { template @@ -2167,7 +2162,7 @@ namespace Catch { } // sets lambda to be used in fun *and* executes benchmark! - template ::value, int> = 0> + template , int> = 0> Benchmark & operator=(Fun func) { auto const* cfg = getCurrentContext().getConfig(); if (!cfg->skipBenchmarks()) { @@ -2496,18 +2491,14 @@ namespace Catch { return rawMemoryToString( &object, sizeof(object) ); } - template - class IsStreamInsertable { - template - static auto test(int) - -> decltype(std::declval() << std::declval(), std::true_type()); + template + static constexpr bool IsStreamInsertable_v = false; - template - static auto test(...)->std::false_type; - - public: - static const bool value = decltype(test(0))::value; - }; + template + static constexpr bool IsStreamInsertable_v< + T, + decltype( void( std::declval() << std::declval() ) )> = + true; template std::string convertUnknownEnumToString( E e ); @@ -2552,7 +2543,7 @@ namespace Catch { struct StringMaker { template static - std::enable_if_t<::Catch::Detail::IsStreamInsertable::value, std::string> + std::enable_if_t<::Catch::Detail::IsStreamInsertable_v, std::string> convert(const Fake& value) { ReusableStringStream rss; // NB: call using the function-like syntax to avoid ambiguity with @@ -2563,7 +2554,7 @@ namespace Catch { template static - std::enable_if_t::value, std::string> + std::enable_if_t, std::string> convert( const Fake& value ) { #if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) return Detail::convertUnstreamable(value); @@ -2955,7 +2946,7 @@ namespace Catch { } template - struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>> { + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable_v>> { static std::string convert( R const& range ) { return rangeToString( range ); } @@ -3792,7 +3783,7 @@ namespace Catch { WarnAbout::What warnings = WarnAbout::Nothing; ShowDurations showDurations = ShowDurations::DefaultForReporter; double minDuration = -1; - TestRunOrder runOrder = TestRunOrder::Declared; + TestRunOrder runOrder = TestRunOrder::Randomized; ColourMode defaultColourMode = ColourMode::PlatformDefault; WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; @@ -3938,6 +3929,19 @@ namespace Catch { #define CATCH_MESSAGE_INFO_HPP_INCLUDED + +#ifndef CATCH_DEPRECATION_MACRO_HPP_INCLUDED +#define CATCH_DEPRECATION_MACRO_HPP_INCLUDED + + +#if !defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS ) +# define DEPRECATED( msg ) [[deprecated( msg )]] +#else +# define DEPRECATED( msg ) +#endif + +#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED + #include namespace Catch { @@ -3953,9 +3957,11 @@ namespace Catch { ResultWas::OfType type; unsigned int sequence; + DEPRECATED( "Explicitly use the 'sequence' member instead" ) bool operator == (MessageInfo const& other) const { return sequence == other.sequence; } + DEPRECATED( "Explicitly use the 'sequence' member instead" ) bool operator < (MessageInfo const& other) const { return sequence < other.sequence; } @@ -4237,15 +4243,13 @@ namespace Catch { }; template - struct is_unary_function : std::false_type {}; + static constexpr bool is_unary_function_v = false; template - struct is_unary_function< + static constexpr bool is_unary_function_v< F, - Catch::Detail::void_t()( fake_arg() ) ) - > - > : std::true_type {}; + Catch::Detail::void_t()( + fake_arg() ) )>> = true; // Traits for extracting arg and return type of lambdas (for single // argument lambdas) @@ -4678,14 +4682,14 @@ namespace Catch { template ::value>> + !Detail::is_unary_function_v>> ParserRefImpl( T& ref, StringRef hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} template ::value>> + Detail::is_unary_function_v>> ParserRefImpl( LambdaT const& ref, StringRef hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} @@ -4752,7 +4756,7 @@ namespace Catch { template ::value>> + Detail::is_unary_function_v>> Opt( LambdaT const& ref, StringRef hint ): ParserRefImpl( ref, hint ) {} @@ -4762,7 +4766,7 @@ namespace Catch { template ::value>> + !Detail::is_unary_function_v>> Opt( T& ref, StringRef hint ): ParserRefImpl( ref, hint ) {} @@ -4932,6 +4936,14 @@ namespace Catch { namespace Catch { + // TODO: Use C++17 `inline` variables + constexpr int UnspecifiedErrorExitCode = 1; + constexpr int NoTestsRunExitCode = 2; + constexpr int UnmatchedTestSpecExitCode = 3; + constexpr int AllTestsSkippedExitCode = 4; + constexpr int InvalidTestSpecExitCode = 5; + constexpr int TestFailureExitCode = 42; + class Session : Detail::NonCopyable { public: @@ -5016,7 +5028,7 @@ namespace Catch { #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + namespace{ const Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED @@ -5193,7 +5205,7 @@ namespace Detail { * when the compiler handles `ExprLhs == b`, it also tries to resolve * the overload set for `b == ExprLhs`. * - * To accomodate these use cases, decomposer ended up rather complex. + * To accommodate these use cases, decomposer ended up rather complex. * * 1) These types are handled by adding SFINAE overloads to our comparison * operators, checking whether `T == U` are comparable with the given @@ -5311,7 +5323,7 @@ namespace Catch { public: constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } constexpr auto getResult() const -> bool { return m_result; } - //! This function **has** to be overriden by the derived class. + //! This function **has** to be overridden by the derived class. virtual void streamReconstructedExpression( std::ostream& os ) const; constexpr ITransientExpression( bool isBinaryExpression, bool result ) @@ -5912,7 +5924,7 @@ namespace Catch { #else // These section definitions imply that at most one section at one level -// will be intered (because only one section's __LINE__ can be equal to +// will be entered (because only one section's __LINE__ can be equal to // the dummy `catchInternalSectionHint` variable from `TEST_CASE`). namespace Catch { @@ -6195,6 +6207,53 @@ static int catchInternalSectionHint = 0; #endif // CATCH_TEST_REGISTRY_HPP_INCLUDED +#ifndef CATCH_UNREACHABLE_HPP_INCLUDED +#define CATCH_UNREACHABLE_HPP_INCLUDED + +/**\file + * Polyfill `std::unreachable` + * + * We need something like `std::unreachable` to tell the compiler that + * some macros, e.g. `FAIL` or `SKIP`, do not continue execution in normal + * manner, and should handle it as such, e.g. not warn if there is no return + * from non-void function after a `FAIL` or `SKIP`. + */ + +#include + +#if defined( __cpp_lib_unreachable ) && __cpp_lib_unreachable > 202202L +# include +namespace Catch { + namespace Detail { + using Unreachable = std::unreachable; + } +} // namespace Catch + +#else // vv If we do not have std::unreachable, we implement something similar + +namespace Catch { + namespace Detail { + + [[noreturn]] + inline void Unreachable() noexcept { +# if defined( NDEBUG ) +# if defined( _MSC_VER ) && !defined( __clang__ ) + __assume( false ); +# elif defined( __GNUC__ ) + __builtin_unreachable(); +# endif +# endif // ^^ NDEBUG + std::terminate(); + } + + } // namespace Detail +} // end namespace Catch + +#endif + +#endif // CATCH_UNREACHABLE_HPP_INCLUDED + + // All of our user-facing macros support configuration toggle, that // forces them to be defined prefixed with CATCH_. We also like to // support another toggle that can minimize (disable) their implementation. @@ -6226,10 +6285,16 @@ static int catchInternalSectionHint = 0; #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL( ... ) do { \ + INTERNAL_CATCH_MSG("CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while ( false ) #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_SKIP( ... ) do { \ + INTERNAL_CATCH_MSG( "CATCH_SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) @@ -6327,10 +6392,16 @@ static int catchInternalSectionHint = 0; #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define FAIL( ... ) do { \ + INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define SKIP( ... ) do { \ + INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) @@ -6424,6 +6495,15 @@ static int catchInternalSectionHint = 0; #endif +namespace Catch { + namespace Detail { + template + struct priority_tag : priority_tag {}; + template <> + struct priority_tag<0> {}; + } +} + #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ #define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) @@ -6483,10 +6563,10 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper(Catch::Detail::priority_tag<1>{})) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper(Catch::Detail::priority_tag<1>{}))) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) #endif @@ -6509,11 +6589,11 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_TYPE_GEN\ template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template\ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TypeList { return {}; }\ template class...> struct TemplateTypeList{};\ template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TemplateTypeList { return {}; }\ template\ struct append;\ template\ @@ -6543,10 +6623,10 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_NTTP_1(signature, ...)\ template struct Nttp{};\ template\ - constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> Nttp<__VA_ARGS__> { return {}; } \ template class...> struct NttpTemplateTypeList{};\ template class...Cs>\ - constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList { return {}; } \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> NttpTemplateTypeList { return {}; } \ \ template< template class Container, template class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\ struct rewrap, List<__VA_ARGS__>> { using type = TypeList>; };\ @@ -6571,13 +6651,14 @@ static int catchInternalSectionHint = 0; template\ static void TestName() -#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\ - template\ - void reg_test(TypeList, Catch::NameAndTags nameAndTags)\ +#define INTERNAL_CATCH_TYPES_REGISTER(TestFunc)\ + template\ + void reg_test(TypeList, Catch::NameAndTags nameAndTags)\ {\ - Catch::AutoReg( Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ } +#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature, ...) #define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\ template\ void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\ @@ -6626,7 +6707,7 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__) #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__) @@ -6636,7 +6717,7 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)) #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)) @@ -6765,11 +6846,11 @@ static int catchInternalSectionHint = 0; constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */\ } \ }; \ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ - using TestInit = typename create()), TypeList>::type; \ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type; \ TestInit t; \ t.reg_tests(); \ return 0; \ @@ -6814,7 +6895,7 @@ static int catchInternalSectionHint = 0; (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\ } \ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ using TestInit = typename convert::type; \ TestInit t; \ t.reg_tests(); \ @@ -6850,7 +6931,7 @@ static int catchInternalSectionHint = 0; (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ TestNameClass();\ return 0;\ }();\ @@ -6897,11 +6978,11 @@ static int catchInternalSectionHint = 0; constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */ \ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ - using TestInit = typename create()), TypeList>::type;\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type;\ TestInit t;\ t.reg_tests();\ return 0;\ @@ -6949,7 +7030,7 @@ static int catchInternalSectionHint = 0; (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ using TestInit = typename convert::type;\ TestInit t;\ t.reg_tests();\ @@ -7201,6 +7282,22 @@ namespace Catch { #endif // CATCH_TEST_CASE_INFO_HPP_INCLUDED +#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED +#define CATCH_TEST_RUN_INFO_HPP_INCLUDED + + +namespace Catch { + + struct TestRunInfo { + constexpr TestRunInfo(StringRef _name) : name(_name) {} + StringRef name; + }; + +} // end namespace Catch + +#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED + + #ifndef CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED #define CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED @@ -7288,7 +7385,7 @@ namespace Catch { static std::string translatorName( signature ); \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + namespace{ const Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static std::string translatorName( signature ) @@ -7349,8 +7446,8 @@ namespace Catch { #define CATCH_VERSION_MACROS_HPP_INCLUDED #define CATCH_VERSION_MAJOR 3 -#define CATCH_VERSION_MINOR 8 -#define CATCH_VERSION_PATCH 1 +#define CATCH_VERSION_MINOR 9 +#define CATCH_VERSION_PATCH 0 #endif // CATCH_VERSION_MACROS_HPP_INCLUDED @@ -7391,7 +7488,7 @@ namespace Catch { m_msg(msg) {} - const char* what() const noexcept override final; + const char* what() const noexcept final; }; } // end namespace Catch @@ -8778,26 +8875,9 @@ auto from_range(Container const& cnt) { #define CATCH_INTERFACES_REPORTER_HPP_INCLUDED - -#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED -#define CATCH_TEST_RUN_INFO_HPP_INCLUDED - - -namespace Catch { - - struct TestRunInfo { - constexpr TestRunInfo(StringRef _name) : name(_name) {} - StringRef name; - }; - -} // end namespace Catch - -#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED - #include #include #include -#include namespace Catch { @@ -8893,6 +8973,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; }; /** @@ -9472,7 +9557,7 @@ namespace Catch { #define CATCH_ENFORCE_HPP_INCLUDED -#include +#include // for `std::exception` in no-exception configuration namespace Catch { #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) @@ -9568,7 +9653,6 @@ namespace Catch { #define CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED -#include #include namespace Catch { @@ -9768,8 +9852,8 @@ namespace Detail { #ifndef CATCH_IS_PERMUTATION_HPP_INCLUDED #define CATCH_IS_PERMUTATION_HPP_INCLUDED -#include #include +#include namespace Catch { namespace Detail { @@ -9906,8 +9990,6 @@ namespace Catch { #include -#include -#include #include namespace Catch { @@ -10491,6 +10573,48 @@ using TestCaseTracking::SectionTracker; #endif // CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#ifndef CATCH_THREAD_SUPPORT_HPP_INCLUDED +#define CATCH_THREAD_SUPPORT_HPP_INCLUDED + + +#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS ) +# include +# include +#endif + + +namespace Catch { + namespace Detail { +#if defined( CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS ) + using Mutex = std::mutex; + using LockGuard = std::lock_guard; + struct AtomicCounts { + std::atomic passed = 0; + std::atomic failed = 0; + std::atomic failedButOk = 0; + std::atomic skipped = 0; + }; +#else // ^^ Use actual mutex, lock and atomics + // vv Dummy implementations for single-thread performance + + struct Mutex { + void lock() {} + void unlock() {} + }; + + struct LockGuard { + LockGuard( Mutex ) {} + }; + + using AtomicCounts = Counts; +#endif + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_THREAD_SUPPORT_HPP_INCLUDED + #include namespace Catch { @@ -10578,18 +10702,18 @@ namespace Catch { bool lastAssertionPassed() override; - void assertionPassed() override; - public: // !TBD We need to do this another way! bool aborting() const; private: + void assertionPassedFastPath( SourceLineInfo lineInfo ); + // Update the non-thread-safe m_totals from the atomic assertion counts. + void updateTotalsFromAtomics(); void runCurrentTest(); void invokeActiveTestCase(); - void resetAssertionInfo(); bool testForMissingAssertions( Counts& assertions ); void assertionEnded( AssertionResult&& result ); @@ -10599,31 +10723,42 @@ namespace Catch { ITransientExpression const *expr, bool negated ); - void populateReaction( AssertionReaction& reaction ); + void populateReaction( AssertionReaction& reaction, bool has_normal_disposition ); + + // Creates dummy info for unexpected exceptions/fatal errors, + // where we do not have the access to one, but we still need + // to send one to the reporters. + AssertionInfo makeDummyAssertionInfo(); private: void handleUnfinishedSections(); - + mutable Detail::Mutex m_assertionMutex; TestRunInfo m_runInfo; TestCaseHandle const* m_activeTestCase = nullptr; ITracker* m_testCaseTracker = nullptr; Optional m_lastResult; - IConfig const* m_config; Totals m_totals; + Detail::AtomicCounts m_atomicAssertionCount; IEventListenerPtr m_reporter; std::vector m_messages; - std::vector m_messageScopes; /* Keeps owners of so-called unscoped messages. */ - AssertionInfo m_lastAssertionInfo; + // Owners for the UNSCOPED_X information macro + std::vector m_messageScopes; std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; Detail::unique_ptr m_outputRedirect; FatalConditionHandler m_fatalConditionhandler; - bool m_lastAssertionPassed = false; + // Caches m_config->abortAfter() to avoid vptr calls/allow inlining + size_t m_abortAfterXFailedAssertions; 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; }; void seedRng(IConfig const& config); @@ -10637,7 +10772,6 @@ namespace Catch { #define CATCH_SHARDING_HPP_INCLUDED #include -#include #include namespace Catch { @@ -11860,19 +11994,19 @@ namespace Matchers { } template - using is_generic_matcher = std::is_base_of< + static constexpr bool is_generic_matcher_v = std::is_base_of< Catch::Matchers::MatcherGenericBase, std::remove_cv_t> - >; + >::value; template - using are_generic_matchers = Catch::Detail::conjunction...>; + static constexpr bool are_generic_matchers_v = Catch::Detail::conjunction>...>::value; template - using is_matcher = std::is_base_of< + static constexpr bool is_matcher_v = std::is_base_of< Catch::Matchers::MatcherUntypedBase, std::remove_cv_t> - >; + >::value; template @@ -11945,7 +12079,7 @@ namespace Matchers { //! Avoids type nesting for `GenericAllOf && some matcher` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAllOfGeneric> operator && ( MatchAllOfGeneric&& lhs, MatcherRHS const& rhs) { @@ -11954,7 +12088,7 @@ namespace Matchers { //! Avoids type nesting for `some matcher && GenericAllOf` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAllOfGeneric> operator && ( MatcherLHS const& lhs, MatchAllOfGeneric&& rhs) { @@ -11999,7 +12133,7 @@ namespace Matchers { //! Avoids type nesting for `GenericAnyOf || some matcher` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAnyOfGeneric> operator || ( MatchAnyOfGeneric&& lhs, MatcherRHS const& rhs) { @@ -12008,7 +12142,7 @@ namespace Matchers { //! Avoids type nesting for `some matcher || GenericAnyOf` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAnyOfGeneric> operator || ( MatcherLHS const& lhs, MatchAnyOfGeneric&& rhs) { @@ -12048,20 +12182,20 @@ namespace Matchers { // compose only generic matchers template - std::enable_if_t::value, Detail::MatchAllOfGeneric> + std::enable_if_t, Detail::MatchAllOfGeneric> operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric> + std::enable_if_t, Detail::MatchAnyOfGeneric> operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } //! Wrap provided generic matcher in generic negator template - std::enable_if_t::value, Detail::MatchNotOfGeneric> + std::enable_if_t, Detail::MatchNotOfGeneric> operator ! (MatcherT const& matcher) { return Detail::MatchNotOfGeneric{matcher}; } @@ -12069,25 +12203,25 @@ namespace Matchers { // compose mixed generic and non-generic matchers template - std::enable_if_t::value, Detail::MatchAllOfGeneric>> + std::enable_if_t, Detail::MatchAllOfGeneric>> operator && (MatcherLHS const& lhs, MatcherBase const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAllOfGeneric, MatcherRHS>> + std::enable_if_t, Detail::MatchAllOfGeneric, MatcherRHS>> operator && (MatcherBase const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric>> + std::enable_if_t, Detail::MatchAnyOfGeneric>> operator || (MatcherLHS const& lhs, MatcherBase const& rhs) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric, MatcherRHS>> + std::enable_if_t, Detail::MatchAnyOfGeneric, MatcherRHS>> operator || (MatcherBase const& lhs, MatcherRHS const& rhs) { return { lhs, rhs }; } @@ -12164,7 +12298,7 @@ namespace Catch { //! Creates a matcher that accepts ranges/containers with specific size HasSizeMatcher SizeIs(std::size_t sz); template - std::enable_if_t::value, + std::enable_if_t, SizeMatchesMatcher> SizeIs(Matcher&& m) { return SizeMatchesMatcher{CATCH_FORWARD(m)}; } @@ -12179,8 +12313,8 @@ namespace Catch { #define CATCH_MATCHERS_CONTAINS_HPP_INCLUDED -#include #include +#include namespace Catch { namespace Matchers { @@ -12242,14 +12376,14 @@ namespace Catch { * Uses `std::equal_to` to do the comparison */ template - std::enable_if_t::value, + std::enable_if_t, ContainsElementMatcher>> Contains(T&& elem) { return { CATCH_FORWARD(elem), std::equal_to<>{} }; } //! Creates a matcher that checks whether a range contains element matching a matcher template - std::enable_if_t::value, + std::enable_if_t, ContainsMatcherMatcher> Contains(Matcher&& matcher) { return { CATCH_FORWARD(matcher) }; } @@ -12626,8 +12760,7 @@ namespace Catch { #define CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED -#include -#include +#include namespace Catch { namespace Matchers { @@ -13211,9 +13344,11 @@ namespace Catch { public: // GCC5 compat: we cannot use inherited constructor, because it // doesn't implement backport of P0136 - AutomakeReporter(ReporterConfig&& _config): - StreamingReporterBase(CATCH_MOVE(_config)) - {} + AutomakeReporter( ReporterConfig&& _config ): + StreamingReporterBase( CATCH_MOVE( _config ) ) { + m_preferences.shouldReportAllAssertionStarts = false; + } + ~AutomakeReporter() override; static std::string getDescription() { @@ -13240,7 +13375,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; @@ -13282,8 +13420,6 @@ namespace Catch { void noMatchingTestCases( StringRef unmatchedSpec ) override; void reportInvalidTestSpec( StringRef arg ) override; - void assertionStarting(AssertionInfo const&) override; - void assertionEnded(AssertionStats const& _assertionStats) override; void sectionStarting(SectionInfo const& _sectionInfo) override; @@ -13637,7 +13773,6 @@ namespace Catch { void sectionStarting( SectionInfo const& sectionInfo ) override; void sectionEnded( SectionStats const& sectionStats ) override; - void assertionStarting( AssertionInfo const& assertionInfo ) override; void assertionEnded( AssertionStats const& assertionStats ) override; //void testRunEndedCumulative() override; @@ -13764,6 +13899,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 ); @@ -13899,22 +14039,24 @@ namespace Catch { #if !defined(CATCH_CONFIG_DISABLE) -# define CATCH_REGISTER_REPORTER( name, reporterType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ReporterRegistrar INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_RegistrarFor )( name ); \ - } \ +# define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ReporterRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \ + name ); \ + } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -# define CATCH_REGISTER_LISTENER( listenerType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ListenerRegistrar INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_RegistrarFor )( #listenerType##_catch_sr ); \ - } \ +# define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ListenerRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \ + #listenerType##_catch_sr ); \ + } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #else // CATCH_CONFIG_DISABLE @@ -13941,6 +14083,7 @@ namespace Catch { , xml(m_stream) { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; m_shouldStoreSuccesfulAssertions = false; } @@ -13989,6 +14132,7 @@ namespace Catch { TAPReporter( ReporterConfig&& config ): StreamingReporterBase( CATCH_MOVE(config) ) { m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; } static std::string getDescription() { @@ -14032,6 +14176,7 @@ namespace Catch { : StreamingReporterBase( CATCH_MOVE(_config) ) { m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertionStarts = false; } ~TeamCityReporter() override; @@ -14099,8 +14244,6 @@ namespace Catch { void sectionStarting(SectionInfo const& sectionInfo) override; - void assertionStarting(AssertionInfo const&) override; - void assertionEnded(AssertionStats const& assertionStats) override; void sectionEnded(SectionStats const& sectionStats) override; diff --git a/meson.build b/meson.build index 6346d44f..021266d8 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,7 @@ project( 'catch2', 'cpp', - version: '3.8.1', # CML version placeholder, don't delete + version: '3.9.0', # CML version placeholder, don't delete license: 'BSL-1.0', meson_version: '>=0.54.1', ) diff --git a/src/catch2/catch_version.cpp b/src/catch2/catch_version.cpp index 2c40ccc3..a012101e 100644 --- a/src/catch2/catch_version.cpp +++ b/src/catch2/catch_version.cpp @@ -36,7 +36,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 3, 8, 1, "", 0 ); + static Version version( 3, 9, 0, "", 0 ); return version; } diff --git a/src/catch2/catch_version_macros.hpp b/src/catch2/catch_version_macros.hpp index 42655fae..a795e4ba 100644 --- a/src/catch2/catch_version_macros.hpp +++ b/src/catch2/catch_version_macros.hpp @@ -9,7 +9,7 @@ #define CATCH_VERSION_MACROS_HPP_INCLUDED #define CATCH_VERSION_MAJOR 3 -#define CATCH_VERSION_MINOR 8 -#define CATCH_VERSION_PATCH 1 +#define CATCH_VERSION_MINOR 9 +#define CATCH_VERSION_PATCH 0 #endif // CATCH_VERSION_MACROS_HPP_INCLUDED