diff --git a/3rdparty/catch/catch_amalgamated.cpp b/3rdparty/catch/catch_amalgamated.cpp index f45c18a01..adafc64bd 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.7.1 -// Generated: 2024-09-17 10:36:45.608896 +// Catch v3.13.0 +// Generated: 2026-02-15 22:55:00.269529 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -332,7 +332,7 @@ namespace Catch { double diff = b - m; return a + diff * diff; } ) / - ( last - first ); + static_cast( last - first ); return std::sqrt( variance ); } @@ -367,7 +367,7 @@ namespace Catch { double* first, double* last ) { auto count = last - first; - double idx = (count - 1) * k / static_cast(q); + double idx = static_cast((count - 1) * k) / static_cast(q); int j = static_cast(idx); double g = idx - j; std::nth_element(first, first + j, last); @@ -470,10 +470,10 @@ namespace Catch { double accel = sum_cubes / ( 6 * std::pow( sum_squares, 1.5 ) ); long n = static_cast( resample.size() ); - double prob_n = + double prob_n = static_cast( std::count_if( resample.begin(), resample.end(), - [point]( double x ) { return x < point; } ) / + [point]( double x ) { return x < point; } )) / static_cast( n ); // degenerate case with uniform samples if ( Catch::Detail::directCompare( prob_n, 0. ) ) { @@ -798,6 +798,10 @@ namespace Catch { lhs.customOptions == rhs.customOptions; } + bool operator==( PathFilter const& lhs, PathFilter const& rhs ) { + return lhs.type == rhs.type && lhs.filter == rhs.filter; + } + Config::Config( ConfigData const& data ): m_data( data ) { // We need to trim filter specs to avoid trouble with superfluous @@ -807,9 +811,6 @@ namespace Catch { for (auto& elem : m_data.testsOrTags) { elem = trim(elem); } - for (auto& elem : m_data.sectionsToRun) { - elem = trim(elem); - } // Insert the default reporter if user hasn't asked for a specific one if ( m_data.reporterSpecifications.empty() ) { @@ -825,6 +826,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(); } @@ -873,7 +876,8 @@ namespace Catch { bool Config::listListeners() const { return m_data.listListeners; } std::vector const& Config::getTestsOrTags() const { return m_data.testsOrTags; } - std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + std::vector const& Config::getPathFilters() const { return m_data.pathFilters; } + bool Config::useNewFilterBehaviour() const { return m_data.useNewPathFilteringBehaviour; } std::vector const& Config::getReporterSpecs() const { return m_data.reporterSpecifications; @@ -889,6 +893,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; } @@ -899,6 +905,9 @@ namespace Catch { bool Config::warnAboutUnmatchedTestSpecs() const { return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec ); } + bool Config::warnAboutInfiniteGenerators() const { + return !!( m_data.warnings & WarnAbout::InfiniteGenerator ); + } bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; } ShowDurations Config::showDurations() const { return m_data.showDurations; } double Config::minDuration() const { return m_data.minDuration; } @@ -950,6 +959,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 +1004,28 @@ 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 ( !uncaught_exceptions() && !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, + bool isScoped): + m_isScoped(isScoped) { auto trimmed = [&] (size_t start, size_t end) { while (names[start] == ',' || isspace(static_cast(names[start]))) { ++start; @@ -1042,8 +1071,8 @@ namespace Catch { case ',': if (start != pos && openings.empty()) { m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(trimmed(start, pos)); - m_messages.back().message += " := "; + m_messages.back().message += trimmed(start, pos); + m_messages.back().message += " := "_sr; start = pos; } break; @@ -1052,21 +1081,26 @@ namespace Catch { } assert(openings.empty() && "Mismatched openings"); m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(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() { - if ( !uncaught_exceptions() ){ - assert( m_captured == m_messages.size() ); - for( size_t i = 0; i < m_captured; ++i ) - m_resultCapture.popScopedMessage( m_messages[i] ); + assert( m_captured == m_messages.size() ); + if ( m_isScoped ) { + 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] ); + if ( m_isScoped ) { + IResultCapture::pushScopedMessage( CATCH_MOVE( m_messages[index] ) ); + } else { + IResultCapture::addUnscopedMessage( CATCH_MOVE( m_messages[index] ) ); + } m_captured++; } @@ -1150,7 +1184,6 @@ namespace Catch { } void cleanUp() { cleanupSingletons(); - cleanUpContext(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); @@ -1161,8 +1194,9 @@ namespace Catch { -#include #include +#include +#include #include #include #include @@ -1170,14 +1204,6 @@ namespace Catch { namespace Catch { namespace { - static constexpr int TestFailureExitCode = 42; - static constexpr int UnspecifiedErrorExitCode = 1; - static constexpr int AllTestsSkippedExitCode = 4; - static constexpr int NoTestsRunExitCode = 2; - static constexpr int UnmatchedTestSpecExitCode = 3; - static constexpr int InvalidTestSpecExitCode = 5; - - IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) { auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config)); CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\''); @@ -1285,6 +1311,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() { @@ -1403,6 +1473,7 @@ namespace Catch { static_cast(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(std::getchar()); @@ -1443,6 +1514,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) { @@ -1472,9 +1547,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; } @@ -1926,7 +2004,7 @@ namespace Catch { return static_cast(getElapsedMicroseconds()/1000); } auto Timer::getElapsedSeconds() const -> double { - return getElapsedMicroseconds()/1000000.0; + return static_cast(getElapsedMicroseconds())/1000000.0; } @@ -1935,7 +2013,6 @@ namespace Catch { -#include #include namespace Catch { @@ -1946,7 +2023,10 @@ namespace Detail { const int hexThreshold = 255; struct Endianness { - enum Arch { Big, Little }; + enum Arch : uint8_t { + Big, + Little + }; static Arch which() { int one = 1; @@ -1978,39 +2058,69 @@ namespace Detail { } } // end unnamed namespace + std::size_t catch_strnlen( const char* str, std::size_t n ) { + auto ret = std::char_traits::find( str, n, '\0' ); + if ( ret != nullptr ) { return static_cast( ret - str ); } + return n; + } + + std::string formatTimeT(std::time_t time) { +#ifdef _MSC_VER + std::tm timeInfo = {}; + const auto err = gmtime_s( &timeInfo, &time ); + if ( err ) { + return "gmtime from provided timepoint has failed. This " + "happens e.g. with pre-1970 dates using Microsoft libc"; + } +#else + std::tm* timeInfo = std::gmtime( &time ); +#endif + + auto const timeStampSize = sizeof( "2017-01-16T17:06:45Z" ); + char timeStamp[timeStampSize]; + const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime( timeStamp, timeStampSize, fmt, &timeInfo ); +#else + std::strftime( timeStamp, timeStampSize, fmt, timeInfo ); +#endif + return std::string( timeStamp, timeStampSize - 1 ); + } + std::string convertIntoString(StringRef string, bool escapeInvisibles) { 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; @@ -2035,6 +2145,13 @@ namespace Detail { rss << std::setw(2) << static_cast(bytes[i]); return rss.str(); } + + std::string makeExceptionHappenedString() { + return "{ stringification failed with an exception: \"" + + translateActiveException() + "\" }"; + + } + } // end Detail namespace @@ -2280,7 +2397,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 3, 7, 1, "", 0 ); + static Version version( 3, 13, 0, "", 0 ); return version; } @@ -2306,14 +2423,6 @@ namespace Catch { namespace Generators { -namespace Detail { - - [[noreturn]] - void throw_generator_exception(char const* msg) { - Catch::throw_exception(GeneratorException{ msg }); - } -} // end namespace Detail - GeneratorUntypedBase::~GeneratorUntypedBase() = default; IGeneratorTracker* acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const& lineInfo ) { @@ -2361,6 +2470,11 @@ namespace Catch { m_current_number = m_pimpl->dist( m_pimpl->rng ); return true; } + + bool RandomFloatingGenerator::isFinite() const { + return false; + } + } // namespace Generators } // namespace Catch @@ -2368,8 +2482,30 @@ namespace Catch { namespace Catch { + namespace Generators { + namespace Detail { + + [[noreturn]] + void throw_generator_exception( char const* msg ) { + Catch::throw_exception( GeneratorException{ msg } ); + } + + } // namespace Detail + } // namespace Generators +} // namespace Catch + + + + +namespace Catch { + namespace Detail { + void missingCaptureInstance() { + CATCH_INTERNAL_ERROR( "No result capture instance" ); + } + } // namespace Detail + IResultCapture::~IResultCapture() = default; -} +} // namespace Catch @@ -2388,6 +2524,7 @@ namespace Catch { + #include namespace Catch { @@ -2402,6 +2539,31 @@ namespace Catch { return ret; } + void GeneratorUntypedBase::skipToNthElementImpl( std::size_t n ) { + for ( size_t i = m_currentElementIndex; i < n; ++i ) { + bool isValid = next(); + if ( !isValid ) { + Detail::throw_generator_exception( + "Coud not jump to Nth element: not enough elements" ); + } + } + } + + void GeneratorUntypedBase::skipToNthElement( std::size_t n ) { + if ( n < m_currentElementIndex ) { + Detail::throw_generator_exception( + "Tried to jump generator backwards" ); + } + if ( n == m_currentElementIndex ) { return; } + + skipToNthElementImpl(n); + // Fixup tracking after moving the generator forward + // * Ensure that the correct element index is set after skipping + // * Invalidate cache + m_currentElementIndex = n; + m_stringReprCache.clear(); + } + StringRef GeneratorUntypedBase::currentElementAsString() const { if ( m_stringReprCache.empty() ) { m_stringReprCache = stringifyImpl(); @@ -2409,6 +2571,8 @@ namespace Catch { return m_stringReprCache; } + bool GeneratorUntypedBase::isFinite() const { return true; } + } // namespace Generators } // namespace Catch @@ -3089,6 +3253,9 @@ namespace Catch { } else if ( warning == "UnmatchedTestSpec" ) { config.warnings = static_cast(config.warnings | WarnAbout::UnmatchedTestSpec); return ParserResult::ok( ParseResultType::Matched ); + } else if ( warning == "InfiniteGenerators" ) { + config.warnings = static_cast(config.warnings | WarnAbout::InfiniteGenerator); + return ParserResult::ok( ParseResultType::Matched ); } return ParserResult ::runtimeError( @@ -3246,6 +3413,19 @@ namespace Catch { config.shardCount = *parsedCount; return ParserResult::ok( ParseResultType::Matched ); }; + auto const setBenchmarkSamples = [&]( std::string const& samples ) { + auto parsedSamples = parseUInt( samples ); + if ( !parsedSamples ) { + return ParserResult::runtimeError( + "Could not parse '" + samples + "' as benchmark samples" ); + } + if ( *parsedSamples == 0 ) { + return ParserResult::runtimeError( + "Benchmark samples must be greater than 0" ); + } + config.benchmarkSamples = *parsedSamples; + return ParserResult::ok( ParseResultType::Matched ); + }; auto const setShardIndex = [&](std::string const& shardIndex) { auto parsedIndex = parseUInt( shardIndex ); @@ -3257,6 +3437,43 @@ namespace Catch { return ParserResult::ok( ParseResultType::Matched ); }; + auto const setSectionFilter = [&]( std::string const& sectionFilter ) { + config.pathFilters.emplace_back( PathFilter::For::Section, trim(sectionFilter) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setGeneratorFilter = [&]( std::string const& generatorFilter ) { + if (generatorFilter != "*") { + // TODO: avoid re-parsing the index? + auto parsedIndex = parseUInt( generatorFilter ); + if ( !parsedIndex ) { + return ParserResult::runtimeError( "Could not parse '" + + generatorFilter + + "' as generator index" ); + } + } + config.useNewPathFilteringBehaviour = true; + config.pathFilters.emplace_back( PathFilter::For::Generator, trim(generatorFilter) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + // Copy-capturing other `setFoo` functions enables calling them later, + // as the config ref remains valid, but the local lambda vars won't. + auto const setPathFilter = [=, &config]( std::string const& pathFilter ) { + config.useNewPathFilteringBehaviour = true; + if ( pathFilter.size() < 3 ) { + return ParserResult::runtimeError( + "Path filter '" + pathFilter + "' is too short" ); + } + if ( startsWith( pathFilter, "g:" ) ) { + return setGeneratorFilter( pathFilter.substr( 2 ) ); + } + if ( startsWith( pathFilter, "c:" ) ) { + return setSectionFilter( pathFilter.substr( 2 ) ); + } + return ParserResult::runtimeError( "Path filter '" + pathFilter + + "' has unknown type prefix" ); + }; + + auto cli = ExeName( config.processName ) | Help( config.showHelp ) @@ -3302,9 +3519,15 @@ namespace Catch { | Opt( config.filenamesAsTags ) ["-#"]["--filenames-as-tags"] ( "adds a tag for the filename" ) - | Opt( config.sectionsToRun, "section name" ) + | Opt( accept_many, setSectionFilter, "section name" ) ["-c"]["--section"] ( "specify section to run" ) + | Opt( accept_many, setGeneratorFilter, "index spec" ) + ["-g"]["--generator-index"] + ( "specify generator elements to try" ) + | Opt( accept_many, setPathFilter, "path filter spec" ) + ["-p"]["--path-filter"] + ( "qualified path filter" ) | Opt( setVerbosity, "quiet|normal|high" ) ["-v"]["--verbosity"] ( "set output verbosity" ) @@ -3322,7 +3545,7 @@ namespace Catch { ( "list all listeners" ) | Opt( setTestOrder, "decl|lex|rand" ) ["--order"] - ( "test case order (defaults to decl)" ) + ( "test case order (defaults to rand)" ) | Opt( setRngSeed, "'time'|'random-device'|number" ) ["--rng-seed"] ( "set a specific seed for random numbers" ) @@ -3338,7 +3561,7 @@ namespace Catch { | Opt( config.skipBenchmarks) ["--skip-benchmarks"] ( "disable running benchmarks") - | Opt( config.benchmarkSamples, "samples" ) + | Opt( setBenchmarkSamples, "samples" ) ["--benchmark-samples"] ( "number of samples to collect (default: 100)" ) | Opt( config.benchmarkResamples, "resamples" ) @@ -3362,6 +3585,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" ); @@ -3516,7 +3742,13 @@ namespace { #endif // Windows/ ANSI/ None -#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) +#if defined( CATCH_PLATFORM_LINUX ) \ + || defined( CATCH_PLATFORM_MAC ) \ + || defined( __GLIBC__ ) \ + || (defined( __FreeBSD__ ) \ + /* PlayStation platform does not have `isatty()` */ \ + && !defined(CATCH_PLATFORM_PLAYSTATION)) \ + || defined( CATCH_PLATFORM_QNX ) # define CATCH_INTERNAL_HAS_ISATTY # include #endif @@ -3640,20 +3872,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() { @@ -3758,7 +3980,7 @@ namespace Catch { #endif } // namespace Catch -#elif defined(CATCH_PLATFORM_LINUX) +#elif defined(CATCH_PLATFORM_LINUX) || defined(CATCH_PLATFORM_QNX) #include #include @@ -3990,10 +4212,10 @@ namespace Catch { // To avoid having to handle TFE explicitly everywhere, we just // rethrow it so that it goes back up the caller. catch( TestFailureException& ) { - std::rethrow_exception(std::current_exception()); + return "{ nested assertion failed }"; } catch( TestSkipException& ) { - std::rethrow_exception(std::current_exception()); + return "{ nested SKIP() called }"; } catch( std::exception const& ex ) { return ex.what(); @@ -4085,30 +4307,34 @@ namespace Catch { // There is no 1-1 mapping between signals and windows exceptions. // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. - static SignalDefs signalDefs[] = { + static constexpr SignalDefs signalDefs[] = { { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, { 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() { @@ -4156,7 +4382,7 @@ namespace Catch { const char* name; }; - static SignalDefs signalDefs[] = { + static constexpr SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, { SIGFPE, "SIGFPE - Floating point error signal" }, @@ -4320,8 +4546,6 @@ namespace Catch { #include #include -#include -#include namespace Catch { @@ -4462,6 +4686,33 @@ namespace Detail { namespace Catch { + + namespace { + static bool needsEscape( char c ) { + return c == '"' || c == '\\' || c == '\b' || c == '\f' || + c == '\n' || c == '\r' || c == '\t'; + } + + static Catch::StringRef makeEscapeStringRef( char c ) { + if ( c == '"' ) { + return "\\\""_sr; + } else if ( c == '\\' ) { + return "\\\\"_sr; + } else if ( c == '\b' ) { + return "\\b"_sr; + } else if ( c == '\f' ) { + return "\\f"_sr; + } else if ( c == '\n' ) { + return "\\n"_sr; + } else if ( c == '\r' ) { + return "\\r"_sr; + } else if ( c == '\t' ) { + return "\\t"_sr; + } + Catch::Detail::Unreachable(); + } + } // namespace + void JsonUtils::indent( std::ostream& os, std::uint64_t level ) { for ( std::uint64_t i = 0; i < level; ++i ) { os << " "; @@ -4571,30 +4822,19 @@ namespace Catch { void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) { if ( quote ) { m_os << '"'; } - for (char c : value) { - // Escape list taken from https://www.json.org/json-en.html, - // string definition. - // Note that while forward slash _can_ be escaped, it does - // not have to be, if JSON is not further embedded somewhere - // where forward slash is meaningful. - if ( c == '"' ) { - m_os << "\\\""; - } else if ( c == '\\' ) { - m_os << "\\\\"; - } else if ( c == '\b' ) { - m_os << "\\b"; - } else if ( c == '\f' ) { - m_os << "\\f"; - } else if ( c == '\n' ) { - m_os << "\\n"; - } else if ( c == '\r' ) { - m_os << "\\r"; - } else if ( c == '\t' ) { - m_os << "\\t"; - } else { - m_os << c; + 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; } } + if ( current_start < value.size() ) { + m_os << value.substr( current_start, value.size() - current_start ); + } if ( quote ) { m_os << '"'; } } @@ -4762,7 +5002,7 @@ namespace Catch { namespace Catch { CATCH_INTERNAL_START_WARNINGS_SUPPRESSION CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS - static LeakDetector leakDetector; + static const LeakDetector leakDetector; CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION } @@ -4791,18 +5031,22 @@ int main (int argc, char * argv[]) { namespace Catch { - MessageInfo::MessageInfo( StringRef _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ) + namespace { + // Messages are owned by their individual threads, so the counter should + // be thread-local as well. Alternative consideration: atomic counter, + // so threads don't share IDs and things are easier to debug. + static CATCH_INTERNAL_THREAD_LOCAL unsigned int messageIDCounter = 0; + } + + MessageInfo::MessageInfo( StringRef _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) : macroName( _macroName ), lineInfo( _lineInfo ), type( _type ), - sequence( ++globalCount ) + sequence( ++messageIDCounter ) {} - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; - } // end namespace Catch @@ -5213,6 +5457,12 @@ namespace Catch { +#if defined( __clang__ ) +# define CATCH2_CLANG_NO_SANITIZE_INTEGER \ + __attribute__( ( no_sanitize( "unsigned-integer-overflow" ) ) ) +#else +# define CATCH2_CLANG_NO_SANITIZE_INTEGER +#endif namespace Catch { namespace { @@ -5222,6 +5472,7 @@ namespace { #pragma warning(disable:4146) // we negate uint32 during the rotate #endif // Safe rotr implementation thanks to John Regehr + CATCH2_CLANG_NO_SANITIZE_INTEGER uint32_t rotate_right(uint32_t val, uint32_t count) { const uint32_t mask = 31; count &= mask; @@ -5255,10 +5506,11 @@ namespace { } } + CATCH2_CLANG_NO_SANITIZE_INTEGER SimplePcg32::result_type SimplePcg32::operator()() { // prepare the output value const uint32_t xorshifted = static_cast(((m_state >> 18u) ^ m_state) >> 27u); - const auto output = rotate_right(xorshifted, m_state >> 59u); + const auto output = rotate_right(xorshifted, static_cast(m_state >> 59u)); // advance state m_state = m_state * 6364136223846793005ULL + s_inc; @@ -5543,6 +5795,7 @@ ReporterSpec::ReporterSpec( #include #include +#include #include namespace Catch { @@ -5552,34 +5805,40 @@ namespace Catch { std::vector> m_streams; std::vector m_unused; std::ostringstream m_referenceStream; // Used for copy state/ flags from + Detail::Mutex m_mutex; - auto add() -> std::size_t { + auto add() -> std::pair { + Detail::LockGuard _( m_mutex ); if( m_unused.empty() ) { m_streams.push_back( Detail::make_unique() ); - 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() }; } } - void release( std::size_t index ) { - m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state - m_unused.push_back(index); + void release( std::size_t index, std::ostream* originalPtr ) { + assert( originalPtr ); + originalPtr->copyfmt( m_referenceStream ); // Restore initial flags and other state + + Detail::LockGuard _( m_mutex ); + assert( originalPtr == m_streams[index].get() && "Mismatch between release index and stream ptr" ); + m_unused.push_back( index ); } }; - ReusableStringStream::ReusableStringStream() - : m_index( Singleton::getMutable().add() ), - m_oss( Singleton::getMutable().m_streams[m_index].get() ) - {} + ReusableStringStream::ReusableStringStream() { + std::tie( m_index, m_oss ) = + Singleton::getMutable().add(); + } ReusableStringStream::~ReusableStringStream() { static_cast( m_oss )->str(""); m_oss->clear(); - Singleton::getMutable().release( m_index ); + Singleton::getMutable().release( m_index, m_oss ); } std::string ReusableStringStream::str() const { @@ -5606,12 +5865,46 @@ namespace Catch { struct GeneratorTracker final : TestCaseTracking::TrackerBase, IGeneratorTracker { GeneratorBasePtr m_generator; + // Filtered generator has moved to specific index due to + // a filter, it needs special handling of `countedNext()` + bool m_isFiltered = false; GeneratorTracker( TestCaseTracking::NameAndLocation&& nameAndLocation, TrackerContext& ctx, - ITracker* parent ): - TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ) {} + ITracker* parent, + GeneratorBasePtr&& generator ): + TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ), + m_generator( CATCH_MOVE( generator ) ) { + assert( m_generator && + "Cannot create tracker without generator" ); + + // Handle potential filter and move forward here... + // Old style filters do not affect generators at all + if (m_newStyleFilters && m_allTrackerDepth < m_filterRef->size()) { + auto const& filter = + ( *m_filterRef )[m_allTrackerDepth]; + // Generator cannot be un-entered the way a section + // 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(); + } + // '*' is the wildcard for "all elements in generator" + // used for filtering sections below the generator, but + // not the generator itself. + if ( filter.filter != "*" ) { + m_isFiltered = true; + // TBD: We assume that the filter was validated as + // number during parsing. We should pass it + // as number from the CLI parser. + size_t targetIndex = std::stoul( filter.filter ); + m_generator->skipToNthElement( targetIndex ); + } + } + } static GeneratorTracker* acquire( TrackerContext& ctx, @@ -5655,9 +5948,6 @@ namespace Catch { // TrackerBase interface bool isGeneratorTracker() const override { return true; } - auto hasGenerator() const -> bool override { - return !!m_generator; - } void close() override { TrackerBase::close(); // If a generator has a child (it is followed by a section) @@ -5686,29 +5976,24 @@ namespace Catch { // _can_ start, and thus we should wait for them, or // they cannot start (due to filters), and we shouldn't // wait for them - ITracker* parent = m_parent; - // This is safe: there is always at least one section - // tracker in a test case tracking tree - while ( !parent->isSectionTracker() ) { - parent = parent->parent(); - } - assert( parent && - "Missing root (test case) level section" ); - auto const& parentSection = - static_cast( *parent ); - auto const& filters = parentSection.getFilters(); - // No filters -> no restrictions on running sections - if ( filters.empty() ) { return true; } + // No filters left -> no restrictions on running sections + size_t childDepth = 1 + (m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth); + if ( childDepth >= m_filterRef->size() ) { + return true; + } + // If we are using the new style filters, we need to check + // whether the successive filter is for section or a generator. + if ( m_newStyleFilters + && (*m_filterRef)[childDepth].type != PathFilter::For::Section ) { + return false; + } + // Look for any child section that could match the remaining filters for ( auto const& child : m_children ) { if ( child->isSectionTracker() && - std::find( filters.begin(), - filters.end(), - static_cast( - *child ) - .trimmedName() ) != - filters.end() ) { + static_cast( *child ) + .trimmedName() == StringRef((*m_filterRef)[childDepth].filter) ) { return true; } } @@ -5720,9 +6005,10 @@ namespace Catch { // value, but we do not want to invoke the side-effect if // this generator is still waiting for any child to start. assert( m_generator && "Tracker without generator" ); - if ( should_wait_for_child || - ( m_runState == CompletedSuccessfully && - m_generator->countedNext() ) ) { + if ( should_wait_for_child + || ( m_runState == CompletedSuccessfully + && !m_isFiltered // filtered generators cannot meaningfully move forward, as they would get past the filter + && m_generator->countedNext() ) ) { m_children.clear(); m_runState = Executing; } @@ -5732,30 +6018,149 @@ namespace Catch { auto getGenerator() const -> GeneratorBasePtr const& override { return m_generator; } - void setGenerator( GeneratorBasePtr&& generator ) override { - m_generator = CATCH_MOVE( generator ); - } }; } // namespace } + namespace Detail { + // Assertions are owned by the thread that is executing them. + // This allows for lock-free progress in common cases where we + // do not need to send the assertion events to the reporter. + // This also implies that messages are owned by their respective + // threads, and should not be shared across different threads. + // + // This implies that various pieces of metadata referring to last + // assertion result/source location/message handling, etc + // should also be thread local. For now we just use naked globals + // below, in the future we will want to allocate piece of memory + // from heap, to avoid consuming too much thread-local storage. + // + // Note that we also don't want non-trivial the thread-local variables + // below be initialized for every thread, only for those that touch + // Catch2. To make this work with both GCC/Clang and MSVC, we have to + // make them thread-local magic statics. (Class-level statics have the + // desired semantics on GCC, but not on MSVC). + + // This is used for the "if" part of CHECKED_IF/CHECKED_ELSE + static CATCH_INTERNAL_THREAD_LOCAL bool g_lastAssertionPassed = false; + + // This is the source location for last encountered macro. It is + // used to provide the users with more precise location of error + // when an unexpected exception/fatal error happens. + static CATCH_INTERNAL_THREAD_LOCAL SourceLineInfo + g_lastKnownLineInfo( "DummyLocation", static_cast( -1 ) ); + + // Should we clear message scopes before sending off the messages to + // reporter? Set in `assertionPassedFastPath` to avoid doing the full + // clear there for performance reasons. + static CATCH_INTERNAL_THREAD_LOCAL bool g_clearMessageScopes = false; + + + // Holds the data for both scoped and unscoped messages together, + // to avoid issues where their lifetimes start in wrong order, + // and then are destroyed in wrong order. + class MessageHolder { + // The actual message vector passed to the reporters + std::vector messages; + // IDs of messages from UNSCOPED_X macros, which we have to + // remove manually. + std::vector unscoped_ids; + + public: + // We do not need to special-case the unscoped messages when + // we only keep around the raw msg ids. + ~MessageHolder() = default; + + void addUnscopedMessage( MessageInfo&& info ) { + repairUnscopedMessageInvariant(); + unscoped_ids.push_back( info.sequence ); + messages.push_back( CATCH_MOVE( info ) ); + } + + void addUnscopedMessage(MessageBuilder&& builder) { + MessageInfo info( CATCH_MOVE( builder.m_info ) ); + info.message = builder.m_stream.str(); + addUnscopedMessage( CATCH_MOVE( info ) ); + } + + void addScopedMessage(MessageInfo&& info) { + messages.push_back( CATCH_MOVE( info ) ); + } + + std::vector const& getMessages() const { + return messages; + } + + void removeMessage( 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 improvement is tiny, and we would have to hand-write + // the loop to avoid terrible codegen of reverse iterators + // in debug mode. + auto iter = + std::find_if( messages.begin(), + messages.end(), + [messageId]( MessageInfo const& msg ) { + return msg.sequence == messageId; + } ); + assert( iter != messages.end() && + "Trying to remove non-existent message." ); + messages.erase( iter ); + } + + void removeUnscopedMessages() { + for ( const auto messageId : unscoped_ids ) { + removeMessage( messageId ); + } + unscoped_ids.clear(); + g_clearMessageScopes = false; + } + + void repairUnscopedMessageInvariant() { + if ( g_clearMessageScopes ) { removeUnscopedMessages(); } + g_clearMessageScopes = false; + } + }; + + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + static MessageHolder& g_messageHolder() { + static CATCH_INTERNAL_THREAD_LOCAL MessageHolder value; + return value; + } + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + } // namespace Detail + RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter) : m_runInfo(_config->name()), m_config(_config), m_reporter(CATCH_MOVE(reporter)), - m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ), - m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + m_abortAfterXFailedAssertions( m_config->abortAfter() ), + m_reportAssertionStarting( m_reporter->getPreferences().shouldReportAllAssertionStarts ), + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ), + m_shouldDebugBreak( m_config->shouldDebugBreak() ) { getCurrentMutableContext().setResultCapture( this ); m_reporter->testRunStarting(m_runInfo); + + // TODO: HACK! + // We need to make sure the underlying cache is initialized + // while we are guaranteed to be running in a single thread, + // because the initialization is not thread-safe. + ReusableStringStream rss; + (void)rss; } RunContext::~RunContext() { + updateTotalsFromAtomics(); m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); } Totals RunContext::runTest(TestCaseHandle const& testCase) { + updateTotalsFromAtomics(); const Totals prevTotals = m_totals; auto const& testInfo = testCase.getTestCaseInfo(); @@ -5766,7 +6171,8 @@ namespace Catch { ITracker& rootTracker = m_trackerContext.startRun(); assert(rootTracker.isSectionTracker()); - static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + rootTracker.setFilters( &m_config->getPathFilters(), + m_config->useNewFilterBehaviour() ); // We intentionally only seed the internal RNG once per test case, // before it is first invoked. The reason for that is a complex @@ -5810,6 +6216,7 @@ namespace Catch { m_reporter->testCasePartialStarting(testInfo, testRuns); + updateTotalsFromAtomics(); const auto beforeRunTotals = m_totals; runCurrentTest(); std::string oneRunCout = m_outputRedirect->getStdout(); @@ -5818,6 +6225,7 @@ namespace Catch { redirectedCout += oneRunCout; redirectedCerr += oneRunCerr; + updateTotalsFromAtomics(); const auto singleRunTotals = m_totals.delta(beforeRunTotals); auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting()); m_reporter->testCasePartialEnded(statsForOneRun, testRuns); @@ -5847,47 +6255,52 @@ namespace Catch { void RunContext::assertionEnded(AssertionResult&& result) { + Detail::g_lastKnownLineInfo = result.m_info.lineInfo; if (result.getResultType() == ResultWas::Ok) { - m_totals.assertions.passed++; - m_lastAssertionPassed = true; + m_atomicAssertionCount.passed++; + Detail::g_lastAssertionPassed = true; } else if (result.getResultType() == ResultWas::ExplicitSkip) { - m_totals.assertions.skipped++; - m_lastAssertionPassed = true; + m_atomicAssertionCount.skipped++; + Detail::g_lastAssertionPassed = true; } else if (!result.succeeded()) { - m_lastAssertionPassed = false; + Detail::g_lastAssertionPassed = false; if (result.isOk()) { } - else if( m_activeTestCase->getTestCaseInfo().okToFail() ) - m_totals.assertions.failedButOk++; + else if( m_activeTestCase->getTestCaseInfo().okToFail() ) // Read from a shared state established before the threads could start, this is fine + m_atomicAssertionCount.failedButOk++; else - m_totals.assertions.failed++; + m_atomicAssertionCount.failed++; } else { - m_lastAssertionPassed = true; + Detail::g_lastAssertionPassed = true; } + auto& msgHolder = Detail::g_messageHolder(); + msgHolder.repairUnscopedMessageInvariant(); + + // From here, we are touching shared state and need mutex. + Detail::LockGuard lock( m_assertionMutex ); { auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ); + updateTotalsFromAtomics(); + m_reporter->assertionEnded( AssertionStats( result, msgHolder.getMessages(), m_totals ) ); } if ( result.getResultType() != ResultWas::Warning ) { - m_messageScopes.clear(); + msgHolder.removeUnscopedMessages(); } // Reset working state. assertion info will be reset after // populateReaction is run if it is needed m_lastResult = CATCH_MOVE( result ); } - void RunContext::resetAssertionInfo() { - m_lastAssertionInfo.macroName = StringRef(); - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; - m_lastAssertionInfo.resultDisposition = ResultDisposition::Normal; - } void RunContext::notifyAssertionStarted( AssertionInfo const& info ) { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->assertionStarting( info ); + if (m_reportAssertionStarting) { + Detail::LockGuard lock( m_assertionMutex ); + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->assertionStarting( info ); + } } bool RunContext::sectionStarted( StringRef sectionName, @@ -5903,13 +6316,14 @@ namespace Catch { m_activeSections.push_back(§ionTracker); SectionInfo sectionInfo( sectionLineInfo, static_cast(sectionName) ); - m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + Detail::g_lastKnownLineInfo = sectionLineInfo; { auto _ = scopedDeactivate( *m_outputRedirect ); m_reporter->sectionStarting( sectionInfo ); } + updateTotalsFromAtomics(); assertions = m_totals.assertions; return true; @@ -5917,12 +6331,11 @@ namespace Catch { IGeneratorTracker* RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) { - using namespace Generators; - GeneratorTracker* tracker = GeneratorTracker::acquire( + auto* tracker = Generators::GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocationRef( generatorName, lineInfo ) ); - m_lastAssertionInfo.lineInfo = lineInfo; + Detail::g_lastKnownLineInfo = lineInfo; return tracker; } @@ -5931,18 +6344,28 @@ 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( currentTracker.nameAndLocation() != nameAndLoc && - "Trying to create tracker for a genreator that already has one" ); - - auto newTracker = Catch::Detail::make_unique( - CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker ); + "Trying to create tracker for a generator that already has one" ); + + auto newTracker = + Catch::Detail::make_unique( + CATCH_MOVE( nameAndLoc ), + m_trackerContext, + ¤tTracker, + CATCH_MOVE( generator ) ); auto ret = newTracker.get(); currentTracker.addChild( CATCH_MOVE( newTracker ) ); - ret->setGenerator( CATCH_MOVE( generator ) ); ret->open(); return ret; } @@ -5954,12 +6377,13 @@ namespace Catch { return false; if (m_trackerContext.currentTracker().hasChildren()) return false; - m_totals.assertions.failed++; + m_atomicAssertionCount.failed++; assertions.failed++; return true; } void RunContext::sectionEnded(SectionEndInfo&& endInfo) { + updateTotalsFromAtomics(); Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions(assertions); @@ -5976,9 +6400,6 @@ namespace Catch { endInfo.durationInSeconds, missingAssertions ) ); } - - m_messages.clear(); - m_messageScopes.clear(); } void RunContext::sectionEndedEarly(SectionEndInfo&& endInfo) { @@ -6009,18 +6430,6 @@ namespace Catch { m_reporter->benchmarkFailed( error ); } - void RunContext::pushScopedMessage(MessageInfo const & message) { - m_messages.push_back(message); - } - - void RunContext::popScopedMessage(MessageInfo const & message) { - m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); - } - - void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) { - m_messageScopes.emplace_back( CATCH_MOVE(builder) ); - } - std::string RunContext::getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name @@ -6028,6 +6437,18 @@ namespace Catch { } const AssertionResult * RunContext::getLastResult() const { + // m_lastResult is updated inside the assertion slow-path, under + // a mutex, so the read needs to happen under mutex as well. + + // TBD: The last result only makes sense if it is a thread-local + // thing, because the answer is different per thread, like + // last line info, whether last assertion passed, and so on. + // + // However, the last result was also never updated in the + // assertion fast path, so it was always somewhat broken, + // and since IResultCapture::getLastResult is deprecated, + // we will leave it as is, until it is finally removed. + Detail::LockGuard _( m_assertionMutex ); return &(*m_lastResult); } @@ -6036,28 +6457,44 @@ namespace Catch { } void RunContext::handleFatalErrorCondition( StringRef message ) { - // TODO: scoped deactivate here? Just give up and do best effort? - // the deactivation can break things further, OTOH so can the - // capture - auto _ = scopedDeactivate( *m_outputRedirect ); + // We lock only when touching the reporters directly, to avoid + // deadlocks when we call into other functions that also want + // to lock the mutex before touching reporters. + // + // This does mean that we allow other threads to run while handling + // a fatal error, but this is all a best effort attempt anyway. + { + Detail::LockGuard lock( m_assertionMutex ); + // TODO: scoped deactivate here? Just give up and do best effort? + // the deactivation can break things further, OTOH so can the + // capture + auto _ = scopedDeactivate( *m_outputRedirect ); - // First notify reporter that bad things happened - m_reporter->fatalErrorEncountered( message ); + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered( message ); + } // Don't rebuild the result -- the stringification itself can cause more fatal errors // Instead, fake a result data. AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); tempResult.message = static_cast(message); - AssertionResult result(m_lastAssertionInfo, CATCH_MOVE(tempResult)); + AssertionResult result( makeDummyAssertionInfo(), + CATCH_MOVE( tempResult ) ); assertionEnded(CATCH_MOVE(result) ); - resetAssertionInfo(); + + + // At this point we touch sections/test cases from this thread + // to try and end them. Technically that is not supported when + // using multiple threads, but the worst thing that can happen + // is that the process aborts harder :-D + Detail::LockGuard lock( m_assertionMutex ); // Best effort cleanup for sections that have not been destructed yet // Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly while (!m_activeSections.empty()) { - auto nl = m_activeSections.back()->nameAndLocation(); - SectionEndInfo endInfo{ SectionInfo(CATCH_MOVE(nl.location), CATCH_MOVE(nl.name)), {}, 0.0 }; + auto const& nl = m_activeSections.back()->nameAndLocation(); + SectionEndInfo endInfo{ SectionInfo(nl.location, nl.name), {}, 0.0 }; sectionEndedEarly(CATCH_MOVE(endInfo)); } handleUnfinishedSections(); @@ -6082,32 +6519,44 @@ namespace Catch { std::string(), false)); m_totals.testCases.failed++; + updateTotalsFromAtomics(); m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); } bool RunContext::lastAssertionPassed() { - return m_lastAssertionPassed; + return Detail::g_lastAssertionPassed; + } + + void RunContext::assertionPassedFastPath(SourceLineInfo lineInfo) { + // We want to save the line info for better experience with unexpected assertions + Detail::g_lastKnownLineInfo = lineInfo; + ++m_atomicAssertionCount.passed; + Detail::g_lastAssertionPassed = true; + Detail::g_clearMessageScopes = true; } - void RunContext::assertionPassed() { - m_lastAssertionPassed = true; - ++m_totals.assertions.passed; - resetAssertionInfo(); - m_messageScopes.clear(); + void RunContext::updateTotalsFromAtomics() { + m_totals.assertions = Counts{ + m_atomicAssertionCount.passed, + m_atomicAssertionCount.failed, + m_atomicAssertionCount.failedButOk, + m_atomicAssertionCount.skipped, + }; } bool RunContext::aborting() const { - return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); + return m_atomicAssertionCount.failed >= m_abortAfterXFailedAssertions; } void RunContext::runCurrentTest() { auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); m_reporter->sectionStarting(testCaseSection); + updateTotalsFromAtomics(); Counts prevAssertions = m_totals.assertions; double duration = 0; m_shouldReportUnexpected = true; - m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + Detail::g_lastKnownLineInfo = testCaseInfo.lineInfo; Timer timer; CATCH_TRY { @@ -6124,18 +6573,23 @@ namespace Catch { } CATCH_CATCH_ALL { // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions // are reported without translation at the point of origin. - if( m_shouldReportUnexpected ) { + if ( m_shouldReportUnexpected ) { AssertionReaction dummyReaction; - handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + handleUnexpectedInflightException( makeDummyAssertionInfo(), + translateActiveException(), + dummyReaction ); } } + updateTotalsFromAtomics(); Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions(assertions); m_testCaseTracker->close(); handleUnfinishedSections(); - m_messages.clear(); - m_messageScopes.clear(); + auto& msgHolder = Detail::g_messageHolder(); + msgHolder.removeUnscopedMessages(); + assert( msgHolder.getMessages().empty() && + "There should be no leftover messages after the test ends" ); SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions); m_reporter->sectionEnded(testCaseSectionStats); @@ -6176,7 +6630,7 @@ namespace Catch { if( result ) { if (!m_includeSuccessfulResults) { - assertionPassed(); + assertionPassedFastPath(info.lineInfo); } else { reportExpr(info, ResultWas::Ok, &expr, negated); @@ -6184,9 +6638,9 @@ namespace Catch { } else { reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); - populateReaction( reaction ); + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); } - resetAssertionInfo(); } void RunContext::reportExpr( AssertionInfo const &info, @@ -6194,7 +6648,7 @@ namespace Catch { ITransientExpression const *expr, bool negated ) { - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( resultType, LazyExpression( negated ) ); AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; @@ -6209,24 +6663,25 @@ namespace Catch { std::string&& message, AssertionReaction& reaction ) { - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( resultType, LazyExpression( false ) ); data.message = CATCH_MOVE( message ); - AssertionResult assertionResult{ m_lastAssertionInfo, + AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; const auto isOk = assertionResult.isOk(); assertionEnded( CATCH_MOVE(assertionResult) ); if ( !isOk ) { - populateReaction( reaction ); + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); } else if ( resultType == ResultWas::ExplicitSkip ) { // TODO: Need to handle this explicitly, as ExplicitSkip is // considered "OK" reaction.shouldSkip = true; } - resetAssertionInfo(); } + void RunContext::handleUnexpectedExceptionNotThrown( AssertionInfo const& info, AssertionReaction& reaction @@ -6239,55 +6694,82 @@ namespace Catch { std::string&& message, AssertionReaction& reaction ) { - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); data.message = CATCH_MOVE(message); AssertionResult assertionResult{ info, CATCH_MOVE(data) }; assertionEnded( CATCH_MOVE(assertionResult) ); - populateReaction( reaction ); - resetAssertionInfo(); + populateReaction( reaction, + info.resultDisposition & ResultDisposition::Normal ); } - void RunContext::populateReaction( AssertionReaction& reaction ) { - reaction.shouldDebugBreak = m_config->shouldDebugBreak(); - reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + void RunContext::populateReaction( AssertionReaction& reaction, + bool has_normal_disposition ) { + reaction.shouldDebugBreak = m_shouldDebugBreak; + reaction.shouldThrow = aborting() || has_normal_disposition; + } + + AssertionInfo RunContext::makeDummyAssertionInfo() { + const bool testCaseJustStarted = + Detail::g_lastKnownLineInfo == + m_activeTestCase->getTestCaseInfo().lineInfo; + + return AssertionInfo{ + testCaseJustStarted ? "TEST_CASE"_sr : StringRef(), + Detail::g_lastKnownLineInfo, + testCaseJustStarted ? StringRef() : "{Unknown expression after the reported line}"_sr, + ResultDisposition::Normal + }; } void RunContext::handleIncomplete( AssertionInfo const& info ) { using namespace std::string_literals; - m_lastAssertionInfo = info; + Detail::g_lastKnownLineInfo = info.lineInfo; AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s; AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; assertionEnded( CATCH_MOVE(assertionResult) ); - resetAssertionInfo(); } + void RunContext::handleNonExpr( AssertionInfo const &info, ResultWas::OfType resultType, AssertionReaction &reaction ) { - m_lastAssertionInfo = info; - AssertionResultData data( resultType, LazyExpression( false ) ); AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; const auto isOk = assertionResult.isOk(); + if ( isOk && !m_includeSuccessfulResults ) { + assertionPassedFastPath( info.lineInfo ); + return; + } + assertionEnded( CATCH_MOVE(assertionResult) ); - if ( !isOk ) { populateReaction( reaction ); } - resetAssertionInfo(); + if ( !isOk ) { + populateReaction( + reaction, info.resultDisposition & ResultDisposition::Normal ); + } } + void IResultCapture::pushScopedMessage( MessageInfo&& message ) { + Detail::g_messageHolder().addScopedMessage( CATCH_MOVE( message ) ); + } - IResultCapture& getResultCapture() { - if (auto* capture = getCurrentContext().getResultCapture()) - return *capture; - else - CATCH_INTERNAL_ERROR("No result capture instance"); + 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) { @@ -6513,7 +6995,7 @@ namespace Catch { std::string origStr = CATCH_MOVE(str); str.clear(); // There is at least one replacement, so reserve with the best guess - // we can make without actually counting the number of occurences. + // we can make without actually counting the number of occurrences. str.reserve(origStr.size() - replaceThis.size() + withThis.size()); do { str.append(origStr, copyBegin, i-copyBegin ); @@ -6559,7 +7041,6 @@ namespace Catch { #include #include #include -#include namespace Catch { StringRef::StringRef( char const* rawChars ) noexcept @@ -6849,6 +7330,17 @@ namespace TestCaseTracking { location( _location ) {} + ITracker::ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ): + m_nameAndLocation( CATCH_MOVE( nameAndLoc ) ), m_parent( parent ) { + if ( m_parent ) { + m_allTrackerDepth = m_parent->m_allTrackerDepth + 1; + // We leave section trackers to bump themselves up, as + // we cannot use `isSectionTracker` in constructor + m_sectionOnlyDepth = m_parent->m_sectionOnlyDepth; + m_filterRef = m_parent->m_filterRef; + m_newStyleFilters = m_parent->m_newStyleFilters; + } + } ITracker::~ITracker() = default; @@ -6981,25 +7473,32 @@ namespace TestCaseTracking { : TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ), m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name))) { - if( parent ) { - while ( !parent->isSectionTracker() ) { - parent = parent->parent(); - } - - SectionTracker& parentSection = static_cast( *parent ); - addNextFilters( parentSection.m_filters ); + if( m_parent ) { + ++m_sectionOnlyDepth; } } bool SectionTracker::isComplete() const { - bool complete = true; - - if (m_filters.empty() - || m_filters[0].empty() - || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) { - complete = TrackerBase::isComplete(); + // If there are active filters AND we do not pass them, + // the section is always "completed" + const size_t filterIndex = + m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth; + if ( filterIndex < m_filterRef->size() ) { + // There is active filter, check it + // 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 complete; + + // Otherwise we delegate to the generic processing + return TrackerBase::isComplete(); } bool SectionTracker::isSectionTracker() const { return true; } @@ -7035,19 +7534,6 @@ namespace TestCaseTracking { open(); } - void SectionTracker::addInitialFilters( std::vector const& filters ) { - if( !filters.empty() ) { - m_filters.reserve( m_filters.size() + filters.size() + 2 ); - m_filters.emplace_back(StringRef{}); // Root - should never be consulted - m_filters.emplace_back(StringRef{}); // Test Case - not a section filter - m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); - } - } - void SectionTracker::addNextFilters( std::vector const& filters ) { - if( filters.size() > 1 ) - m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() ); - } - StringRef SectionTracker::trimmedName() const { return m_trimmed_name; } @@ -7856,7 +8342,7 @@ namespace { void hexEscapeChar(std::ostream& os, unsigned char c) { std::ios_base::fmtflags f(os.flags()); - os << "\\x" + os << "\\x"_sr << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast(c); os.flags(f); @@ -7875,95 +8361,111 @@ namespace { void XmlEncode::encodeTo( std::ostream& os ) const { // Apostrophe escaping not necessary if we always use " to write attributes // (see: http://www.w3.org/TR/xml/#syntax) + size_t last_start = 0; + auto write_to = [&]( size_t idx ) { + if ( last_start < idx ) { + os << m_str.substr( last_start, idx - last_start ); + } + last_start = idx + 1; + }; - for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { - unsigned char c = static_cast(m_str[idx]); - switch (c) { - case '<': os << "<"; break; - case '&': os << "&"; break; + for ( std::size_t idx = 0; idx < m_str.size(); ++idx ) { + unsigned char c = static_cast( m_str[idx] ); + switch ( c ) { + case '<': + write_to( idx ); + os << "<"_sr; + break; + case '&': + write_to( idx ); + os << "&"_sr; + break; case '>': // See: http://www.w3.org/TR/xml/#syntax - if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') - os << ">"; - else - os << c; + if ( idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']' ) { + write_to( idx ); + os << ">"_sr; + } break; case '\"': - if (m_forWhat == ForAttributes) - os << """; - else - os << c; + if ( m_forWhat == ForAttributes ) { + write_to( idx ); + os << """_sr; + } break; default: // Check for control characters and invalid utf-8 // Escape control characters in standard ascii - // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 - if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { - hexEscapeChar(os, c); + // see + // http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if ( c < 0x09 || ( c > 0x0D && c < 0x20 ) || c == 0x7F ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } // Plain ASCII: Write it to stream - if (c < 0x7F) { - os << c; + if ( c < 0x7F ) { break; } // UTF-8 territory - // Check if the encoding is valid and if it is not, hex escape bytes. - // Important: We do not check the exact decoded values for validity, only the encoding format - // First check that this bytes is a valid lead byte: - // This means that it is not encoded as 1111 1XXX + // Check if the encoding is valid and if it is not, hex escape + // bytes. Important: We do not check the exact decoded values for + // validity, only the encoding format First check that this bytes is + // a valid lead byte: This means that it is not encoded as 1111 1XXX // Or as 10XX XXXX - if (c < 0xC0 || - c >= 0xF8) { - hexEscapeChar(os, c); + if ( c < 0xC0 || c >= 0xF8 ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } - auto encBytes = trailingBytes(c); - // Are there enough bytes left to avoid accessing out-of-bounds memory? - if (idx + encBytes - 1 >= m_str.size()) { - hexEscapeChar(os, c); + auto encBytes = trailingBytes( c ); + // Are there enough bytes left to avoid accessing out-of-bounds + // memory? + if ( idx + encBytes - 1 >= m_str.size() ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } // The header is valid, check data // The next encBytes bytes must together be a valid utf-8 - // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + // This means: bitpattern 10XX XXXX and the extracted value is sane + // (ish) bool valid = true; - uint32_t value = headerValue(c); - for (std::size_t n = 1; n < encBytes; ++n) { - unsigned char nc = static_cast(m_str[idx + n]); - valid &= ((nc & 0xC0) == 0x80); - value = (value << 6) | (nc & 0x3F); + uint32_t value = headerValue( c ); + for ( std::size_t n = 1; n < encBytes; ++n ) { + unsigned char nc = static_cast( m_str[idx + n] ); + valid &= ( ( nc & 0xC0 ) == 0x80 ); + value = ( value << 6 ) | ( nc & 0x3F ); } if ( // Wrong bit pattern of following bytes - (!valid) || + ( !valid ) || // Overlong encodings - (value < 0x80) || - (0x80 <= value && value < 0x800 && encBytes > 2) || - (0x800 < value && value < 0x10000 && encBytes > 3) || + ( value < 0x80 ) || + ( 0x80 <= value && value < 0x800 && encBytes > 2 ) || + ( 0x800 < value && value < 0x10000 && encBytes > 3 ) || // Encoded value out of range - (value >= 0x110000) - ) { - hexEscapeChar(os, c); + ( value >= 0x110000 ) ) { + write_to( idx ); + hexEscapeChar( os, c ); break; } // If we got here, this is in fact a valid(ish) utf-8 sequence - for (std::size_t n = 0; n < encBytes; ++n) { - os << m_str[idx + n]; - } idx += encBytes - 1; break; } } + + write_to( m_str.size() ); } std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { @@ -8874,7 +9376,8 @@ class AssertionPrinter { << m_config->testSpec() << '\n'; } - m_stream << "RNG seed: " << getSeed() << '\n'; + m_stream << "RNG seed: " << getSeed() << '\n' + << std::flush; } void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { @@ -9108,7 +9611,7 @@ struct RowBreak {}; struct OutputFlush {}; class Duration { - enum class Unit { + enum class Unit : uint8_t { Auto, Nanoseconds, Microseconds, @@ -9180,7 +9683,10 @@ class Duration { }; } // end anon namespace -enum class Justification { Left, Right }; +enum class Justification : uint8_t { + Left, + Right +}; struct ColumnInfo { std::string name; @@ -9292,7 +9798,10 @@ ConsoleReporter::ConsoleReporter(ReporterConfig&& config): { "est run time high mean high std dev", 14, Justification::Right } }; } - }())) {} + }())) { + m_preferences.shouldReportAllAssertionStarts = false; +} + ConsoleReporter::~ConsoleReporter() = default; std::string ConsoleReporter::getDescription() { @@ -9307,8 +9816,6 @@ void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) { m_stream << "Invalid Filter: " << arg << '\n'; } -void ConsoleReporter::assertionStarting(AssertionInfo const&) {} - void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { AssertionResult const& result = _assertionStats.assertionResult; @@ -9420,7 +9927,8 @@ void ConsoleReporter::testRunStarting(TestRunInfo const& _testRunInfo) { m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: " << m_config->testSpec() << '\n'; } - m_stream << "Randomness seeded to: " << getSeed() << '\n'; + m_stream << "Randomness seeded to: " << getSeed() << '\n' + << std::flush; } void ConsoleReporter::lazyPrint() { @@ -9748,6 +10256,7 @@ namespace Catch { #include #include +#include #include #include #include @@ -9911,9 +10420,29 @@ namespace Catch { out << "All available tags:\n"; } + // minimum whitespace to pad tag counts, possibly overwritten below + size_t maxTagCountLen = 2; + + // determine necessary padding for tag count column + if ( ! tags.empty() ) { + const auto maxTagCount = + std::max_element( tags.begin(), + tags.end(), + []( auto const& lhs, auto const& rhs ) { + return lhs.count < rhs.count; + } ) + ->count; + + // more padding necessary for 3+ digits + if (maxTagCount >= 100) { + auto numDigits = 1 + std::floor( std::log10( maxTagCount ) ); + maxTagCountLen = static_cast( numDigits ); + } + } + for ( auto const& tagCount : tags ) { ReusableStringStream rss; - rss << " " << std::setw( 2 ) << tagCount.count << " "; + rss << " " << std::setw( maxTagCountLen ) << tagCount.count << " "; auto str = rss.str(); auto wrapper = TextFlow::Column( tagCount.all() ) .initialIndent( 0 ) @@ -10111,6 +10640,8 @@ namespace Catch { // not, but for machine-parseable reporters I think the answer // should be yes. m_preferences.shouldReportAllAssertions = true; + // We only handle assertions when they end + m_preferences.shouldReportAllAssertionStarts = false; m_objectWriters.emplace( m_stream ); m_writers.emplace( Writer::Object ); @@ -10340,7 +10871,6 @@ namespace Catch { endObject(); } - void JsonReporter::assertionStarting( AssertionInfo const& /*assertionInfo*/ ) {} void JsonReporter::assertionEnded( AssertionStats const& assertionStats ) { // TODO: There is lot of different things to handle here, but // we can fill it in later, after we show that the basic @@ -10507,6 +11037,7 @@ namespace Catch { { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; m_shouldStoreSuccesfulAssertions = false; } @@ -10737,6 +11268,8 @@ namespace Catch { reporterish.getPreferences().shouldRedirectStdOut; m_preferences.shouldReportAllAssertions |= reporterish.getPreferences().shouldReportAllAssertions; + m_preferences.shouldReportAllAssertionStarts |= + reporterish.getPreferences().shouldReportAllAssertionStarts; } void MultiReporter::addListener( IEventListenerPtr&& listener ) { @@ -11294,7 +11827,8 @@ namespace Catch { if ( m_config->testSpec().hasFilters() ) { m_stream << "# filters: " << m_config->testSpec() << '\n'; } - m_stream << "# rng-seed: " << m_config->rngSeed() << '\n'; + m_stream << "# rng-seed: " << m_config->rngSeed() << '\n' + << std::flush; } void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) { @@ -11508,6 +12042,7 @@ namespace Catch { { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; } XmlReporter::~XmlReporter() = default; @@ -11564,8 +12099,6 @@ namespace Catch { } } - void XmlReporter::assertionStarting( AssertionInfo const& ) { } - void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { AssertionResult const& result = assertionStats.assertionResult; diff --git a/3rdparty/catch/catch_amalgamated.hpp b/3rdparty/catch/catch_amalgamated.hpp index b7c768b8d..6de74f80f 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.7.1 -// Generated: 2024-09-17 10:36:40.974985 +// Catch v3.13.0 +// Generated: 2026-02-15 22:54:59.817776 // ---------------------------------------------------------- // 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 @@ -150,7 +153,7 @@ # define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ _Pragma( "GCC diagnostic ignored \"-Wshadow\"" ) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) +# define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P #endif @@ -174,35 +177,13 @@ // clang-cl defines _MSC_VER as well as __clang__, which could cause the // start/stop internal suppression macros to be double defined. #if defined(__clang__) && !defined(_MSC_VER) - +# define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) - #endif // __clang__ && !_MSC_VER #if defined(__clang__) -// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug -// which results in calls to destructors being emitted for each temporary, -// without a matching initialization. In practice, this can result in something -// like `std::string::~string` being called on an uninitialized value. -// -// For example, this code will likely segfault under IBM XL: -// ``` -// REQUIRE(std::string("12") + "34" == "1234") -// ``` -// -// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which -// results in calls to the immediately evaluated lambda expressions to be -// reported as unevaluated lambdas. -// https://developer.nvidia.com/nvidia_bug/3321845. -// -// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. -# if !defined(__ibmxl__) && !defined(__CUDACC__) && !defined( __NVCOMPILER ) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ -# endif - - # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") @@ -213,8 +194,16 @@ # define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) +# if (__clang_major__ >= 20) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wvariadic-macro-arguments-omitted\"" ) +# elif (__clang_major__ == 19) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wc++20-extensions\"" ) +# else +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) +# endif # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) @@ -227,6 +216,27 @@ #endif // __clang__ +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which +// results in calls to the immediately evaluated lambda expressions to be +// reported as unevaluated lambdas. +// https://developer.nvidia.com/nvidia_bug/3321845. +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +#if defined( __ibmxl__ ) || defined( __CUDACC__ ) || defined( __NVCOMPILER ) +# define CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P +#endif + + //////////////////////////////////////////////////////////////////////////////// // We know some environments not to support full POSIX signals @@ -289,7 +299,7 @@ //////////////////////////////////////////////////////////////////////////////// // Visual C++ -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) // We want to defer to nvcc-specific warning suppression if we are compiled // with nvcc masquerading for MSVC. @@ -300,14 +310,23 @@ __pragma( warning( pop ) ) # endif +// Suppress MSVC C++ Core Guidelines checker warning 26426: +// "Global initializer calls a non-constexpr function (i.22)" +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + __pragma( warning( disable : 26426 ) ) + // 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 @@ -450,6 +469,22 @@ #endif +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if defined( CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P ) && \ + !defined( CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P ) && !defined(CATCH_CONFIG_USE_BUILTIN_CONSTANT_P) +#define CATCH_CONFIG_USE_BUILTIN_CONSTANT_P +#endif + +#if defined( CATCH_CONFIG_USE_BUILTIN_CONSTANT_P ) && \ + !defined( CATCH_CONFIG_NO_USE_BUILTIN_CONSTANT_P ) +# define CATCH_INTERNAL_IGNORE_BUT_WARN( ... ) \ + (void)__builtin_constant_p( __VA_ARGS__ ) /* NOLINT(cppcoreguidelines-pro-type-vararg, \ + hicpp-vararg) */ +#else +# define CATCH_INTERNAL_IGNORE_BUT_WARN( ... ) +#endif + // Even if we do not think the compiler has that warning, we still have // to provide a macro that can be used by the code. #if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) @@ -486,13 +521,6 @@ # define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS #endif - -// The goal of this macro is to avoid evaluation of the arguments, but -// still have the compiler warn on problems inside... -#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) -#endif - #if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #elif defined(__clang__) && (__clang_major__ < 5) @@ -548,11 +576,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 { @@ -563,21 +589,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(); } @@ -680,11 +699,30 @@ namespace Catch { #ifndef CATCH_STRINGREF_HPP_INCLUDED #define CATCH_STRINGREF_HPP_INCLUDED + + + +#ifndef CATCH_LIFETIMEBOUND_HPP_INCLUDED +#define CATCH_LIFETIMEBOUND_HPP_INCLUDED + +#if !defined( __has_cpp_attribute ) +# define CATCH_ATTR_LIFETIMEBOUND +#elif __has_cpp_attribute( msvc::lifetimebound ) +# define CATCH_ATTR_LIFETIMEBOUND [[msvc::lifetimebound]] +#elif __has_cpp_attribute( clang::lifetimebound ) +# define CATCH_ATTR_LIFETIMEBOUND [[clang::lifetimebound]] +#elif __has_cpp_attribute( lifetimebound ) +# define CATCH_ATTR_LIFETIMEBOUND [[lifetimebound]] +#else +# define CATCH_ATTR_LIFETIMEBOUND +#endif + +#endif // CATCH_LIFETIMEBOUND_HPP_INCLUDED + #include #include #include #include - #include namespace Catch { @@ -708,14 +746,16 @@ namespace Catch { public: // construction constexpr StringRef() noexcept = default; - StringRef( char const* rawChars ) noexcept; + StringRef( char const* rawChars CATCH_ATTR_LIFETIMEBOUND ) noexcept; - constexpr StringRef( char const* rawChars, size_type size ) noexcept + constexpr StringRef( char const* rawChars CATCH_ATTR_LIFETIMEBOUND, + size_type size ) noexcept : m_start( rawChars ), m_size( size ) {} - StringRef( std::string const& stdString ) noexcept + StringRef( + std::string const& stdString CATCH_ATTR_LIFETIMEBOUND ) noexcept : m_start( stdString.c_str() ), m_size( stdString.size() ) {} @@ -761,7 +801,7 @@ namespace Catch { } // Returns the current start pointer. May not be null-terminated. - constexpr char const* data() const noexcept { + constexpr char const* data() const noexcept CATCH_ATTR_LIFETIMEBOUND { return m_start; } @@ -942,7 +982,7 @@ namespace Detail { } explicit operator bool() const { - return m_ptr; + return m_ptr != nullptr; } friend void swap(unique_ptr& lhs, unique_ptr& rhs) { @@ -998,7 +1038,7 @@ namespace Catch { // 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 dependees + // we can prevent multiple declarations in dependencies template struct BenchmarkStats; @@ -1053,10 +1093,10 @@ 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 addUnscopedMessage( MessageInfo&& message ); + static void emplaceUnscopedMessage( MessageBuilder&& builder ); virtual void handleFatalErrorCondition( StringRef message ) = 0; @@ -1084,9 +1124,7 @@ namespace Catch { AssertionReaction &reaction ) = 0; - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; // Deprecated, do not use: virtual std::string getCurrentTestName() const = 0; @@ -1094,7 +1132,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 @@ -1113,6 +1162,7 @@ namespace Catch { //! Deriving classes become noncopyable and nonmovable class NonCopyable { + public: NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable&& ) = delete; NonCopyable& operator=( NonCopyable const& ) = delete; @@ -1128,7 +1178,6 @@ namespace Catch { #endif // CATCH_NONCOPYABLE_HPP_INCLUDED #include -#include #include #include @@ -1146,6 +1195,8 @@ namespace Catch { NoAssertions = 0x01, //! A command line test spec matched no test cases UnmatchedTestSpec = 0x02, + //! The resulting generator in GENERATE is infinite + InfiniteGenerator = 0x04, }; }; enum class ShowDurations { @@ -1177,6 +1228,7 @@ namespace Catch { class TestSpec; class IStream; + struct PathFilter; class IConfig : public Detail::NonCopyable { public: @@ -1188,6 +1240,7 @@ namespace Catch { virtual bool shouldDebugBreak() const = 0; virtual bool warnAboutMissingAssertions() const = 0; virtual bool warnAboutUnmatchedTestSpecs() const = 0; + virtual bool warnAboutInfiniteGenerators() const = 0; virtual bool zeroTestsCountAsSuccess() const = 0; virtual int abortAfter() const = 0; virtual bool showInvisibles() const = 0; @@ -1201,7 +1254,9 @@ namespace Catch { virtual unsigned int shardCount() const = 0; virtual unsigned int shardIndex() const = 0; virtual ColourMode defaultColourMode() const = 0; - virtual std::vector const& getSectionsToRun() const = 0; + virtual std::vector const& getPathFilters() const = 0; + virtual bool useNewFilterBehaviour() const = 0; + virtual Verbosity verbosity() const = 0; virtual bool skipBenchmarks() const = 0; @@ -1583,8 +1638,7 @@ namespace Catch { namespace Benchmark { namespace Detail { template - struct is_related - : std::is_same, std::decay_t> {}; + static constexpr bool is_related_v = std::is_same, std::decay_t>::value; /// We need to reinvent std::function because every piece of code that might add overhead /// in a measurement context needs to have consistent performance characteristics so that we @@ -1625,7 +1679,7 @@ namespace Catch { BenchmarkFunction(); template ::value, int> = 0> + std::enable_if_t, int> = 0> BenchmarkFunction(Fun&& fun) : f(new model>(CATCH_FORWARD(fun))) {} @@ -1751,8 +1805,6 @@ namespace Catch { #define CATCH_TIMING_HPP_INCLUDED -#include - namespace Catch { namespace Benchmark { template @@ -2140,9 +2192,7 @@ namespace Catch { auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; getResultCapture().benchmarkEnded(stats); - } CATCH_CATCH_ANON (TestFailureException const&) { - getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); - } CATCH_CATCH_ALL{ + } CATCH_CATCH_ALL { getResultCapture().benchmarkFailed(translateActiveException()); // We let the exception go further up so that the // test case is marked as failed. @@ -2151,7 +2201,7 @@ namespace Catch { } // sets lambda to be used in fun *and* executes benchmark! - template ::value, int> = 0> + template , int> = 0> Benchmark & operator=(Fun func) { auto const* cfg = getCurrentContext().getConfig(); if (!cfg->skipBenchmarks()) { @@ -2288,7 +2338,7 @@ namespace Catch { #ifndef CATCH_TOSTRING_HPP_INCLUDED #define CATCH_TOSTRING_HPP_INCLUDED - +#include #include #include #include @@ -2456,13 +2506,9 @@ namespace Catch { namespace Detail { - inline std::size_t catch_strnlen(const char *str, std::size_t n) { - auto ret = std::char_traits::find(str, n, '\0'); - if (ret != nullptr) { - return static_cast(ret - str); - } - return n; - } + std::size_t catch_strnlen(const char *str, std::size_t n); + + std::string formatTimeT( std::time_t time ); constexpr StringRef unprintableString = "{?}"_sr; @@ -2480,18 +2526,14 @@ namespace Catch { return rawMemoryToString( &object, sizeof(object) ); } - template - class IsStreamInsertable { - template - static auto test(int) - -> decltype(std::declval() << std::declval(), std::true_type()); - - template - static auto test(...)->std::false_type; + template + static constexpr bool IsStreamInsertable_v = false; - public: - static const bool value = decltype(test(0))::value; - }; + template + static constexpr bool IsStreamInsertable_v< + T, + decltype( void( std::declval() << std::declval() ) )> = + true; template std::string convertUnknownEnumToString( E e ); @@ -2536,7 +2578,7 @@ namespace Catch { struct StringMaker { template static - std::enable_if_t<::Catch::Detail::IsStreamInsertable::value, std::string> + std::enable_if_t<::Catch::Detail::IsStreamInsertable_v, std::string> convert(const Fake& value) { ReusableStringStream rss; // NB: call using the function-like syntax to avoid ambiguity with @@ -2547,7 +2589,7 @@ namespace Catch { template static - std::enable_if_t::value, std::string> + std::enable_if_t, std::string> convert( const Fake& value ) { #if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) return Detail::convertUnstreamable(value); @@ -2559,11 +2601,17 @@ namespace Catch { namespace Detail { + std::string makeExceptionHappenedString(); + // This function dispatches all stringification requests inside of Catch. // Should be preferably called fully qualified, like ::Catch::Detail::stringify template - std::string stringify(const T& e) { - return ::Catch::StringMaker>>::convert(e); + std::string stringify( const T& e ) { + CATCH_TRY { + return ::Catch::StringMaker< + std::remove_cv_t>>::convert( e ); + } + CATCH_CATCH_ALL { return makeExceptionHappenedString(); } } template @@ -2825,44 +2873,38 @@ namespace Catch { // Separate std::tuple specialization #if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) -#include +# include +# include namespace Catch { namespace Detail { - template< - typename Tuple, - std::size_t N = 0, - bool = (N < std::tuple_size::value) - > - struct TupleElementPrinter { - static void print(const Tuple& tuple, std::ostream& os) { - os << (N ? ", " : " ") - << ::Catch::Detail::stringify(std::get(tuple)); - TupleElementPrinter::print(tuple, os); - } - }; - - template< - typename Tuple, - std::size_t N - > - struct TupleElementPrinter { - static void print(const Tuple&, std::ostream&) {} - }; - - } + template + void PrintTuple( const Tuple& tuple, + std::ostream& os, + std::index_sequence ) { + // 1 + Account for when the tuple is empty + char a[1 + sizeof...( Is )] = { + ( ( os << ( Is ? ", " : " " ) + << ::Catch::Detail::stringify( std::get( tuple ) ) ), + '\0' )... }; + (void)a; + } + } // namespace Detail - template + template struct StringMaker> { - static std::string convert(const std::tuple& tuple) { + static std::string convert( const std::tuple& tuple ) { ReusableStringStream rss; rss << '{'; - Detail::TupleElementPrinter>::print(tuple, rss.get()); + Detail::PrintTuple( + tuple, + rss.get(), + std::make_index_sequence{} ); rss << " }"; return rss.str(); } }; -} +} // namespace Catch #endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER #if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) @@ -2939,7 +2981,7 @@ namespace Catch { } template - struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>> { + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable_v>> { static std::string convert( R const& range ) { return rangeToString( range ); } @@ -3046,25 +3088,10 @@ struct ratio_string { template struct StringMaker> { static std::string convert(std::chrono::time_point const& time_point) { - auto converted = std::chrono::system_clock::to_time_t(time_point); - -#ifdef _MSC_VER - std::tm timeInfo = {}; - gmtime_s(&timeInfo, &converted); -#else - std::tm* timeInfo = std::gmtime(&converted); -#endif - - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - char timeStamp[timeStampSize]; - const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; - -#ifdef _MSC_VER - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); -#else - std::strftime(timeStamp, timeStampSize, fmt, timeInfo); -#endif - return std::string(timeStamp, timeStampSize - 1); + const auto systemish = std::chrono::time_point_cast< + std::chrono::system_clock::duration>( time_point ); + const auto as_time_t = std::chrono::system_clock::to_time_t( systemish ); + return ::Catch::Detail::formatTimeT( as_time_t ); } }; } @@ -3615,6 +3642,33 @@ namespace Catch { #endif // CATCH_OPTIONAL_HPP_INCLUDED +#ifndef CATCH_PATH_FILTER_HPP_INCLUDED +#define CATCH_PATH_FILTER_HPP_INCLUDED + + +#include + +namespace Catch { + + struct PathFilter { + enum class For { + Section, + Generator, + }; + PathFilter( For type_, std::string filter_ ): + type( type_ ), filter( CATCH_MOVE( filter_ ) ) {} + + For type; + std::string filter; + + friend bool operator==( PathFilter const& lhs, PathFilter const& rhs ); + }; + +} // end namespace Catch + +#endif // CATCH_PATH_FILTER_HPP_INCLUDED + + #ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED #define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED @@ -3772,7 +3826,7 @@ namespace Catch { WarnAbout::What warnings = WarnAbout::Nothing; ShowDurations showDurations = ShowDurations::DefaultForReporter; double minDuration = -1; - TestRunOrder runOrder = TestRunOrder::Declared; + TestRunOrder runOrder = TestRunOrder::Randomized; ColourMode defaultColourMode = ColourMode::PlatformDefault; WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; @@ -3782,7 +3836,10 @@ namespace Catch { std::vector reporterSpecifications; std::vector testsOrTags; - std::vector sectionsToRun; + std::vector pathFilters; + bool useNewPathFilteringBehaviour = false; + + std::string prematureExitGuardFilePath; }; @@ -3803,19 +3860,23 @@ namespace Catch { getProcessedReporterSpecs() const; std::vector const& getTestsOrTags() const override; - std::vector const& getSectionsToRun() const override; + std::vector const& getPathFilters() const override; + bool useNewFilterBehaviour() const override; TestSpec const& testSpec() const override; bool hasTestFilters() const override; bool showHelp() const; + std::string const& getExitGuardFilePath() const; + // IConfig interface bool allowThrows() const override; StringRef name() const override; bool includeSuccessfulResults() const override; bool warnAboutMissingAssertions() const override; bool warnAboutUnmatchedTestSpecs() const override; + bool warnAboutInfiniteGenerators() const override; bool zeroTestsCountAsSuccess() const override; ShowDurations showDurations() const override; double minDuration() const override; @@ -3918,6 +3979,19 @@ namespace Catch { #define CATCH_MESSAGE_INFO_HPP_INCLUDED + +#ifndef CATCH_DEPRECATION_MACRO_HPP_INCLUDED +#define CATCH_DEPRECATION_MACRO_HPP_INCLUDED + + +#if !defined( CATCH_CONFIG_NO_DEPRECATION_ANNOTATIONS ) +# define CATCH_DEPRECATED( msg ) [[deprecated( msg )]] +#else +# define CATCH_DEPRECATED( msg ) +#endif + +#endif // CATCH_DEPRECATION_MACRO_HPP_INCLUDED + #include namespace Catch { @@ -3931,16 +4005,17 @@ 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; + CATCH_DEPRECATED( "Explicitly use the 'sequence' member instead" ) bool operator == (MessageInfo const& other) const { return sequence == other.sequence; } + CATCH_DEPRECATED( "Explicitly use the 'sequence' member instead" ) bool operator < (MessageInfo const& other) const { return sequence < other.sequence; } - private: - static unsigned int globalCount; }; } // end namespace Catch @@ -3988,16 +4063,16 @@ namespace Catch { ScopedMessage( ScopedMessage&& old ) noexcept; ~ScopedMessage(); - MessageInfo m_info; + unsigned int m_messageId; bool m_moved = false; }; class Capturer { std::vector m_messages; - IResultCapture& m_resultCapture; size_t m_captured = 0; + bool m_isScoped = false; public: - Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names, bool isScoped ); Capturer(Capturer const&) = delete; Capturer& operator=(Capturer const&) = delete; @@ -4029,11 +4104,12 @@ namespace Catch { } while( false ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ - Catch::Capturer varName( macroName##_catch_sr, \ - CATCH_INTERNAL_LINEINFO, \ - Catch::ResultWas::Info, \ - #__VA_ARGS__##_catch_sr ); \ +#define INTERNAL_CATCH_CAPTURE( varName, macroName, scopedCapture, ... ) \ + Catch::Capturer varName( macroName##_catch_sr, \ + CATCH_INTERNAL_LINEINFO, \ + Catch::ResultWas::Info, \ + #__VA_ARGS__##_catch_sr, \ + scopedCapture ); \ varName.captureValues( 0, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// @@ -4042,7 +4118,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) @@ -4050,28 +4126,32 @@ namespace Catch { #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) #define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) - #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ ) + #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", true, __VA_ARGS__ ) + #define CATCH_UNSCOPED_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_UNSCOPED_CAPTURE", false, __VA_ARGS__ ) #elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) - #define CATCH_INFO( msg ) (void)(0) - #define CATCH_UNSCOPED_INFO( msg ) (void)(0) - #define CATCH_WARN( msg ) (void)(0) - #define CATCH_CAPTURE( ... ) (void)(0) + #define CATCH_INFO( msg ) (void)(0) + #define CATCH_UNSCOPED_INFO( msg ) (void)(0) + #define CATCH_WARN( msg ) (void)(0) + #define CATCH_CAPTURE( ... ) (void)(0) + #define CATCH_UNSCOPED_CAPTURE( ... ) (void)(0) #elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) #define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg ) #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) - #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ ) + #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", true, __VA_ARGS__ ) + #define UNSCOPED_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "UNSCOPED_CAPTURE", false, __VA_ARGS__ ) #elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) - #define INFO( msg ) (void)(0) - #define UNSCOPED_INFO( msg ) (void)(0) - #define WARN( msg ) (void)(0) - #define CAPTURE( ... ) (void)(0) + #define INFO( msg ) (void)(0) + #define UNSCOPED_INFO( msg ) (void)(0) + #define WARN( msg ) (void)(0) + #define CAPTURE( ... ) (void)(0) + #define UNSCOPED_CAPTURE( ... ) (void)(0) #endif // end of user facing macro declarations @@ -4217,15 +4297,13 @@ namespace Catch { }; template - struct is_unary_function : std::false_type {}; + static constexpr bool is_unary_function_v = false; template - struct is_unary_function< + static constexpr bool is_unary_function_v< F, - Catch::Detail::void_t()( fake_arg() ) ) - > - > : std::true_type {}; + Catch::Detail::void_t()( + fake_arg() ) )>> = true; // Traits for extracting arg and return type of lambdas (for single // argument lambdas) @@ -4658,14 +4736,14 @@ namespace Catch { template ::value>> + !Detail::is_unary_function_v>> ParserRefImpl( T& ref, StringRef hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} template ::value>> + Detail::is_unary_function_v>> ParserRefImpl( LambdaT const& ref, StringRef hint ): m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} @@ -4732,7 +4810,7 @@ namespace Catch { template ::value>> + Detail::is_unary_function_v>> Opt( LambdaT const& ref, StringRef hint ): ParserRefImpl( ref, hint ) {} @@ -4742,7 +4820,7 @@ namespace Catch { template ::value>> + !Detail::is_unary_function_v>> Opt( T& ref, StringRef hint ): ParserRefImpl( ref, hint ) {} @@ -4912,6 +4990,14 @@ namespace Catch { namespace Catch { + // TODO: Use C++17 `inline` variables + constexpr int UnspecifiedErrorExitCode = 1; + constexpr int NoTestsRunExitCode = 2; + constexpr int UnmatchedTestSpecExitCode = 3; + constexpr int AllTestsSkippedExitCode = 4; + constexpr int InvalidTestSpecExitCode = 5; + constexpr int TestFailureExitCode = 42; + class Session : Detail::NonCopyable { public: @@ -4996,7 +5082,7 @@ namespace Catch { #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + namespace{ const Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED @@ -5173,7 +5259,7 @@ namespace Detail { * when the compiler handles `ExprLhs == b`, it also tries to resolve * the overload set for `b == ExprLhs`. * - * To accomodate these use cases, decomposer ended up rather complex. + * To accommodate these use cases, decomposer ended up rather complex. * * 1) These types are handled by adding SFINAE overloads to our comparison * operators, checking whether `T == U` are comparable with the given @@ -5206,10 +5292,11 @@ namespace Detail { * * 3) If a type has no linkage, we also cannot capture it by reference. * The solution is once again to capture them by value. We handle - * the common cases by using `std::is_arithmetic` as the default - * for `Catch::capture_by_value`, but that is only a some-effort - * heuristic. But as with 2), users can specialize `capture_by_value` - * for their own types as needed. + * the common cases by using `std::is_arithmetic` and `std::is_enum` + * as the default for `Catch::capture_by_value`, but that is only a + * some-effort heuristic. These combine to capture all possible bitfield + * bases, and also some trait-like types. As with 2), users can + * specialize `capture_by_value` for their own types as needed. * * 4) To support C++20 and make the SFINAE on our decomposing operators * work, the SFINAE has to happen in return type, rather than in @@ -5261,13 +5348,22 @@ namespace Catch { using RemoveCVRef_t = std::remove_cv_t>; } - // Note: There is nothing that stops us from extending this, - // e.g. to `std::is_scalar`, but the more encompassing - // traits are usually also more expensive. For now we - // keep this as it used to be and it can be changed later. + // Note: This is about as much as we can currently reasonably support. + // In an ideal world, we could capture by value small trivially + // copyable types, but the actual `std::is_trivially_copyable` + // trait is a huge mess with standard-violating results on + // GCC and Clang, which are unlikely to be fixed soon due to ABI + // concerns. + // `std::is_scalar` also causes issues due to the `is_pointer` + // component, which causes ambiguity issues with (references-to) + // function pointer. If those are resolved, we still need to + // disambiguate the overload set for arrays, through explicit + // overload for references to sized arrays. template struct capture_by_value - : std::integral_constant{}> {}; + : std::integral_constant::value || + std::is_enum::value> {}; #if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS ) template <> @@ -5291,7 +5387,7 @@ namespace Catch { public: constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } constexpr auto getResult() const -> bool { return m_result; } - //! This function **has** to be overriden by the derived class. + //! This function **has** to be overridden by the derived class. virtual void streamReconstructedExpression( std::ostream& os ) const; constexpr ITransientExpression( bool isBinaryExpression, bool result ) @@ -5892,7 +5988,7 @@ namespace Catch { #else // These section definitions imply that at most one section at one level -// will be intered (because only one section's __LINE__ can be equal to +// will be entered (because only one section's __LINE__ can be equal to // the dummy `catchInternalSectionHint` variable from `TEST_CASE`). namespace Catch { @@ -6175,6 +6271,57 @@ static int catchInternalSectionHint = 0; #endif // CATCH_TEST_REGISTRY_HPP_INCLUDED +#ifndef CATCH_UNREACHABLE_HPP_INCLUDED +#define CATCH_UNREACHABLE_HPP_INCLUDED + +/**\file + * Polyfill `std::unreachable` + * + * We need something like `std::unreachable` to tell the compiler that + * some macros, e.g. `FAIL` or `SKIP`, do not continue execution in normal + * manner, and should handle it as such, e.g. not warn if there is no return + * from non-void function after a `FAIL` or `SKIP`. + */ + +#include + +#if defined( __cpp_lib_unreachable ) && __cpp_lib_unreachable > 202202L +# include +namespace Catch { + namespace Detail { + using Unreachable = std::unreachable; + } +} // namespace Catch + +#else // vv If we do not have std::unreachable, we implement something similar + +namespace Catch { + namespace Detail { + + [[noreturn]] + inline void Unreachable() noexcept { +# if defined( NDEBUG ) +# if defined( _MSC_VER ) && !defined( __clang__ ) + __assume( false ); +# elif defined( __GNUC__ ) + __builtin_unreachable(); +# else // vv platform without known optimization hint + std::terminate(); +# endif +# else // ^^ NDEBUG + // For non-release builds, we prefer termination on bug over UB + std::terminate(); +# endif // + } + + } // namespace Detail +} // end namespace Catch + +#endif + +#endif // CATCH_UNREACHABLE_HPP_INCLUDED + + // All of our user-facing macros support configuration toggle, that // forces them to be defined prefixed with CATCH_. We also like to // support another toggle that can minimize (disable) their implementation. @@ -6206,10 +6353,16 @@ static int catchInternalSectionHint = 0; #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL( ... ) do { \ + INTERNAL_CATCH_MSG("CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while ( false ) #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_SKIP( ... ) do { \ + INTERNAL_CATCH_MSG( "CATCH_SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) @@ -6307,10 +6460,16 @@ static int catchInternalSectionHint = 0; #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define FAIL( ... ) do { \ + INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define SKIP( ... ) do { \ + INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ); \ + Catch::Detail::Unreachable(); \ + } while (false) #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) @@ -6404,6 +6563,15 @@ static int catchInternalSectionHint = 0; #endif +namespace Catch { + namespace Detail { + template + struct priority_tag : priority_tag {}; + template <> + struct priority_tag<0> {}; + } +} + #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ #define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) @@ -6463,10 +6631,10 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper(Catch::Detail::priority_tag<1>{})) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper(Catch::Detail::priority_tag<1>{}))) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) #endif @@ -6489,11 +6657,11 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_TYPE_GEN\ template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template\ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TypeList { return {}; }\ template class...> struct TemplateTypeList{};\ template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + constexpr auto get_wrapper(Catch::Detail::priority_tag<1>) noexcept -> TemplateTypeList { return {}; }\ template\ struct append;\ template\ @@ -6523,10 +6691,10 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_NTTP_1(signature, ...)\ template struct Nttp{};\ template\ - constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> Nttp<__VA_ARGS__> { return {}; } \ template class...> struct NttpTemplateTypeList{};\ template class...Cs>\ - constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList { return {}; } \ + constexpr auto get_wrapper(Catch::Detail::priority_tag<0>) noexcept -> NttpTemplateTypeList { return {}; } \ \ template< template class Container, template class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\ struct rewrap, List<__VA_ARGS__>> { using type = TypeList>; };\ @@ -6551,13 +6719,14 @@ static int catchInternalSectionHint = 0; template\ static void TestName() -#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\ - template\ - void reg_test(TypeList, Catch::NameAndTags nameAndTags)\ +#define INTERNAL_CATCH_TYPES_REGISTER(TestFunc)\ + template\ + void reg_test(TypeList, Catch::NameAndTags nameAndTags)\ {\ - Catch::AutoReg( Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ } +#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature, ...) #define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\ template\ void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\ @@ -6606,7 +6775,7 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__) #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__) #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__) @@ -6616,7 +6785,7 @@ static int catchInternalSectionHint = 0; #define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)) #define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)) -#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_TYPES_REGISTER(TestFunc) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) #define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)) #define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)) @@ -6745,11 +6914,11 @@ static int catchInternalSectionHint = 0; constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */\ } \ }; \ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ - using TestInit = typename create()), TypeList>::type; \ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type; \ TestInit t; \ t.reg_tests(); \ return 0; \ @@ -6794,7 +6963,7 @@ static int catchInternalSectionHint = 0; (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\ } \ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ using TestInit = typename convert::type; \ TestInit t; \ t.reg_tests(); \ @@ -6830,7 +6999,7 @@ static int catchInternalSectionHint = 0; (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ TestNameClass();\ return 0;\ }();\ @@ -6877,11 +7046,11 @@ static int catchInternalSectionHint = 0; constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ - (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */ \ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + types_list[index % num_types] + '>', Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ - using TestInit = typename create()), TypeList>::type;\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename create(Catch::Detail::priority_tag<1>{})), TypeList>::type;\ TestInit t;\ t.reg_tests();\ return 0;\ @@ -6929,7 +7098,7 @@ static int catchInternalSectionHint = 0; (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \ }\ };\ - static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ using TestInit = typename convert::type;\ TestInit t;\ t.reg_tests();\ @@ -7181,6 +7350,22 @@ namespace Catch { #endif // CATCH_TEST_CASE_INFO_HPP_INCLUDED +#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED +#define CATCH_TEST_RUN_INFO_HPP_INCLUDED + + +namespace Catch { + + struct TestRunInfo { + constexpr TestRunInfo(StringRef _name) : name(_name) {} + StringRef name; + }; + +} // end namespace Catch + +#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED + + #ifndef CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED #define CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED @@ -7268,7 +7453,7 @@ namespace Catch { static std::string translatorName( signature ); \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + namespace{ const Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static std::string translatorName( signature ) @@ -7329,8 +7514,8 @@ namespace Catch { #define CATCH_VERSION_MACROS_HPP_INCLUDED #define CATCH_VERSION_MAJOR 3 -#define CATCH_VERSION_MINOR 7 -#define CATCH_VERSION_PATCH 1 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 0 #endif // CATCH_VERSION_MACROS_HPP_INCLUDED @@ -7371,7 +7556,7 @@ namespace Catch { m_msg(msg) {} - const char* what() const noexcept override final; + const char* what() const noexcept final; }; } // end namespace Catch @@ -7384,6 +7569,24 @@ namespace Catch { +#ifndef CATCH_GENERATORS_THROW_HPP_INCLUDED +#define CATCH_GENERATORS_THROW_HPP_INCLUDED + +namespace Catch { + namespace Generators { + namespace Detail { + + //! Throws GeneratorException with the provided message + [[noreturn]] + void throw_generator_exception( char const* msg ); + + } // namespace Detail + } // namespace Generators +} // namespace Catch + +#endif // CATCH_GENERATORS_THROW_HPP_INCLUDED + + #ifndef CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED #define CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED @@ -7412,6 +7615,15 @@ namespace Catch { //! Customization point for `currentElementAsString` virtual std::string stringifyImpl() const = 0; + /** + * Customization point for skipping to the n-th element + * + * Defaults to successively calling `countedNext`. If there + * are not enough elements to reach the nth one, will throw + * an error. + */ + virtual void skipToNthElementImpl( std::size_t n ); + public: GeneratorUntypedBase() = default; // Generation of copy ops is deprecated (and Clang will complain) @@ -7435,6 +7647,13 @@ namespace Catch { std::size_t currentElementIndex() const { return m_currentElementIndex; } + /** + * Moves the generator forward **to** the n-th element + * + * Cannot move backwards. Can stay in place. + */ + void skipToNthElement( std::size_t n ); + /** * Returns generator's current element as user-friendly string. * @@ -7449,6 +7668,15 @@ namespace Catch { * comes first. */ StringRef currentElementAsString() const; + + /** + * Returns true if calls to `next` will eventually return false + * + * Note that for backwards compatibility this is currently defaulted + * to return `true`, but in the future all generators will have to + * provide their own implementation. + */ + virtual bool isFinite() const; }; using GeneratorBasePtr = Catch::Detail::unique_ptr; @@ -7457,9 +7685,7 @@ namespace Catch { class IGeneratorTracker { public: virtual ~IGeneratorTracker(); // = default; - virtual auto hasGenerator() const -> bool = 0; virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; - virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; }; } // namespace Catch @@ -7473,14 +7699,6 @@ namespace Catch { namespace Generators { -namespace Detail { - - //! Throws GeneratorException with the provided message - [[noreturn]] - void throw_generator_exception(char const * msg); - -} // end namespace detail - template class IGenerator : public GeneratorUntypedBase { std::string stringifyImpl() const override { @@ -7515,6 +7733,9 @@ namespace Detail { bool next() { return m_generator->countedNext(); } + + bool isFinite() const { return m_generator->isFinite(); } + void skipToNthElement( size_t n ) { m_generator->skipToNthElement(n); } }; @@ -7535,6 +7756,8 @@ namespace Detail { bool next() override { return false; } + + bool isFinite() const override { return true; } }; template @@ -7544,6 +7767,15 @@ namespace Detail { "specialization, use SingleValue Generator instead."); std::vector m_values; size_t m_idx = 0; + + void skipToNthElementImpl( std::size_t n ) override { + if ( n >= m_values.size() ) { + Detail::throw_generator_exception( + "Coud not jump to Nth element: not enough elements" ); + } + m_idx = n; + } + public: FixedValuesGenerator( std::initializer_list values ) : m_values( values ) {} @@ -7554,6 +7786,8 @@ namespace Detail { ++m_idx; return m_idx < m_values.size(); } + + bool isFinite() const override { return true; } }; template > @@ -7618,6 +7852,14 @@ namespace Detail { } return m_current < m_generators.size(); } + + bool isFinite() const override { + for (auto const& gen : m_generators) { + if (!gen.isFinite()) { return false; + } + } + return true; + } }; @@ -7709,6 +7951,17 @@ namespace Generators { GeneratorWrapper m_generator; size_t m_returned = 0; size_t m_target; + + void skipToNthElementImpl( std::size_t n ) override { + if ( n >= m_target ) { + Detail::throw_generator_exception( + "Coud not jump to Nth element: not enough elements" ); + } + + m_generator.skipToNthElement( n ); + m_returned = n; + } + public: TakeGenerator(size_t target, GeneratorWrapper&& generator): m_generator(CATCH_MOVE(generator)), @@ -7733,6 +7986,8 @@ namespace Generators { } return success; } + + bool isFinite() const override { return true; } }; template @@ -7745,8 +8000,9 @@ namespace Generators { class FilterGenerator final : public IGenerator { GeneratorWrapper m_generator; Predicate m_predicate; + static_assert(!std::is_reference::value, "This would most likely result in a dangling reference"); public: - template + template FilterGenerator(P&& pred, GeneratorWrapper&& generator): m_generator(CATCH_MOVE(generator)), m_predicate(CATCH_FORWARD(pred)) @@ -7773,12 +8029,14 @@ namespace Generators { while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true); return success; } + + bool isFinite() const override { return m_generator.isFinite(); } }; template GeneratorWrapper filter(Predicate&& pred, GeneratorWrapper&& generator) { - return GeneratorWrapper(Catch::Detail::make_unique>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); + return GeneratorWrapper(Catch::Detail::make_unique::type>>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); } template @@ -7797,6 +8055,9 @@ namespace Generators { m_target_repeats(repeats) { assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); + if (!m_generator.isFinite()) { + Detail::throw_generator_exception( "Cannot repeat infinite generator" ); + } } T const& get() const override { @@ -7830,6 +8091,8 @@ namespace Generators { } return m_current_repeat < m_target_repeats; } + + bool isFinite() const override { return m_generator.isFinite(); } }; template @@ -7843,25 +8106,30 @@ namespace Generators { GeneratorWrapper m_generator; Func m_function; // To avoid returning dangling reference, we have to save the values - T m_cache; + mutable Optional m_cache; + + void skipToNthElementImpl( std::size_t n ) override { + m_generator.skipToNthElement( n ); + m_cache.reset(); + } + public: template MapGenerator(F2&& function, GeneratorWrapper&& generator) : m_generator(CATCH_MOVE(generator)), - m_function(CATCH_FORWARD(function)), - m_cache(m_function(m_generator.get())) + m_function(CATCH_FORWARD(function)) {} T const& get() const override { - return m_cache; + if ( !m_cache ) { m_cache = m_function( m_generator.get() ); } + return *m_cache; } bool next() override { - const auto success = m_generator.next(); - if (success) { - m_cache = m_function(m_generator.get()); - } - return success; + m_cache.reset(); + return m_generator.next(); } + + bool isFinite() const override { return m_generator.isFinite(); } }; template > @@ -7883,7 +8151,6 @@ namespace Generators { std::vector m_chunk; size_t m_chunk_size; GeneratorWrapper m_generator; - bool m_used_up = false; public: ChunkGenerator(size_t size, GeneratorWrapper generator) : m_chunk_size(size), m_generator(CATCH_MOVE(generator)) @@ -7912,6 +8179,8 @@ namespace Generators { } return true; } + + bool isFinite() const override { return m_generator.isFinite(); } }; template @@ -7921,6 +8190,56 @@ namespace Generators { ); } + template + class ConcatGenerator final : public IGenerator { + std::vector> m_generators; + size_t m_current_generator = 0; + + void InsertGenerators( GeneratorWrapper&& gen ) { + m_generators.push_back( CATCH_MOVE( gen ) ); + } + + template + void InsertGenerators( GeneratorWrapper&& gen, Generators&&... gens ) { + m_generators.push_back( CATCH_MOVE( gen ) ); + InsertGenerators( CATCH_MOVE( gens )... ); + } + + public: + template + ConcatGenerator( Generators&&... generators ) { + InsertGenerators( CATCH_MOVE( generators )... ); + } + + T const& get() const override { + return m_generators[m_current_generator].get(); + } + bool next() override { + const bool success = m_generators[m_current_generator].next(); + if ( success ) { return true; } + + // If current generator is used up, we have to move to the next one + ++m_current_generator; + return m_current_generator < m_generators.size(); + } + + bool isFinite() const override { + for ( auto const& gen : m_generators ) { + if ( !gen.isFinite() ) { return false; } + } + return true; + } + }; + + template + GeneratorWrapper cat( GeneratorWrapper&& generator, + Generators&&... generators ) { + return GeneratorWrapper( + Catch::Detail::make_unique>( + CATCH_MOVE( generator ), CATCH_MOVE( generators )... ) ); + } + + } // namespace Generators } // namespace Catch @@ -8563,6 +8882,7 @@ class RandomFloatingGenerator final : public IGenerator { m_current_number = m_dist(m_rng); return true; } + bool isFinite() const override { return false; } }; template <> @@ -8580,6 +8900,7 @@ class RandomFloatingGenerator final : public IGenerator @@ -8601,6 +8922,7 @@ class RandomIntegerGenerator final : public IGenerator { m_current_number = m_dist(m_rng); return true; } + bool isFinite() const override { return false; } }; template @@ -8670,6 +8992,8 @@ class RangeGenerator final : public IGenerator { m_current += m_step; return (m_positive) ? (m_current < m_end) : (m_current > m_end); } + + bool isFinite() const override { return true; } }; template @@ -8709,11 +9033,13 @@ class IteratorGenerator final : public IGenerator { ++m_current; return m_current != m_elems.size(); } + + bool isFinite() const override { return true; } }; template ::value_type> + typename ResultType = std::remove_const_t::value_type>> GeneratorWrapper from_range(InputIterator from, InputSentinel to) { return GeneratorWrapper(Catch::Detail::make_unique>(from, to)); } @@ -8758,26 +9084,9 @@ auto from_range(Container const& cnt) { #define CATCH_INTERFACES_REPORTER_HPP_INCLUDED - -#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED -#define CATCH_TEST_RUN_INFO_HPP_INCLUDED - - -namespace Catch { - - struct TestRunInfo { - constexpr TestRunInfo(StringRef _name) : name(_name) {} - StringRef name; - }; - -} // end namespace Catch - -#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED - #include #include #include -#include namespace Catch { @@ -8873,6 +9182,11 @@ namespace Catch { //! Catch2 should call `Reporter::assertionEnded` even for passing //! assertions bool shouldReportAllAssertions = false; + //! Catch2 should call `Reporter::assertionStarting` for all assertions + // Defaults to true for backwards compatibility, but none of our current + // reporters actually want this, and it enables a fast path in assertion + // handling. + bool shouldReportAllAssertionStarts = true; }; /** @@ -9395,6 +9709,17 @@ namespace Catch { bool isDebuggerActive(); } +#if !defined( CATCH_TRAP ) && defined( __clang__ ) && defined( __has_builtin ) +# if __has_builtin( __builtin_debugtrap ) +# define CATCH_TRAP() __builtin_debugtrap() +# endif +#endif + +#if !defined( CATCH_TRAP ) && defined( _MSC_VER ) +# define CATCH_TRAP() __debugbreak() +#endif + +#if !defined(CATCH_TRAP) // If we couldn't use compiler-specific impl from above, we get into platform-specific options #ifdef CATCH_PLATFORM_MAC #if defined(__i386__) || defined(__x86_64__) @@ -9419,7 +9744,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. @@ -9430,15 +9755,15 @@ namespace Catch { #define CATCH_TRAP() raise(SIGTRAP) #endif -#elif defined(_MSC_VER) - #define CATCH_TRAP() __debugbreak() #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); #define CATCH_TRAP() DebugBreak() #endif +#endif // ^^ CATCH_TRAP is not defined yet, so we define it -#ifndef CATCH_BREAK_INTO_DEBUGGER - #ifdef CATCH_TRAP + +#if !defined(CATCH_BREAK_INTO_DEBUGGER) + #if defined(CATCH_TRAP) #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }() #else #define CATCH_BREAK_INTO_DEBUGGER() []{}() @@ -9452,7 +9777,7 @@ namespace Catch { #define CATCH_ENFORCE_HPP_INCLUDED -#include +#include // for `std::exception` in no-exception configuration namespace Catch { #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) @@ -9548,7 +9873,6 @@ namespace Catch { #define CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED -#include #include namespace Catch { @@ -9748,8 +10072,8 @@ namespace Detail { #ifndef CATCH_IS_PERMUTATION_HPP_INCLUDED #define CATCH_IS_PERMUTATION_HPP_INCLUDED -#include #include +#include namespace Catch { namespace Detail { @@ -9886,8 +10210,6 @@ namespace Catch { #include -#include -#include #include namespace Catch { @@ -9948,8 +10270,8 @@ namespace Catch { class JsonValueWriter { public: - JsonValueWriter( std::ostream& os ); - JsonValueWriter( std::ostream& os, std::uint64_t indent_level ); + JsonValueWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND ); + JsonValueWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level ); JsonObjectWriter writeObject() &&; JsonArrayWriter writeArray() &&; @@ -9983,8 +10305,8 @@ namespace Catch { class JsonObjectWriter { public: - JsonObjectWriter( std::ostream& os ); - JsonObjectWriter( std::ostream& os, std::uint64_t indent_level ); + JsonObjectWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND ); + JsonObjectWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level ); JsonObjectWriter( JsonObjectWriter&& source ) noexcept; JsonObjectWriter& operator=( JsonObjectWriter&& source ) = delete; @@ -10002,8 +10324,8 @@ namespace Catch { class JsonArrayWriter { public: - JsonArrayWriter( std::ostream& os ); - JsonArrayWriter( std::ostream& os, std::uint64_t indent_level ); + JsonArrayWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND ); + JsonArrayWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND, std::uint64_t indent_level ); JsonArrayWriter( JsonArrayWriter&& source ) noexcept; JsonArrayWriter& operator=( JsonArrayWriter&& source ) = delete; @@ -10244,6 +10566,9 @@ namespace Catch { #include namespace Catch { + +struct PathFilter; + namespace TestCaseTracking { struct NameAndLocation { @@ -10276,7 +10601,7 @@ namespace TestCaseTracking { StringRef name; SourceLineInfo location; - constexpr NameAndLocationRef( StringRef name_, + constexpr NameAndLocationRef( StringRef name_ CATCH_ATTR_LIFETIMEBOUND, SourceLineInfo location_ ): name( name_ ), location( location_ ) {} @@ -10319,12 +10644,23 @@ namespace TestCaseTracking { Children m_children; CycleState m_runState = NotStarted; - public: - ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ): - m_nameAndLocation( CATCH_MOVE(nameAndLoc) ), - m_parent( parent ) - {} + // Members for path filtering + std::vector const* m_filterRef = nullptr; + + // Note: There are 2 dummy section trackers (root, test-case) before + // the first "real" section tracker can be encountered. We start + // the default tracker at -2, so that the first "real" section + // tracker overflows to index 0. + // Nesting depth of this tracker, used to decide which new-style filter applies. + size_t m_allTrackerDepth = static_cast( -2 ); + // Nesting depth of sections (inc. this tracker), used for old-style filters. + // Must be updated by the section tracker on its own. + size_t m_sectionOnlyDepth = static_cast( -2 ); + // Transitory: Remove once we remove backwards compatibility with old-style (v3.x) filters + bool m_newStyleFilters = false; + public: + ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ); // static queries NameAndLocation const& nameAndLocation() const { @@ -10350,6 +10686,11 @@ namespace TestCaseTracking { //! Returns true iff tracker has started bool hasStarted() const; + void setFilters( std::vector const* filters, bool newStyleFilters ) { + m_filterRef = filters; + m_newStyleFilters = newStyleFilters; + } + // actions virtual void close() = 0; // Successfully complete virtual void fail() = 0; @@ -10435,8 +10776,7 @@ namespace TestCaseTracking { void moveToThis(); }; - class SectionTracker : public TrackerBase { - std::vector m_filters; + class SectionTracker final : public TrackerBase { // Note that lifetime-wise we piggy back off the name stored in the `ITracker` parent`. // Currently it allocates owns the name, so this is safe. If it is later refactored // to not own the name, the name still has to outlive the `ITracker` parent, so @@ -10453,10 +10793,6 @@ namespace TestCaseTracking { void tryOpen(); - void addInitialFilters( std::vector const& filters ); - void addNextFilters( std::vector const& filters ); - //! Returns filters active in this tracker - std::vector const& getFilters() const { return m_filters; } //! Returns whitespace-trimmed name of the tracked section StringRef trimmedName() const; }; @@ -10471,6 +10807,48 @@ using TestCaseTracking::SectionTracker; #endif // CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#ifndef CATCH_THREAD_SUPPORT_HPP_INCLUDED +#define CATCH_THREAD_SUPPORT_HPP_INCLUDED + + +#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS ) +# include +# include +#endif + + +namespace Catch { + namespace Detail { +#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS ) + using Mutex = std::mutex; + using LockGuard = std::lock_guard; + struct AtomicCounts { + std::atomic passed{ 0 }; + std::atomic failed{ 0 }; + std::atomic failedButOk{ 0 }; + std::atomic skipped{ 0 }; + }; +#else // ^^ Use actual mutex, lock and atomics + // vv Dummy implementations for single-thread performance + + struct Mutex { + void lock() {} + void unlock() {} + }; + + struct LockGuard { + LockGuard( Mutex ) {} + }; + + using AtomicCounts = Counts; +#endif + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_THREAD_SUPPORT_HPP_INCLUDED + #include namespace Catch { @@ -10543,11 +10921,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; @@ -10558,18 +10931,18 @@ namespace Catch { bool lastAssertionPassed() override; - void assertionPassed() override; - public: // !TBD We need to do this another way! bool aborting() const; private: + void assertionPassedFastPath( SourceLineInfo lineInfo ); + // Update the non-thread-safe m_totals from the atomic assertion counts. + void updateTotalsFromAtomics(); void runCurrentTest(); void invokeActiveTestCase(); - void resetAssertionInfo(); bool testForMissingAssertions( Counts& assertions ); void assertionEnded( AssertionResult&& result ); @@ -10579,31 +10952,39 @@ namespace Catch { ITransientExpression const *expr, bool negated ); - void populateReaction( AssertionReaction& reaction ); + void populateReaction( AssertionReaction& reaction, bool has_normal_disposition ); + + // Creates dummy info for unexpected exceptions/fatal errors, + // where we do not have the access to one, but we still need + // to send one to the reporters. + AssertionInfo makeDummyAssertionInfo(); private: void handleUnfinishedSections(); - + mutable Detail::Mutex m_assertionMutex; TestRunInfo m_runInfo; TestCaseHandle const* m_activeTestCase = nullptr; ITracker* m_testCaseTracker = nullptr; Optional m_lastResult; - IConfig const* m_config; Totals m_totals; + Detail::AtomicCounts m_atomicAssertionCount; IEventListenerPtr m_reporter; - std::vector m_messages; - std::vector m_messageScopes; /* Keeps owners of so-called unscoped messages. */ - AssertionInfo m_lastAssertionInfo; std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; Detail::unique_ptr m_outputRedirect; FatalConditionHandler m_fatalConditionhandler; - bool m_lastAssertionPassed = false; + // Caches m_config->abortAfter() to avoid vptr calls/allow inlining + size_t m_abortAfterXFailedAssertions; bool m_shouldReportUnexpected = true; + // Caches whether `assertionStarting` events should be sent to the reporter. + bool m_reportAssertionStarting; + // Caches whether `assertionEnded` events for successful assertions should be sent to the reporter bool m_includeSuccessfulResults; + // Caches m_config->shouldDebugBreak() to avoid vptr calls/allow inlining + bool m_shouldDebugBreak; }; void seedRng(IConfig const& config); @@ -10617,7 +10998,6 @@ namespace Catch { #define CATCH_SHARDING_HPP_INCLUDED #include -#include #include namespace Catch { @@ -10752,10 +11132,10 @@ namespace Catch { //! Returns a new string without whitespace at the start/end std::string trim( std::string const& str ); //! Returns a substring of the original ref without whitespace. Beware lifetimes! - StringRef trim(StringRef ref); + StringRef trim( StringRef ref CATCH_ATTR_LIFETIMEBOUND ); // !!! Be aware, returns refs into original string - make sure original string outlives them - std::vector splitStringRef( StringRef str, char delimiter ); + std::vector splitStringRef( StringRef str CATCH_ATTR_LIFETIMEBOUND, char delimiter ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); /** @@ -10773,7 +11153,7 @@ namespace Catch { StringRef m_label; public: - constexpr pluralise(std::uint64_t count, StringRef label): + constexpr pluralise(std::uint64_t count, StringRef label CATCH_ATTR_LIFETIMEBOUND): m_count(count), m_label(label) {} @@ -11252,6 +11632,19 @@ namespace Catch { #endif // CATCH_TEXTFLOW_HPP_INCLUDED +#ifndef CATCH_THREAD_LOCAL_HPP_INCLUDED +#define CATCH_THREAD_LOCAL_HPP_INCLUDED + + +#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS ) +#define CATCH_INTERNAL_THREAD_LOCAL thread_local +#else +#define CATCH_INTERNAL_THREAD_LOCAL +#endif + +#endif // CATCH_THREAD_LOCAL_HPP_INCLUDED + + #ifndef CATCH_TO_STRING_HPP_INCLUDED #define CATCH_TO_STRING_HPP_INCLUDED @@ -11320,7 +11713,7 @@ namespace Catch { public: enum ForWhat { ForTextNodes, ForAttributes }; - constexpr XmlEncode( StringRef str, ForWhat forWhat = ForTextNodes ): + constexpr XmlEncode( StringRef str CATCH_ATTR_LIFETIMEBOUND, ForWhat forWhat = ForTextNodes ): m_str( str ), m_forWhat( forWhat ) {} @@ -11338,7 +11731,7 @@ namespace Catch { class ScopedElement { public: - ScopedElement( XmlWriter* writer, XmlFormatting fmt ); + ScopedElement( XmlWriter* writer CATCH_ATTR_LIFETIMEBOUND, XmlFormatting fmt ); ScopedElement( ScopedElement&& other ) noexcept; ScopedElement& operator=( ScopedElement&& other ) noexcept; @@ -11370,7 +11763,7 @@ namespace Catch { XmlFormatting m_fmt; }; - XmlWriter( std::ostream& os ); + XmlWriter( std::ostream& os CATCH_ATTR_LIFETIMEBOUND ); ~XmlWriter(); XmlWriter( XmlWriter const& ) = delete; @@ -11628,11 +12021,15 @@ namespace Matchers { return description; } - friend MatchAllOf operator&& (MatchAllOf&& lhs, MatcherBase const& rhs) { + friend MatchAllOf operator&&( MatchAllOf&& lhs, + MatcherBase const& rhs + CATCH_ATTR_LIFETIMEBOUND ) { lhs.m_matchers.push_back(&rhs); return CATCH_MOVE(lhs); } - friend MatchAllOf operator&& (MatcherBase const& lhs, MatchAllOf&& rhs) { + friend MatchAllOf + operator&&( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAllOf&& rhs ) { rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); return CATCH_MOVE(rhs); } @@ -11680,11 +12077,15 @@ namespace Matchers { return description; } - friend MatchAnyOf operator|| (MatchAnyOf&& lhs, MatcherBase const& rhs) { + friend MatchAnyOf operator||( MatchAnyOf&& lhs, + MatcherBase const& rhs + CATCH_ATTR_LIFETIMEBOUND ) { lhs.m_matchers.push_back(&rhs); return CATCH_MOVE(lhs); } - friend MatchAnyOf operator|| (MatcherBase const& lhs, MatchAnyOf&& rhs) { + friend MatchAnyOf + operator||( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAnyOf&& rhs ) { rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); return CATCH_MOVE(rhs); } @@ -11704,7 +12105,8 @@ namespace Matchers { MatcherBase const& m_underlyingMatcher; public: - explicit MatchNotOf( MatcherBase const& underlyingMatcher ): + explicit MatchNotOf( MatcherBase const& underlyingMatcher + CATCH_ATTR_LIFETIMEBOUND ): m_underlyingMatcher( underlyingMatcher ) {} @@ -11720,16 +12122,22 @@ namespace Matchers { } // namespace Detail template - Detail::MatchAllOf operator&& (MatcherBase const& lhs, MatcherBase const& rhs) { + Detail::MatchAllOf + operator&&( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherBase const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return Detail::MatchAllOf{} && lhs && rhs; } + template - Detail::MatchAnyOf operator|| (MatcherBase const& lhs, MatcherBase const& rhs) { + Detail::MatchAnyOf + operator||( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherBase const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return Detail::MatchAnyOf{} || lhs || rhs; } template - Detail::MatchNotOf operator! (MatcherBase const& matcher) { + Detail::MatchNotOf + operator!( MatcherBase const& matcher CATCH_ATTR_LIFETIMEBOUND ) { return Detail::MatchNotOf{ matcher }; } @@ -11840,19 +12248,19 @@ namespace Matchers { } template - using is_generic_matcher = std::is_base_of< + static constexpr bool is_generic_matcher_v = std::is_base_of< Catch::Matchers::MatcherGenericBase, std::remove_cv_t> - >; + >::value; template - using are_generic_matchers = Catch::Detail::conjunction...>; + static constexpr bool are_generic_matchers_v = Catch::Detail::conjunction>...>::value; template - using is_matcher = std::is_base_of< + static constexpr bool is_matcher_v = std::is_base_of< Catch::Matchers::MatcherUntypedBase, std::remove_cv_t> - >; + >::value; template @@ -11896,7 +12304,8 @@ namespace Matchers { MatchAllOfGeneric(MatchAllOfGeneric&&) = default; MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default; - MatchAllOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {} + MatchAllOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND) + : m_matchers{ {std::addressof(matchers)...} } {} explicit MatchAllOfGeneric(std::array matchers) : m_matchers{matchers} {} template @@ -11918,26 +12327,26 @@ namespace Matchers { template friend MatchAllOfGeneric operator && ( - MatchAllOfGeneric&& lhs, - MatchAllOfGeneric&& rhs) { + MatchAllOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAllOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND ) { return MatchAllOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; } //! Avoids type nesting for `GenericAllOf && some matcher` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAllOfGeneric> operator && ( - MatchAllOfGeneric&& lhs, - MatcherRHS const& rhs) { + MatchAllOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return MatchAllOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast(&rhs))}; } //! Avoids type nesting for `some matcher && GenericAllOf` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAllOfGeneric> operator && ( - MatcherLHS const& lhs, - MatchAllOfGeneric&& rhs) { + MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAllOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND ) { return MatchAllOfGeneric{array_cat(static_cast(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; } }; @@ -11951,7 +12360,8 @@ namespace Matchers { MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default; MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default; - MatchAnyOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {} + MatchAnyOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND) + : m_matchers{ {std::addressof(matchers)...} } {} explicit MatchAnyOfGeneric(std::array matchers) : m_matchers{matchers} {} template @@ -11972,26 +12382,26 @@ namespace Matchers { //! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case template friend MatchAnyOfGeneric operator || ( - MatchAnyOfGeneric&& lhs, - MatchAnyOfGeneric&& rhs) { + MatchAnyOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAnyOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND ) { return MatchAnyOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; } //! Avoids type nesting for `GenericAnyOf || some matcher` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAnyOfGeneric> operator || ( - MatchAnyOfGeneric&& lhs, - MatcherRHS const& rhs) { + MatchAnyOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return MatchAnyOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast(std::addressof(rhs)))}; } //! Avoids type nesting for `some matcher || GenericAnyOf` case template - friend std::enable_if_t::value, + friend std::enable_if_t, MatchAnyOfGeneric> operator || ( - MatcherLHS const& lhs, - MatchAnyOfGeneric&& rhs) { + MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatchAnyOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND) { return MatchAnyOfGeneric{array_cat(static_cast(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; } }; @@ -12007,7 +12417,8 @@ namespace Matchers { MatchNotOfGeneric(MatchNotOfGeneric&&) = default; MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default; - explicit MatchNotOfGeneric(MatcherT const& matcher) : m_matcher{matcher} {} + explicit MatchNotOfGeneric(MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND) + : m_matcher{matcher} {} template bool match(Arg&& arg) const { @@ -12019,7 +12430,9 @@ namespace Matchers { } //! Negating negation can just unwrap and return underlying matcher - friend MatcherT const& operator ! (MatchNotOfGeneric const& matcher) { + friend MatcherT const& + operator!( MatchNotOfGeneric const& matcher + CATCH_ATTR_LIFETIMEBOUND ) { return matcher.m_matcher; } }; @@ -12028,47 +12441,53 @@ namespace Matchers { // compose only generic matchers template - std::enable_if_t::value, Detail::MatchAllOfGeneric> - operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) { + std::enable_if_t, Detail::MatchAllOfGeneric> + operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric> - operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) { + std::enable_if_t, Detail::MatchAnyOfGeneric> + operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return { lhs, rhs }; } //! Wrap provided generic matcher in generic negator template - std::enable_if_t::value, Detail::MatchNotOfGeneric> - operator ! (MatcherT const& matcher) { + std::enable_if_t, Detail::MatchNotOfGeneric> + operator!( MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND ) { return Detail::MatchNotOfGeneric{matcher}; } // compose mixed generic and non-generic matchers template - std::enable_if_t::value, Detail::MatchAllOfGeneric>> - operator && (MatcherLHS const& lhs, MatcherBase const& rhs) { + std::enable_if_t, Detail::MatchAllOfGeneric>> + operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherBase const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAllOfGeneric, MatcherRHS>> - operator && (MatcherBase const& lhs, MatcherRHS const& rhs) { + std::enable_if_t, Detail::MatchAllOfGeneric, MatcherRHS>> + operator&&( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric>> - operator || (MatcherLHS const& lhs, MatcherBase const& rhs) { + std::enable_if_t, Detail::MatchAnyOfGeneric>> + operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherBase const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return { lhs, rhs }; } template - std::enable_if_t::value, Detail::MatchAnyOfGeneric, MatcherRHS>> - operator || (MatcherBase const& lhs, MatcherRHS const& rhs) { + std::enable_if_t, Detail::MatchAnyOfGeneric, MatcherRHS>> + operator||( MatcherBase const& lhs CATCH_ATTR_LIFETIMEBOUND, + MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return { lhs, rhs }; } @@ -12144,7 +12563,7 @@ namespace Catch { //! Creates a matcher that accepts ranges/containers with specific size HasSizeMatcher SizeIs(std::size_t sz); template - std::enable_if_t::value, + std::enable_if_t, SizeMatchesMatcher> SizeIs(Matcher&& m) { return SizeMatchesMatcher{CATCH_FORWARD(m)}; } @@ -12159,8 +12578,8 @@ namespace Catch { #define CATCH_MATCHERS_CONTAINS_HPP_INCLUDED -#include #include +#include namespace Catch { namespace Matchers { @@ -12222,14 +12641,14 @@ namespace Catch { * Uses `std::equal_to` to do the comparison */ template - std::enable_if_t::value, + std::enable_if_t, ContainsElementMatcher>> Contains(T&& elem) { return { CATCH_FORWARD(elem), std::equal_to<>{} }; } //! Creates a matcher that checks whether a range contains element matching a matcher template - std::enable_if_t::value, + std::enable_if_t, ContainsMatcherMatcher> Contains(Matcher&& matcher) { return { CATCH_FORWARD(matcher) }; } @@ -12606,8 +13025,7 @@ namespace Catch { #define CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED -#include -#include +#include namespace Catch { namespace Matchers { @@ -12691,55 +13109,64 @@ namespace Catch { * Creates a matcher that checks if all elements in a range are equal * to all elements in another range. * - * Uses `std::equal_to` to do the comparison + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) */ - template + template {} )> constexpr - std::enable_if_t::value, - RangeEqualsMatcher>> - RangeEquals( RangeLike&& range ) { - return { CATCH_FORWARD( range ), std::equal_to<>{} }; + RangeEqualsMatcher + RangeEquals( RangeLike&& range, + Equality&& predicate = std::equal_to<>{} ) { + return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) }; } /** * Creates a matcher that checks if all elements in a range are equal - * to all elements in another range. + * to all elements in an initializer list. * - * Uses to provided predicate `predicate` to do the comparisons + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) */ - template + template {} )> constexpr - RangeEqualsMatcher - RangeEquals( RangeLike&& range, Equality&& predicate ) { - return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) }; + RangeEqualsMatcher, Equality> + RangeEquals( std::initializer_list range, + Equality&& predicate = std::equal_to<>{} ) { + return { range, CATCH_FORWARD( predicate ) }; } /** * Creates a matcher that checks if all elements in a range are equal - * to all elements in another range, in some permutation + * to all elements in another range, in some permutation. * - * Uses `std::equal_to` to do the comparison + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) */ - template + template {} )> constexpr - std::enable_if_t< - !Detail::is_matcher::value, - UnorderedRangeEqualsMatcher>> - UnorderedRangeEquals( RangeLike&& range ) { - return { CATCH_FORWARD( range ), std::equal_to<>{} }; + UnorderedRangeEqualsMatcher + UnorderedRangeEquals( RangeLike&& range, + Equality&& predicate = std::equal_to<>{} ) { + return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) }; } /** * Creates a matcher that checks if all elements in a range are equal - * to all elements in another range, in some permutation. + * to all elements in an initializer list, in some permutation. * - * Uses to provided predicate `predicate` to do the comparisons + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) */ - template + template {} )> constexpr - UnorderedRangeEqualsMatcher - UnorderedRangeEquals( RangeLike&& range, Equality&& predicate ) { - return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) }; + UnorderedRangeEqualsMatcher, Equality> + UnorderedRangeEquals( std::initializer_list range, + Equality&& predicate = std::equal_to<>{} ) { + return { range, CATCH_FORWARD( predicate ) }; } } // namespace Matchers } // namespace Catch @@ -13182,9 +13609,11 @@ namespace Catch { public: // GCC5 compat: we cannot use inherited constructor, because it // doesn't implement backport of P0136 - AutomakeReporter(ReporterConfig&& _config): - StreamingReporterBase(CATCH_MOVE(_config)) - {} + AutomakeReporter( ReporterConfig&& _config ): + StreamingReporterBase( CATCH_MOVE( _config ) ) { + m_preferences.shouldReportAllAssertionStarts = false; + } + ~AutomakeReporter() override; static std::string getDescription() { @@ -13211,7 +13640,10 @@ namespace Catch { class CompactReporter final : public StreamingReporterBase { public: - using StreamingReporterBase::StreamingReporterBase; + CompactReporter( ReporterConfig&& _config ): + StreamingReporterBase( CATCH_MOVE( _config ) ) { + m_preferences.shouldReportAllAssertionStarts = false; + } ~CompactReporter() override; @@ -13253,8 +13685,6 @@ namespace Catch { void noMatchingTestCases( StringRef unmatchedSpec ) override; void reportInvalidTestSpec( StringRef arg ) override; - void assertionStarting(AssertionInfo const&) override; - void assertionEnded(AssertionStats const& _assertionStats) override; void sectionStarting(SectionInfo const& _sectionInfo) override; @@ -13608,7 +14038,6 @@ namespace Catch { void sectionStarting( SectionInfo const& sectionInfo ) override; void sectionEnded( SectionStats const& sectionStats ) override; - void assertionStarting( AssertionInfo const& assertionInfo ) override; void assertionEnded( AssertionStats const& assertionStats ) override; //void testRunEndedCumulative() override; @@ -13735,6 +14164,11 @@ namespace Catch { void updatePreferences(IEventListener const& reporterish); public: + MultiReporter( IConfig const* config ): + IEventListener( config ) { + m_preferences.shouldReportAllAssertionStarts = false; + } + using IEventListener::IEventListener; void addListener( IEventListenerPtr&& listener ); @@ -13870,22 +14304,24 @@ namespace Catch { #if !defined(CATCH_CONFIG_DISABLE) -# define CATCH_REGISTER_REPORTER( name, reporterType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ReporterRegistrar INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_RegistrarFor )( name ); \ - } \ +# define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ReporterRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \ + name ); \ + } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -# define CATCH_REGISTER_LISTENER( listenerType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace { \ - Catch::ListenerRegistrar INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_RegistrarFor )( #listenerType##_catch_sr ); \ - } \ +# define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + const Catch::ListenerRegistrar \ + INTERNAL_CATCH_UNIQUE_NAME( catch_internal_RegistrarFor )( \ + #listenerType##_catch_sr ); \ + } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #else // CATCH_CONFIG_DISABLE @@ -13912,6 +14348,7 @@ namespace Catch { , xml(m_stream) { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = false; + m_preferences.shouldReportAllAssertionStarts = false; m_shouldStoreSuccesfulAssertions = false; } @@ -13960,6 +14397,7 @@ namespace Catch { TAPReporter( ReporterConfig&& config ): StreamingReporterBase( CATCH_MOVE(config) ) { m_preferences.shouldReportAllAssertions = true; + m_preferences.shouldReportAllAssertionStarts = false; } static std::string getDescription() { @@ -14003,6 +14441,7 @@ namespace Catch { : StreamingReporterBase( CATCH_MOVE(_config) ) { m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertionStarts = false; } ~TeamCityReporter() override; @@ -14070,8 +14509,6 @@ namespace Catch { void sectionStarting(SectionInfo const& sectionInfo) override; - void assertionStarting(AssertionInfo const&) override; - void assertionEnded(AssertionStats const& assertionStats) override; void sectionEnded(SectionStats const& sectionStats) override; diff --git a/tests/thread_test.cpp b/tests/thread_test.cpp index bc86f0b24..9ac9ab5e7 100644 --- a/tests/thread_test.cpp +++ b/tests/thread_test.cpp @@ -60,16 +60,16 @@ TEST_CASE("Thread", "") REQUIRE(!th.isRunning() ); REQUIRE(th.getExitCode() == 1); -} -TEST_CASE("MpScUnboundedBlockingQueue", "") -{ - void* p0 = s_mpsc.pop(); - void* p1 = s_mpsc.pop(); + SECTION("MpScUnboundedBlockingQueue") + { + void* p0 = s_mpsc.pop(); + void* p1 = s_mpsc.pop(); - uintptr_t result = uintptr_t(p0) | uintptr_t(p1); + uintptr_t result = uintptr_t(p0) | uintptr_t(p1); - REQUIRE(result == 0x1389); + REQUIRE(result == 0x1389); + } } #endif // BX_CONFIG_SUPPORTS_THREADING