diff --git a/3rdparty/catch/catch_amalgamated.cpp b/3rdparty/catch/catch_amalgamated.cpp index adafc64..0fae1d5 100644 --- a/3rdparty/catch/catch_amalgamated.cpp +++ b/3rdparty/catch/catch_amalgamated.cpp @@ -6,8 +6,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.13.0 -// Generated: 2026-02-15 22:55:00.269529 +// Catch v3.16.0 +// Generated: 2026-08-25 09:29:23.172704 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -40,6 +40,54 @@ +#include +#include +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + + Environment measure_environment_default() { + return Detail::measure_environment(); + } + + ExecutionPlan prepare_default( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + // This mirrors Benchmark::prepare(), but with the + // clock fixed so it is instantiated once here in the library. + auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = std::max( + min_time, + std::chrono::duration_cast( + cfg.benchmarkWarmupTime() ) ); + auto&& test = Detail::run_for_at_least( + std::chrono::duration_cast( run_time ), 1, fun ); + int new_iters = static_cast( + std::ceil( min_time * test.iterations / test.elapsed ) ); + return { new_iters, + test.elapsed / test.iterations * new_iters * + cfg.benchmarkSamples(), + CATCH_MOVE( fun ), + std::chrono::duration_cast( + cfg.benchmarkWarmupTime() ), + Detail::warmup_iterations }; + } + + std::vector run_plan_default( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ) { + return plan.run( cfg, env ); + } + + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + + + namespace Catch { namespace Benchmark { namespace Detail { @@ -74,11 +122,11 @@ namespace Catch { samples.data(), samples.data() + samples.size() ); auto wrap_estimate = [](Estimate e) { - return Estimate { - FDuration(e.point), - FDuration(e.lower_bound), - FDuration(e.upper_bound), - e.confidence_interval, + return Estimate{ + FDuration( e.point ), + FDuration( e.lower_bound ), + FDuration( e.upper_bound ), + e.confidence_interval, }; }; std::vector samples2; @@ -147,7 +195,7 @@ namespace Catch { namespace Catch { namespace Benchmark { namespace Detail { - struct optimized_away_error : std::exception { + struct optimized_away_error final : std::exception { const char* what() const noexcept override; }; @@ -494,9 +542,10 @@ namespace Catch { double b2 = bias - z1; double a1 = a( b1 ); double a2 = a( b2 ); - auto lo = static_cast( (std::max)( cumn( a1 ), 0l ) ); - auto hi = - static_cast( (std::min)( cumn( a2 ), n - 1 ) ); + const long clo = std::isnan( a1 ) ? 0l : cumn( a1 ); + const long chi = std::isnan( a2 ) ? n - 1 : cumn( a2 ); + auto lo = static_cast( (std::min)( (std::max)( clo, 0l ), n - 1 ) ); + auto hi = static_cast( (std::min)( (std::max)( chi, 0l ), n - 1 ) ); return { point, resample[lo], resample[hi], confidence_level }; } @@ -795,6 +844,7 @@ namespace Catch { return lhs.name == rhs.name && lhs.outputFilename == rhs.outputFilename && lhs.colourMode == rhs.colourMode && + lhs.verbosity == rhs.verbosity && lhs.customOptions == rhs.customOptions; } @@ -863,6 +913,7 @@ namespace Catch { reporterSpec.outputFile() ? *reporterSpec.outputFile() : data.defaultOutputFilename, reporterSpec.colourMode().valueOr( data.defaultColourMode ), + reporterSpec.verbosity().valueOr( data.verbosity ), reporterSpec.customOptions() } ); } } @@ -913,6 +964,7 @@ namespace Catch { double Config::minDuration() const { return m_data.minDuration; } TestRunOrder Config::runOrder() const { return m_data.runOrder; } uint32_t Config::rngSeed() const { return m_data.rngSeed; } + bool Config::rngSeedWasFixed() const { return m_data.rngSeedWasFixed; } unsigned int Config::shardCount() const { return m_data.shardCount; } unsigned int Config::shardIndex() const { return m_data.shardIndex; } ColourMode Config::defaultColourMode() const { return m_data.defaultColourMode; } @@ -938,7 +990,7 @@ namespace Catch { if ( bazelOutputFile ) { m_data.reporterSpecifications.push_back( - { "junit", std::string( bazelOutputFile ), {}, {} } ); + { "junit", std::string( bazelOutputFile ), {}, {}, {} } ); } const auto bazelTestSpec = Detail::getEnv( "TESTBRIDGE_TEST_ONLY" ); @@ -977,6 +1029,7 @@ namespace Catch { << bazelRandomSeed << "') as proper seed.\n"; } else { m_data.rngSeed = *parsedSeed; + m_data.rngSeedWasFixed = true; } } } @@ -995,6 +1048,7 @@ namespace Catch { + #include #include @@ -1007,7 +1061,7 @@ namespace Catch { m_messageId( builder.m_info.sequence ) { MessageInfo info( CATCH_MOVE( builder.m_info ) ); info.message = builder.m_stream.str(); - IResultCapture::pushScopedMessage( CATCH_MOVE( info ) ); + Detail::pushScopedMessage( CATCH_MOVE( info ) ); } ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: @@ -1016,7 +1070,7 @@ namespace Catch { } ScopedMessage::~ScopedMessage() { - if ( !m_moved ) { IResultCapture::popScopedMessage( m_messageId ); } + if ( !m_moved ) { Detail::popScopedMessage( m_messageId ); } } @@ -1088,7 +1142,7 @@ namespace Catch { assert( m_captured == m_messages.size() ); if ( m_isScoped ) { for ( auto const& message : m_messages ) { - IResultCapture::popScopedMessage( message.sequence ); + Detail::popScopedMessage( message.sequence ); } } } @@ -1097,9 +1151,9 @@ namespace Catch { assert( index < m_messages.size() ); m_messages[index].message += value; if ( m_isScoped ) { - IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) ); + Detail::pushScopedMessage( CATCH_MOVE( m_messages[index] ) ); } else { - IResultCapture::addUnscopedMessage( CATCH_MOVE( m_messages[index] ) ); + Detail::addUnscopedMessage( CATCH_MOVE( m_messages[index] ) ); } m_captured++; } @@ -1160,8 +1214,9 @@ namespace Catch { CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); #endif } - IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override { - return m_enumValuesRegistry; + + ITestCaseRegistry& getMutableTestCaseRegistry() override { + return m_testCaseRegistry; } private: @@ -1170,7 +1225,6 @@ namespace Catch { ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; TagAliasRegistry m_tagAliasRegistry; StartupExceptionRegistry m_exceptionRegistry; - Detail::EnumValuesRegistry m_enumValuesRegistry; }; } @@ -1220,6 +1274,7 @@ namespace Catch { ReporterConfig( config, makeStream( spec.outputFilename ), spec.colourMode, + spec.verbosity, spec.customOptions ) ); } @@ -1236,6 +1291,7 @@ namespace Catch { ReporterConfig( config, makeStream( reporterSpec.outputFilename ), reporterSpec.colourMode, + reporterSpec.verbosity, reporterSpec.customOptions ) ) ); } @@ -1306,9 +1362,7 @@ namespace Catch { }; void applyFilenamesAsTags() { - for (auto const& testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) { - testInfo->addFilenameTag(); - } + getMutableRegistryHub().getMutableTestCaseRegistry().enableFilenameTags(); } // Creates empty file at path. The path must be writable, we do not @@ -1514,6 +1568,19 @@ namespace Catch { CATCH_TRY { config(); // Force config to be constructed + if ( m_config->shardCount() > 1 && + m_config->runOrder() == TestRunOrder::Randomized && + !m_config->rngSeedWasFixed() ) { + Catch::cerr() + << "Warning: using sharding (--shard-count) with random " + "order (--order rand, the default) and without a fixed " + "numeric --rng-seed does not guarantee disjoint coverage " + "between shard invocations. Pass the same numeric " + "--rng-seed to every shard, or use --order decl or " + "--order lex instead.\n" + << std::flush; + } + // 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() ); @@ -2397,7 +2464,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 3, 13, 0, "", 0 ); + static Version version( 3, 16, 0, "", 0 ); return version; } @@ -2544,7 +2611,7 @@ namespace Catch { bool isValid = next(); if ( !isValid ) { Detail::throw_generator_exception( - "Coud not jump to Nth element: not enough elements" ); + "Could not jump to Nth element: not enough elements" ); } } } @@ -2594,10 +2661,12 @@ namespace Catch { IConfig const* _fullConfig, Detail::unique_ptr _stream, ColourMode colourMode, + Verbosity verbosity, std::map customOptions ): m_stream( CATCH_MOVE(_stream) ), m_fullConfig( _fullConfig ), m_colourMode( colourMode ), + m_verbosity( verbosity ), m_customOptions( CATCH_MOVE( customOptions ) ) {} Detail::unique_ptr ReporterConfig::takeStream() && { @@ -2606,6 +2675,7 @@ namespace Catch { } IConfig const * ReporterConfig::fullConfig() const { return m_fullConfig; } ColourMode ReporterConfig::colourMode() const { return m_colourMode; } + Verbosity ReporterConfig::verbosity() const { return m_verbosity; } std::map const& ReporterConfig::customOptions() const { @@ -2686,13 +2756,17 @@ namespace Catch { namespace Catch { + void AssertionHandler::finishIncomplete() { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + AssertionHandler::AssertionHandler ( StringRef macroName, SourceLineInfo const& lineInfo, StringRef capturedExpression, ResultDisposition::Flags resultDisposition ) : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, - m_resultCapture( getResultCapture() ) + m_resultCapture( static_cast(getResultCapture()) ) { m_resultCapture.notifyAssertionStarted( m_assertionInfo ); } @@ -3296,9 +3370,11 @@ namespace Catch { auto const setRngSeed = [&]( std::string const& seed ) { if( seed == "time" ) { config.rngSeed = generateRandomSeed(GenerateFrom::Time); + config.rngSeedWasFixed = false; return ParserResult::ok(ParseResultType::Matched); } else if (seed == "random-device") { config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice); + config.rngSeedWasFixed = false; return ParserResult::ok(ParseResultType::Matched); } @@ -3309,6 +3385,7 @@ namespace Catch { return ParserResult::runtimeError( "Could not parse '" + seed + "' as seed" ); } config.rngSeed = *parsedSeed; + config.rngSeedWasFixed = true; return ParserResult::ok( ParseResultType::Matched ); }; auto const setDefaultColourMode = [&]( std::string const& colourMode ) { @@ -4088,12 +4165,11 @@ namespace Catch { + #include namespace Catch { - IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() = default; - namespace Detail { namespace { @@ -4119,9 +4195,7 @@ namespace Catch { return parsed; } - EnumInfo::~EnumInfo() = default; - - StringRef EnumInfo::lookup( int value ) const { + StringRef EnumInfo::lookup( int64_t value ) const { for( auto const& valueToName : m_values ) { if( valueToName.first == value ) return valueToName.second; @@ -4129,25 +4203,20 @@ namespace Catch { return "{** unexpected enum value **}"_sr; } - Catch::Detail::unique_ptr makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector const& values ) { - auto enumInfo = Catch::Detail::make_unique(); - enumInfo->m_name = enumName; - enumInfo->m_values.reserve( values.size() ); + EnumInfo makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector const& values ) { + EnumInfo enumInfo; + enumInfo.m_name = enumName; + enumInfo.m_values.reserve( values.size() ); const auto valueNames = Catch::Detail::parseEnums( allValueNames ); assert( valueNames.size() == values.size() ); - std::size_t i = 0; - for( auto value : values ) - enumInfo->m_values.emplace_back(value, valueNames[i++]); + for (size_t i = 0; i < values.size(); ++i) { + enumInfo.m_values.emplace_back( values[i], valueNames[i] ); + } return enumInfo; } - EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector const& values ) { - m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values)); - return *m_enumInfos.back(); - } - } // Detail } // Catch @@ -4685,12 +4754,28 @@ namespace Detail { +#include +#include + namespace Catch { namespace { - static bool needsEscape( char c ) { - return c == '"' || c == '\\' || c == '\b' || c == '\f' || - c == '\n' || c == '\r' || c == '\t'; + struct EscapeLUT { + bool escape[256] = {}; + constexpr EscapeLUT() { + escape[static_cast( '"' )] = true; + escape[static_cast( '\\' )] = true; + escape[static_cast( '\b' )] = true; + escape[static_cast( '\f' )] = true; + escape[static_cast( '\n' )] = true; + escape[static_cast( '\r' )] = true; + escape[static_cast( '\t' )] = true; + } + }; + static constexpr EscapeLUT escapeLUT{}; + + static constexpr bool needsEscape( char c ) { + return escapeLUT.escape[static_cast( c )]; } static Catch::StringRef makeEscapeStringRef( char c ) { @@ -4802,7 +4887,10 @@ namespace Catch { JsonValueWriter::JsonValueWriter( std::ostream& os, std::uint64_t indent_level ): - m_os{ os }, m_indent_level{ indent_level } {} + m_os{ os }, m_indent_level{ indent_level } { + // We use C locale so that writing of numerical values is locale-independent. + m_sstream.imbue( std::locale::classic() ); + } JsonObjectWriter JsonValueWriter::writeObject() && { return JsonObjectWriter{ m_os, m_indent_level }; @@ -4816,26 +4904,55 @@ namespace Catch { writeImpl( value, true ); } + void JsonValueWriter::write( float value ) && { + writeFloatingPoint( value ); + } + + void JsonValueWriter::write( double value ) && { + writeFloatingPoint( value ); + } + void JsonValueWriter::write( bool value ) && { writeImpl( value ? "true"_sr : "false"_sr, false ); } + template + void JsonValueWriter::writeFloatingPoint( T value ) { + if ( Catch::isnan( value ) ) { + writeImpl( "NaN"_sr, false ); + } else if ( std::isinf( value ) ) { + writeImpl( value < 0 ? "-Infinity"_sr : "Infinity"_sr, false ); + } else { + m_sstream << value; + writeImpl( m_sstream.str(), false ); + } + } + void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) { - if ( quote ) { m_os << '"'; } - size_t current_start = 0; - for ( size_t i = 0; i < value.size(); ++i ) { - if ( needsEscape( value[i] ) ) { - if ( current_start < i ) { - m_os << value.substr( current_start, i - current_start ); + // Escaping only makes sense for actual strings, which are passed + // with `quote == true`. Non-quoted values are things like bools + // and numbers, which cannot create inputs that need escaping. + if ( !quote ) { + m_os << value; + } else { + m_os << '"'; + size_t current_start = 0; + for ( size_t i = 0; i < value.size(); ++i ) { + if ( needsEscape( value[i] ) ) { + if ( current_start < i ) { + m_os + << value.substr( current_start, i - current_start ); + } + m_os << makeEscapeStringRef( value[i] ); + current_start = i + 1; } - m_os << makeEscapeStringRef( value[i] ); - current_start = i + 1; } + if ( current_start < value.size() ) { + m_os << value.substr( current_start, + value.size() - current_start ); + } + m_os << '"'; } - if ( current_start < value.size() ) { - m_os << value.substr( current_start, value.size() - current_start ); - } - if ( quote ) { m_os << '"'; } } } // namespace Catch @@ -5072,7 +5189,7 @@ namespace Catch { namespace { //! A no-op implementation, used if no reporter wants output //! redirection. - class NoopRedirect : public OutputRedirect { + class NoopRedirect final : public OutputRedirect { void activateImpl() override {} void deactivateImpl() override {} std::string getStdout() override { return {}; } @@ -5109,7 +5226,7 @@ namespace Catch { * Redirects the `std::cout`, `std::cerr`, `std::clog` streams, * but does not touch the actual `stdout`/`stderr` file descriptors. */ - class StreamRedirect : public OutputRedirect { + class StreamRedirect final : public OutputRedirect { ReusableStringStream m_redirectedOut, m_redirectedErr; RedirectedStreamNew m_cout, m_cerr, m_clog; @@ -5220,7 +5337,7 @@ namespace Catch { * Works by replacing the file descriptors numbered 1 and 2 * with an open temporary file. */ - class FileRedirect : public OutputRedirect { + class FileRedirect final : public OutputRedirect { TempFile m_outFile, m_errFile; int m_originalOut = -1; int m_originalErr = -1; @@ -5712,6 +5829,18 @@ namespace Catch { return {}; } } + + Optional stringToVerbosity( StringRef verbosity ) { + if (verbosity == "quiet") { return Verbosity::Quiet; + } else if ( verbosity == "normal" ) { + return Verbosity::Normal; + } else if ( verbosity == "high" ) { + return Verbosity::High; + } else { + return {}; + } + } + } // namespace Detail @@ -5719,6 +5848,7 @@ namespace Catch { return lhs.m_name == rhs.m_name && lhs.m_outputFileName == rhs.m_outputFileName && lhs.m_colourMode == rhs.m_colourMode && + lhs.m_verbosity == rhs.m_verbosity && lhs.m_customOptions == rhs.m_customOptions; } @@ -5730,6 +5860,7 @@ namespace Catch { std::map kvPairs; Optional outputFileName; Optional colourMode; + Optional verbosity; // First part is always reporter name, so we skip it for ( size_t i = 1; i < parts.size(); ++i ) { @@ -5767,6 +5898,12 @@ namespace Catch { if ( !colourMode ) { return {}; } + } else if ( key == "verbosity" ) { + // Duplicated key + if ( verbosity ) { return {}; } + verbosity = Detail::stringToVerbosity( value ); + // Parsing failed + if ( !verbosity ) { return {}; } } else { // Unrecognized option return {}; @@ -5776,6 +5913,7 @@ namespace Catch { return ReporterSpec{ CATCH_MOVE( parts[0] ), CATCH_MOVE( outputFileName ), CATCH_MOVE( colourMode ), + CATCH_MOVE( verbosity), CATCH_MOVE( kvPairs ) }; } @@ -5783,10 +5921,12 @@ ReporterSpec::ReporterSpec( std::string name, Optional outputFileName, Optional colourMode, + Optional verbosity, std::map customOptions ): m_name( CATCH_MOVE( name ) ), m_outputFileName( CATCH_MOVE( outputFileName ) ), m_colourMode( CATCH_MOVE( colourMode ) ), + m_verbosity( CATCH_MOVE( verbosity ) ), m_customOptions( CATCH_MOVE( customOptions ) ) {} } // namespace Catch @@ -5888,9 +6028,14 @@ namespace Catch { // can be, so the tracker has to throw for a wrong // filter to stop the execution flow. if (filter.type == PathFilter::For::Section) { - // TBD: Explicit SKIP, or new exception that says - // "don't continue", but doesn't show in totals? - SKIP(); + // We want the semantics of `SKIP()`, but we inline it + // to avoid issues with conditionally prefixed macros + INTERNAL_CATCH_MSG( + "SKIP", + Catch::ResultWas::ExplicitSkip, + Catch::ResultDisposition::Normal, + "" ); + Catch::Detail::Unreachable(); } // '*' is the wildcard for "all elements in generator" // used for filtering sections below the generator, but @@ -6018,6 +6163,8 @@ namespace Catch { auto getGenerator() const -> GeneratorBasePtr const& override { return m_generator; } + + bool isFilteredImpl() const override { return m_isFiltered; } }; } // namespace } @@ -6131,6 +6278,25 @@ namespace Catch { } CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + void pushScopedMessage( MessageInfo&& message ) { + Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) ); + } + + void popScopedMessage( unsigned int messageId ) { + Detail::g_messageHolder().removeMessage( messageId ); + } + + void emplaceUnscopedMessage( MessageBuilder&& builder ) { + Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) ); + } + + void addUnscopedMessage( MessageInfo&& message ) { + Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( message ) ); + } + + bool lastAssertionPassed() { return Detail::g_lastAssertionPassed; } + } // namespace Detail RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter) @@ -6264,14 +6430,13 @@ namespace Catch { Detail::g_lastAssertionPassed = true; } else if (!result.succeeded()) { Detail::g_lastAssertionPassed = false; - if (result.isOk()) { - } - else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine + if (result.isOk()) {} + else if( m_activeTestCase->getTestCaseInfo().okToFail() ) { // Read from a shared state established before the threads could start, this is fine m_atomicAssertionCount.failedButOk++; - else + } else { m_atomicAssertionCount.failed++; - } - else { + } + } else { Detail::g_lastAssertionPassed = true; } @@ -6344,13 +6509,6 @@ namespace Catch { SourceLineInfo lineInfo, Generators::GeneratorBasePtr&& generator ) { - // TBD: Do we want to avoid the warning if the generator is filtered? - if ( m_config->warnAboutInfiniteGenerators() && - !generator->isFinite() ) { - // TBD: Would it be better to expand this macro inline? - FAIL( "GENERATE() would run infinitely" ); - } - auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast( generatorName ), lineInfo ); auto& currentTracker = m_trackerContext.currentTracker(); assert( @@ -6363,11 +6521,24 @@ namespace Catch { m_trackerContext, ¤tTracker, CATCH_MOVE( generator ) ); - auto ret = newTracker.get(); + + // The warning shouldn't fire if the generator is infinite, **but** filtered down. + if ( m_config->warnAboutInfiniteGenerators() && + !newTracker->m_generator->isFinite() && + !newTracker->isFiltered() ) { + // We want the semantics of `FAIL()`, but we inline it + // to avoid issues with conditionally prefixed macros + INTERNAL_CATCH_MSG( "FAIL", + Catch::ResultWas::ExplicitFailure, + Catch::ResultDisposition::Normal, + "GENERATE() would run infinitely" ); + } + + auto returnPtr = newTracker.get(); currentTracker.addChild( CATCH_MOVE( newTracker ) ); - ret->open(); - return ret; + returnPtr->open(); + return returnPtr; } bool RunContext::testForMissingAssertions(Counts& assertions) { @@ -6449,7 +6620,7 @@ namespace Catch { // and since IResultCapture::getLastResult is deprecated, // we will leave it as is, until it is finally removed. Detail::LockGuard _( m_assertionMutex ); - return &(*m_lastResult); + return &*m_lastResult; } void RunContext::exceptionEarlyReported() { @@ -6523,10 +6694,6 @@ namespace Catch { m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); } - bool RunContext::lastAssertionPassed() { - return Detail::g_lastAssertionPassed; - } - void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) { // We want to save the line info for better experience with unexpected assertions Detail::g_lastKnownLineInfo = lineInfo; @@ -6705,7 +6872,7 @@ namespace Catch { } void RunContext::populateReaction( AssertionReaction& reaction, - bool has_normal_disposition ) { + bool has_normal_disposition ) const { reaction.shouldDebugBreak = m_shouldDebugBreak; reaction.shouldThrow = aborting() || has_normal_disposition; } @@ -6756,22 +6923,6 @@ namespace Catch { } } - void IResultCapture::pushScopedMessage( MessageInfo&& message ) { - Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) ); - } - - void IResultCapture::popScopedMessage( unsigned int messageId ) { - Detail::g_messageHolder().removeMessage( messageId ); - } - - void IResultCapture::emplaceUnscopedMessage( MessageBuilder&& builder ) { - Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( builder ) ); - } - - void IResultCapture::addUnscopedMessage( MessageInfo&& message ) { - Detail::g_messageHolder().addUnscopedMessage( CATCH_MOVE( message ) ); - } - void seedRng(IConfig const& config) { sharedRng().seed(config.rngSeed()); } @@ -7103,10 +7254,11 @@ namespace Catch { TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { auto it = m_registry.find( alias ); - if( it != m_registry.end() ) - return &(it->second); - else + if ( it != m_registry.end() ) { + return &it->second; + } else { return nullptr; + } } std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { @@ -7181,6 +7333,9 @@ namespace Catch { namespace Catch { namespace { + // Picked small-ish number at random + static size_t kInitialTestCount = 120; + static void enforceNoDuplicateTestCases( std::vector const& tests ) { auto testInfoCmp = []( TestCaseInfo const* lhs, @@ -7282,17 +7437,26 @@ namespace Catch { return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); } + + TestRegistry::TestRegistry() { + // We pre-reserve some reasonable number of tests to avoid the + // initial geometric growth churning during test registration. + m_handles.reserve( kInitialTestCount ); + m_test_infos.reserve( kInitialTestCount ); + m_invokers.reserve( kInitialTestCount ); + } TestRegistry::~TestRegistry() = default; void TestRegistry::registerTest(Detail::unique_ptr testInfo, Detail::unique_ptr testInvoker) { m_handles.emplace_back(testInfo.get(), testInvoker.get()); - m_viewed_test_infos.push_back(testInfo.get()); - m_owned_test_infos.push_back(CATCH_MOVE(testInfo)); + m_test_infos.push_back(CATCH_MOVE(testInfo)); m_invokers.push_back(CATCH_MOVE(testInvoker)); } - std::vector const& TestRegistry::getAllInfos() const { - return m_viewed_test_infos; + void TestRegistry::enableFilenameTags() { + for (auto& info : m_test_infos) { + info->addFilenameTag(); + } } std::vector const& TestRegistry::getAllTests() const { @@ -7469,6 +7633,28 @@ namespace TestCaseTracking { m_ctx.setCurrentTracker( this ); } + bool SectionTracker::isFilteredImpl() const { + // TBD: This is currently _very_ similar to the block in `isComplete`. + // Is this neccessarily that way, or just accident of current semantics? + const size_t filterIndex = + m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth; + + if ( filterIndex < m_filterRef->size() ) { + // 1) New style filter must explicitly target section + if ( m_newStyleFilters && ( *m_filterRef )[filterIndex].type != + PathFilter::For::Section ) { + return true; + } + // 2) Both style filters must match the trimmed name exactly + if ( m_trimmed_name != + StringRef( ( *m_filterRef )[filterIndex].filter ) ) { + return true; + } + } + + return false; + } + SectionTracker::SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ) : TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ), m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name))) @@ -7553,7 +7739,7 @@ namespace Catch { void throw_test_failure_exception() { #if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) - throw TestFailureException{}; + throw TestFailureException{}; //NOLINT(bugprone-std-exception-baseclass) #else CATCH_ERROR( "Test failure requires aborting test!" ); #endif @@ -7561,7 +7747,7 @@ namespace Catch { void throw_test_skip_exception() { #if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) - throw Catch::TestSkipException(); + throw Catch::TestSkipException(); //NOLINT(bugprone-std-exception-baseclass) #else CATCH_ERROR( "Explicitly skipping tests during runtime requires exceptions" ); #endif @@ -7893,6 +8079,10 @@ namespace { return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; } + bool isUtf8ContinuationByte( char c ) { + return ( static_cast( c ) & 0xC0 ) == 0x80; + } + } // namespace namespace Catch { @@ -7919,6 +8109,11 @@ namespace Catch { if ( it != m_string.end() ) { ++m_size; ++it; + // Skip UTF-8 continuation bytes + while ( it != m_string.end() && + isUtf8ContinuationByte( *it ) ) { + ++it; + } } } } @@ -7981,6 +8176,11 @@ namespace Catch { void AnsiSkippingString::const_iterator::advance() { assert( m_it != m_string->end() ); m_it++; + // Skip UTF-8 continuation bytes + while ( m_it != m_string->end() && + isUtf8ContinuationByte( *m_it ) ) { + m_it++; + } tryParseAnsiEscapes(); } @@ -8000,6 +8200,11 @@ namespace Catch { assert( *m_it == '\033' ); m_it--; } + // Skip back over UTF-8 continuation bytes to the leading byte + while ( m_it != m_string->begin() && + isUtf8ContinuationByte( *m_it ) ) { + m_it--; + } } static bool isBoundary( AnsiSkippingString const& line, @@ -8646,13 +8851,13 @@ namespace Catch { namespace Matchers { std::string MatcherUntypedBase::toString() const { - if (m_cachedToString.empty()) { - m_cachedToString = describe(); - } - return m_cachedToString; + return describe(); } - MatcherUntypedBase::~MatcherUntypedBase() = default; + std::string MatcherUntypedBase::describe() const { + using namespace std::string_literals; + return "Undescribed matcher"s; + } } // namespace Matchers } // namespace Catch @@ -8673,14 +8878,6 @@ namespace Matchers { return sstr.str(); } - IsEmptyMatcher IsEmpty() { - return {}; - } - - HasSizeMatcher SizeIs(std::size_t sz) { - return HasSizeMatcher{ sz }; - } - } // end namespace Matchers } // end namespace Catch @@ -8934,17 +9131,13 @@ std::string Catch::Matchers::Detail::finalizeDescription(const std::string& desc namespace Catch { namespace Matchers { - std::string AllTrueMatcher::describe() const { return "contains only true"; } - AllTrueMatcher AllTrue() { return AllTrueMatcher{}; } + std::string AllTrueMatcher::describe() const { return "contains only true"; } std::string NoneTrueMatcher::describe() const { return "contains no true"; } - NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; } - std::string AnyTrueMatcher::describe() const { return "contains at least one true"; } - AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; } } // namespace Matchers } // namespace Catch @@ -8953,66 +9146,101 @@ namespace Catch { #include namespace Catch { + + namespace { + constexpr StringRef caseSensitivitySuffix( CaseSensitive caseSensitivity ) { + return caseSensitivity == CaseSensitive::Yes + ? StringRef{} + : " (case insensitive)"_sr; + } + } // namespace + namespace Matchers { - CasedString::CasedString( std::string const& str, CaseSensitive caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - m_str( adjustString( str ) ) - {} - std::string CasedString::adjustString( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No - ? toLower( str ) - : str; - } - StringRef CasedString::caseSensitivitySuffix() const { - return m_caseSensitivity == CaseSensitive::Yes - ? StringRef() - : " (case insensitive)"_sr; - } + StringMatcherBase::StringMatcherBase( std::string target, + StringRef operation, + CaseSensitive caseSensitivity ): + m_target( CATCH_MOVE( target ) ), + m_operation( operation ), + m_caseSensitivity( caseSensitivity ) {} - StringMatcherBase::StringMatcherBase( StringRef operation, CasedString const& comparator ) - : m_comparator( comparator ), - m_operation( operation ) { - } - std::string StringMatcherBase::describe() const { std::string description; - description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + - m_comparator.caseSensitivitySuffix().size()); + description.reserve(5 + m_operation.size() + m_target.size() + + caseSensitivitySuffix(m_caseSensitivity).size()); description += m_operation; description += ": \""; - description += m_comparator.m_str; + description += m_target; description += '"'; - description += m_comparator.caseSensitivitySuffix(); + description += caseSensitivitySuffix(m_caseSensitivity); return description; } - StringEqualsMatcher::StringEqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals"_sr, comparator ) {} + + StringEqualsMatcher::StringEqualsMatcher( std::string comparator, CaseSensitive caseSensitivity ): + StringMatcherBase( CATCH_MOVE( comparator ), "equals"_sr, caseSensitivity ) {} bool StringEqualsMatcher::match( std::string const& source ) const { - return m_comparator.adjustString( source ) == m_comparator.m_str; + if (m_caseSensitivity == CaseSensitive::Yes) { + return m_target == source; + } + if (m_target.size() != source.size()) { return false; } + Catch::Detail::CaseInsensitiveEqualTo eq; + return eq( m_target, source ); } - StringContainsMatcher::StringContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains"_sr, comparator ) {} + StringContainsMatcher::StringContainsMatcher( + std::string comparator, CaseSensitive caseSensitivity ): + StringMatcherBase( CATCH_MOVE( comparator ), "contains"_sr, caseSensitivity ) {} bool StringContainsMatcher::match( std::string const& source ) const { - return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + if ( m_caseSensitivity == CaseSensitive::Yes ) { + return contains( source, m_target ); + } + if ( source.size() < m_target.size() ) { return false; } + StringRef as_ref( source ); + // The worst case of this is O(m*n), which is terrible, BUT: + // * The average case is much better, the worst case only happens rarely + // * We can implement BMH/other better searchers later if it matters + Catch::Detail::CaseInsensitiveEqualTo eq; + for (size_t i = 0; i < source.size(); ++i) { + const auto substr = as_ref.substr( i, m_target.size() ); + bool found = eq( substr, m_target ); + if ( found ) { return true; } + } + return false; } - StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with"_sr, comparator ) {} + StartsWithMatcher::StartsWithMatcher( std::string comparator, + CaseSensitive caseSensitivity ): + StringMatcherBase( CATCH_MOVE( comparator ), "starts with"_sr, caseSensitivity ) {} bool StartsWithMatcher::match( std::string const& source ) const { - return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + if ( m_caseSensitivity == CaseSensitive::Yes ) { + return startsWith( source, m_target ); + } + if (source.size() < m_target.size()) { return false; } + Catch::Detail::CaseInsensitiveEqualTo eq; + return eq( + StringRef( source ).substr( 0, m_target.size() ), m_target ); } - EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with"_sr, comparator ) {} + EndsWithMatcher::EndsWithMatcher( std::string comparator, + CaseSensitive caseSensitivity ): + StringMatcherBase( CATCH_MOVE( comparator ), "ends with"_sr, caseSensitivity ) {} bool EndsWithMatcher::match( std::string const& source ) const { - return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + if ( m_caseSensitivity == CaseSensitive::Yes ) { + return endsWith( source, m_target ); + } + if ( source.size() < m_target.size() ) { return false; } + Catch::Detail::CaseInsensitiveEqualTo eq; + const size_t start_point = source.size() - m_target.size(); + return eq( StringRef( source ).substr( start_point, m_target.size() ), m_target ); } @@ -9033,21 +9261,21 @@ namespace Matchers { } - StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity ) { - return StringEqualsMatcher( CasedString( str, caseSensitivity) ); + StringEqualsMatcher Equals( std::string str, CaseSensitive caseSensitivity ) { + return StringEqualsMatcher( CATCH_MOVE( str ), caseSensitivity ); } - StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity ) { - return StringContainsMatcher( CasedString( str, caseSensitivity) ); + StringContainsMatcher ContainsSubstring( std::string str, CaseSensitive caseSensitivity ) { + return StringContainsMatcher( CATCH_MOVE( str ), caseSensitivity ); } - EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity ) { - return EndsWithMatcher( CasedString( str, caseSensitivity) ); + EndsWithMatcher EndsWith( std::string str, CaseSensitive caseSensitivity ) { + return EndsWithMatcher( CATCH_MOVE( str ), caseSensitivity ); } - StartsWithMatcher StartsWith( std::string const& str, CaseSensitive caseSensitivity ) { - return StartsWithMatcher( CasedString( str, caseSensitivity) ); + StartsWithMatcher StartsWith( std::string str, CaseSensitive caseSensitivity ) { + return StartsWithMatcher( CATCH_MOVE( str ), caseSensitivity ); } - RegexMatcher Matches(std::string const& regex, CaseSensitive caseSensitivity) { - return RegexMatcher(regex, caseSensitivity); + RegexMatcher Matches(std::string regex, CaseSensitive caseSensitivity) { + return RegexMatcher( CATCH_MOVE( regex ), caseSensitivity ); } } // namespace Matchers @@ -9057,7 +9285,11 @@ namespace Matchers { namespace Catch { namespace Matchers { - MatcherGenericBase::~MatcherGenericBase() = default; + + std::string MatcherGenericBase::describe() const { + using namespace std::string_literals; + return "Undescribed generic matcher"s; + } namespace Detail { @@ -9145,6 +9377,7 @@ namespace Catch { m_wrapped_stream( CATCH_MOVE(config).takeStream() ), m_stream( m_wrapped_stream->stream() ), m_colour( makeColourImpl( config.colourMode(), m_wrapped_stream.get() ) ), + m_verbosity( config.verbosity() ), m_customOptions( config.customOptions() ) {} @@ -9152,12 +9385,12 @@ namespace Catch { void ReporterBase::listReporters( std::vector const& descriptions ) { - defaultListReporters(m_stream, descriptions, m_config->verbosity()); + defaultListReporters( m_stream, descriptions, m_verbosity ); } void ReporterBase::listListeners( std::vector const& descriptions ) { - defaultListListeners( m_stream, descriptions ); + defaultListListeners( m_stream, descriptions, m_verbosity ); } void ReporterBase::listTests(std::vector const& tests) { @@ -9165,11 +9398,11 @@ namespace Catch { m_colour.get(), tests, m_config->hasTestFilters(), - m_config->verbosity()); + m_verbosity); } void ReporterBase::listTags(std::vector const& tags) { - defaultListTags( m_stream, tags, m_config->hasTestFilters() ); + defaultListTags( m_stream, tags, m_config->hasTestFilters(), m_verbosity ); } } // namespace Catch @@ -10080,9 +10313,8 @@ namespace Catch { bool operator()( Detail::unique_ptr const& node ) const { - return ( - ( node->stats.sectionInfo.name == m_other.name ) && - ( node->stats.sectionInfo.lineInfo == m_other.lineInfo ) ); + return node->stats.sectionInfo.name == m_other.name + && node->stats.sectionInfo.lineInfo == m_other.lineInfo; } void operator=( BySectionInfo const& ) = delete; @@ -10380,7 +10612,15 @@ namespace Catch { } void defaultListListeners( std::ostream& out, - std::vector const& descriptions ) { + std::vector const& descriptions, + Verbosity verbosity ) { + if ( verbosity == Verbosity::Quiet ) { + for ( auto const& desc : descriptions ) { + out << desc.name << '\n'; + } + return; + } + out << "Registered listeners:\n"; if(descriptions.empty()) { @@ -10413,7 +10653,14 @@ namespace Catch { void defaultListTags( std::ostream& out, std::vector const& tags, - bool isFiltered ) { + bool isFiltered, + Verbosity verbosity ) { + if (verbosity == Verbosity::Quiet) { + for (auto const& tagCount : tags) { + out << tagCount.all() << '\n'; + } + return; + } if ( isFiltered ) { out << "Tags for matching test cases:\n"; } else { @@ -10421,7 +10668,7 @@ namespace Catch { } // minimum whitespace to pad tag counts, possibly overwritten below - size_t maxTagCountLen = 2; + int maxTagCountLen = 2; // determine necessary padding for tag count column if ( ! tags.empty() ) { @@ -10432,11 +10679,11 @@ namespace Catch { 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 ); + maxTagCountLen = static_cast( numDigits ); } } @@ -10605,6 +10852,8 @@ namespace Catch { namespace Catch { namespace { + static size_t kJsonOutputVersion = 2; + void writeSourceInfo( JsonObjectWriter& writer, SourceLineInfo const& sourceInfo ) { auto source_location_writer = @@ -10647,7 +10896,7 @@ namespace Catch { m_writers.emplace( Writer::Object ); auto& writer = m_objectWriters.top(); - writer.write( "version"_sr ).write( 1 ); + writer.write( "version"_sr ).write( kJsonOutputVersion ); { auto metadata_writer = writer.write( "metadata"_sr ).writeObject(); @@ -10933,14 +11182,18 @@ namespace Catch { auto const& info = test.getTestCaseInfo(); desc_writer.write( "name"_sr ).write( info.name ); - desc_writer.write( "class-name"_sr ).write( info.className ); - { + if (!info.className.empty()) { + desc_writer.write( "class-name"_sr ).write( info.className ); + } + if ( m_verbosity >= Verbosity::Normal ) { auto tag_writer = desc_writer.write( "tags"_sr ).writeArray(); for ( auto const& tag : info.tags ) { tag_writer.write( tag.original ); } } - writeSourceInfo( desc_writer, info.lineInfo ); + if ( m_verbosity >= Verbosity::High) { + writeSourceInfo( desc_writer, info.lineInfo ); + } } } void JsonReporter::listTags( std::vector const& tags ) { @@ -11189,70 +11442,77 @@ namespace Catch { void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { if (assertionOrBenchmark.isAssertion()) { - writeAssertion(assertionOrBenchmark.asAssertion()); + // JUnit XML format supports only 1 error/failure/skip + // assertion elements per test case + if (writeAssertion(assertionOrBenchmark.asAssertion())) { + break; + } } } } - void JunitReporter::writeAssertion( AssertionStats const& stats ) { + bool JunitReporter::writeAssertion( AssertionStats const& stats ) { AssertionResult const& result = stats.assertionResult; - if ( !result.isOk() || - result.getResultType() == ResultWas::ExplicitSkip ) { - std::string elementName; - switch( result.getResultType() ) { - case ResultWas::ThrewException: - case ResultWas::FatalErrorCondition: - elementName = "error"; - break; - case ResultWas::ExplicitFailure: - case ResultWas::ExpressionFailed: - case ResultWas::DidntThrowException: - elementName = "failure"; - break; - case ResultWas::ExplicitSkip: - elementName = "skipped"; - break; - // We should never see these here: - case ResultWas::Info: - case ResultWas::Warning: - case ResultWas::Ok: - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - elementName = "internalError"; - break; - } - - XmlWriter::ScopedElement e = xml.scopedElement( elementName ); - - xml.writeAttribute( "message"_sr, result.getExpression() ); - xml.writeAttribute( "type"_sr, result.getTestMacroName() ); - - ReusableStringStream rss; - if ( result.getResultType() == ResultWas::ExplicitSkip ) { - rss << "SKIPPED\n"; - } else { - rss << "FAILED" << ":\n"; - if (result.hasExpression()) { - rss << " "; - rss << result.getExpressionInMacro(); - rss << '\n'; - } - if (result.hasExpandedExpression()) { - rss << "with expansion:\n"; - rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n'; - } - } - - if( result.hasMessage() ) - rss << result.getMessage() << '\n'; - for( auto const& msg : stats.infoMessages ) - if( msg.type == ResultWas::Info ) - rss << msg.message << '\n'; - - rss << "at " << result.getSourceInfo(); - xml.writeText( rss.str(), XmlFormatting::Newline ); + if ( result.isOk() && + result.getResultType() != ResultWas::ExplicitSkip ) { + return false; } + std::string elementName; + switch ( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + case ResultWas::ExpressionFailed: + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + case ResultWas::ExplicitSkip: + elementName = "skipped"; + break; + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message"_sr, result.getExpression() ); + xml.writeAttribute( "type"_sr, result.getTestMacroName() ); + + ReusableStringStream rss; + if ( result.getResultType() == ResultWas::ExplicitSkip ) { + rss << "SKIPPED\n"; + } else { + rss << "FAILED:\n"; + if ( result.hasExpression() ) { + rss << " "; + rss << result.getExpressionInMacro(); + rss << '\n'; + } + if ( result.hasExpandedExpression() ) { + rss << "with expansion:\n"; + rss << TextFlow::Column( result.getExpandedExpression() ) + .indent( 2 ) + << '\n'; + } + } + + if ( result.hasMessage() ) { rss << result.getMessage() << '\n'; } + for ( auto const& msg : stats.infoMessages ) { + if ( msg.type == ResultWas::Info ) { rss << msg.message << '\n'; } + } + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), XmlFormatting::Newline ); + return true; } } // end namespace Catch diff --git a/3rdparty/catch/catch_amalgamated.hpp b/3rdparty/catch/catch_amalgamated.hpp index 6de74f8..7a21dd5 100644 --- a/3rdparty/catch/catch_amalgamated.hpp +++ b/3rdparty/catch/catch_amalgamated.hpp @@ -6,8 +6,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.13.0 -// Generated: 2026-02-15 22:54:59.817776 +// Catch v3.16.0 +// Generated: 2026-08-25 09:29:22.652020 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -118,16 +118,24 @@ #endif // CATCH_PLATFORM_HPP_INCLUDED -#ifdef __cplusplus +#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +#endif -# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define CATCH_CPP17_OR_GREATER -# endif - -# if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) -# define CATCH_CPP20_OR_GREATER -# endif +#if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +# define CATCH_CPP20_OR_GREATER +#endif +// Matchers are only constexpr-able in C++20 +#if defined( CATCH_CPP20_OR_GREATER ) && \ + defined( __cpp_constexpr_dynamic_alloc ) && \ + __cpp_constexpr_dynamic_alloc >= 201907L && \ + /* GCC < 13 define the feature macro, but compiler bugs stop us from using it */ \ + ( !defined( __GNUC__ ) || __GNUC__ >= 13 || defined(__clang__) ) +# define CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED +# define CATCH_DESTRUCTOR_CONSTEXPR constexpr +#else +# define CATCH_DESTRUCTOR_CONSTEXPR #endif // Only GCC compiler should be used in this block, so other compilers trying to @@ -201,10 +209,15 @@ # define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wc++20-extensions\"" ) # else -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) # endif +# if ( __clang_major__ >= 22 ) +# define CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wc2y-extensions\"" ) +# endif + # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) @@ -520,6 +533,9 @@ #if !defined( CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS ) # define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS #endif +#if !defined( CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS +#endif #if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS @@ -678,8 +694,20 @@ namespace Catch { #endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED + +// Fixme: Clang 22 has an annoying bug where the localized suppression +// below does not actually suppress the extension warning from +// using __COUNTER__, so we have to leak the suppression for the +// whole TU. Hopefully Clang 23 fixes this before full release. +// As AppleClang does its own thing version-wise, we ignore it +// completely. +#if defined( __clang__ ) && ( __clang_major__ >= 22 ) && !defined( __APPLE__ ) +CATCH_INTERNAL_SUPPRESS_COUNTER_WARNINGS +#endif + #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) + #ifdef CATCH_CONFIG_COUNTER # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else @@ -1011,35 +1039,16 @@ namespace Detail { #ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED #define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_CLOCK_HPP_INCLUDED -#define CATCH_CLOCK_HPP_INCLUDED - -#include - namespace Catch { - namespace Benchmark { - using IDuration = std::chrono::nanoseconds; - using FDuration = std::chrono::duration; - template - using TimePoint = typename Clock::time_point; - - using default_clock = std::chrono::steady_clock; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_CLOCK_HPP_INCLUDED - -namespace Catch { + namespace Detail { + struct DummyTemplateArgPlaceholder; + } // We cannot forward declare the type with default template argument // multiple times, so it is split out into a separate header so that // we can prevent multiple declarations in dependencies - template + template struct BenchmarkStats; } // end namespace Catch @@ -1052,8 +1061,6 @@ namespace Catch { struct AssertionInfo; struct SectionInfo; struct SectionEndInfo; - struct MessageInfo; - struct MessageBuilder; struct Counts; struct AssertionReaction; struct SourceLineInfo; @@ -1093,11 +1100,6 @@ namespace Catch { virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; virtual void benchmarkFailed( StringRef error ) = 0; - static void pushScopedMessage( MessageInfo&& message ); - static void popScopedMessage( unsigned int messageId ); - static void addUnscopedMessage( MessageInfo&& message ); - static void emplaceUnscopedMessage( MessageBuilder&& builder ); - virtual void handleFatalErrorCondition( StringRef message ) = 0; virtual void handleExpr @@ -1123,9 +1125,6 @@ namespace Catch { ResultWas::OfType resultType, AssertionReaction &reaction ) = 0; - - virtual bool lastAssertionPassed() = 0; - // Deprecated, do not use: virtual std::string getCurrentTestName() const = 0; virtual const AssertionResult* getLastResult() const = 0; @@ -1230,7 +1229,7 @@ namespace Catch { class IStream; struct PathFilter; - class IConfig : public Detail::NonCopyable { + class IConfig : Detail::NonCopyable { public: virtual ~IConfig(); @@ -1318,7 +1317,8 @@ namespace Catch { virtual void registerTranslator( Detail::unique_ptr&& translator ) = 0; virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; virtual void registerStartupException() noexcept = 0; - virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; + + virtual ITestCaseRegistry& getMutableTestCaseRegistry() = 0; }; IRegistryHub const& getRegistryHub(); @@ -1336,6 +1336,28 @@ namespace Catch { +// Adapted from donated nonius code. + +#ifndef CATCH_CLOCK_HPP_INCLUDED +#define CATCH_CLOCK_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + using IDuration = std::chrono::nanoseconds; + using FDuration = std::chrono::duration; + + template + using TimePoint = typename Clock::time_point; + + using default_clock = std::chrono::steady_clock; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CLOCK_HPP_INCLUDED + + // Adapted from donated nonius code. #ifndef CATCH_ESTIMATE_HPP_INCLUDED @@ -1486,7 +1508,7 @@ namespace Catch { } // namespace Detail #elif defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) -#if defined(_MSVC_VER) +#if defined(_MSC_VER) #pragma optimize("", off) #elif defined(__IAR_SYSTEMS_ICC__) // For IAR the pragma only affects the following function @@ -1523,6 +1545,13 @@ namespace Catch { template inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t::value> { CATCH_FORWARD((fn)) (CATCH_FORWARD(args)...); + // In the non-void case, we pass the result through `deoptimize_value` + // to force the compiler to keep it. We have no return value here, + // but add an optimizer barrier (ideally a memory clobber) to force + // the _side effects_ of the loop be visible (e.g. writes to globals). + // Note that writes to benchmark-locals can be optimized away, as + // we would expect in normal code. + Detail::optimizer_barrier(); } } // namespace Benchmark } // namespace Catch @@ -1658,7 +1687,7 @@ namespace Catch { callable& operator=(callable&&) = default; }; template - struct model : public callable { + struct model final : public callable { model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {} model(Fun const& fun_) : fun(fun_) {} @@ -2139,9 +2168,94 @@ namespace Catch { #include #include #include +#include namespace Catch { namespace Benchmark { + namespace Detail { + template + ExecutionPlan prepare( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + auto min_time = + env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = + std::max( min_time, + std::chrono::duration_cast( + cfg.benchmarkWarmupTime() ) ); + auto&& test = Detail::run_for_at_least( + std::chrono::duration_cast( run_time ), 1, fun ); + int new_iters = static_cast( + std::ceil( min_time * test.iterations / test.elapsed ) ); + return { new_iters, + test.elapsed / test.iterations * new_iters * + cfg.benchmarkSamples(), + CATCH_MOVE( fun ), + std::chrono::duration_cast( + cfg.benchmarkWarmupTime() ), + Detail::warmup_iterations }; + } + + // These are wrappers for their respective function templated + // over `default_clock`. This allows outlining the usual use + // of the template into single TU and save on compilation costs. + + Environment measure_environment_default(); + ExecutionPlan prepare_default( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ); + std::vector run_plan_default( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ); + + template + std::enable_if_t::value, + Environment> + measureEnvironmentDispatch() { + return measure_environment_default(); + } + template + std::enable_if_t::value, + Environment> + measureEnvironmentDispatch() { + return measure_environment(); + } + + template + std::enable_if_t::value, + std::vector> + runPlanDispatch( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ) { + return run_plan_default( plan, cfg, env ); + } + template + std::enable_if_t::value, + std::vector> + runPlanDispatch( ExecutionPlan const& plan, + const IConfig& cfg, + Environment env ) { + return plan.template run( cfg, env ); + } + + template + std::enable_if_t::value, + ExecutionPlan> + prepareDispatch( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + return prepare_default( cfg, env, CATCH_MOVE( fun ) ); + } + template + std::enable_if_t::value, + ExecutionPlan> + prepareDispatch( const IConfig& cfg, + Environment env, + BenchmarkFunction&& fun ) { + return prepare( cfg, env, CATCH_MOVE( fun ) ); + } + } // namespace Detail + struct Benchmark { Benchmark(std::string&& benchmarkName) : name(CATCH_MOVE(benchmarkName)) {} @@ -2150,27 +2264,18 @@ namespace Catch { Benchmark(std::string&& benchmarkName , FUN &&func) : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} - template - ExecutionPlan prepare(const IConfig &cfg, Environment env) { - auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; - auto run_time = std::max(min_time, std::chrono::duration_cast(cfg.benchmarkWarmupTime())); - auto&& test = Detail::run_for_at_least(std::chrono::duration_cast(run_time), 1, fun); - int new_iters = static_cast(std::ceil(min_time * test.iterations / test.elapsed)); - return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), CATCH_MOVE(fun), std::chrono::duration_cast(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; - } - template void run() { static_assert( Clock::is_steady, "Benchmarking clock should be steady" ); auto const* cfg = getCurrentContext().getConfig(); - auto env = Detail::measure_environment(); + auto env = Detail::measureEnvironmentDispatch(); getResultCapture().benchmarkPreparing(name); CATCH_TRY{ auto plan = user_code([&] { - return prepare(*cfg, env); + return Detail::prepareDispatch( *cfg, env, CATCH_MOVE(fun) ); }); BenchmarkInfo info { @@ -2186,7 +2291,7 @@ namespace Catch { getResultCapture().benchmarkStarting(info); auto samples = user_code([&] { - return plan.template run(*cfg, env); + return Detail::runPlanDispatch( plan, *cfg, env ); }); auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); @@ -2446,45 +2551,46 @@ namespace Catch { #endif // CATCH_VOID_TYPE_HPP_INCLUDED -#ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED -#define CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED +#ifndef CATCH_ENUM_INFO_HPP_INCLUDED +#define CATCH_ENUM_INFO_HPP_INCLUDED +#include +#include #include namespace Catch { - namespace Detail { + struct EnumInfo { StringRef m_name; - std::vector> m_values; + std::vector> m_values; - ~EnumInfo(); - - StringRef lookup( int value ) const; + StringRef lookup( int64_t value ) const; }; - } // namespace Detail - class IMutableEnumValuesRegistry { - public: - virtual ~IMutableEnumValuesRegistry(); // = default; - - virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values ) = 0; - - template - Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list values ) { - static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); - std::vector intValues; + EnumInfo makeEnumInfo( StringRef enumName, + StringRef allValueNames, + std::vector const& values ); + template + Detail::EnumInfo makeEnumInfo( StringRef enumName, + StringRef allEnums, + std::initializer_list values ) { + static_assert( sizeof( int64_t ) >= sizeof( E ), + "Cannot serialize enum to int64_t" ); + std::vector intValues; intValues.reserve( values.size() ); - for( auto enumValue : values ) - intValues.push_back( static_cast( enumValue ) ); - return registerEnum( enumName, allEnums, intValues ); + for ( auto enumValue : values ) + intValues.push_back( static_cast( enumValue ) ); + return makeEnumInfo( enumName, allEnums, intValues ); } - }; -} // Catch + std::vector parseEnums( StringRef enums ); -#endif // CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + } // namespace Detail +} // namespace Catch + +#endif // CATCH_ENUM_INFO_HPP_INCLUDED #ifdef CATCH_CONFIG_CPP17_STRING_VIEW #include @@ -2849,7 +2955,10 @@ namespace Catch { } #endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER -#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) +#if defined( CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER ) && \ + defined( CATCH_CONFIG_CPP17_OPTIONAL ) && \ + /* P3168 turned optional into a range, making this ambigous with the range support */ \ + !defined( __cpp_lib_optional_range_support ) #include namespace Catch { template @@ -3096,16 +3205,21 @@ struct ratio_string { }; } - -#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ -namespace Catch { \ - template<> struct StringMaker { \ - static std::string convert( enumName value ) { \ - static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ - return static_cast(enumInfo.lookup( static_cast( value ) )); \ - } \ - }; \ -} +#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ + namespace Catch { \ + template <> \ + struct StringMaker { \ + static std::string convert( enumName value ) { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + static const auto enumInfo = ::Catch::Detail::makeEnumInfo( \ + #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + return static_cast( \ + enumInfo.lookup( static_cast( value ) ) ); \ + } \ + }; \ + } #define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) @@ -3707,6 +3821,7 @@ namespace Catch { std::vector splitReporterSpec( StringRef reporterSpec ); Optional stringToColourMode( StringRef colourMode ); + Optional stringToVerbosity( StringRef verbosity ); } /** @@ -3721,6 +3836,7 @@ namespace Catch { std::string m_name; Optional m_outputFileName; Optional m_colourMode; + Optional m_verbosity; std::map m_customOptions; friend bool operator==( ReporterSpec const& lhs, @@ -3735,6 +3851,7 @@ namespace Catch { std::string name, Optional outputFileName, Optional colourMode, + Optional verbosity, std::map customOptions ); std::string const& name() const { return m_name; } @@ -3745,13 +3862,15 @@ namespace Catch { Optional const& colourMode() const { return m_colourMode; } + Optional const& verbosity() const { return m_verbosity; } + std::map const& customOptions() const { return m_customOptions; } }; /** - * Parses provided reporter spec string into + * Parses provided reporter spec string into actual `ReporterSpec` * * Returns empty optional on errors, e.g. * * field that is not first and not a key+value pair @@ -3784,6 +3903,7 @@ namespace Catch { std::string name; std::string outputFilename; ColourMode colourMode; + Verbosity verbosity; std::map customOptions; friend bool operator==( ProcessedReporterSpec const& lhs, ProcessedReporterSpec const& rhs ); @@ -3811,6 +3931,7 @@ namespace Catch { int abortAfter = -1; uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default); + bool rngSeedWasFixed = false; unsigned int shardCount = 1; unsigned int shardIndex = 0; @@ -3882,6 +4003,7 @@ namespace Catch { double minDuration() const override; TestRunOrder runOrder() const override; uint32_t rngSeed() const override; + bool rngSeedWasFixed() const; unsigned int shardCount() const override; unsigned int shardIndex() const override; ColourMode defaultColourMode() const override; @@ -4026,9 +4148,21 @@ namespace Catch { #include namespace Catch { + struct MessageInfo; + struct MessageBuilder; + + namespace Detail { + // The message state affecting functions have to be defined in + // the TU where the thread-local message holders are defined. + // Currently this is catch_run_context.cpp + + void pushScopedMessage( MessageInfo&& message ); + void popScopedMessage( unsigned int messageId ); + void addUnscopedMessage( MessageInfo&& message ); + void emplaceUnscopedMessage( MessageBuilder&& builder ); + } // namespace Detail struct SourceLineInfo; - class IResultCapture; struct MessageStream { @@ -4118,7 +4252,7 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ - Catch::IResultCapture::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + Catch::Detail::emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) #if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) @@ -5407,7 +5541,7 @@ namespace Catch { void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); template - class BinaryExpr : public ITransientExpression { + class BinaryExpr final : public ITransientExpression { LhsT m_lhs; StringRef m_op; RhsT m_rhs; @@ -5483,7 +5617,7 @@ namespace Catch { }; template - class UnaryExpr : public ITransientExpression { + class UnaryExpr final : public ITransientExpression { LhsT m_lhs; void streamReconstructedExpression( std::ostream &os ) const override { @@ -5684,6 +5818,8 @@ namespace Catch { namespace Catch { + class RunContext; + struct AssertionReaction { bool shouldDebugBreak = false; bool shouldThrow = false; @@ -5694,7 +5830,12 @@ namespace Catch { AssertionInfo m_assertionInfo; AssertionReaction m_reaction; bool m_completed = false; - IResultCapture& m_resultCapture; + // Since all uses are hidden in the .cpp file, we can directly use + // the final type and avoid going through virtual dispatch, without + // massive compilation time overhead. + RunContext& m_resultCapture; + + void finishIncomplete(); public: AssertionHandler @@ -5703,9 +5844,9 @@ namespace Catch { StringRef capturedExpression, ResultDisposition::Flags resultDisposition ); ~AssertionHandler() { - if ( !m_completed ) { - m_resultCapture.handleIncomplete( m_assertionInfo ); - } + // We want the common fast path inlinable, and the virtual + // dispatch in a function in single TU. + if ( !m_completed ) { finishIncomplete(); } } @@ -5748,6 +5889,13 @@ namespace Catch { #endif // CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED +namespace Catch { + namespace Detail { + // Defined in catch_run_context.cpp, where the thread-local data lives. + bool lastAssertionPassed(); + } +} + // We need this suppression to leak, because it took until GCC 10 // for the front end to handle local suppression via _Pragma properly #if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ <= 9 @@ -5790,12 +5938,12 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ - if( Catch::getResultCapture().lastAssertionPassed() ) + if( Catch::Detail::lastAssertionPassed() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ - if( !Catch::getResultCapture().lastAssertionPassed() ) + if( !Catch::Detail::lastAssertionPassed() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ @@ -6078,7 +6226,7 @@ namespace Catch { namespace Catch { template -class TestInvokerAsMethod : public ITestInvoker { +class TestInvokerAsMethod final : public ITestInvoker { void (C::*m_testAsMethod)(); public: constexpr TestInvokerAsMethod( void ( C::*testAsMethod )() ) noexcept: @@ -6098,7 +6246,7 @@ Detail::unique_ptr makeTestInvoker( void (C::*testAsMethod)() ) { } template -class TestInvokerFixture : public ITestInvoker { +class TestInvokerFixture final : public ITestInvoker { void ( C::*m_testAsMethod )() const; Detail::unique_ptr m_fixture = nullptr; @@ -6173,7 +6321,7 @@ struct AutoReg : Detail::NonCopyable { namespace Catch { namespace Detail { struct DummyUse { - DummyUse( void ( * )( int ), Catch::NameAndTags const& ); + DummyUse( void ( * )( int ), Catch::NameAndTags const& ) noexcept; }; } // namespace Detail } // namespace Catch @@ -6569,6 +6717,51 @@ namespace Catch { struct priority_tag : priority_tag {}; template <> struct priority_tag<0> {}; + + // This is a bunch of helpers for the templated test case handling. + // They should live elsewhere in the long run, but as an in-between + // step we toss them all here. + template struct TypeList {}; + template + constexpr auto get_wrapper( priority_tag<1> ) noexcept -> TypeList { return {}; } + template