mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 12:17:11 +01:00 
			
		
		
		
	v3.11.0
This commit is contained in:
		| @@ -35,7 +35,7 @@ if(CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) | ||||
| endif() | ||||
|  | ||||
| project(Catch2 | ||||
|   VERSION 3.10.0 # CML version placeholder, don't delete | ||||
|   VERSION 3.11.0 # CML version placeholder, don't delete | ||||
|   LANGUAGES CXX | ||||
|   HOMEPAGE_URL "https://github.com/catchorg/Catch2" | ||||
|   DESCRIPTION "A modern, C++-native, unit test framework." | ||||
|   | ||||
| @@ -73,7 +73,7 @@ test execution. Specifically it understands | ||||
|  | ||||
| > Support for `TESTBRIDGE_TEST_ONLY` and sharding was introduced in Catch2 3.2.0 | ||||
|  | ||||
| > Support for `TEST_PREMATURE_EXIT_FILE` and `TEST_RANDOM_SEED` was introduced in Catch2 X.Y.Z | ||||
| > Support for `TEST_PREMATURE_EXIT_FILE` and `TEST_RANDOM_SEED` was introduced in Catch2 3.11.0 | ||||
|  | ||||
| This integration is enabled via either a [compile time configuration | ||||
| option](configuration.md#bazel-support), or via `BAZEL_TEST` environment | ||||
|   | ||||
| @@ -653,7 +653,7 @@ Verbosity defaults to _normal_. | ||||
| ## Create file to guard against silent early termination | ||||
| <pre>--premature-exit-guard-file <path></pre> | ||||
|  | ||||
| > Introduced in Catch2 X.Y.Z | ||||
| > Introduced in Catch2 3.11.0 | ||||
|  | ||||
| Tells Catch2 to create an empty file at specified path before the tests | ||||
| start, and delete it after the tests finish. If the file is present after | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| # Release notes | ||||
| **Contents**<br> | ||||
| [3.11.0](#3110)<br> | ||||
| [3.10.0](#3100)<br> | ||||
| [3.9.1](#391)<br> | ||||
| [3.9.0](#390)<br> | ||||
| @@ -70,6 +71,28 @@ | ||||
| [Even Older versions](#even-older-versions)<br> | ||||
|  | ||||
|  | ||||
| ## 3.11.0 | ||||
|  | ||||
| ### Fixes | ||||
| * Fixed building on non-desktop GDK platforms (#3029) | ||||
| * Fixed message macros being susceptible to race in specific scenario (#3031) | ||||
| * Catch2's SEH filter will call the previously installed filter after reporting the error (#3033) | ||||
|  | ||||
| ### Improvements | ||||
| * Handling of scoped messages (e.g. `CAPTURE`) is a bit faster. | ||||
| * Better out-of-the-box support for QNX (#2953) | ||||
| * Improved performance of assertions by up-to 10% | ||||
|   * Release mode assertion fast-path sees the biggest improvement. | ||||
| * Faster processing of non-escaped strings in `--invisibles` mode. | ||||
| * Added support for Bazel's `TEST_RANDOM_SEED` env var (#3021) | ||||
| * Added support for Bazel's `TEST_PREMATURE_EXIT_FILE` env var (#3020) | ||||
|   * This creates a file that is deleted if the tests exit normally, but stays around if the process dies unexpectedly. | ||||
|   * This functionality is also exposed through CLI as `--premature-exit-guard-file` | ||||
|  | ||||
| ### Miscellaneous | ||||
| * **[Tuple.app](https://tuple.app/catch2) has sponsored Catch2** | ||||
|  | ||||
|  | ||||
| ## 3.10.0 | ||||
|  | ||||
| ### Fixes | ||||
|   | ||||
| @@ -6,8 +6,8 @@ | ||||
|  | ||||
| // SPDX-License-Identifier: BSL-1.0 | ||||
|  | ||||
| //  Catch v3.10.0 | ||||
| //  Generated: 2025-08-24 16:18:04.775778 | ||||
| //  Catch v3.11.0 | ||||
| //  Generated: 2025-09-30 10:49:12.549018 | ||||
| //  ---------------------------------------------------------- | ||||
| //  This file is an amalgamation of multiple different files. | ||||
| //  You probably shouldn't edit it directly. | ||||
| @@ -825,6 +825,8 @@ namespace Catch { | ||||
|             m_data.reporterSpecifications.push_back( std::move( *parsed ) ); | ||||
|         } | ||||
|  | ||||
|         // Reading bazel env vars can change some parts of the config data, | ||||
|         // so we have to process the bazel env before acting on the config. | ||||
|         if ( enableBazelEnvSupport() ) { | ||||
|             readBazelEnvVars(); | ||||
|         } | ||||
| @@ -889,6 +891,8 @@ namespace Catch { | ||||
|  | ||||
|     bool Config::showHelp() const { return m_data.showHelp; } | ||||
|  | ||||
|     std::string const& Config::getExitGuardFilePath() const { return m_data.prematureExitGuardFilePath; } | ||||
|  | ||||
|     // IConfig interface | ||||
|     bool Config::allowThrows() const                   { return !m_data.noThrow; } | ||||
|     StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } | ||||
| @@ -950,6 +954,26 @@ namespace Catch { | ||||
|                 m_data.shardCount = bazelShardOptions->shardCount; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const auto bazelExitGuardFile = Detail::getEnv( "TEST_PREMATURE_EXIT_FILE" ); | ||||
|         if (bazelExitGuardFile) { | ||||
|             m_data.prematureExitGuardFilePath = bazelExitGuardFile; | ||||
|         } | ||||
|  | ||||
|         const auto bazelRandomSeed = Detail::getEnv( "TEST_RANDOM_SEED" ); | ||||
|         if ( bazelRandomSeed ) { | ||||
|             auto parsedSeed = parseUInt( bazelRandomSeed, 0 ); | ||||
|             if ( !parsedSeed ) { | ||||
|                 // Currently we handle issues with parsing other Bazel Env | ||||
|                 // options by warning and ignoring the issue. So we do the | ||||
|                 // same for random seed option. | ||||
|                 Catch::cerr() | ||||
|                     << "Warning: could not parse 'TEST_RANDOM_SEED' ('" | ||||
|                     << bazelRandomSeed << "') as proper seed.\n"; | ||||
|             } else { | ||||
|                 m_data.rngSeed = *parsedSeed; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } // end namespace Catch | ||||
| @@ -975,28 +999,26 @@ namespace Catch { | ||||
|  | ||||
|  | ||||
|     ScopedMessage::ScopedMessage( MessageBuilder&& builder ): | ||||
|         m_info( CATCH_MOVE(builder.m_info) ) { | ||||
|         m_info.message = builder.m_stream.str(); | ||||
|         getResultCapture().pushScopedMessage( m_info ); | ||||
|         m_messageId( builder.m_info.sequence ) { | ||||
|         MessageInfo info( CATCH_MOVE( builder.m_info ) ); | ||||
|         info.message = builder.m_stream.str(); | ||||
|         IResultCapture::pushScopedMessage( CATCH_MOVE( info ) ); | ||||
|     } | ||||
|  | ||||
|     ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: | ||||
|         m_info( CATCH_MOVE( old.m_info ) ) { | ||||
|         m_messageId( old.m_messageId ) { | ||||
|         old.m_moved = true; | ||||
|     } | ||||
|  | ||||
|     ScopedMessage::~ScopedMessage() { | ||||
|         if ( !m_moved ){ | ||||
|             getResultCapture().popScopedMessage(m_info); | ||||
|         } | ||||
|         if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     Capturer::Capturer( StringRef macroName, | ||||
|                         SourceLineInfo const& lineInfo, | ||||
|                         ResultWas::OfType resultType, | ||||
|                         StringRef names ): | ||||
|         m_resultCapture( getResultCapture() ) { | ||||
|                         StringRef names ) { | ||||
|         auto trimmed = [&] (size_t start, size_t end) { | ||||
|             while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) { | ||||
|                 ++start; | ||||
| @@ -1042,8 +1064,8 @@ namespace Catch { | ||||
|             case ',': | ||||
|                 if (start != pos && openings.empty()) { | ||||
|                     m_messages.emplace_back(macroName, lineInfo, resultType); | ||||
|                     m_messages.back().message = static_cast<std::string>(trimmed(start, pos)); | ||||
|                     m_messages.back().message += " := "; | ||||
|                     m_messages.back().message += trimmed(start, pos); | ||||
|                     m_messages.back().message += " := "_sr; | ||||
|                     start = pos; | ||||
|                 } | ||||
|                 break; | ||||
| @@ -1052,20 +1074,20 @@ namespace Catch { | ||||
|         } | ||||
|         assert(openings.empty() && "Mismatched openings"); | ||||
|         m_messages.emplace_back(macroName, lineInfo, resultType); | ||||
|         m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1)); | ||||
|         m_messages.back().message += " := "; | ||||
|         m_messages.back().message += trimmed(start, names.size() - 1); | ||||
|         m_messages.back().message += " := "_sr; | ||||
|     } | ||||
|     Capturer::~Capturer() { | ||||
|         assert( m_captured == m_messages.size() ); | ||||
|         for ( size_t i = 0; i < m_captured; ++i ) { | ||||
|             m_resultCapture.popScopedMessage( m_messages[i] ); | ||||
|         for (auto const& message : m_messages) { | ||||
|             IResultCapture::popScopedMessage( message.sequence ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void Capturer::captureValue( size_t index, std::string const& value ) { | ||||
|         assert( index < m_messages.size() ); | ||||
|         m_messages[index].message += value; | ||||
|         m_resultCapture.pushScopedMessage( m_messages[index] ); | ||||
|         IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) ); | ||||
|         m_captured++; | ||||
|     } | ||||
|  | ||||
| @@ -1149,7 +1171,6 @@ namespace Catch { | ||||
|     } | ||||
|     void cleanUp() { | ||||
|         cleanupSingletons(); | ||||
|         cleanUpContext(); | ||||
|     } | ||||
|     std::string translateActiveException() { | ||||
|         return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); | ||||
| @@ -1161,6 +1182,8 @@ namespace Catch { | ||||
|  | ||||
|  | ||||
| #include <cassert> | ||||
| #include <cstdio> | ||||
| #include <cstdlib> | ||||
| #include <exception> | ||||
| #include <iomanip> | ||||
| #include <set> | ||||
| @@ -1275,6 +1298,50 @@ namespace Catch { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Creates empty file at path. The path must be writable, we do not | ||||
|         // try to create directories in path because that's hard in C++14. | ||||
|         void setUpGuardFile( std::string const& guardFilePath ) { | ||||
|             if ( !guardFilePath.empty() ) { | ||||
| #if defined( _MSC_VER ) | ||||
|                 std::FILE* file = nullptr; | ||||
|                 if ( fopen_s( &file, guardFilePath.c_str(), "w" ) ) { | ||||
|                     char msgBuffer[100]; | ||||
|                     const auto err = errno; | ||||
|                     std::string errMsg; | ||||
|                     if ( !strerror_s( msgBuffer, err ) ) { | ||||
|                         errMsg = msgBuffer; | ||||
|                     } else { | ||||
|                         errMsg = "Could not translate errno to a string"; | ||||
|                     } | ||||
|  | ||||
| #else | ||||
|                 std::FILE* file = std::fopen( guardFilePath.c_str(), "w" ); | ||||
|                 if ( !file ) { | ||||
|                     const auto err = errno; | ||||
|                     const char* errMsg = std::strerror( err ); | ||||
| #endif | ||||
|  | ||||
|                     CATCH_RUNTIME_ERROR( "Could not open the exit guard file '" | ||||
|                                          << guardFilePath << "' because '" | ||||
|                                          << errMsg << "' (" << err << ')' ); | ||||
|                 } | ||||
|                 const int ret = std::fclose( file ); | ||||
|                 CATCH_ENFORCE( | ||||
|                     ret == 0, | ||||
|                     "Error when closing the exit guard file: " << ret ); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Removes file at path. Assuming we created it in setUpGuardFile. | ||||
|         void tearDownGuardFile( std::string const& guardFilePath ) { | ||||
|             if ( !guardFilePath.empty() ) { | ||||
|                 const int ret = std::remove( guardFilePath.c_str() ); | ||||
|                 CATCH_ENFORCE( | ||||
|                     ret == 0, | ||||
|                     "Error when removing the exit guard file: " << ret ); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } // anon namespace | ||||
|  | ||||
|     Session::Session() { | ||||
| @@ -1393,6 +1460,7 @@ namespace Catch { | ||||
|             static_cast<void>(std::getchar()); | ||||
|         } | ||||
|         int exitCode = runInternal(); | ||||
|  | ||||
|         if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { | ||||
|             Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush; | ||||
|             static_cast<void>(std::getchar()); | ||||
| @@ -1433,6 +1501,10 @@ namespace Catch { | ||||
|         CATCH_TRY { | ||||
|             config(); // Force config to be constructed | ||||
|  | ||||
|             // We need to retrieve potential Bazel config with the full Config | ||||
|             // constructor, so we have to create the guard file after it is created. | ||||
|             setUpGuardFile( m_config->getExitGuardFilePath() ); | ||||
|  | ||||
|             seedRng( *m_config ); | ||||
|  | ||||
|             if (m_configData.filenamesAsTags) { | ||||
| @@ -1462,9 +1534,12 @@ namespace Catch { | ||||
|             TestGroup tests { CATCH_MOVE(reporter), m_config.get() }; | ||||
|             auto const totals = tests.execute(); | ||||
|  | ||||
|             // If we got here, running the tests finished normally-enough. | ||||
|             // They might've failed, but that would've been reported elsewhere. | ||||
|             tearDownGuardFile( m_config->getExitGuardFilePath() ); | ||||
|  | ||||
|             if ( tests.hadUnmatchedTestSpecs() | ||||
|                 && m_config->warnAboutUnmatchedTestSpecs() ) { | ||||
|                 // UnmatchedTestSpecExitCode | ||||
|                 return UnmatchedTestSpecExitCode; | ||||
|             } | ||||
|  | ||||
| @@ -1974,35 +2049,35 @@ namespace Detail { | ||||
|         std::string ret; | ||||
|         // This is enough for the "don't escape invisibles" case, and a good | ||||
|         // lower bound on the "escape invisibles" case. | ||||
|         ret.reserve(string.size() + 2); | ||||
|         ret.reserve( string.size() + 2 ); | ||||
|  | ||||
|         if (!escapeInvisibles) { | ||||
|         if ( !escapeInvisibles ) { | ||||
|             ret += '"'; | ||||
|             ret += string; | ||||
|             ret += '"'; | ||||
|             return ret; | ||||
|         } | ||||
|  | ||||
|         size_t last_start = 0; | ||||
|         auto write_to = [&]( size_t idx ) { | ||||
|             if ( last_start < idx ) { | ||||
|                 ret += string.substr( last_start, idx - last_start ); | ||||
|             } | ||||
|             last_start = idx + 1; | ||||
|         }; | ||||
|  | ||||
|         ret += '"'; | ||||
|         for (char c : string) { | ||||
|             switch (c) { | ||||
|             case '\r': | ||||
|                 ret.append("\\r"); | ||||
|                 break; | ||||
|             case '\n': | ||||
|                 ret.append("\\n"); | ||||
|                 break; | ||||
|             case '\t': | ||||
|                 ret.append("\\t"); | ||||
|                 break; | ||||
|             case '\f': | ||||
|                 ret.append("\\f"); | ||||
|                 break; | ||||
|             default: | ||||
|                 ret.push_back(c); | ||||
|                 break; | ||||
|         for ( size_t i = 0; i < string.size(); ++i ) { | ||||
|             const char c = string[i]; | ||||
|             if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) { | ||||
|                 write_to( i ); | ||||
|                 if ( c == '\r' ) { ret.append( "\\r" ); } | ||||
|                 if ( c == '\n' ) { ret.append( "\\n" ); } | ||||
|                 if ( c == '\t' ) { ret.append( "\\t" ); } | ||||
|                 if ( c == '\f' ) { ret.append( "\\f" ); } | ||||
|             } | ||||
|         } | ||||
|         write_to( string.size() ); | ||||
|         ret += '"'; | ||||
|  | ||||
|         return ret; | ||||
| @@ -2279,7 +2354,7 @@ namespace Catch { | ||||
|     } | ||||
|  | ||||
|     Version const& libraryVersion() { | ||||
|         static Version version( 3, 10, 0, "", 0 ); | ||||
|         static Version version( 3, 11, 0, "", 0 ); | ||||
|         return version; | ||||
|     } | ||||
|  | ||||
| @@ -2367,8 +2442,14 @@ namespace Catch { | ||||
|  | ||||
|  | ||||
| namespace Catch { | ||||
|     namespace Detail { | ||||
|         void missingCaptureInstance() { | ||||
|             CATCH_INTERNAL_ERROR( "No result capture instance" ); | ||||
|         } | ||||
|     } // namespace Detail | ||||
|  | ||||
|     IResultCapture::~IResultCapture() = default; | ||||
| } | ||||
| } // namespace Catch | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -3361,6 +3442,9 @@ namespace Catch { | ||||
|             | Opt( config.allowZeroTests ) | ||||
|                 ["--allow-running-no-tests"] | ||||
|                 ( "Treat 'No tests run' as a success" ) | ||||
|             | Opt( config.prematureExitGuardFilePath, "path" ) | ||||
|                 ["--premature-exit-guard-file"] | ||||
|                 ( "create a file before running tests and delete it during clean exit" ) | ||||
|             | Arg( config.testsOrTags, "test name|pattern|tags" ) | ||||
|                 ( "which test or tests to use" ); | ||||
|  | ||||
| @@ -3515,7 +3599,11 @@ namespace { | ||||
| #endif // Windows/ ANSI/ None | ||||
|  | ||||
|  | ||||
| #if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) || defined( __GLIBC__ ) || defined(__FreeBSD__) | ||||
| #if defined( CATCH_PLATFORM_LINUX ) \ | ||||
|  || defined( CATCH_PLATFORM_MAC ) \ | ||||
|  || defined( __GLIBC__ ) \ | ||||
|  || defined( __FreeBSD__ ) \ | ||||
|  || defined( CATCH_PLATFORM_QNX ) | ||||
| #    define CATCH_INTERNAL_HAS_ISATTY | ||||
| #    include <unistd.h> | ||||
| #endif | ||||
| @@ -3639,20 +3727,10 @@ namespace Catch { | ||||
|  | ||||
| namespace Catch { | ||||
|  | ||||
|     Context* Context::currentContext = nullptr; | ||||
|  | ||||
|     void cleanUpContext() { | ||||
|         delete Context::currentContext; | ||||
|         Context::currentContext = nullptr; | ||||
|     } | ||||
|     void Context::createContext() { | ||||
|         currentContext = new Context(); | ||||
|     } | ||||
|     Context Context::currentContext; | ||||
|  | ||||
|     Context& getCurrentMutableContext() { | ||||
|         if ( !Context::currentContext ) { Context::createContext(); } | ||||
|         // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) | ||||
|         return *Context::currentContext; | ||||
|         return Context::currentContext; | ||||
|     } | ||||
|  | ||||
|     SimplePcg32& sharedRng() { | ||||
| @@ -3757,7 +3835,7 @@ namespace Catch { | ||||
|         #endif | ||||
|     } // namespace Catch | ||||
|  | ||||
| #elif defined(CATCH_PLATFORM_LINUX) | ||||
| #elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX) | ||||
|     #include <fstream> | ||||
|     #include <string> | ||||
|  | ||||
| @@ -4091,23 +4169,27 @@ namespace Catch { | ||||
|         { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, | ||||
|     }; | ||||
|  | ||||
|     // Since we do not support multiple instantiations, we put these | ||||
|     // into global variables and rely on cleaning them up in outlined | ||||
|     // constructors/destructors | ||||
|     static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; | ||||
|  | ||||
|  | ||||
|     static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { | ||||
|         for (auto const& def : signalDefs) { | ||||
|             if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { | ||||
|                 reportFatal(def.name); | ||||
|             } | ||||
|         } | ||||
|         // If its not an exception we care about, pass it along. | ||||
|         // If a filter was previously registered, invoke it | ||||
|         if (previousTopLevelExceptionFilter) { | ||||
|             return previousTopLevelExceptionFilter(ExceptionInfo); | ||||
|         } | ||||
|         // Otherwise, pass along all exceptions. | ||||
|         // This stops us from eating debugger breaks etc. | ||||
|         return EXCEPTION_CONTINUE_SEARCH; | ||||
|     } | ||||
|  | ||||
|     // Since we do not support multiple instantiations, we put these | ||||
|     // into global variables and rely on cleaning them up in outlined | ||||
|     // constructors/destructors | ||||
|     static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; | ||||
|  | ||||
|  | ||||
|     // For MSVC, we reserve part of the stack memory for handling | ||||
|     // memory overflow structured exception. | ||||
|     FatalConditionHandler::FatalConditionHandler() { | ||||
| @@ -5565,6 +5647,7 @@ ReporterSpec::ReporterSpec( | ||||
|  | ||||
| #include <cstdio> | ||||
| #include <sstream> | ||||
| #include <tuple> | ||||
| #include <vector> | ||||
|  | ||||
| namespace Catch { | ||||
| @@ -5576,16 +5659,16 @@ namespace Catch { | ||||
|         std::ostringstream m_referenceStream; // Used for copy state/ flags from | ||||
|         Detail::Mutex m_mutex; | ||||
|  | ||||
|         auto add() -> std::size_t { | ||||
|         auto add() -> std::pair<std::size_t, std::ostringstream*> { | ||||
|             Detail::LockGuard _( m_mutex ); | ||||
|             if( m_unused.empty() ) { | ||||
|                 m_streams.push_back( Detail::make_unique<std::ostringstream>() ); | ||||
|                 return m_streams.size()-1; | ||||
|                 return { m_streams.size()-1, m_streams.back().get() }; | ||||
|             } | ||||
|             else { | ||||
|                 auto index = m_unused.back(); | ||||
|                 m_unused.pop_back(); | ||||
|                 return index; | ||||
|                 return { index, m_streams[index].get() }; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -5599,10 +5682,10 @@ namespace Catch { | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     ReusableStringStream::ReusableStringStream() | ||||
|     :   m_index( Singleton<StringStreams>::getMutable().add() ), | ||||
|         m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() ) | ||||
|     {} | ||||
|     ReusableStringStream::ReusableStringStream() { | ||||
|         std::tie( m_index, m_oss ) = | ||||
|             Singleton<StringStreams>::getMutable().add(); | ||||
|     } | ||||
|  | ||||
|     ReusableStringStream::~ReusableStringStream() { | ||||
|         static_cast<std::ostringstream*>( m_oss )->str(""); | ||||
| @@ -6085,28 +6168,6 @@ namespace Catch { | ||||
|         m_reporter->benchmarkFailed( error ); | ||||
|     } | ||||
|  | ||||
|     void RunContext::pushScopedMessage( MessageInfo const& message ) { | ||||
|         Detail::g_messages.push_back( message ); | ||||
|     } | ||||
|  | ||||
|     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. | ||||
|         Detail::g_messages.erase( | ||||
|             std::find_if( Detail::g_messages.begin(), | ||||
|                           Detail::g_messages.end(), | ||||
|                           [id = message.sequence]( MessageInfo const& msg ) { | ||||
|                               return msg.sequence == id; | ||||
|                           } ) ); | ||||
|     } | ||||
|  | ||||
|     void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) { | ||||
|         Detail::g_messageScopes.emplace_back( CATCH_MOVE(builder) ); | ||||
|     } | ||||
|  | ||||
|     std::string RunContext::getCurrentTestName() const { | ||||
|         return m_activeTestCase | ||||
|             ? m_activeTestCase->getTestCaseInfo().name | ||||
| @@ -6433,11 +6494,26 @@ namespace Catch { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     IResultCapture& getResultCapture() { | ||||
|         if (auto* capture = getCurrentContext().getResultCapture()) | ||||
|             return *capture; | ||||
|         else | ||||
|             CATCH_INTERNAL_ERROR("No result capture instance"); | ||||
|     void IResultCapture::pushScopedMessage( MessageInfo&& message ) { | ||||
|         Detail::g_messages.push_back( CATCH_MOVE( message ) ); | ||||
|     } | ||||
|  | ||||
|     void IResultCapture::popScopedMessage( unsigned int messageId ) { | ||||
|         // Note: On average, it would probably be better to look for the message | ||||
|         //       backwards. However, we do not expect to have to deal with more | ||||
|         //       messages than low single digits, so the optimization is tiny, | ||||
|         //       and we would have to hand-write the loop to avoid terrible | ||||
|         //       codegen of reverse iterators in debug mode. | ||||
|         Detail::g_messages.erase( std::find_if( Detail::g_messages.begin(), | ||||
|                                                 Detail::g_messages.end(), | ||||
|                                                 [=]( MessageInfo const& msg ) { | ||||
|                                                     return msg.sequence == | ||||
|                                                            messageId; | ||||
|                                                 } ) ); | ||||
|     } | ||||
|  | ||||
|     void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) { | ||||
|         Detail::g_messageScopes.emplace_back( CATCH_MOVE( builder ) ); | ||||
|     } | ||||
|  | ||||
|     void seedRng(IConfig const& config) { | ||||
|   | ||||
| @@ -6,8 +6,8 @@ | ||||
|  | ||||
| // SPDX-License-Identifier: BSL-1.0 | ||||
|  | ||||
| //  Catch v3.10.0 | ||||
| //  Generated: 2025-08-24 16:18:04.055916 | ||||
| //  Catch v3.11.0 | ||||
| //  Generated: 2025-09-30 10:49:11.225746 | ||||
| //  ---------------------------------------------------------- | ||||
| //  This file is an amalgamation of multiple different files. | ||||
| //  You probably shouldn't edit it directly. | ||||
| @@ -101,6 +101,9 @@ | ||||
| #elif defined(linux) || defined(__linux) || defined(__linux__) | ||||
| #  define CATCH_PLATFORM_LINUX | ||||
|  | ||||
| #elif defined(__QNX__) | ||||
| #  define CATCH_PLATFORM_QNX | ||||
|  | ||||
| #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) | ||||
| #  define CATCH_PLATFORM_WINDOWS | ||||
|  | ||||
| @@ -308,13 +311,17 @@ | ||||
| #    endif | ||||
|  | ||||
| // Universal Windows platform does not support SEH | ||||
| // Or console colours (or console at all...) | ||||
| #  if defined(CATCH_PLATFORM_WINDOWS_UWP) | ||||
| #    define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 | ||||
| #  else | ||||
| #  if !defined(CATCH_PLATFORM_WINDOWS_UWP) | ||||
| #    define CATCH_INTERNAL_CONFIG_WINDOWS_SEH | ||||
| #  endif | ||||
|  | ||||
| // Only some Windows platform families support the console | ||||
| #  if defined(WINAPI_FAMILY_PARTITION) | ||||
| #    if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) | ||||
| #      define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 | ||||
| #    endif | ||||
| #  endif | ||||
|  | ||||
| // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ | ||||
| // _MSVC_TRADITIONAL == 0 means new conformant preprocessor | ||||
| // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor | ||||
| @@ -564,11 +571,9 @@ namespace Catch { | ||||
|         IConfig const* m_config = nullptr; | ||||
|         IResultCapture* m_resultCapture = nullptr; | ||||
|  | ||||
|         CATCH_EXPORT static Context* currentContext; | ||||
|         CATCH_EXPORT static Context currentContext; | ||||
|         friend Context& getCurrentMutableContext(); | ||||
|         friend Context const& getCurrentContext(); | ||||
|         static void createContext(); | ||||
|         friend void cleanUpContext(); | ||||
|  | ||||
|     public: | ||||
|         constexpr IResultCapture* getResultCapture() const { | ||||
| @@ -579,21 +584,14 @@ namespace Catch { | ||||
|             m_resultCapture = resultCapture; | ||||
|         } | ||||
|         constexpr void setConfig( IConfig const* config ) { m_config = config; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     Context& getCurrentMutableContext(); | ||||
|  | ||||
|     inline Context const& getCurrentContext() { | ||||
|         // We duplicate the logic from `getCurrentMutableContext` here, | ||||
|         // to avoid paying the call overhead in debug mode. | ||||
|         if ( !Context::currentContext ) { Context::createContext(); } | ||||
|         // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) | ||||
|         return *Context::currentContext; | ||||
|         return Context::currentContext; | ||||
|     } | ||||
|  | ||||
|     void cleanUpContext(); | ||||
|  | ||||
|     class SimplePcg32; | ||||
|     SimplePcg32& sharedRng(); | ||||
| } | ||||
| @@ -1069,10 +1067,9 @@ namespace Catch { | ||||
|         virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; | ||||
|         virtual void benchmarkFailed( StringRef error ) = 0; | ||||
|  | ||||
|         virtual void pushScopedMessage( MessageInfo const& message ) = 0; | ||||
|         virtual void popScopedMessage( MessageInfo const& message ) = 0; | ||||
|  | ||||
|         virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0; | ||||
|         static void pushScopedMessage( MessageInfo&& message ); | ||||
|         static void popScopedMessage( unsigned int messageId ); | ||||
|         static void emplaceUnscopedMessage( MessageBuilder&& builder ); | ||||
|  | ||||
|         virtual void handleFatalErrorCondition( StringRef message ) = 0; | ||||
|  | ||||
| @@ -1108,7 +1105,18 @@ namespace Catch { | ||||
|         virtual void exceptionEarlyReported() = 0; | ||||
|     }; | ||||
|  | ||||
|     IResultCapture& getResultCapture(); | ||||
|     namespace Detail { | ||||
|         [[noreturn]] | ||||
|         void missingCaptureInstance(); | ||||
|     } | ||||
|     inline IResultCapture& getResultCapture() { | ||||
|         if (auto* capture = getCurrentContext().getResultCapture()) { | ||||
|             return *capture; | ||||
|         } else { | ||||
|             Detail::missingCaptureInstance(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED | ||||
| @@ -3800,6 +3808,8 @@ namespace Catch { | ||||
|  | ||||
|         std::vector<std::string> testsOrTags; | ||||
|         std::vector<std::string> sectionsToRun; | ||||
|  | ||||
|         std::string prematureExitGuardFilePath; | ||||
|     }; | ||||
|  | ||||
|  | ||||
| @@ -3827,6 +3837,8 @@ namespace Catch { | ||||
|  | ||||
|         bool showHelp() const; | ||||
|  | ||||
|         std::string const& getExitGuardFilePath() const; | ||||
|  | ||||
|         // IConfig interface | ||||
|         bool allowThrows() const override; | ||||
|         StringRef name() const override; | ||||
| @@ -3961,6 +3973,7 @@ namespace Catch { | ||||
|         std::string message; | ||||
|         SourceLineInfo lineInfo; | ||||
|         ResultWas::OfType type; | ||||
|         // The "ID" of the message, used to know when to remove it from reporter context. | ||||
|         unsigned int sequence; | ||||
|  | ||||
|         DEPRECATED( "Explicitly use the 'sequence' member instead" ) | ||||
| @@ -4020,13 +4033,12 @@ namespace Catch { | ||||
|         ScopedMessage( ScopedMessage&& old ) noexcept; | ||||
|         ~ScopedMessage(); | ||||
|  | ||||
|         MessageInfo m_info; | ||||
|         unsigned int m_messageId; | ||||
|         bool m_moved = false; | ||||
|     }; | ||||
|  | ||||
|     class Capturer { | ||||
|         std::vector<MessageInfo> m_messages; | ||||
|         IResultCapture& m_resultCapture; | ||||
|         size_t m_captured = 0; | ||||
|     public: | ||||
|         Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); | ||||
| @@ -4074,7 +4086,7 @@ namespace Catch { | ||||
|  | ||||
| /////////////////////////////////////////////////////////////////////////////// | ||||
| #define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ | ||||
|     Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) | ||||
|     Catch::IResultCapture::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) | ||||
|  | ||||
|  | ||||
| #if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) | ||||
| @@ -7466,7 +7478,7 @@ namespace Catch { | ||||
| #define CATCH_VERSION_MACROS_HPP_INCLUDED | ||||
|  | ||||
| #define CATCH_VERSION_MAJOR 3 | ||||
| #define CATCH_VERSION_MINOR 10 | ||||
| #define CATCH_VERSION_MINOR 11 | ||||
| #define CATCH_VERSION_PATCH 0 | ||||
|  | ||||
| #endif // CATCH_VERSION_MACROS_HPP_INCLUDED | ||||
| @@ -7882,8 +7894,9 @@ namespace Generators { | ||||
|     class FilterGenerator final : public IGenerator<T> { | ||||
|         GeneratorWrapper<T> m_generator; | ||||
|         Predicate m_predicate; | ||||
|         static_assert(!std::is_reference<Predicate>::value, "This would most likely result in a dangling reference"); | ||||
|     public: | ||||
|         template <typename P = Predicate> | ||||
|         template <typename P> | ||||
|         FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator): | ||||
|             m_generator(CATCH_MOVE(generator)), | ||||
|             m_predicate(CATCH_FORWARD(pred)) | ||||
| @@ -7915,7 +7928,7 @@ namespace Generators { | ||||
|  | ||||
|     template <typename T, typename Predicate> | ||||
|     GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) { | ||||
|         return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, Predicate>>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); | ||||
|         return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, typename std::remove_reference<Predicate>::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
| @@ -9555,7 +9568,7 @@ namespace Catch { | ||||
|         #define CATCH_TRAP()  __asm__(".inst 0xde01") | ||||
|     #endif | ||||
|  | ||||
| #elif defined(CATCH_PLATFORM_LINUX) | ||||
| #elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX) | ||||
|     // If we can use inline assembler, do it because this allows us to break | ||||
|     // directly at the location of the failing check instead of breaking inside | ||||
|     // raise() called from it, i.e. one stack frame below. | ||||
| @@ -10718,11 +10731,6 @@ namespace Catch { | ||||
|         void benchmarkEnded( BenchmarkStats<> const& stats ) override; | ||||
|         void benchmarkFailed( StringRef error ) override; | ||||
|  | ||||
|         void pushScopedMessage( MessageInfo const& message ) override; | ||||
|         void popScopedMessage( MessageInfo const& message ) override; | ||||
|  | ||||
|         void emplaceUnscopedMessage( MessageBuilder&& builder ) override; | ||||
|  | ||||
|         std::string getCurrentTestName() const override; | ||||
|  | ||||
|         const AssertionResult* getLastResult() const override; | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| project( | ||||
|   'catch2', | ||||
|   'cpp', | ||||
|   version: '3.10.0', # CML version placeholder, don't delete | ||||
|   version: '3.11.0', # CML version placeholder, don't delete | ||||
|   license: 'BSL-1.0', | ||||
|   meson_version: '>=0.54.1', | ||||
| ) | ||||
|   | ||||
| @@ -36,7 +36,7 @@ namespace Catch { | ||||
|     } | ||||
|  | ||||
|     Version const& libraryVersion() { | ||||
|         static Version version( 3, 10, 0, "", 0 ); | ||||
|         static Version version( 3, 11, 0, "", 0 ); | ||||
|         return version; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| #define CATCH_VERSION_MACROS_HPP_INCLUDED | ||||
|  | ||||
| #define CATCH_VERSION_MAJOR 3 | ||||
| #define CATCH_VERSION_MINOR 10 | ||||
| #define CATCH_VERSION_MINOR 11 | ||||
| #define CATCH_VERSION_PATCH 0 | ||||
|  | ||||
| #endif // CATCH_VERSION_MACROS_HPP_INCLUDED | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský