diff options
author | Sven Gothel <[email protected]> | 2022-06-19 05:52:19 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2022-06-19 05:52:19 +0200 |
commit | 4a3f09a9fb5f836f74a1bd083b82caf5edf95fa4 (patch) | |
tree | f96545f530c60fb93b823b83baa86c7f937f48ac /include | |
parent | fa440d41eaad783c80a169bc5a1c46c7f0ab05d5 (diff) |
Catch2: Update to v3.0.1 Generated: 2022-05-17
Diffstat (limited to 'include')
-rw-r--r-- | include/catch2/catch_amalgamated.cpp | 4430 | ||||
-rw-r--r-- | include/catch2/catch_amalgamated.hpp | 4841 |
2 files changed, 5715 insertions, 3556 deletions
diff --git a/include/catch2/catch_amalgamated.cpp b/include/catch2/catch_amalgamated.cpp index 3053887..bd8c811 100644 --- a/include/catch2/catch_amalgamated.cpp +++ b/include/catch2/catch_amalgamated.cpp @@ -5,8 +5,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.0.0-preview.3 -// Generated: 2020-10-08 13:59:26.616931 +// Catch v3.0.1 +// Generated: 2022-05-17 22:08:47.054486 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -20,6 +20,7 @@ #include <cassert> +#include <cstddef> #include <iterator> #include <random> @@ -33,8 +34,8 @@ namespace { using Catch::Benchmark::Detail::sample; template <typename URng, typename Estimator> - sample resample(URng& rng, int resamples, std::vector<double>::iterator first, std::vector<double>::iterator last, Estimator& estimator) { - auto n = last - first; + sample resample(URng& rng, unsigned int resamples, std::vector<double>::iterator first, std::vector<double>::iterator last, Estimator& estimator) { + auto n = static_cast<size_t>(last - first); std::uniform_int_distribution<decltype(n)> dist(0, n - 1); sample out; @@ -42,7 +43,7 @@ using Catch::Benchmark::Detail::sample; std::generate_n(std::back_inserter(out), resamples, [n, first, &estimator, &dist, &rng] { std::vector<double> resampled; resampled.reserve(n); - std::generate_n(std::back_inserter(resampled), n, [first, &dist, &rng] { return first[dist(rng)]; }); + std::generate_n(std::back_inserter(resampled), n, [first, &dist, &rng] { return first[static_cast<std::ptrdiff_t>(dist(rng))]; }); return estimator(resampled.begin(), resampled.end()); }); std::sort(out.begin(), out.end()); @@ -127,11 +128,15 @@ using Catch::Benchmark::Detail::sample; double standard_deviation(std::vector<double>::iterator first, std::vector<double>::iterator last) { auto m = Catch::Benchmark::Detail::mean(first, last); - double variance = std::accumulate(first, last, 0., [m](double a, double b) { - double diff = b - m; - return a + diff * diff; - }) / (last - first); - return std::sqrt(variance); + double variance = std::accumulate( first, + last, + 0., + [m]( double a, double b ) { + double diff = b - m; + return a + diff * diff; + } ) / + ( last - first ); + return std::sqrt( variance ); } } @@ -140,6 +145,15 @@ namespace Catch { namespace Benchmark { namespace Detail { +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + bool directCompare( double lhs, double rhs ) { return lhs == rhs; } +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic pop +#endif + double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last) { auto count = last - first; double idx = (count - 1) * k / static_cast<double>(q); @@ -147,7 +161,9 @@ namespace Catch { double g = idx - j; std::nth_element(first, first + j, last); auto xj = first[j]; - if (g == 0) return xj; + if ( directCompare( g, 0 ) ) { + return xj; + } auto xj1 = *std::min_element(first + (j + 1), last); return xj + g * (xj1 - xj); @@ -179,7 +195,7 @@ namespace Catch { double sb = stddev.point; double mn = mean.point / n; double mg_min = mn / 2.; - double sg = std::min(mg_min / 4., sb / std::sqrt(n)); + double sg = (std::min)(mg_min / 4., sb / std::sqrt(n)); double sg2 = sg * sg; double sb2 = sb * sb; @@ -190,7 +206,7 @@ namespace Catch { double k0 = -n * nd; double k1 = sb2 - n * sg2 + nd; double det = k1 * k1 - 4 * sg2 * k0; - return (int)(-2. * k0 / (k1 + std::sqrt(det))); + return static_cast<int>(-2. * k0 / (k1 + std::sqrt(det))); }; auto var_out = [n, sb2, sg2](double c) { @@ -198,11 +214,11 @@ namespace Catch { return (nc / n) * (sb2 - nc * sg2); }; - return std::min(var_out(1), var_out(std::min(c_max(0.), c_max(mg_min)))) / sb2; + return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2; } - bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last) { + bootstrap_analysis analyse_samples(double confidence_level, unsigned int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last) { CATCH_INTERNAL_START_WARNINGS_SUPPRESSION CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS static std::random_device entropy; @@ -289,25 +305,6 @@ namespace Catch { } // namespace Catch -//////////////////////////////////////////////// -// vvv formerly catch_complete_invoke.cpp vvv // -//////////////////////////////////////////////// - - -namespace Catch { - namespace Benchmark { - namespace Detail { - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS - const std::string benchmarkErrorMsg = "a benchmark failed to run successfully"; - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - - - - ///////////////////////////////////////////////// // vvv formerly catch_run_for_at_least.cpp vvv // ///////////////////////////////////////////////// @@ -351,7 +348,7 @@ bool marginComparison(double lhs, double rhs, double margin) { namespace Catch { Approx::Approx ( double value ) - : m_epsilon( std::numeric_limits<float>::epsilon()*100 ), + : m_epsilon( std::numeric_limits<float>::epsilon()*100. ), m_margin( 0.0 ), m_scale( 0.0 ), m_value( value ) @@ -495,7 +492,7 @@ namespace Catch { : expr; } - std::string AssertionResult::getMessage() const { + StringRef AssertionResult::getMessage() const { return m_resultData.message; } SourceLineInfo AssertionResult::getSourceInfo() const { @@ -510,13 +507,18 @@ namespace Catch { - namespace Catch { - Config::Config( ConfigData const& data ) - : m_data( data ), - m_stream( openStream() ) - { + bool operator==( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ) { + return lhs.name == rhs.name && + lhs.outputFilename == rhs.outputFilename && + lhs.colourMode == rhs.colourMode && + lhs.customOptions == rhs.customOptions; + } + + Config::Config( ConfigData const& data ): + m_data( data ) { // We need to trim filter specs to avoid trouble with superfluous // whitespace (esp. important for bdd macros, as those are manually // aligned with whitespace). @@ -528,6 +530,7 @@ namespace Catch { elem = trim(elem); } + TestSpecParser parser(ITagAliasRegistry::get()); if (!m_data.testsOrTags.empty()) { m_hasTestFilters = true; @@ -536,25 +539,86 @@ namespace Catch { } } m_testSpec = parser.testSpec(); - } - Config::~Config() = default; + // Insert the default reporter if user hasn't asked for a specfic one + if ( m_data.reporterSpecifications.empty() ) { + m_data.reporterSpecifications.push_back( { +#if defined( CATCH_CONFIG_DEFAULT_REPORTER ) + CATCH_CONFIG_DEFAULT_REPORTER, +#else + "console", +#endif + {}, {}, {} + } ); + } + +#if defined( CATCH_CONFIG_BAZEL_SUPPORT ) + // Register a JUnit reporter for Bazel. Bazel sets an environment + // variable with the path to XML output. If this file is written to + // during test, Bazel will not generate a default XML output. + // This allows the XML output file to contain higher level of detail + // than what is possible otherwise. +# if defined( _MSC_VER ) + // On Windows getenv throws a warning as there is no input validation, + // since the key is hardcoded, this should not be an issue. +# pragma warning( push ) +# pragma warning( disable : 4996 ) +# endif + const auto bazelOutputFilePtr = std::getenv( "XML_OUTPUT_FILE" ); +# if defined( _MSC_VER ) +# pragma warning( pop ) +# endif + if ( bazelOutputFilePtr != nullptr ) { + m_data.reporterSpecifications.push_back( + { "junit", std::string( bazelOutputFilePtr ), {}, {} } ); + } +#endif + + + // We now fixup the reporter specs to handle default output spec, + // default colour spec, etc + bool defaultOutputUsed = false; + for ( auto const& reporterSpec : m_data.reporterSpecifications ) { + // We do the default-output check separately, while always + // using the default output below to make the code simpler + // and avoid superfluous copies. + if ( reporterSpec.outputFile().none() ) { + CATCH_ENFORCE( !defaultOutputUsed, + "Internal error: cannot use default output for " + "multiple reporters" ); + defaultOutputUsed = true; + } - std::string const& Config::getFilename() const { - return m_data.outputFilename ; + m_processedReporterSpecs.push_back( ProcessedReporterSpec{ + reporterSpec.name(), + reporterSpec.outputFile() ? *reporterSpec.outputFile() + : data.defaultOutputFilename, + reporterSpec.colourMode().valueOr( data.defaultColourMode ), + reporterSpec.customOptions() } ); + } } + Config::~Config() = default; + + bool Config::listTests() const { return m_data.listTests; } bool Config::listTags() const { return m_data.listTags; } bool Config::listReporters() const { return m_data.listReporters; } - - std::string Config::getProcessName() const { return m_data.processName; } - std::string const& Config::getReporterName() const { return m_data.reporterName; } + bool Config::listListeners() const { return m_data.listListeners; } std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; } std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + std::vector<ReporterSpec> const& Config::getReporterSpecs() const { + return m_data.reporterSpecifications; + } + + std::vector<ProcessedReporterSpec> const& + Config::getProcessedReporterSpecs() const { + return m_processedReporterSpecs; + } + TestSpec const& Config::testSpec() const { return m_testSpec; } bool Config::hasTestFilters() const { return m_hasTestFilters; } @@ -562,31 +626,34 @@ namespace Catch { // IConfig interface bool Config::allowThrows() const { return !m_data.noThrow; } - std::ostream& Config::stream() const { return m_stream->stream(); } - std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } - bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } - bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } + bool Config::warnAboutMissingAssertions() const { + return !!( m_data.warnings & WarnAbout::NoAssertions ); + } + bool Config::warnAboutUnmatchedTestSpecs() const { + return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec ); + } + bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; } ShowDurations Config::showDurations() const { return m_data.showDurations; } double Config::minDuration() const { return m_data.minDuration; } TestRunOrder Config::runOrder() const { return m_data.runOrder; } - unsigned int Config::rngSeed() const { return m_data.rngSeed; } - UseColour Config::useColour() const { return m_data.useColour; } + uint32_t Config::rngSeed() const { return m_data.rngSeed; } + unsigned int Config::shardCount() const { return m_data.shardCount; } + unsigned int Config::shardIndex() const { return m_data.shardIndex; } + ColourMode Config::defaultColourMode() const { return m_data.defaultColourMode; } bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } int Config::abortAfter() const { return m_data.abortAfter; } bool Config::showInvisibles() const { return m_data.showInvisibles; } Verbosity Config::verbosity() const { return m_data.verbosity; } + bool Config::skipBenchmarks() const { return m_data.skipBenchmarks; } bool Config::benchmarkNoAnalysis() const { return m_data.benchmarkNoAnalysis; } - int Config::benchmarkSamples() const { return m_data.benchmarkSamples; } + unsigned int Config::benchmarkSamples() const { return m_data.benchmarkSamples; } double Config::benchmarkConfidenceInterval() const { return m_data.benchmarkConfidenceInterval; } unsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; } std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); } - IStream const* Config::openStream() { - return Catch::makeStream(m_data.outputFilename); - } - } // end namespace Catch @@ -598,13 +665,6 @@ namespace Catch { //////////////////////////////////////////////////////////////////////////// - Catch::MessageBuilder::MessageBuilder( StringRef const& macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ) - :m_info(macroName, lineInfo, type) {} - - //////////////////////////////////////////////////////////////////////////// - ScopedMessage::ScopedMessage( MessageBuilder const& builder ): m_info( builder.m_info ) { @@ -613,7 +673,7 @@ namespace Catch { } ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: - m_info( std::move( old.m_info ) ) { + m_info( CATCH_MOVE( old.m_info ) ) { old.m_moved = true; } @@ -729,16 +789,16 @@ namespace Catch { public: // IMutableRegistryHub void registerReporter( std::string const& name, IReporterFactoryPtr factory ) override { - m_reporterRegistry.registerReporter( name, std::move(factory) ); + m_reporterRegistry.registerReporter( name, CATCH_MOVE(factory) ); } - void registerListener( IReporterFactoryPtr factory ) override { - m_reporterRegistry.registerListener( std::move(factory) ); + void registerListener( Detail::unique_ptr<EventListenerFactory> factory ) override { + m_reporterRegistry.registerListener( CATCH_MOVE(factory) ); } void registerTest( Detail::unique_ptr<TestCaseInfo>&& testInfo, Detail::unique_ptr<ITestInvoker>&& invoker ) override { - m_testCaseRegistry.registerTest( std::move(testInfo), std::move(invoker) ); + m_testCaseRegistry.registerTest( CATCH_MOVE(testInfo), CATCH_MOVE(invoker) ); } - void registerTranslator( const IExceptionTranslator* translator ) override { - m_exceptionTranslatorRegistry.registerTranslator( translator ); + void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( CATCH_MOVE(translator) ); } void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { m_tagAliasRegistry.add( alias, tag, lineInfo ); @@ -786,6 +846,7 @@ namespace Catch { #include <algorithm> +#include <cassert> #include <iomanip> #include <set> @@ -794,58 +855,78 @@ namespace Catch { namespace { const int MaxExitCode = 255; - IStreamingReporterPtr createReporter(std::string const& reporterName, IConfig const* config) { - auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); - CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + 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 << '\''); return reporter; } - IStreamingReporterPtr makeReporter(Config const* config) { - if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { - return createReporter(config->getReporterName(), config); + IEventListenerPtr prepareReporters(Config const* config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty() + && config->getProcessedReporterSpecs().size() == 1) { + auto const& spec = config->getProcessedReporterSpecs()[0]; + return createReporter( + spec.name, + ReporterConfig( config, + makeStream( spec.outputFilename ), + spec.colourMode, + spec.customOptions ) ); } - // On older platforms, returning unique_ptr<ListeningReporter> - // when the return type is unique_ptr<IStreamingReporter> - // doesn't compile without a std::move call. However, this causes - // a warning on newer platforms. Thus, we have to work around - // it a bit and downcast the pointer manually. - auto ret = Detail::unique_ptr<IStreamingReporter>(new ListeningReporter); - auto& multi = static_cast<ListeningReporter&>(*ret); + auto multi = Detail::make_unique<MultiReporter>(config); + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); for (auto const& listener : listeners) { - multi.addListener(listener->create(Catch::ReporterConfig(config))); + multi->addListener(listener->create(config)); } - multi.addReporter(createReporter(config->getReporterName(), config)); - return ret; + + std::size_t reporterIdx = 0; + for ( auto const& reporterSpec : config->getProcessedReporterSpecs() ) { + multi->addReporter( createReporter( + reporterSpec.name, + ReporterConfig( config, + makeStream( reporterSpec.outputFilename ), + reporterSpec.colourMode, + reporterSpec.customOptions ) ) ); + reporterIdx++; + } + + return multi; } class TestGroup { public: - explicit TestGroup(IStreamingReporterPtr&& reporter, Config const* config): + explicit TestGroup(IEventListenerPtr&& reporter, Config const* config): m_reporter(reporter.get()), m_config{config}, - m_context{config, std::move(reporter)} { + m_context{config, CATCH_MOVE(reporter)} { - auto const& allTestCases = getAllTestCasesSorted(*m_config); - m_matches = m_config->testSpec().matchesByFilter(allTestCases, *m_config); - auto const& invalidArgs = m_config->testSpec().getInvalidArgs(); + assert( m_config->testSpec().getInvalidSpecs().empty() && + "Invalid test specs should be handled before running tests" ); - if (m_matches.empty() && invalidArgs.empty()) { - for (auto const& test : allTestCases) - if (!test.getTestCaseInfo().isHidden()) - m_tests.emplace(&test); + auto const& allTestCases = getAllTestCasesSorted(*m_config); + auto const& testSpec = m_config->testSpec(); + if ( !testSpec.hasFilters() ) { + for ( auto const& test : allTestCases ) { + if ( !test.getTestCaseInfo().isHidden() ) { + m_tests.emplace( &test ); + } + } } else { - for (auto const& match : m_matches) - m_tests.insert(match.tests.begin(), match.tests.end()); + m_matches = + testSpec.matchesByFilter( allTestCases, *m_config ); + for ( auto const& match : m_matches ) { + m_tests.insert( match.tests.begin(), + match.tests.end() ); + } } + + m_tests = createShard(m_tests, m_config->shardCount(), m_config->shardIndex()); } Totals execute() { - auto const& invalidArgs = m_config->testSpec().getInvalidArgs(); Totals totals; - m_context.testGroupStarting(m_config->name(), 1, 1); for (auto const& testCase : m_tests) { if (!m_context.aborting()) totals += m_context.runTest(*testCase); @@ -855,28 +936,26 @@ namespace Catch { for (auto const& match : m_matches) { if (match.tests.empty()) { - m_reporter->noMatchingTestCases(match.name); - totals.error = -1; + m_unmatchedTestSpecs = true; + m_reporter->noMatchingTestCases( match.name ); } } - if (!invalidArgs.empty()) { - for (auto const& invalidArg: invalidArgs) - m_reporter->reportInvalidArguments(invalidArg); - } - - m_context.testGroupEnded(m_config->name(), totals, 1, 1); return totals; } - private: - using Tests = std::set<TestCaseHandle const*>; + bool hadUnmatchedTestSpecs() const { + return m_unmatchedTestSpecs; + } + - IStreamingReporter* m_reporter; + private: + IEventListener* m_reporter; Config const* m_config; RunContext m_context; - Tests m_tests; + std::set<TestCaseHandle const*> m_tests; TestSpec::Matches m_matches; + bool m_unmatchedTestSpecs = false; }; void applyFilenamesAsTags() { @@ -902,14 +981,17 @@ namespace Catch { getCurrentMutableContext().setConfig(m_config.get()); m_startupExceptions = true; - Colour colourGuard( Colour::Red ); - Catch::cerr() << "Errors occurred during startup!" << '\n'; + auto errStream = makeStream( "%stderr" ); + auto colourImpl = makeColourImpl( + ColourMode::PlatformDefault, errStream.get() ); + auto guard = colourImpl->guardColour( Colour::Red ); + errStream->stream() << "Errors occurred during startup!" << '\n'; // iterate over all exceptions and notify user for ( const auto& ex_ptr : exceptions ) { try { std::rethrow_exception(ex_ptr); } catch ( std::exception const& ex ) { - Catch::cerr() << TextFlow::Column( ex.what() ).indent(2) << '\n'; + errStream->stream() << TextFlow::Column( ex.what() ).indent(2) << '\n'; } } } @@ -924,16 +1006,16 @@ namespace Catch { void Session::showHelp() const { Catch::cout() - << "\nCatch v" << libraryVersion() << "\n" - << m_cli << std::endl - << "For more detailed usage please see the project docs\n" << std::endl; + << "\nCatch2 v" << libraryVersion() << '\n' + << m_cli << '\n' + << "For more detailed usage please see the project docs\n\n" << std::flush; } void Session::libIdentify() { Catch::cout() << std::left << std::setw(16) << "description: " << "A Catch2 test executable\n" << std::left << std::setw(16) << "category: " << "testframework\n" - << std::left << std::setw(16) << "framework: " << "Catch Test\n" - << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; + << std::left << std::setw(16) << "framework: " << "Catch2\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << '\n' << std::flush; } int Session::applyCommandLine( int argc, char const * const * argv ) { @@ -941,15 +1023,19 @@ namespace Catch { return 1; auto result = m_cli.parse( Clara::Args( argc, argv ) ); + if( !result ) { config(); getCurrentMutableContext().setConfig(m_config.get()); - Catch::cerr() - << Colour( Colour::Red ) + auto errStream = makeStream( "%stderr" ); + auto colour = makeColourImpl( ColourMode::PlatformDefault, errStream.get() ); + + errStream->stream() + << colour->guardColour( Colour::Red ) << "\nError(s) in input:\n" << TextFlow::Column( result.errorMessage() ).indent( 2 ) << "\n\n"; - Catch::cerr() << "Run with -? for usage\n" << std::endl; + errStream->stream() << "Run with -? for usage\n\n" << std::flush; return MaxExitCode; } @@ -957,6 +1043,7 @@ namespace Catch { showHelp(); if( m_configData.libIdentify ) libIdentify(); + m_config.reset(); return 0; } @@ -992,12 +1079,12 @@ namespace Catch { int Session::run() { if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + Catch::cout() << "...waiting for enter/ return before starting\n" << std::flush; static_cast<void>(std::getchar()); } int exitCode = runInternal(); if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush; static_cast<void>(std::getchar()); } return exitCode; @@ -1026,6 +1113,14 @@ namespace Catch { return 0; } + if ( m_configData.shardIndex >= m_configData.shardCount ) { + Catch::cerr() << "The shard count (" << m_configData.shardCount + << ") must be greater than the shard index (" + << m_configData.shardIndex << ")\n" + << std::flush; + return 1; + } + CATCH_TRY { config(); // Force config to be constructed @@ -1039,27 +1134,43 @@ namespace Catch { getCurrentMutableContext().setConfig(m_config.get()); // Create reporter(s) so we can route listings through them - auto reporter = makeReporter(m_config.get()); + auto reporter = prepareReporters(m_config.get()); + + auto const& invalidSpecs = m_config->testSpec().getInvalidSpecs(); + if ( !invalidSpecs.empty() ) { + for ( auto const& spec : invalidSpecs ) { + reporter->reportInvalidTestSpec( spec ); + } + return 1; + } + // Handle list request if (list(*reporter, *m_config)) { return 0; } - TestGroup tests { std::move(reporter), m_config.get() }; + TestGroup tests { CATCH_MOVE(reporter), m_config.get() }; auto const totals = tests.execute(); - if( m_config->warnAboutNoTests() && totals.error == -1 ) + if ( tests.hadUnmatchedTestSpecs() + && m_config->warnAboutUnmatchedTestSpecs() ) { + return 3; + } + + if ( totals.testCases.total() == 0 + && !m_config->zeroTestsCountAsSuccess() ) { return 2; + } // Note that on unices only the lower 8 bits are usually used, clamping // the return value to 255 prevents false negative when some multiple // of 256 tests has failed - return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed))); + return (std::min) (MaxExitCode, static_cast<int>(totals.assertions.failed)); } #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) catch( std::exception& ex ) { - Catch::cerr() << ex.what() << std::endl; + Catch::cerr() << ex.what() << '\n' << std::flush; return MaxExitCode; } #endif @@ -1117,7 +1228,7 @@ namespace Catch { else if( tag == "!nonportable"_sr ) return TestCaseProperties::NonPortable; else if( tag == "!benchmark"_sr ) - return static_cast<TestCaseProperties>(TestCaseProperties::Benchmark | TestCaseProperties::IsHidden ); + return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden; else return TestCaseProperties::None; } @@ -1159,16 +1270,25 @@ namespace Catch { const size_t extras = 3 + 3; return extractFilenamePart(filepath).size() + extras; } + } // end unnamed namespace + + bool operator<( Tag const& lhs, Tag const& rhs ) { + Detail::CaseInsensitiveLess cmp; + return cmp( lhs.original, rhs.original ); + } + bool operator==( Tag const& lhs, Tag const& rhs ) { + Detail::CaseInsensitiveEqualTo cmp; + return cmp( lhs.original, rhs.original ); } Detail::unique_ptr<TestCaseInfo> - makeTestCaseInfo(std::string const& _className, + makeTestCaseInfo(StringRef _className, NameAndTags const& nameAndTags, SourceLineInfo const& _lineInfo ) { - return Detail::unique_ptr<TestCaseInfo>(new TestCaseInfo(_className, nameAndTags, _lineInfo)); + return Detail::make_unique<TestCaseInfo>(_className, nameAndTags, _lineInfo); } - TestCaseInfo::TestCaseInfo(std::string const& _className, + TestCaseInfo::TestCaseInfo(StringRef _className, NameAndTags const& _nameAndTags, SourceLineInfo const& _lineInfo): name( _nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name ), @@ -1180,7 +1300,6 @@ namespace Catch { // (including optional hidden tag and filename tag) auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file); backingTags.reserve(requiredSize); - backingLCaseTags.reserve(requiredSize); // We cannot copy the tags directly, as we need to normalize // some tags, so that [.foo] is copied as [.][foo]. @@ -1204,6 +1323,7 @@ namespace Catch { // it over to backing storage and actually reference the // backing storage in the saved tags StringRef tagStr = originalTags.substr(tagStart+1, tagEnd - tagStart - 1); + CATCH_ENFORCE(!tagStr.empty(), "Empty tags are not allowed"); enforceNotReservedTag(tagStr, lineInfo); properties |= parseSpecialTag(tagStr); // When copying a tag to the backing storage, we need to @@ -1225,9 +1345,8 @@ namespace Catch { } // Sort and prepare tags - toLowerInPlace(backingLCaseTags); - std::sort(begin(tags), end(tags), [](Tag lhs, Tag rhs) { return lhs.lowerCased < rhs.lowerCased; }); - tags.erase(std::unique(begin(tags), end(tags), [](Tag lhs, Tag rhs) {return lhs.lowerCased == rhs.lowerCased; }), + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); } @@ -1273,24 +1392,22 @@ namespace Catch { backingTags += tagStr; const auto backingEnd = backingTags.size(); backingTags += ']'; - backingLCaseTags += '['; - // We append the tag to the lower-case backing storage as-is, - // because we will perform the lower casing later, in bulk - backingLCaseTags += tagStr; - backingLCaseTags += ']'; - tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart), - StringRef(backingLCaseTags.c_str() + backingStart, backingEnd - backingStart)); + tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart)); } - - bool TestCaseHandle::operator == ( TestCaseHandle const& rhs ) const { - return m_invoker == rhs.m_invoker - && m_info->name == rhs.m_info->name - && m_info->className == rhs.m_info->className; - } - - bool TestCaseHandle::operator < ( TestCaseHandle const& rhs ) const { - return m_info->name < rhs.m_info->name; + bool operator<( TestCaseInfo const& lhs, TestCaseInfo const& rhs ) { + // We want to avoid redoing the string comparisons multiple times, + // so we store the result of a three-way comparison before using + // it in the actual comparison logic. + const auto cmpName = lhs.name.compare( rhs.name ); + if ( cmpName != 0 ) { + return cmpName < 0; + } + const auto cmpClassName = lhs.className.compare( rhs.className ); + if ( cmpClassName != 0 ) { + return cmpClassName < 0; + } + return lhs.tags < rhs.tags; } TestCaseInfo const& TestCaseHandle::getTestCaseInfo() const { @@ -1330,15 +1447,13 @@ namespace Catch { TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString ) : Pattern( filterString ) - , m_tag( toLower( tag ) ) + , m_tag( tag ) {} bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { - return std::find_if(begin(testCase.tags), - end(testCase.tags), - [&](Tag const& tag) { - return tag.lowerCased == m_tag; - }) != end(testCase.tags); + return std::find( begin( testCase.tags ), + end( testCase.tags ), + Tag( m_tag ) ) != end( testCase.tags ); } bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { @@ -1390,8 +1505,8 @@ namespace Catch { return matches; } - const TestSpec::vectorStrings& TestSpec::getInvalidArgs() const{ - return (m_invalidArgs); + const TestSpec::vectorStrings& TestSpec::getInvalidSpecs() const { + return m_invalidSpecs; } } @@ -1400,49 +1515,13 @@ namespace Catch { #include <chrono> -static const uint64_t nanosecondsInSecond = 1000000000; - namespace Catch { - auto getCurrentNanosecondsSinceEpoch() -> uint64_t { - return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); - } - namespace { - auto estimateClockResolution() -> uint64_t { - uint64_t sum = 0; - static const uint64_t iterations = 1000000; - - auto startTime = getCurrentNanosecondsSinceEpoch(); - - for( std::size_t i = 0; i < iterations; ++i ) { - - uint64_t ticks; - uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); - do { - ticks = getCurrentNanosecondsSinceEpoch(); - } while( ticks == baseTicks ); - - auto delta = ticks - baseTicks; - sum += delta; - - // If we have been calibrating for over 3 seconds -- the clock - // is terrible and we should move on. - // TBD: How to signal that the measured resolution is probably wrong? - if (ticks > startTime + 3 * nanosecondsInSecond) { - return sum / ( i + 1u ); - } - } - - // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers - // - and potentially do more iterations if there's a high variance. - return sum/iterations; + static auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); } - } - auto getEstimatedClockResolution() -> uint64_t { - static auto s_resolution = estimateClockResolution(); - return s_resolution; - } + } // end unnamed namespace void Timer::start() { m_nanoseconds = getCurrentNanosecondsSinceEpoch(); @@ -1464,12 +1543,6 @@ namespace Catch { } // namespace Catch -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -# pragma clang diagnostic ignored "-Wglobal-constructors" -#endif - #include <cmath> @@ -1479,8 +1552,6 @@ namespace Catch { namespace Detail { - const std::string unprintableString = "{?}"; - namespace { const int hexThreshold = 255; @@ -1517,6 +1588,48 @@ namespace Detail { } } // end unnamed namespace + std::string convertIntoString(StringRef string, bool escape_invisibles) { + 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); + + if (!escape_invisibles) { + ret += '"'; + ret += string; + ret += '"'; + return ret; + } + + 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; + } + } + ret += '"'; + + return ret; + } + + std::string convertIntoString(StringRef string) { + return convertIntoString(string, getCurrentContext().getConfig()->showInvisibles()); + } + std::string rawMemoryToString( const void *object, std::size_t size ) { // Reverse order for little endian architectures int i = 0, end = static_cast<int>( size ), inc = 1; @@ -1543,44 +1656,25 @@ namespace Detail { //// ======================================================= //// std::string StringMaker<std::string>::convert(const std::string& str) { - if (!getCurrentContext().getConfig()->showInvisibles()) { - return '"' + str + '"'; - } - - std::string s("\""); - for (char c : str) { - switch (c) { - case '\n': - s.append("\\n"); - break; - case '\t': - s.append("\\t"); - break; - default: - s.push_back(c); - break; - } - } - s.append("\""); - return s; + return Detail::convertIntoString( str ); } #ifdef CATCH_CONFIG_CPP17_STRING_VIEW std::string StringMaker<std::string_view>::convert(std::string_view str) { - return ::Catch::Detail::stringify(std::string{ str }); + return Detail::convertIntoString( StringRef( str.data(), str.size() ) ); } #endif std::string StringMaker<char const*>::convert(char const* str) { if (str) { - return ::Catch::Detail::stringify(std::string{ str }); + return Detail::convertIntoString( str ); } else { return{ "{null string}" }; } } std::string StringMaker<char*>::convert(char* str) { if (str) { - return ::Catch::Detail::stringify(std::string{ str }); + return Detail::convertIntoString( str ); } else { return{ "{null string}" }; } @@ -1693,11 +1787,6 @@ std::string StringMaker<double>::convert(double value) { } // end namespace Catch -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - - namespace Catch { @@ -1717,7 +1806,7 @@ namespace Catch { return *this; } - std::size_t Counts::total() const { + std::uint64_t Counts::total() const { return passed + failed + failedButOk; } bool Counts::allPassed() const { @@ -1784,13 +1873,19 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 3, 0, 0, "preview", 3 ); + static Version version( 3, 0, 1, "", 0 ); return version; } } + + + +std::uint32_t Catch::Generators::Detail::getSeed() { return sharedRng()(); } + + /** \file * This is a special TU that combines what would otherwise be a very * small generator-related TUs into one bigger TU. @@ -1900,16 +1995,6 @@ namespace Catch { } -////////////////////////////////////////////////// -// vvv formerly catch_interfaces_runner.cpp vvv // -////////////////////////////////////////////////// - - -namespace Catch { - IRunner::~IRunner() = default; -} - - //////////////////////////////////////////////////// // vvv formerly catch_interfaces_testcase.cpp vvv // //////////////////////////////////////////////////// @@ -1921,6 +2006,7 @@ namespace Catch { } + namespace Catch { IReporterRegistry::~IReporterRegistry() = default; } @@ -1929,38 +2015,70 @@ namespace Catch { namespace Catch { IReporterFactory::~IReporterFactory() = default; + EventListenerFactory::~EventListenerFactory() = default; } +#include <string> + +namespace Catch { + namespace Generators { + + bool GeneratorUntypedBase::countedNext() { + auto ret = next(); + if ( ret ) { + m_stringReprCache.clear(); + ++m_currentElementIndex; + } + return ret; + } + + StringRef GeneratorUntypedBase::currentElementAsString() const { + if ( m_stringReprCache.empty() ) { + m_stringReprCache = stringifyImpl(); + } + return m_stringReprCache; + } + + } // namespace Generators +} // namespace Catch + + + #include <algorithm> +#include <cassert> #include <iomanip> namespace Catch { - ReporterConfig::ReporterConfig( IConfig const* _fullConfig ) - : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + ReporterConfig::ReporterConfig( + IConfig const* _fullConfig, + Detail::unique_ptr<IStream> _stream, + ColourMode colourMode, + std::map<std::string, std::string> customOptions ): + m_stream( CATCH_MOVE(_stream) ), + m_fullConfig( _fullConfig ), + m_colourMode( colourMode ), + m_customOptions( CATCH_MOVE( customOptions ) ) {} - ReporterConfig::ReporterConfig( IConfig const* _fullConfig, std::ostream& _stream ) - : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} - - std::ostream& ReporterConfig::stream() const { return *m_stream; } + Detail::unique_ptr<IStream> ReporterConfig::takeStream() && { + assert( m_stream ); + return CATCH_MOVE( m_stream ); + } IConfig const * ReporterConfig::fullConfig() const { return m_fullConfig; } + ColourMode ReporterConfig::colourMode() const { return m_colourMode; } + std::map<std::string, std::string> const& + ReporterConfig::customOptions() const { + return m_customOptions; + } - TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} + ReporterConfig::~ReporterConfig() = default; - GroupInfo::GroupInfo( std::string const& _name, - std::size_t _groupIndex, - std::size_t _groupsCount ) - : name( _name ), - groupIndex( _groupIndex ), - groupsCounts( _groupsCount ) - {} - - AssertionStats::AssertionStats( AssertionResult const& _assertionResult, - std::vector<MessageInfo> const& _infoMessages, - Totals const& _totals ) + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ) : assertionResult( _assertionResult ), infoMessages( _infoMessages ), totals( _totals ) @@ -2002,20 +2120,6 @@ namespace Catch { {} - TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, - Totals const& _totals, - bool _aborting ) - : groupInfo( _groupInfo ), - totals( _totals ), - aborting( _aborting ) - {} - - TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) - : groupInfo( _groupInfo ), - aborting( false ) - {} - - TestRunStats::TestRunStats( TestRunInfo const& _runInfo, Totals const& _totals, bool _aborting ) @@ -2024,84 +2128,7 @@ namespace Catch { aborting( _aborting ) {} - void IStreamingReporter::fatalErrorEncountered( StringRef ) {} - - void IStreamingReporter::listReporters(std::vector<ReporterDescription> const& descriptions, IConfig const& config) { - Catch::cout() << "Available reporters:\n"; - const auto maxNameLen = std::max_element(descriptions.begin(), descriptions.end(), - [](ReporterDescription const& lhs, ReporterDescription const& rhs) { return lhs.name.size() < rhs.name.size(); }) - ->name.size(); - - for (auto const& desc : descriptions) { - if (config.verbosity() == Verbosity::Quiet) { - Catch::cout() - << TextFlow::Column(desc.name) - .indent(2) - .width(5 + maxNameLen) << '\n'; - } else { - Catch::cout() - << TextFlow::Column(desc.name + ":") - .indent(2) - .width(5 + maxNameLen) - + TextFlow::Column(desc.description) - .initialIndent(0) - .indent(2) - .width(CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8) - << '\n'; - } - } - Catch::cout() << std::endl; - } - - void IStreamingReporter::listTests(std::vector<TestCaseHandle> const& tests, IConfig const& config) { - if (config.hasTestFilters()) { - Catch::cout() << "Matching test cases:\n"; - } else { - Catch::cout() << "All available test cases:\n"; - } - - for (auto const& test : tests) { - auto const& testCaseInfo = test.getTestCaseInfo(); - Colour::Code colour = testCaseInfo.isHidden() - ? Colour::SecondaryText - : Colour::None; - Colour colourGuard(colour); - - Catch::cout() << TextFlow::Column(testCaseInfo.name).initialIndent(2).indent(4) << '\n'; - if (config.verbosity() >= Verbosity::High) { - Catch::cout() << TextFlow::Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << std::endl; - } - if (!testCaseInfo.tags.empty() && config.verbosity() > Verbosity::Quiet) { - Catch::cout() << TextFlow::Column(testCaseInfo.tagsAsString()).indent(6) << '\n'; - } - } - - if (!config.hasTestFilters()) { - Catch::cout() << pluralise(tests.size(), "test case") << '\n' << std::endl; - } else { - Catch::cout() << pluralise(tests.size(), "matching test case") << '\n' << std::endl; - } - } - - void IStreamingReporter::listTags(std::vector<TagInfo> const& tags, IConfig const& config) { - if (config.hasTestFilters()) { - Catch::cout() << "Tags for matching test cases:\n"; - } else { - Catch::cout() << "All available tags:\n"; - } - - for (auto const& tagCount : tags) { - ReusableStringStream rss; - rss << " " << std::setw(2) << tagCount.count << " "; - auto str = rss.str(); - auto wrapper = TextFlow::Column(tagCount.all()) - .initialIndent(0) - .indent(str.size()) - .width(CATCH_CONFIG_CONSOLE_WIDTH - 10); - Catch::cout() << str << wrapper << '\n'; - } - Catch::cout() << pluralise(tags.size(), "tag") << '\n' << std::endl; - } + IEventListener::~IEventListener() = default; } // end namespace Catch @@ -2110,7 +2137,7 @@ namespace Catch { namespace Catch { AssertionHandler::AssertionHandler - ( StringRef const& macroName, + ( StringRef macroName, SourceLineInfo const& lineInfo, StringRef capturedExpression, ResultDisposition::Flags resultDisposition ) @@ -2121,7 +2148,7 @@ namespace Catch { void AssertionHandler::handleExpr( ITransientExpression const& expr ) { m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); } - void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef message) { m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); } @@ -2172,14 +2199,45 @@ namespace Catch { // This is the overload that takes a string and infers the Equals matcher from it // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ) { + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ) { handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); } } // namespace Catch + + +#include <algorithm> + +namespace Catch { + namespace Detail { + + bool CaseInsensitiveLess::operator()( StringRef lhs, + StringRef rhs ) const { + return std::lexicographical_compare( + lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + []( char l, char r ) { return toLower( l ) < toLower( r ); } ); + } + + bool + CaseInsensitiveEqualTo::operator()( StringRef lhs, + StringRef rhs ) const { + return std::equal( + lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + []( char l, char r ) { return toLower( l ) == toLower( r ); } ); + } + + } // namespace Detail +} // namespace Catch + + + + #include <algorithm> +#include <ostream> namespace { bool isOptPrefix( char c ) { @@ -2282,7 +2340,7 @@ namespace Catch { } else { return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + - source + "'" ); + source + '\'' ); } return ParserResult::ok( ParseResultType::Matched ); } @@ -2399,7 +2457,7 @@ namespace Catch { return Detail::InternalParseResult::runtimeError( "Expected argument following " + token.token); - auto result = valueRef->setValue(argToken.token); + const auto result = valueRef->setValue(argToken.token); if (!result) return Detail::InternalParseResult(result); if (result.value() == @@ -2788,7 +2846,7 @@ Catch::LeakDetector::~LeakDetector() { namespace Catch { - MessageInfo::MessageInfo( StringRef const& _macroName, + MessageInfo::MessageInfo( StringRef _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ) : macroName( _macroName ), @@ -2813,11 +2871,11 @@ namespace Catch { auto operator << (std::ostream& os, LazyExpression const& lazyExpr) -> std::ostream& { if (lazyExpr.m_isNegated) - os << "!"; + os << '!'; if (lazyExpr) { if (lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression()) - os << "(" << *lazyExpr.m_transientExpression << ")"; + os << '(' << *lazyExpr.m_transientExpression << ')'; else os << *lazyExpr.m_transientExpression; } else { @@ -2832,7 +2890,7 @@ namespace Catch { #include <fstream> -#include <ctime> +#include <string> namespace Catch { @@ -2841,25 +2899,21 @@ namespace Catch { using namespace Clara; auto const setWarning = [&]( std::string const& warning ) { - auto warningSet = [&]() { - if( warning == "NoAssertions" ) - return WarnAbout::NoAssertions; - - if ( warning == "NoTests" ) - return WarnAbout::NoTests; - - return WarnAbout::Nothing; - }(); - - if (warningSet == WarnAbout::Nothing) - return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); - config.warnings = static_cast<WarnAbout::What>( config.warnings | warningSet ); + if ( warning == "NoAssertions" ) { + config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::NoAssertions); return ParserResult::ok( ParseResultType::Matched ); - }; + } else if ( warning == "UnmatchedTestSpec" ) { + config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::UnmatchedTestSpec); + return ParserResult::ok( ParseResultType::Matched ); + } + + return ParserResult ::runtimeError( + "Unrecognised warning option: '" + warning + '\'' ); + }; auto const loadTestNamesFromFile = [&]( std::string const& filename ) { std::ifstream f( filename.c_str() ); if( !f.is_open() ) - return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); + return ParserResult::runtimeError( "Unable to load input file: '" + filename + '\'' ); std::string line; while( std::getline( f, line ) ) { @@ -2885,28 +2939,53 @@ namespace Catch { else if( startsWith( "random", order ) ) config.runOrder = TestRunOrder::Randomized; else - return ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return ParserResult::runtimeError( "Unrecognised ordering: '" + order + '\'' ); return ParserResult::ok( ParseResultType::Matched ); }; auto const setRngSeed = [&]( std::string const& seed ) { - if( seed != "time" ) - return Clara::Detail::convertInto( seed, config.rngSeed ); - config.rngSeed = static_cast<unsigned int>( std::time(nullptr) ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setColourUsage = [&]( std::string const& useColour ) { - auto mode = toLower( useColour ); - - if( mode == "yes" ) - config.useColour = UseColour::Yes; - else if( mode == "no" ) - config.useColour = UseColour::No; - else if( mode == "auto" ) - config.useColour = UseColour::Auto; - else - return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); - return ParserResult::ok( ParseResultType::Matched ); + if( seed == "time" ) { + config.rngSeed = generateRandomSeed(GenerateFrom::Time); + return ParserResult::ok(ParseResultType::Matched); + } else if (seed == "random-device") { + config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice); + return ParserResult::ok(ParseResultType::Matched); + } + + CATCH_TRY { + std::size_t parsedTo = 0; + unsigned long parsedSeed = std::stoul(seed, &parsedTo, 0); + if (parsedTo != seed.size()) { + return ParserResult::runtimeError("Could not parse '" + seed + "' as seed"); + } + + // TODO: Ideally we could parse unsigned int directly, + // but the stdlib doesn't provide helper for that + // type. After this is refactored to use fixed size + // type, we should check the parsed value is in range + // of the underlying type. + config.rngSeed = static_cast<unsigned int>(parsedSeed); + return ParserResult::ok(ParseResultType::Matched); + } CATCH_CATCH_ANON(std::exception const&) { + return ParserResult::runtimeError("Could not parse '" + seed + "' as seed"); + } }; + auto const setDefaultColourMode = [&]( std::string const& colourMode ) { + Optional<ColourMode> maybeMode = Catch::Detail::stringToColourMode(toLower( colourMode )); + if ( !maybeMode ) { + return ParserResult::runtimeError( + "colour mode must be one of: default, ansi, win32, " + "or none. '" + + colourMode + "' is not recognised" ); + } + auto mode = *maybeMode; + if ( !isColourImplAvailable( mode ) ) { + return ParserResult::runtimeError( + "colour mode '" + colourMode + + "' is not supported in this binary" ); + } + config.defaultColourMode = mode; + return ParserResult::ok( ParseResultType::Matched ); + }; auto const setWaitForKeypress = [&]( std::string const& keypress ) { auto keypressLc = toLower( keypress ); if (keypressLc == "never") @@ -2930,31 +3009,95 @@ namespace Catch { else if( lcVerbosity == "high" ) config.verbosity = Verbosity::High; else - return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + '\'' ); return ParserResult::ok( ParseResultType::Matched ); }; - auto const setReporter = [&]( std::string const& reporter ) { - IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + auto const setReporter = [&]( std::string const& userReporterSpec ) { + if ( userReporterSpec.empty() ) { + return ParserResult::runtimeError( "Received empty reporter spec." ); + } - auto lcReporter = toLower( reporter ); - auto result = factories.find( lcReporter ); + Optional<ReporterSpec> parsed = + parseReporterSpec( userReporterSpec ); + if ( !parsed ) { + return ParserResult::runtimeError( + "Could not parse reporter spec '" + userReporterSpec + + "'" ); + } + + auto const& reporterSpec = *parsed; + + IReporterRegistry::FactoryMap const& factories = + getRegistryHub().getReporterRegistry().getFactories(); + auto result = factories.find( reporterSpec.name() ); + + if ( result == factories.end() ) { + return ParserResult::runtimeError( + "Unrecognized reporter, '" + reporterSpec.name() + + "'. Check available with --list-reporters" ); + } + + + const bool hadOutputFile = reporterSpec.outputFile().some(); + config.reporterSpecifications.push_back( CATCH_MOVE( *parsed ) ); + // It would be enough to check this only once at the very end, but + // there is not a place where we could call this check, so do it + // every time it could fail. For valid inputs, this is still called + // at most once. + if (!hadOutputFile) { + int n_reporters_without_file = 0; + for (auto const& spec : config.reporterSpecifications) { + if (spec.outputFile().none()) { + n_reporters_without_file++; + } + } + if (n_reporters_without_file > 1) { + return ParserResult::runtimeError( "Only one reporter may have unspecified output file." ); + } + } - if( factories.end() != result ) - config.reporterName = lcReporter; - else - return ParserResult::runtimeError( "Unrecognized reporter, '" + reporter + "'. Check available with --list-reporters" ); return ParserResult::ok( ParseResultType::Matched ); }; + auto const setShardCount = [&]( std::string const& shardCount ) { + CATCH_TRY{ + std::size_t parsedTo = 0; + int64_t parsedCount = std::stoll(shardCount, &parsedTo, 0); + if (parsedTo != shardCount.size()) { + return ParserResult::runtimeError("Could not parse '" + shardCount + "' as shard count"); + } + if (parsedCount <= 0) { + return ParserResult::runtimeError("Shard count must be a positive number"); + } + + config.shardCount = static_cast<unsigned int>(parsedCount); + return ParserResult::ok(ParseResultType::Matched); + } CATCH_CATCH_ANON(std::exception const&) { + return ParserResult::runtimeError("Could not parse '" + shardCount + "' as shard count"); + } + }; + + auto const setShardIndex = [&](std::string const& shardIndex) { + CATCH_TRY{ + std::size_t parsedTo = 0; + int64_t parsedIndex = std::stoll(shardIndex, &parsedTo, 0); + if (parsedTo != shardIndex.size()) { + return ParserResult::runtimeError("Could not parse '" + shardIndex + "' as shard index"); + } + if (parsedIndex < 0) { + return ParserResult::runtimeError("Shard index must be a non-negative number"); + } + + config.shardIndex = static_cast<unsigned int>(parsedIndex); + return ParserResult::ok(ParseResultType::Matched); + } CATCH_CATCH_ANON(std::exception const&) { + return ParserResult::runtimeError("Could not parse '" + shardIndex + "' as shard index"); + } + }; + auto cli = ExeName( config.processName ) | Help( config.showHelp ) - | Opt( config.listTests ) - ["-l"]["--list-tests"] - ( "list all/matching test cases" ) - | Opt( config.listTags ) - ["-t"]["--list-tags"] - ( "list all/matching tags" ) | Opt( config.showSuccessfulTests ) ["-s"]["--success"] ( "include successful tests in output" ) @@ -2967,10 +3110,10 @@ namespace Catch { | Opt( config.showInvisibles ) ["-i"]["--invisibles"] ( "show invisibles (tabs, newlines)" ) - | Opt( config.outputFilename, "filename" ) + | Opt( config.defaultOutputFilename, "filename" ) ["-o"]["--out"] - ( "output filename" ) - | Opt( setReporter, "name" ) + ( "default output filename" ) + | Opt( accept_many, setReporter, "name[::key=value]*" ) ["-r"]["--reporter"] ( "reporter to use (defaults to console)" ) | Opt( config.name, "name" ) @@ -2982,7 +3125,7 @@ namespace Catch { | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) ["-x"]["--abortx"] ( "abort after x failures" ) - | Opt( setWarning, "warning name" ) + | Opt( accept_many, setWarning, "warning name" ) ["-w"]["--warn"] ( "enable warnings" ) | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) @@ -3003,24 +3146,36 @@ namespace Catch { | Opt( setVerbosity, "quiet|normal|high" ) ["-v"]["--verbosity"] ( "set output verbosity" ) + | Opt( config.listTests ) + ["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["--list-tags"] + ( "list all/matching tags" ) | Opt( config.listReporters ) ["--list-reporters"] - ( "list all reporters" ) + ( "list all available reporters" ) + | Opt( config.listListeners ) + ["--list-listeners"] + ( "list all listeners" ) | Opt( setTestOrder, "decl|lex|rand" ) ["--order"] ( "test case order (defaults to decl)" ) - | Opt( setRngSeed, "'time'|number" ) + | Opt( setRngSeed, "'time'|'random-device'|number" ) ["--rng-seed"] ( "set a specific seed for random numbers" ) - | Opt( setColourUsage, "yes|no" ) - ["--use-colour"] - ( "should output be colourised" ) + | Opt( setDefaultColourMode, "ansi|win32|none|default" ) + ["--colour-mode"] + ( "what color mode should be used as default" ) | Opt( config.libIdentify ) ["--libidentify"] ( "report name and version according to libidentify standard" ) | Opt( setWaitForKeypress, "never|start|exit|both" ) ["--wait-for-keypress"] ( "waits for a keypress before exiting" ) + | Opt( config.skipBenchmarks) + ["--skip-benchmarks"] + ( "disable running benchmarks") | Opt( config.benchmarkSamples, "samples" ) ["--benchmark-samples"] ( "number of samples to collect (default: 100)" ) @@ -3036,6 +3191,15 @@ namespace Catch { | Opt( config.benchmarkWarmupTime, "benchmarkWarmupTime" ) ["--benchmark-warmup-time"] ( "amount of time in milliseconds spent on warming up each test (default: 100)" ) + | Opt( setShardCount, "shard count" ) + ["--shard-count"] + ( "split the tests to execute into this many groups" ) + | Opt( setShardIndex, "shard index" ) + ["--shard-index"] + ( "index of the group of tests to execute (see --shard-count)" ) | + Opt( config.allowZeroTests ) + ["--allow-running-no-tests"] + ( "Treat 'No tests run' as a success" ) | Arg( config.testsOrTags, "test name|pattern|tags" ) ( "which test or tests to use" ); @@ -3045,87 +3209,113 @@ namespace Catch { } // end namespace Catch +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + + -#include <cstring> +#include <cassert> #include <ostream> +#include <utility> namespace Catch { - bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { - return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); - } - bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { - // We can assume that the same file will usually have the same pointer. - // Thus, if the pointers are the same, there is no point in calling the strcmp - return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); - } + ColourImpl::~ColourImpl() = default; - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { -#ifndef __GNUG__ - os << info.file << '(' << info.line << ')'; -#else - os << info.file << ':' << info.line; -#endif - return os; + ColourImpl::ColourGuard ColourImpl::guardColour( Colour::Code colourCode ) { + return ColourGuard(colourCode, this ); } -} // end namespace Catch + void ColourImpl::ColourGuard::engageImpl( std::ostream& stream ) { + assert( &stream == &m_colourImpl->m_stream->stream() && + "Engaging colour guard for different stream than used by the " + "parent colour implementation" ); + static_cast<void>( stream ); + m_engaged = true; + m_colourImpl->use( m_code ); + } -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif + ColourImpl::ColourGuard::ColourGuard( Colour::Code code, + ColourImpl const* colour ): + m_colourImpl( colour ), m_code( code ) { + } + ColourImpl::ColourGuard::ColourGuard( ColourGuard&& rhs ) noexcept: + m_colourImpl( rhs.m_colourImpl ), + m_code( rhs.m_code ), + m_engaged( rhs.m_engaged ) { + rhs.m_engaged = false; + } + ColourImpl::ColourGuard& + ColourImpl::ColourGuard::operator=( ColourGuard&& rhs ) noexcept { + using std::swap; + swap( m_colourImpl, rhs.m_colourImpl ); + swap( m_code, rhs.m_code ); + swap( m_engaged, rhs.m_engaged ); + return *this; + } + ColourImpl::ColourGuard::~ColourGuard() { + if ( m_engaged ) { + m_colourImpl->use( Colour::None ); + } + } + ColourImpl::ColourGuard& + ColourImpl::ColourGuard::engage( std::ostream& stream ) & { + engageImpl( stream ); + return *this; + } -#include <ostream> + ColourImpl::ColourGuard&& + ColourImpl::ColourGuard::engage( std::ostream& stream ) && { + engageImpl( stream ); + return CATCH_MOVE(*this); + } -namespace Catch { namespace { + //! A do-nothing implementation of colour, used as fallback for unknown + //! platforms, and when the user asks to deactivate all colours. + class NoColourImpl : public ColourImpl { + public: + NoColourImpl( IStream* stream ): ColourImpl( stream ) {} - struct IColourImpl { - virtual ~IColourImpl() = default; - virtual void use( Colour::Code _colourCode ) = 0; + private: + void use( Colour::Code ) const override {} }; + } // namespace - struct NoColourImpl : IColourImpl { - void use( Colour::Code ) override {} - static IColourImpl* instance() { - static NoColourImpl s_instance; - return &s_instance; - } - }; - - } // anon namespace } // namespace Catch -#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) -# ifdef CATCH_PLATFORM_WINDOWS -# define CATCH_CONFIG_COLOUR_WINDOWS -# else -# define CATCH_CONFIG_COLOUR_ANSI -# endif -#endif - -#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// +#if defined ( CATCH_CONFIG_COLOUR_WIN32 ) ///////////////////////////////////////// namespace Catch { namespace { - class Win32ColourImpl : public IColourImpl { + class Win32ColourImpl : public ColourImpl { public: - Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) - { + Win32ColourImpl(IStream* stream): + ColourImpl(stream) { CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), + &csbiInfo ); originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); } - void use( Colour::Code _colourCode ) override { + static bool useImplementationForStream(IStream const& stream) { + // Win32 text colour APIs can only be used on console streams + // We cannot check that the output hasn't been redirected, + // so we just check that the original stream is console stream. + return stream.isConsole(); + } + + private: + void use( Colour::Code _colourCode ) const override { switch( _colourCode ) { case Colour::None: return setTextAttribute( originalForegroundAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); @@ -3149,45 +3339,63 @@ namespace { } } - private: - void setTextAttribute( WORD _textAttribute ) { - SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + void setTextAttribute( WORD _textAttribute ) const { + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), + _textAttribute | + originalBackgroundAttributes ); } - HANDLE stdoutHandle; WORD originalForegroundAttributes; WORD originalBackgroundAttributes; }; - IColourImpl* platformColourInstance() { - static Win32ColourImpl s_instance; - - auto const* config = getCurrentContext().getConfig(); - UseColour colourMode = config? - config->useColour() : UseColour::Auto; - if( colourMode == UseColour::Auto ) - colourMode = UseColour::Yes; - return colourMode == UseColour::Yes - ? &s_instance - : NoColourImpl::instance(); - } - } // end anon namespace } // end namespace Catch -#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// +#endif // Windows/ ANSI/ None + -#include <unistd.h> +#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) +# define CATCH_INTERNAL_HAS_ISATTY +# include <unistd.h> +#endif namespace Catch { namespace { - // use POSIX/ ANSI console terminal codes - // Thanks to Adam Strzelecki for original contribution - // (http://github.com/nanoant) - // https://github.com/philsquared/Catch/pull/131 - class PosixColourImpl : public IColourImpl { + class ANSIColourImpl : public ColourImpl { public: - void use( Colour::Code _colourCode ) override { + ANSIColourImpl( IStream* stream ): ColourImpl( stream ) {} + + static bool useImplementationForStream(IStream const& stream) { + // This is kinda messy due to trying to support a bunch of + // different platforms at once. + // The basic idea is that if we are asked to do autodetection (as + // opposed to being told to use posixy colours outright), then we + // only want to use the colours if we are writing to console. + // However, console might be redirected, so we make an attempt at + // checking for that on platforms where we know how to do that. + bool useColour = stream.isConsole(); +#if defined( CATCH_INTERNAL_HAS_ISATTY ) && \ + !( defined( __DJGPP__ ) && defined( __STRICT_ANSI__ ) ) + ErrnoGuard _; // for isatty + useColour = useColour && isatty( STDOUT_FILENO ); +# endif +# if defined( CATCH_PLATFORM_MAC ) || defined( CATCH_PLATFORM_IPHONE ) + useColour = useColour && !isDebuggerActive(); +# endif + + return useColour; + } + + private: + void use( Colour::Code _colourCode ) const override { + auto setColour = [&out = + m_stream->stream()]( char const* escapeCode ) { + // The escape sequence must be flushed to console, otherwise + // if stdin and stderr are intermixed, we'd get accidentally + // coloured output. + out << '\033' << escapeCode << std::flush; + }; switch( _colourCode ) { case Colour::None: case Colour::White: return setColour( "[0m" ); @@ -3208,89 +3416,59 @@ namespace { default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); } } - static IColourImpl* instance() { - static PosixColourImpl s_instance; - return &s_instance; - } - - private: - void setColour( const char* _escapeCode ) { - // The escape sequence must be flushed to console, otherwise if - // stdin and stderr are intermixed, we'd get accidentally coloured output. - getCurrentContext().getConfig()->stream() - << '\033' << _escapeCode << std::flush; - } }; - bool useColourOnPlatform() { - return -#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) - !isDebuggerActive() && -#endif -#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) - isatty(STDOUT_FILENO) -#else - false -#endif - ; - } - IColourImpl* platformColourInstance() { - ErrnoGuard guard; - auto const* config = getCurrentContext().getConfig(); - UseColour colourMode = config - ? config->useColour() - : UseColour::Auto; - if( colourMode == UseColour::Auto ) - colourMode = useColourOnPlatform() - ? UseColour::Yes - : UseColour::No; - return colourMode == UseColour::Yes - ? PosixColourImpl::instance() - : NoColourImpl::instance(); - } - } // end anon namespace } // end namespace Catch -#else // not Windows or ANSI /////////////////////////////////////////////// - namespace Catch { - static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } - -} // end namespace Catch - -#endif // Windows/ ANSI/ None + Detail::unique_ptr<ColourImpl> makeColourImpl( ColourMode implSelection, + IStream* stream ) { + if ( implSelection == ColourMode::None ) { + return Detail::make_unique<NoColourImpl>( stream ); + } + if ( implSelection == ColourMode::ANSI ) { + return Detail::make_unique<ANSIColourImpl>( stream ); + } +#if defined( CATCH_CONFIG_COLOUR_WIN32 ) + if ( implSelection == ColourMode::Win32 ) { + return Detail::make_unique<Win32ColourImpl>( stream ); + } +#endif -namespace Catch { + // todo: check win32 eligibility under ifdef, otherwise ansi + if ( implSelection == ColourMode::PlatformDefault) { +#if defined (CATCH_CONFIG_COLOUR_WIN32) + if ( Win32ColourImpl::useImplementationForStream( *stream ) ) { + return Detail::make_unique<Win32ColourImpl>( stream ); + } else { + return Detail::make_unique<NoColourImpl>( stream ); + } +#endif + if ( ANSIColourImpl::useImplementationForStream( *stream ) ) { + return Detail::make_unique<ANSIColourImpl>( stream ); + } + return Detail::make_unique<NoColourImpl>( stream ); + } - Colour::Colour( Code _colourCode ) { use( _colourCode ); } - Colour::Colour( Colour&& other ) noexcept { - m_moved = other.m_moved; - other.m_moved = true; + CATCH_ERROR( "Could not create colour impl for selection " << static_cast<int>(implSelection) ); } - Colour& Colour::operator=( Colour&& other ) noexcept { - m_moved = other.m_moved; - other.m_moved = true; - return *this; - } - - Colour::~Colour(){ if( !m_moved ) use( None ); } - void Colour::use( Code _colourCode ) { - static IColourImpl* impl = platformColourInstance(); - // Strictly speaking, this cannot possibly happen. - // However, under some conditions it does happen (see #1626), - // and this change is small enough that we can let practicality - // triumph over purity in this case. - if (impl != nullptr) { - impl->use( _colourCode ); + bool isColourImplAvailable( ColourMode colourSelection ) { + switch ( colourSelection ) { +#if defined( CATCH_CONFIG_COLOUR_WIN32 ) + case ColourMode::Win32: +#endif + case ColourMode::ANSI: + case ColourMode::None: + case ColourMode::PlatformDefault: + return true; + default: + return false; } } - std::ostream& operator << ( std::ostream& os, Colour const& ) { - return os; - } } // end namespace Catch @@ -3309,9 +3487,6 @@ namespace Catch { IResultCapture* getResultCapture() override { return m_resultCapture; } - IRunner* getRunner() override { - return m_runner; - } IConfig const* getConfig() const override { return m_config; @@ -3323,9 +3498,6 @@ namespace Catch { void setResultCapture( IResultCapture* resultCapture ) override { m_resultCapture = resultCapture; } - void setRunner( IRunner* runner ) override { - m_runner = runner; - } void setConfig( IConfig const* config ) override { m_config = config; } @@ -3334,7 +3506,6 @@ namespace Catch { private: IConfig const* m_config = nullptr; - IRunner* m_runner = nullptr; IResultCapture* m_resultCapture = nullptr; }; @@ -3354,7 +3525,7 @@ namespace Catch { Context::~Context() = default; - SimplePcg32& rng() { + SimplePcg32& sharedRng() { static SimplePcg32 s_rng; return s_rng; } @@ -3363,6 +3534,10 @@ namespace Catch { + + +#include <ostream> + #if defined(CATCH_CONFIG_ANDROID_LOGWRITE) #include <android/log.h> @@ -3436,7 +3611,7 @@ namespace Catch { size = sizeof(info); if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { - Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n\n" << std::flush; return false; } @@ -3542,7 +3717,7 @@ namespace Catch { namespace Catch { - IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {} + IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() = default; namespace Detail { @@ -3550,7 +3725,7 @@ namespace Catch { // Extracts the actual name part of an enum instance // In other words, it returns the Blue part of Bikeshed::Colour::Blue StringRef extractInstanceName(StringRef enumInstance) { - // Find last occurence of ":" + // Find last occurrence of ":" size_t name_start = enumInstance.size(); while (name_start > 0 && enumInstance[name_start - 1] != ':') { --name_start; @@ -3609,33 +3784,38 @@ namespace Catch { ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { } - void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { - m_translators.push_back( Detail::unique_ptr<const IExceptionTranslator>( translator ) ); + void ExceptionTranslatorRegistry::registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) { + m_translators.push_back( CATCH_MOVE( translator ) ); } #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) std::string ExceptionTranslatorRegistry::translateActiveException() const { + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these do + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if ( std::current_exception() == nullptr ) { + return "Non C++ exception. Possibly a CLR exception."; + } + + // First we try user-registered translators. If none of them can + // handle the exception, it will be rethrown handled by our defaults. try { - // Compiling a mixed mode project with MSVC means that CLR - // exceptions will be caught in (...) as well. However, these - // do not fill-in std::current_exception and thus lead to crash - // when attempting rethrow. - // /EHa switch also causes structured exceptions to be caught - // here, but they fill-in current_exception properly, so - // at worst the output should be a little weird, instead of - // causing a crash. - if (std::current_exception() == nullptr) { - return "Non C++ exception. Possibly a CLR exception."; - } return tryTranslators(); } + // 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()); } - catch( std::exception& ex ) { + catch( std::exception const& ex ) { return ex.what(); } - catch( std::string& msg ) { + catch( std::string const& msg ) { return msg; } catch( const char* msg ) { @@ -3669,39 +3849,80 @@ namespace Catch { +/** \file + * This file provides platform specific implementations of FatalConditionHandler + * + * This means that there is a lot of conditional compilation, and platform + * specific code. Currently, Catch2 supports a dummy handler (if no + * handler is desired), and 2 platform specific handlers: + * * Windows' SEH + * * POSIX signals + * + * Consequently, various pieces of code below are compiled if either of + * the platform specific handlers is enabled, or if none of them are + * enabled. It is assumed that both cannot be enabled at the same time, + * and doing so should cause a compilation error. + * + * If another platform specific handler is added, the compile guards + * below will need to be updated taking these assumptions into account. + */ -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif + + +#include <algorithm> + +#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + // If neither SEH nor signal handling is required, the handler impls + // do not have to do anything, and can be empty. + void FatalConditionHandler::engage_platform() {} + void FatalConditionHandler::disengage_platform() noexcept {} + FatalConditionHandler::FatalConditionHandler() = default; + FatalConditionHandler::~FatalConditionHandler() = default; + +} // end namespace Catch + +#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) +#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" +#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) namespace { - // Report the error condition + //! Signals fatal error message to the run context void reportFatal( char const * const message ) { Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); } -} -#endif // signals/SEH handling + //! Minimal size Catch2 needs for its own fatal error handling. + //! Picked empirically, so it might not be sufficient on all + //! platforms, and for all configurations. + constexpr std::size_t minStackSizeForErrors = 32 * 1024; +} // end unnamed namespace + +#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; // 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_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION), "SIGILL - Illegal instruction signal" }, - { static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow" }, - { static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal" }, - { static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, + { 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" }, }; - LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { for (auto const& def : signalDefs) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { reportFatal(def.name); @@ -3712,35 +3933,51 @@ namespace Catch { 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() { - isSet = true; - // 32k seems enough for Catch to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = nullptr; - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); + ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; + } + } + + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + FatalConditionHandler::~FatalConditionHandler() = default; + + + void FatalConditionHandler::engage_platform() { + // Register as a the top level exception filter. + previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter); } - void FatalConditionHandler::reset() { - if (isSet) { - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = nullptr; - isSet = false; + void FatalConditionHandler::disengage_platform() noexcept { + if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) { + Catch::cerr() + << "Unexpected SEH unhandled exception filter on disengage." + << " The filter was restored, but might be rolled back unexpectedly."; } + previousTopLevelExceptionFilter = nullptr; } -bool FatalConditionHandler::isSet = false; -ULONG FatalConditionHandler::guaranteeSize = 0; -PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; +} // end namespace Catch +#endif // CATCH_CONFIG_WINDOWS_SEH -} // namespace Catch +#if defined( CATCH_CONFIG_POSIX_SIGNALS ) -#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) +#include <signal.h> namespace Catch { @@ -3749,10 +3986,6 @@ namespace Catch { const char* name; }; - // 32kb for the alternate stack seems to be sufficient. However, this value - // is experimentally determined, so that's not guaranteed. - static constexpr std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; - static SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, @@ -3762,8 +3995,32 @@ namespace Catch { { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif - void FatalConditionHandler::handleSignal( int sig ) { + static char* altStackMem = nullptr; + static std::size_t altStackSize = 0; + static stack_t oldSigStack{}; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; + + static void restorePreviousSignalHandlers() noexcept { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + } + + static void handleSignal( int sig ) { char const * name = "<unknown signal>"; for (auto const& def : signalDefs) { if (sig == def.id) { @@ -3771,16 +4028,33 @@ namespace Catch { break; } } - reset(); - reportFatal(name); + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal( name ); raise( sig ); } FatalConditionHandler::FatalConditionHandler() { - isSet = true; + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors); + } + altStackMem = new char[altStackSize](); + } + + FatalConditionHandler::~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = nullptr; + } + + void FatalConditionHandler::engage_platform() { stack_t sigStack; sigStack.ss_sp = altStackMem; - sigStack.ss_size = sigStackSize; + sigStack.ss_size = altStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = { }; @@ -3792,68 +4066,229 @@ namespace Catch { } } - void FatalConditionHandler::reset() { - if( isSet ) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + + void FatalConditionHandler::disengage_platform() noexcept { + restorePreviousSignalHandlers(); } - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[sigStackSize] = {}; +} // end namespace Catch +#endif // CATCH_CONFIG_POSIX_SIGNALS -} // namespace Catch -#endif // signals/SEH handling -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif + +#include <cstring> + +namespace Catch { + namespace Detail { + + uint32_t convertToBits(float f) { + static_assert(sizeof(float) == sizeof(uint32_t), "Important ULP matcher assumption violated"); + uint32_t i; + std::memcpy(&i, &f, sizeof(f)); + return i; + } + + uint64_t convertToBits(double d) { + static_assert(sizeof(double) == sizeof(uint64_t), "Important ULP matcher assumption violated"); + uint64_t i; + std::memcpy(&i, &d, sizeof(d)); + return i; + } + + } // end namespace Detail +} // end namespace Catch + +#include <cstdio> +#include <fstream> +#include <sstream> +#include <vector> namespace Catch { + + Catch::IStream::~IStream() = default; + +namespace Detail { namespace { + template<typename WriterF, std::size_t bufferSize=256> + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; - void listTests(IStreamingReporter& reporter, IConfig const& config) { + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept override { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast<char>( c ) ) ); + else + sputc( static_cast<char>( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const& str ) { + if ( !str.empty() ) { + writeToDebugConsole( str ); + } + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream : public IStream { + std::ofstream m_ofs; + public: + FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << '\'' ); + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() override { return m_os; } + bool isConsole() const override { return true; } + }; + + class CerrStream : public IStream { + std::ostream m_os; + + public: + // Store the streambuf from cerr up-front because + // cout may get redirected when running tests + CerrStream(): m_os( Catch::cerr().rdbuf() ) {} + ~CerrStream() override = default; + + public: // IStream + std::ostream& stream() override { return m_os; } + bool isConsole() const override { return true; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + Detail::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf; + std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( Detail::make_unique<StreamBufImpl<OutputDebugWriter>>() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() override { return m_os; } + }; + + } // unnamed namespace +} // namespace Detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( std::string const& filename ) -> Detail::unique_ptr<IStream> { + if ( filename.empty() || filename == "-" ) { + return Detail::make_unique<Detail::CoutStream>(); + } + if( filename[0] == '%' ) { + if ( filename == "%debug" ) { + return Detail::make_unique<Detail::DebugOutStream>(); + } else if ( filename == "%stderr" ) { + return Detail::make_unique<Detail::CerrStream>(); + } else if ( filename == "%stdout" ) { + return Detail::make_unique<Detail::CoutStream>(); + } else { + CATCH_ERROR( "Unrecognised stream: '" << filename << '\'' ); + } + } + return Detail::make_unique<Detail::FileStream>( filename ); + } + +} + + + + + +namespace Catch { + namespace { + + void listTests(IEventListener& reporter, IConfig const& config) { auto const& testSpec = config.testSpec(); auto matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); - reporter.listTests(matchedTestCases, config); + reporter.listTests(matchedTestCases); } - void listTags(IStreamingReporter& reporter, IConfig const& config) { + void listTags(IEventListener& reporter, IConfig const& config) { auto const& testSpec = config.testSpec(); std::vector<TestCaseHandle> matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); - std::map<StringRef, TagInfo> tagCounts; + std::map<StringRef, TagInfo, Detail::CaseInsensitiveLess> tagCounts; for (auto const& testCase : matchedTestCases) { for (auto const& tagName : testCase.getTestCaseInfo().tags) { - auto it = tagCounts.find(tagName.lowerCased); + auto it = tagCounts.find(tagName.original); if (it == tagCounts.end()) - it = tagCounts.insert(std::make_pair(tagName.lowerCased, TagInfo())).first; + it = tagCounts.insert(std::make_pair(tagName.original, TagInfo())).first; it->second.add(tagName.original); } } std::vector<TagInfo> infos; infos.reserve(tagCounts.size()); for (auto& tagc : tagCounts) { - infos.push_back(std::move(tagc.second)); + infos.push_back(CATCH_MOVE(tagc.second)); } - reporter.listTags(infos, config); + reporter.listTags(infos); } - void listReporters(IStreamingReporter& reporter, IConfig const& config) { + void listReporters(IEventListener& reporter) { std::vector<ReporterDescription> descriptions; IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); @@ -3862,7 +4297,20 @@ namespace Catch { descriptions.push_back({ fac.first, fac.second->getDescription() }); } - reporter.listReporters(descriptions, config); + reporter.listReporters(descriptions); + } + + void listListeners(IEventListener& reporter) { + std::vector<ListenerDescription> descriptions; + + auto const& factories = + getRegistryHub().getReporterRegistry().getListeners(); + descriptions.reserve( factories.size() ); + for ( auto const& fac : factories ) { + descriptions.push_back( { fac->getName(), fac->getDescription() } ); + } + + reporter.listListeners( descriptions ); } } // end anonymous namespace @@ -3888,7 +4336,7 @@ namespace Catch { return out; } - bool list( IStreamingReporter& reporter, Config const& config ) { + bool list( IEventListener& reporter, Config const& config ) { bool listed = false; if (config.listTests()) { listed = true; @@ -3900,25 +4348,32 @@ namespace Catch { } if (config.listReporters()) { listed = true; - listReporters(reporter, config); + listReporters(reporter); + } + if ( config.listListeners() ) { + listed = true; + listListeners( reporter ); } return listed; } } // end namespace Catch -#if 0 + namespace Catch { CATCH_INTERNAL_START_WARNINGS_SUPPRESSION CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS - LeakDetector leakDetector; + static LeakDetector leakDetector; CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION } +// Allow users of amalgamated .cpp file to remove our main and provide their own. +#if !defined(CATCH_AMALGAMATED_CUSTOM_MAIN) + #if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) // Standard C/C++ Win32 Unicode wmain entry point -extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +extern "C" int __cdecl wmain (int argc, wchar_t * argv[], wchar_t * []) { #else // Standard C/C++ main entry point int main (int argc, char * argv[]) { @@ -3931,7 +4386,8 @@ int main (int argc, char * argv[]) { return Catch::Session().run( argc, argv ); } -#endif +#endif // !defined(CATCH_AMALGAMATED_CUSTOM_MAIN + #include <cstdio> @@ -4137,36 +4593,67 @@ namespace { + +#include <ctime> +#include <random> + +namespace Catch { + + std::uint32_t generateRandomSeed( GenerateFrom from ) { + switch ( from ) { + case GenerateFrom::Time: + return static_cast<std::uint32_t>( std::time( nullptr ) ); + + case GenerateFrom::Default: + case GenerateFrom::RandomDevice: + // In theory, a platform could have random_device that returns just + // 16 bits. That is still some randomness, so we don't care too much + return static_cast<std::uint32_t>( std::random_device{}() ); + + default: + CATCH_ERROR("Unknown generation method"); + } + } + +} // end namespace Catch + + + + namespace Catch { ReporterRegistry::ReporterRegistry() { // Because it is impossible to move out of initializer list, // we have to add the elements manually - m_factories["automake"] = Detail::make_unique<ReporterFactory<AutomakeReporter>>(); + m_factories["Automake"] = Detail::make_unique<ReporterFactory<AutomakeReporter>>(); m_factories["compact"] = Detail::make_unique<ReporterFactory<CompactReporter>>(); m_factories["console"] = Detail::make_unique<ReporterFactory<ConsoleReporter>>(); - m_factories["junit"] = Detail::make_unique<ReporterFactory<JunitReporter>>(); - m_factories["sonarqube"] = Detail::make_unique<ReporterFactory<SonarQubeReporter>>(); - m_factories["tap"] = Detail::make_unique<ReporterFactory<TAPReporter>>(); - m_factories["teamcity"] = Detail::make_unique<ReporterFactory<TeamCityReporter>>(); - m_factories["xml"] = Detail::make_unique<ReporterFactory<XmlReporter>>(); + m_factories["JUnit"] = Detail::make_unique<ReporterFactory<JunitReporter>>(); + m_factories["SonarQube"] = Detail::make_unique<ReporterFactory<SonarQubeReporter>>(); + m_factories["TAP"] = Detail::make_unique<ReporterFactory<TAPReporter>>(); + m_factories["TeamCity"] = Detail::make_unique<ReporterFactory<TeamCityReporter>>(); + m_factories["XML"] = Detail::make_unique<ReporterFactory<XmlReporter>>(); } ReporterRegistry::~ReporterRegistry() = default; - IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfig const* config ) const { + IEventListenerPtr ReporterRegistry::create( std::string const& name, ReporterConfig&& config ) const { auto it = m_factories.find( name ); if( it == m_factories.end() ) return nullptr; - return it->second->create( ReporterConfig( config ) ); + return it->second->create( CATCH_MOVE(config) ); } void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr factory ) { - m_factories.emplace(name, std::move(factory)); + CATCH_ENFORCE( name.find( "::" ) == name.npos, + "'::' is not allowed in reporter name: '" + name + '\'' ); + auto ret = m_factories.emplace(name, CATCH_MOVE(factory)); + CATCH_ENFORCE( ret.second, "reporter using '" + name + "' as name was already registered" ); } - void ReporterRegistry::registerListener( IReporterFactoryPtr factory ) { - m_listeners.push_back( std::move(factory) ); + void ReporterRegistry::registerListener( + Detail::unique_ptr<EventListenerFactory> factory ) { + m_listeners.push_back( CATCH_MOVE(factory) ); } IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { @@ -4180,6 +4667,171 @@ namespace Catch { + + +#include <algorithm> + +namespace Catch { + + namespace { + struct kvPair { + StringRef key, value; + }; + + kvPair splitKVPair(StringRef kvString) { + auto splitPos = static_cast<size_t>( std::distance( + kvString.begin(), + std::find( kvString.begin(), kvString.end(), '=' ) ) ); + + return { kvString.substr( 0, splitPos ), + kvString.substr( splitPos + 1, kvString.size() ) }; + } + } + + namespace Detail { + std::vector<std::string> splitReporterSpec( StringRef reporterSpec ) { + static constexpr auto separator = "::"; + static constexpr size_t separatorSize = 2; + + size_t separatorPos = 0; + auto findNextSeparator = [&reporterSpec]( size_t startPos ) { + static_assert( + separatorSize == 2, + "The code below currently assumes 2 char separator" ); + + auto currentPos = startPos; + do { + while ( currentPos < reporterSpec.size() && + reporterSpec[currentPos] != separator[0] ) { + ++currentPos; + } + if ( currentPos + 1 < reporterSpec.size() && + reporterSpec[currentPos + 1] == separator[1] ) { + return currentPos; + } + ++currentPos; + } while ( currentPos < reporterSpec.size() ); + + return static_cast<size_t>( -1 ); + }; + + std::vector<std::string> parts; + + while ( separatorPos < reporterSpec.size() ) { + const auto nextSeparator = findNextSeparator( separatorPos ); + parts.push_back( static_cast<std::string>( reporterSpec.substr( + separatorPos, nextSeparator - separatorPos ) ) ); + + if ( nextSeparator == static_cast<size_t>( -1 ) ) { + break; + } + separatorPos = nextSeparator + separatorSize; + } + + // Handle a separator at the end. + // This is not a valid spec, but we want to do validation in a + // centralized place + if ( separatorPos == reporterSpec.size() ) { + parts.emplace_back(); + } + + return parts; + } + + Optional<ColourMode> stringToColourMode( StringRef colourMode ) { + if ( colourMode == "default" ) { + return ColourMode::PlatformDefault; + } else if ( colourMode == "ansi" ) { + return ColourMode::ANSI; + } else if ( colourMode == "win32" ) { + return ColourMode::Win32; + } else if ( colourMode == "none" ) { + return ColourMode::None; + } else { + return {}; + } + } + } // namespace Detail + + + bool operator==( ReporterSpec const& lhs, ReporterSpec const& rhs ) { + return lhs.m_name == rhs.m_name && + lhs.m_outputFileName == rhs.m_outputFileName && + lhs.m_colourMode == rhs.m_colourMode && + lhs.m_customOptions == rhs.m_customOptions; + } + + Optional<ReporterSpec> parseReporterSpec( StringRef reporterSpec ) { + auto parts = Detail::splitReporterSpec( reporterSpec ); + + assert( parts.size() > 0 && "Split should never return empty vector" ); + + std::map<std::string, std::string> kvPairs; + Optional<std::string> outputFileName; + Optional<ColourMode> colourMode; + + // First part is always reporter name, so we skip it + for ( size_t i = 1; i < parts.size(); ++i ) { + auto kv = splitKVPair( parts[i] ); + auto key = kv.key, value = kv.value; + + if ( key.empty() || value.empty() ) { + return {}; + } else if ( key[0] == 'X' ) { + // This is a reporter-specific option, we don't check these + // apart from basic sanity checks + if ( key.size() == 1 ) { + return {}; + } + + auto ret = kvPairs.emplace( kv.key, kv.value ); + if ( !ret.second ) { + // Duplicated key. We might want to handle this differently, + // e.g. by overwriting the existing value? + return {}; + } + } else if ( key == "out" ) { + // Duplicated key + if ( outputFileName ) { + return {}; + } + outputFileName = static_cast<std::string>( value ); + } else if ( key == "colour-mode" ) { + // Duplicated key + if ( colourMode ) { + return {}; + } + colourMode = Detail::stringToColourMode( value ); + // Parsing failed + if ( !colourMode ) { + return {}; + } + } else { + // Unrecognized option + return {}; + } + } + + return ReporterSpec{ CATCH_MOVE( parts[0] ), + CATCH_MOVE( outputFileName ), + CATCH_MOVE( colourMode ), + CATCH_MOVE( kvPairs ) }; + } + +ReporterSpec::ReporterSpec( + std::string name, + Optional<std::string> outputFileName, + Optional<ColourMode> colourMode, + std::map<std::string, std::string> customOptions ): + m_name( CATCH_MOVE( name ) ), + m_outputFileName( CATCH_MOVE( outputFileName ) ), + m_colourMode( CATCH_MOVE( colourMode ) ), + m_customOptions( CATCH_MOVE( customOptions ) ) {} + +} // namespace Catch + + + namespace Catch { bool isOk( ResultWas::OfType resultType ) { @@ -4200,6 +4852,60 @@ namespace Catch { +#include <cstdio> +#include <sstream> +#include <vector> + +namespace Catch { + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector<Detail::unique_ptr<std::ostringstream>> m_streams; + std::vector<std::size_t> m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( Detail::make_unique<std::ostringstream>() ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + }; + + ReusableStringStream::ReusableStringStream() + : m_index( Singleton<StringStreams>::getMutable().add() ), + m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() ) + {} + + ReusableStringStream::~ReusableStringStream() { + static_cast<std::ostringstream*>( m_oss )->str(""); + m_oss->clear(); + Singleton<StringStreams>::getMutable().release( m_index ); + } + + std::string ReusableStringStream::str() const { + return static_cast<std::ostringstream*>( m_oss )->str(); + } + + void ReusableStringStream::str( std::string const& str ) { + static_cast<std::ostringstream*>( m_oss )->str( str ); + } + + +} + + + #include <cassert> #include <algorithm> @@ -4213,10 +4919,10 @@ namespace Catch { GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) : TrackerBase( nameAndLocation, ctx, parent ) {} - ~GeneratorTracker(); + ~GeneratorTracker() override; static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { - std::shared_ptr<GeneratorTracker> tracker; + GeneratorTracker* tracker; ITracker& currentTracker = ctx.currentTracker(); // Under specific circumstances, the generator we want @@ -4230,18 +4936,23 @@ namespace Catch { // } // // without it, the code above creates 5 nested generators. - if (currentTracker.nameAndLocation() == nameAndLocation) { - auto thisTracker = currentTracker.parent().findChild(nameAndLocation); - assert(thisTracker); - assert(thisTracker->isGeneratorTracker()); - tracker = std::static_pointer_cast<GeneratorTracker>(thisTracker); - } else if ( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + if ( currentTracker.nameAndLocation() == nameAndLocation ) { + auto thisTracker = + currentTracker.parent()->findChild( nameAndLocation ); + assert( thisTracker ); + assert( thisTracker->isGeneratorTracker() ); + tracker = static_cast<GeneratorTracker*>( thisTracker ); + } else if ( ITracker* childTracker = + currentTracker.findChild( nameAndLocation ) ) { assert( childTracker ); assert( childTracker->isGeneratorTracker() ); - tracker = std::static_pointer_cast<GeneratorTracker>( childTracker ); + tracker = static_cast<GeneratorTracker*>( childTracker ); } else { - tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, ¤tTracker ); - currentTracker.addChild( tracker ); + auto newTracker = + Catch::Detail::make_unique<GeneratorTracker>( + nameAndLocation, ctx, ¤tTracker ); + tracker = newTracker.get(); + currentTracker.addChild( CATCH_MOVE(newTracker) ); } if( !tracker->isComplete() ) { @@ -4265,13 +4976,53 @@ namespace Catch { // `SECTION`s. // **The check for m_children.empty cannot be removed**. // doing so would break `GENERATE` _not_ followed by `SECTION`s. - const bool should_wait_for_child = - !m_children.empty() && - std::find_if( m_children.begin(), - m_children.end(), - []( TestCaseTracking::ITrackerPtr tracker ) { - return tracker->hasStarted(); - } ) == m_children.end(); + const bool should_wait_for_child = [&]() { + // No children -> nobody to wait for + if ( m_children.empty() ) { + return false; + } + // If at least one child started executing, don't wait + if ( std::find_if( + m_children.begin(), + m_children.end(), + []( TestCaseTracking::ITrackerPtr const& tracker ) { + return tracker->hasStarted(); + } ) != m_children.end() ) { + return false; + } + + // No children have started. We need to check if they _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<SectionTracker const&>( *parent ); + auto const& filters = parentSection.getFilters(); + // No filters -> no restrictions on running sections + if ( filters.empty() ) { + return true; + } + + for ( auto const& child : m_children ) { + if ( child->isSectionTracker() && + std::find( + filters.begin(), + filters.end(), + static_cast<SectionTracker const&>( *child ) + .trimmedName() ) != filters.end() ) { + return true; + } + } + return false; + }(); // This check is a bit tricky, because m_generator->next() // has a side-effect, where it consumes generator's current @@ -4279,7 +5030,7 @@ namespace Catch { // this generator is still waiting for any child to start. if ( should_wait_for_child || ( m_runState == CompletedSuccessfully && - m_generator->next() ) ) { + m_generator->countedNext() ) ) { m_children.clear(); m_runState = Executing; } @@ -4290,21 +5041,20 @@ namespace Catch { return m_generator; } void setGenerator( GeneratorBasePtr&& generator ) override { - m_generator = std::move( generator ); + m_generator = CATCH_MOVE( generator ); } }; - GeneratorTracker::~GeneratorTracker() {} + GeneratorTracker::~GeneratorTracker() = default; } - RunContext::RunContext(IConfig const* _config, IStreamingReporterPtr&& reporter) + RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter) : m_runInfo(_config->name()), m_context(getCurrentMutableContext()), m_config(_config), - m_reporter(std::move(reporter)), + m_reporter(CATCH_MOVE(reporter)), m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) { - m_context.setRunner(this); m_context.setResultCapture(this); m_reporter->testRunStarting(m_runInfo); } @@ -4313,16 +5063,8 @@ namespace Catch { m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); } - void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { - m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); - } - - void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { - m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); - } - Totals RunContext::runTest(TestCaseHandle const& testCase) { - Totals prevTotals = m_totals; + const Totals prevTotals = m_totals; std::string redirectedCout; std::string redirectedCerr; @@ -4337,10 +5079,58 @@ namespace Catch { ITracker& rootTracker = m_trackerContext.startRun(); assert(rootTracker.isSectionTracker()); static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + + // We intentionally only seed the internal RNG once per test case, + // before it is first invoked. The reason for that is a complex + // interplay of generator/section implementation details and the + // Random*Generator types. + // + // The issue boils down to us needing to seed the Random*Generators + // with different seed each, so that they return different sequences + // of random numbers. We do this by giving them a number from the + // shared RNG instance as their seed. + // + // However, this runs into an issue if the reseeding happens each + // time the test case is entered (as opposed to first time only), + // because multiple generators could get the same seed, e.g. in + // ```cpp + // TEST_CASE() { + // auto i = GENERATE(take(10, random(0, 100)); + // SECTION("A") { + // auto j = GENERATE(take(10, random(0, 100)); + // } + // SECTION("B") { + // auto k = GENERATE(take(10, random(0, 100)); + // } + // } + // ``` + // `i` and `j` would properly return values from different sequences, + // but `i` and `k` would return the same sequence, because their seed + // would be the same. + // (The reason their seeds would be the same is that the generator + // for k would be initialized when the test case is entered the second + // time, after the shared RNG instance was reset to the same value + // it had when the generator for i was initialized.) + seedRng( *m_config ); + + uint64_t testRuns = 0; do { m_trackerContext.startCycle(); m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); - runCurrentTest(redirectedCout, redirectedCerr); + + m_reporter->testCasePartialStarting(testInfo, testRuns); + + const auto beforeRunTotals = m_totals; + std::string oneRunCout, oneRunCerr; + runCurrentTest(oneRunCout, oneRunCerr); + redirectedCout += oneRunCout; + redirectedCerr += oneRunCerr; + + const auto singleRunTotals = m_totals.delta(beforeRunTotals); + auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, oneRunCout, oneRunCerr, aborting()); + + m_reporter->testCasePartialEnded(statsForOneRun, testRuns); + ++testRuns; } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); Totals deltaTotals = m_totals.delta(prevTotals); @@ -4367,9 +5157,11 @@ namespace Catch { if (result.getResultType() == ResultWas::Ok) { m_totals.assertions.passed++; m_lastAssertionPassed = true; - } else if (!result.isOk()) { + } else if (!result.succeeded()) { m_lastAssertionPassed = false; - if( m_activeTestCase->getTestCaseInfo().okToFail() ) + if (result.isOk()) { + } + else if( m_activeTestCase->getTestCaseInfo().okToFail() ) m_totals.assertions.failedButOk++; else m_totals.assertions.failed++; @@ -4378,9 +5170,7 @@ namespace Catch { m_lastAssertionPassed = true; } - // We have no use for the return value (whether messages should be cleared), because messages were made scoped - // and should be let to clear themselves out. - static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)); if (result.getResultType() != ResultWas::Warning) m_messageScopes.clear(); @@ -4452,7 +5242,7 @@ namespace Catch { m_unfinishedSections.push_back(endInfo); } - void RunContext::benchmarkPreparing(std::string const& name) { + void RunContext::benchmarkPreparing( StringRef name ) { m_reporter->benchmarkPreparing(name); } void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { @@ -4461,8 +5251,8 @@ namespace Catch { void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) { m_reporter->benchmarkEnded( stats ); } - void RunContext::benchmarkFailed(std::string const & error) { - m_reporter->benchmarkFailed(error); + void RunContext::benchmarkFailed( StringRef error ) { + m_reporter->benchmarkFailed( error ); } void RunContext::pushScopedMessage(MessageInfo const & message) { @@ -4525,7 +5315,6 @@ namespace Catch { std::string(), false)); m_totals.testCases.failed++; - testGroupEnded(std::string(), m_totals, 1, 1); m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); } @@ -4553,8 +5342,6 @@ namespace Catch { m_shouldReportUnexpected = true; m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; - seedRng(*m_config); - Timer timer; CATCH_TRY { if (m_reporter->getPreferences().shouldRedirectStdOut) { @@ -4596,10 +5383,10 @@ namespace Catch { } void RunContext::invokeActiveTestCase() { - // We need to register a handler for signals/structured exceptions + // We need to engage a handler for signals/structured exceptions // before running the tests themselves, or the binary can crash // without failed test being reported. - FatalConditionHandler _; + FatalConditionHandlerGuard _(&m_fatalConditionhandler); m_activeTestCase->invoke(); } @@ -4655,7 +5442,7 @@ namespace Catch { void RunContext::handleMessage( AssertionInfo const& info, ResultWas::OfType resultType, - StringRef const& message, + StringRef message, AssertionReaction& reaction ) { m_reporter->assertionStarting( info ); @@ -4729,10 +5516,7 @@ namespace Catch { } void seedRng(IConfig const& config) { - if (config.rngSeed() != 0) { - std::srand(config.rngSeed()); - rng().seed(config.rngSeed()); - } + sharedRng().seed(config.rngSeed()); } unsigned int rngSeed() { @@ -4743,12 +5527,10 @@ namespace Catch { -#include <utility> - namespace Catch { Section::Section( SectionInfo&& info ): - m_info( std::move( info ) ), + m_info( CATCH_MOVE( info ) ), m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) { // Non-"included" sections will not use the timing information @@ -4791,7 +5573,7 @@ namespace Catch { } } - ISingleton::~ISingleton() {} + ISingleton::~ISingleton() = default; void addSingleton(ISingleton* singleton ) { getSingletons()->push_back( singleton ); @@ -4808,182 +5590,47 @@ namespace Catch { -#include <cstdio> -#include <iostream> -#include <fstream> -#include <sstream> -#include <vector> +#include <cstring> +#include <ostream> namespace Catch { - Catch::IStream::~IStream() = default; - -namespace Detail { - namespace { - template<typename WriterF, std::size_t bufferSize=256> - class StreamBufImpl : public std::streambuf { - char data[bufferSize]; - WriterF m_writer; - - public: - StreamBufImpl() { - setp( data, data + sizeof(data) ); - } - - ~StreamBufImpl() noexcept { - StreamBufImpl::sync(); - } - - private: - int overflow( int c ) override { - sync(); - - if( c != EOF ) { - if( pbase() == epptr() ) - m_writer( std::string( 1, static_cast<char>( c ) ) ); - else - sputc( static_cast<char>( c ) ); - } - return 0; - } - - int sync() override { - if( pbase() != pptr() ) { - m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); - setp( pbase(), epptr() ); - } - return 0; - } - }; - - /////////////////////////////////////////////////////////////////////////// - - struct OutputDebugWriter { - - void operator()( std::string const&str ) { - writeToDebugConsole( str ); - } - }; - - /////////////////////////////////////////////////////////////////////////// - - class FileStream : public IStream { - mutable std::ofstream m_ofs; - public: - FileStream( StringRef filename ) { - m_ofs.open( filename.c_str() ); - CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); - } - ~FileStream() override = default; - public: // IStream - std::ostream& stream() const override { - return m_ofs; - } - }; - - /////////////////////////////////////////////////////////////////////////// - - class CoutStream : public IStream { - mutable std::ostream m_os; - public: - // Store the streambuf from cout up-front because - // cout may get redirected when running tests - CoutStream() : m_os( Catch::cout().rdbuf() ) {} - ~CoutStream() override = default; - - public: // IStream - std::ostream& stream() const override { return m_os; } - }; - - /////////////////////////////////////////////////////////////////////////// - - class DebugOutStream : public IStream { - Detail::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf; - mutable std::ostream m_os; - public: - DebugOutStream() - : m_streamBuf( Detail::make_unique<StreamBufImpl<OutputDebugWriter>>() ), - m_os( m_streamBuf.get() ) - {} - - ~DebugOutStream() override = default; - - public: // IStream - std::ostream& stream() const override { return m_os; } - }; - - } // unnamed namespace -} // namespace Detail - - /////////////////////////////////////////////////////////////////////////// - - auto makeStream( StringRef const &filename ) -> IStream const* { - if( filename.empty() ) - return new Detail::CoutStream(); - else if( filename[0] == '%' ) { - if( filename == "%debug" ) - return new Detail::DebugOutStream(); - else - CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); - } - else - return new Detail::FileStream( filename ); + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); } + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } - // This class encapsulates the idea of a pool of ostringstreams that can be reused. - struct StringStreams { - std::vector<Detail::unique_ptr<std::ostringstream>> m_streams; - std::vector<std::size_t> m_unused; - std::ostringstream m_referenceStream; // Used for copy state/ flags from - - auto add() -> std::size_t { - if( m_unused.empty() ) { - m_streams.push_back( Detail::unique_ptr<std::ostringstream>( new std::ostringstream ) ); - return m_streams.size()-1; - } - else { - auto index = m_unused.back(); - m_unused.pop_back(); - return index; - } - } - - void release( std::size_t index ) { - m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state - m_unused.push_back(index); - } - }; - - ReusableStringStream::ReusableStringStream() - : m_index( Singleton<StringStreams>::getMutable().add() ), - m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() ) - {} +} // end namespace Catch - ReusableStringStream::~ReusableStringStream() { - static_cast<std::ostringstream*>( m_oss )->str(""); - m_oss->clear(); - Singleton<StringStreams>::getMutable().release( m_index ); - } - std::string ReusableStringStream::str() const { - return static_cast<std::ostringstream*>( m_oss )->str(); - } - void ReusableStringStream::str( std::string const& str ) { - static_cast<std::ostringstream*>( m_oss )->str( str ); - } - /////////////////////////////////////////////////////////////////////////// +#include <iostream> +namespace Catch { -#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions +// If you #define this you must implement these functions +#if !defined( CATCH_CONFIG_NOSTDOUT ) std::ostream& cout() { return std::cout; } std::ostream& cerr() { return std::cerr; } std::ostream& clog() { return std::clog; } #endif -} + +} // namespace Catch @@ -4995,16 +5642,10 @@ namespace Detail { namespace Catch { - namespace { - char toLowerCh(char c) { - return static_cast<char>( std::tolower( static_cast<unsigned char>(c) ) ); - } - } - bool startsWith( std::string const& s, std::string const& prefix ) { return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); } - bool startsWith( std::string const& s, char prefix ) { + bool startsWith( StringRef s, char prefix ) { return !s.empty() && s[0] == prefix; } bool endsWith( std::string const& s, std::string const& suffix ) { @@ -5017,13 +5658,19 @@ namespace Catch { return s.find( infix ) != std::string::npos; } void toLowerInPlace( std::string& s ) { - std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + std::transform( s.begin(), s.end(), s.begin(), []( char c ) { + return toLower( c ); + } ); } std::string toLower( std::string const& s ) { std::string lc = s; toLowerInPlace( lc ); return lc; } + char toLower(char c) { + return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); + } + std::string trim( std::string const& str ) { static char const* whitespaceChars = "\n\r\t "; std::string::size_type start = str.find_first_not_of( whitespaceChars ); @@ -5073,11 +5720,6 @@ namespace Catch { return subStrings; } - pluralise::pluralise( std::size_t count, std::string const& label ) - : m_count( count ), - m_label( label ) - {} - std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { os << pluraliser.m_count << ' ' << pluraliser.m_label; if( pluraliser.m_count != 1 ) @@ -5096,28 +5738,44 @@ namespace Catch { namespace Catch { StringRef::StringRef( char const* rawChars ) noexcept - : StringRef( rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars) ) ) + : StringRef( rawChars, std::strlen(rawChars) ) {} - auto StringRef::c_str() const -> char const* { - CATCH_ENFORCE(isNullTerminated(), "Called StringRef::c_str() on a non-null-terminated instance"); - return m_start; - } - - auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + auto StringRef::operator == ( StringRef other ) const noexcept -> bool { return m_size == other.m_size && (std::memcmp( m_start, other.m_start, m_size ) == 0); } - bool StringRef::operator<(StringRef const& rhs) const noexcept { + bool StringRef::operator<(StringRef rhs) const noexcept { if (m_size < rhs.m_size) { return strncmp(m_start, rhs.m_start, m_size) <= 0; } return strncmp(m_start, rhs.m_start, rhs.m_size) < 0; } - auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { - return os.write(str.data(), str.size()); + int StringRef::compare( StringRef rhs ) const { + auto cmpResult = + strncmp( m_start, rhs.m_start, std::min( m_size, rhs.m_size ) ); + + // This means that strncmp found a difference before the strings + // ended, and we can return it directly + if ( cmpResult != 0 ) { + return cmpResult; + } + + // If strings are equal up to length, then their comparison results on + // their size + if ( m_size < rhs.m_size ) { + return -1; + } else if ( m_size > rhs.m_size ) { + return 1; + } else { + return 0; + } + } + + auto operator << ( std::ostream& os, StringRef str ) -> std::ostream& { + return os.write(str.data(), static_cast<std::streamsize>(str.size())); } std::string operator+(StringRef lhs, StringRef rhs) { @@ -5128,7 +5786,7 @@ namespace Catch { return ret; } - auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { + auto operator+=( std::string& lhs, StringRef rhs ) -> std::string& { lhs.append(rhs.data(), rhs.size()); return lhs; } @@ -5172,7 +5830,7 @@ namespace Catch { << "\tRedefined at: " << lineInfo ); } - ITagAliasRegistry::~ITagAliasRegistry() {} + ITagAliasRegistry::~ITagAliasRegistry() = default; ITagAliasRegistry const& ITagAliasRegistry::get() { return getRegistryHub().getTagAliasRegistry(); @@ -5183,33 +5841,42 @@ namespace Catch { -#include <algorithm> -#include <set> - namespace Catch { - -namespace { - struct HashTest { - explicit HashTest(SimplePcg32& rng_inst) { - basis = rng_inst(); - basis <<= 32; - basis |= rng_inst(); - } - - uint64_t basis; - - uint64_t operator()(TestCaseInfo const& t) const { - // Modified FNV-1a hash - static constexpr uint64_t prime = 1099511628211; - uint64_t hash = basis; - for (const char c : t.name) { + TestCaseInfoHasher::TestCaseInfoHasher( hash_t seed ): m_seed( seed ) {} + + uint32_t TestCaseInfoHasher::operator()( TestCaseInfo const& t ) const { + // FNV-1a hash algorithm that is designed for uniqueness: + const hash_t prime = 1099511628211u; + hash_t hash = 14695981039346656037u; + for ( const char c : t.name ) { + hash ^= c; + hash *= prime; + } + for ( const char c : t.className ) { + hash ^= c; + hash *= prime; + } + for ( const Tag& tag : t.tags ) { + for ( const char c : tag.original ) { hash ^= c; hash *= prime; } - return hash; } - }; -} // end anonymous namespace + hash ^= m_seed; + hash *= prime; + const uint32_t low{ static_cast<uint32_t>( hash ) }; + const uint32_t high{ static_cast<uint32_t>( hash >> 32 ) }; + return low * high; + } +} // namespace Catch + + + + +#include <algorithm> +#include <set> + +namespace Catch { std::vector<TestCaseHandle> sortTests( IConfig const& config, std::vector<TestCaseHandle> const& unsortedTestCases ) { switch (config.runOrder()) { @@ -5218,20 +5885,36 @@ namespace { case TestRunOrder::LexicographicallySorted: { std::vector<TestCaseHandle> sorted = unsortedTestCases; - std::sort(sorted.begin(), sorted.end()); + std::sort( + sorted.begin(), + sorted.end(), + []( TestCaseHandle const& lhs, TestCaseHandle const& rhs ) { + return lhs.getTestCaseInfo() < rhs.getTestCaseInfo(); + } + ); return sorted; } case TestRunOrder::Randomized: { seedRng(config); - HashTest h(rng()); - std::vector<std::pair<uint64_t, TestCaseHandle>> indexed_tests; + using TestWithHash = std::pair<TestCaseInfoHasher::hash_t, TestCaseHandle>; + + TestCaseInfoHasher h{ config.rngSeed() }; + std::vector<TestWithHash> indexed_tests; indexed_tests.reserve(unsortedTestCases.size()); for (auto const& handle : unsortedTestCases) { indexed_tests.emplace_back(h(handle.getTestCaseInfo()), handle); } - std::sort(indexed_tests.begin(), indexed_tests.end()); + std::sort( indexed_tests.begin(), + indexed_tests.end(), + []( TestWithHash const& lhs, TestWithHash const& rhs ) { + if ( lhs.first == rhs.first ) { + return lhs.second.getTestCaseInfo() < + rhs.second.getTestCaseInfo(); + } + return lhs.first < rhs.first; + } ); std::vector<TestCaseHandle> randomized; randomized.reserve(indexed_tests.size()); @@ -5255,14 +5938,22 @@ namespace { return testSpec.matches( testCase.getTestCaseInfo() ) && isThrowSafe( testCase, config ); } - void enforceNoDuplicateTestCases( std::vector<TestCaseHandle> const& functions ) { - std::set<TestCaseHandle> seenFunctions; - for( auto const& function : functions ) { - auto prev = seenFunctions.insert( function ); - CATCH_ENFORCE( prev.second, - "error: TEST_CASE( \"" << function.getTestCaseInfo().name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); + void + enforceNoDuplicateTestCases( std::vector<TestCaseHandle> const& tests ) { + auto testInfoCmp = []( TestCaseInfo const* lhs, + TestCaseInfo const* rhs ) { + return *lhs < *rhs; + }; + std::set<TestCaseInfo const*, decltype(testInfoCmp)> seenTests(testInfoCmp); + for ( auto const& test : tests ) { + const auto infoPtr = &test.getTestCaseInfo(); + const auto prev = seenTests.insert( infoPtr ); + CATCH_ENFORCE( + prev.second, + "error: test case \"" << infoPtr->name << "\", with tags \"" + << infoPtr->tagsAsString() << "\" already defined.\n" + << "\tFirst seen at " << ( *prev.first )->lineInfo << "\n" + << "\tRedefined at " << infoPtr->lineInfo ); } } @@ -5275,7 +5966,7 @@ namespace { filtered.push_back(testCase); } } - return filtered; + return createShard(filtered, config.shardCount(), config.shardIndex()); } std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config ) { return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); @@ -5284,8 +5975,8 @@ namespace { void TestRegistry::registerTest(Detail::unique_ptr<TestCaseInfo> testInfo, Detail::unique_ptr<ITestInvoker> testInvoker) { m_handles.emplace_back(testInfo.get(), testInvoker.get()); m_viewed_test_infos.push_back(testInfo.get()); - m_owned_test_infos.push_back(std::move(testInfo)); - m_invokers.push_back(std::move(testInvoker)); + m_owned_test_infos.push_back(CATCH_MOVE(testInfo)); + m_invokers.push_back(CATCH_MOVE(testInvoker)); } std::vector<TestCaseInfo*> const& TestRegistry::getAllInfos() const { @@ -5313,19 +6004,6 @@ namespace { m_testAsFunction(); } - std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { - std::string className(classOrQualifiedMethodName); - if( startsWith( className, '&' ) ) - { - std::size_t lastColons = className.rfind( "::" ); - std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); - if( penultimateColons == std::string::npos ) - penultimateColons = 1; - className = className.substr( penultimateColons, lastColons-penultimateColons ); - } - return className; - } - } // end namespace Catch @@ -5333,7 +6011,6 @@ namespace { #include <algorithm> #include <cassert> -#include <memory> #if defined(__clang__) # pragma clang diagnostic push @@ -5351,11 +6028,15 @@ namespace TestCaseTracking { ITracker::~ITracker() = default; - void ITracker::addChild( ITrackerPtr const& child ) { - m_children.push_back( child ); + void ITracker::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void ITracker::addChild( ITrackerPtr&& child ) { + m_children.push_back( CATCH_MOVE(child) ); } - ITrackerPtr ITracker::findChild( NameAndLocation const& nameAndLocation ) { + ITracker* ITracker::findChild( NameAndLocation const& nameAndLocation ) { auto it = std::find_if( m_children.begin(), m_children.end(), @@ -5364,14 +6045,37 @@ namespace TestCaseTracking { nameAndLocation.location && tracker->nameAndLocation().name == nameAndLocation.name; } ); - return ( it != m_children.end() ) ? *it : nullptr; + return ( it != m_children.end() ) ? it->get() : nullptr; } + bool ITracker::isSectionTracker() const { return false; } + bool ITracker::isGeneratorTracker() const { return false; } + bool ITracker::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + + bool ITracker::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + + bool ITracker::hasStarted() const { return m_runState != NotStarted; } + + void ITracker::openChild() { + if (m_runState != ExecutingChildren) { + m_runState = ExecutingChildren; + if (m_parent) { + m_parent->openChild(); + } + } + } ITracker& TrackerContext::startRun() { using namespace std::string_literals; - m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}"s, CATCH_INTERNAL_LINEINFO ), *this, nullptr ); + m_rootTracker = Catch::Detail::make_unique<SectionTracker>( + NameAndLocation( "{root}"s, CATCH_INTERNAL_LINEINFO ), + *this, + nullptr ); m_currentTracker = nullptr; m_runState = Executing; return *m_rootTracker; @@ -5403,36 +6107,13 @@ namespace TestCaseTracking { TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ): - ITracker(nameAndLocation), - m_ctx( ctx ), - m_parent( parent ) + ITracker(nameAndLocation, parent), + m_ctx( ctx ) {} bool TrackerBase::isComplete() const { return m_runState == CompletedSuccessfully || m_runState == Failed; } - bool TrackerBase::isSuccessfullyCompleted() const { - return m_runState == CompletedSuccessfully; - } - bool TrackerBase::isOpen() const { - return m_runState != NotStarted && !isComplete(); - } - - ITracker& TrackerBase::parent() { - assert( m_parent ); // Should always be non-null except for root - return *m_parent; - } - - void TrackerBase::openChild() { - if( m_runState != ExecutingChildren ) { - m_runState = ExecutingChildren; - if( m_parent ) - m_parent->openChild(); - } - } - - bool TrackerBase::isSectionTracker() const { return false; } - bool TrackerBase::isGeneratorTracker() const { return false; } void TrackerBase::open() { m_runState = Executing; @@ -5477,9 +6158,6 @@ namespace TestCaseTracking { moveToParent(); m_ctx.completeCycle(); } - void TrackerBase::markAsNeedingAnotherRun() { - m_runState = NeedsAnotherRun; - } void TrackerBase::moveToParent() { assert( m_parent ); @@ -5495,7 +6173,7 @@ namespace TestCaseTracking { { if( parent ) { while( !parent->isSectionTracker() ) - parent = &parent->parent(); + parent = parent->parent(); SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); addNextFilters( parentSection.m_filters ); @@ -5506,7 +6184,7 @@ namespace TestCaseTracking { bool complete = true; if (m_filters.empty() - || m_filters[0] == "" + || m_filters[0].empty() || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) { complete = TrackerBase::isComplete(); } @@ -5516,17 +6194,19 @@ namespace TestCaseTracking { bool SectionTracker::isSectionTracker() const { return true; } SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { - std::shared_ptr<SectionTracker> section; + SectionTracker* section; ITracker& currentTracker = ctx.currentTracker(); - if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + if ( ITracker* childTracker = + currentTracker.findChild( nameAndLocation ) ) { assert( childTracker ); assert( childTracker->isSectionTracker() ); - section = std::static_pointer_cast<SectionTracker>( childTracker ); - } - else { - section = std::make_shared<SectionTracker>( nameAndLocation, ctx, ¤tTracker ); - currentTracker.addChild( section ); + section = static_cast<SectionTracker*>( childTracker ); + } else { + auto newSection = Catch::Detail::make_unique<SectionTracker>( + nameAndLocation, ctx, ¤tTracker ); + section = newSection.get(); + currentTracker.addChild( CATCH_MOVE( newSection ) ); } if( !ctx.completedCycle() ) section->tryOpen(); @@ -5541,21 +6221,25 @@ namespace TestCaseTracking { void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) { if( !filters.empty() ) { m_filters.reserve( m_filters.size() + filters.size() + 2 ); - m_filters.emplace_back(""); // Root - should never be consulted - m_filters.emplace_back(""); // Test Case - not a section filter + 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<std::string> const& filters ) { + void SectionTracker::addNextFilters( std::vector<StringRef> const& filters ) { if( filters.size() > 1 ) m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() ); } -} // namespace TestCaseTracking + std::vector<StringRef> const& SectionTracker::getFilters() const { + return m_filters; + } + + StringRef SectionTracker::trimmedName() const { + return m_trimmed_name; + } -using TestCaseTracking::ITracker; -using TestCaseTracking::TrackerContext; -using TestCaseTracking::SectionTracker; +} // namespace TestCaseTracking } // namespace Catch @@ -5565,13 +6249,46 @@ using TestCaseTracking::SectionTracker; +#include <algorithm> +#include <iterator> + namespace Catch { + namespace { + StringRef extractClassName( StringRef classOrMethodName ) { + if ( !startsWith( classOrMethodName, '&' ) ) { + return classOrMethodName; + } + + // Remove the leading '&' to avoid having to special case it later + const auto methodName = + classOrMethodName.substr( 1, classOrMethodName.size() ); + + auto reverseStart = std::make_reverse_iterator( methodName.end() ); + auto reverseEnd = std::make_reverse_iterator( methodName.begin() ); + + // We make a simplifying assumption that ":" is only present + // in the input as part of "::" from C++ typenames (this is + // relatively safe assumption because the input is generated + // as stringification of type through preprocessor). + auto lastColons = std::find( reverseStart, reverseEnd, ':' ) + 1; + auto secondLastColons = + std::find( lastColons + 1, reverseEnd, ':' ); + + auto const startIdx = reverseEnd - secondLastColons; + auto const classNameSize = secondLastColons - lastColons - 1; + + return methodName.substr( + static_cast<std::size_t>( startIdx ), + static_cast<std::size_t>( classNameSize ) ); + } + } // namespace + Detail::unique_ptr<ITestInvoker> makeTestInvoker( void(*testAsFunction)() ) { - return Detail::unique_ptr<ITestInvoker>( new TestInvokerAsFunction( testAsFunction )); + return Detail::make_unique<TestInvokerAsFunction>( testAsFunction ); } - AutoReg::AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { + AutoReg::AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept { CATCH_TRY { getMutableRegistryHub() .registerTest( @@ -5579,7 +6296,7 @@ namespace Catch { extractClassName( classOrMethod ), nameAndTags, lineInfo), - std::move(invoker) + CATCH_MOVE(invoker) ); } CATCH_CATCH_ALL { // Do not throw when constructing global objects, instead register the exception to be processed later @@ -5608,7 +6325,7 @@ namespace Catch { for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) //if visitChar fails if( !visitChar( m_arg[m_pos] ) ){ - m_testSpec.m_invalidArgs.push_back(arg); + m_testSpec.m_invalidSpecs.push_back(arg); break; } endMode(); @@ -5616,7 +6333,7 @@ namespace Catch { } TestSpec TestSpecParser::testSpec() { addFilter(); - return std::move(m_testSpec); + return CATCH_MOVE(m_testSpec); } bool TestSpecParser::visitChar( char c ) { if( (m_mode != EscapedName) && (c == '\\') ) { @@ -5732,7 +6449,7 @@ namespace Catch { void TestSpecParser::addFilter() { if( !m_currentFilter.m_required.empty() || !m_currentFilter.m_forbidden.empty() ) { - m_testSpec.m_filters.push_back( std::move(m_currentFilter) ); + m_testSpec.m_filters.push_back( CATCH_MOVE(m_currentFilter) ); m_currentFilter = TestSpec::Filter(); } } @@ -5825,6 +6542,8 @@ namespace Catch { } // namespace Catch + +#include <algorithm> #include <cstring> #include <ostream> @@ -5858,96 +6577,107 @@ namespace { namespace Catch { namespace TextFlow { - void Column::iterator::calcLength() { - m_suffix = false; - auto width = m_column.m_width - indent(); - m_end = m_pos; + void Column::const_iterator::calcLength() { + m_addHyphen = false; + m_parsedTo = m_lineStart; + std::string const& current_line = m_column.m_string; - if ( current_line[m_pos] == '\n' ) { - ++m_end; + if ( current_line[m_lineStart] == '\n' ) { + ++m_parsedTo; } - while ( m_end < current_line.size() && - current_line[m_end] != '\n' ) { - ++m_end; + + const auto maxLineLength = m_column.m_width - indentSize(); + const auto maxParseTo = std::min(current_line.size(), m_lineStart + maxLineLength); + while ( m_parsedTo < maxParseTo && + current_line[m_parsedTo] != '\n' ) { + ++m_parsedTo; } - if ( m_end < m_pos + width ) { - m_len = m_end - m_pos; + // If we encountered a newline before the column is filled, + // then we linebreak at the newline and consider this line + // finished. + if ( m_parsedTo < m_lineStart + maxLineLength ) { + m_lineLength = m_parsedTo - m_lineStart; } else { - size_t len = width; - while ( len > 0 && !isBoundary( current_line, m_pos + len ) ) { - --len; + // Look for a natural linebreak boundary in the column + // (We look from the end, so that the first found boundary is + // the right one) + size_t newLineLength = maxLineLength; + while ( newLineLength > 0 && !isBoundary( current_line, m_lineStart + newLineLength ) ) { + --newLineLength; } - while ( len > 0 && - isWhitespace( current_line[m_pos + len - 1] ) ) { - --len; + while ( newLineLength > 0 && + isWhitespace( current_line[m_lineStart + newLineLength - 1] ) ) { + --newLineLength; } - if ( len > 0 ) { - m_len = len; + // If we found one, then that is where we linebreak + if ( newLineLength > 0 ) { + m_lineLength = newLineLength; } else { - m_suffix = true; - m_len = width - 1; + // Otherwise we have to split text with a hyphen + m_addHyphen = true; + m_lineLength = maxLineLength - 1; } } } - size_t Column::iterator::indent() const { + size_t Column::const_iterator::indentSize() const { auto initial = - m_pos == 0 ? m_column.m_initialIndent : std::string::npos; + m_lineStart == 0 ? m_column.m_initialIndent : std::string::npos; return initial == std::string::npos ? m_column.m_indent : initial; } std::string - Column::iterator::addIndentAndSuffix( size_t position, + Column::const_iterator::addIndentAndSuffix( size_t position, size_t length ) const { std::string ret; - const auto desired_indent = indent(); - ret.reserve( desired_indent + length + m_suffix ); + const auto desired_indent = indentSize(); + ret.reserve( desired_indent + length + m_addHyphen ); ret.append( desired_indent, ' ' ); ret.append( m_column.m_string, position, length ); - if ( m_suffix ) { + if ( m_addHyphen ) { ret.push_back( '-' ); } return ret; } - Column::iterator::iterator( Column const& column ): m_column( column ) { + Column::const_iterator::const_iterator( Column const& column ): m_column( column ) { assert( m_column.m_width > m_column.m_indent ); assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); calcLength(); - if ( m_len == 0 ) { - m_pos = m_column.m_string.size(); + if ( m_lineLength == 0 ) { + m_lineStart = m_column.m_string.size(); } } - std::string Column::iterator::operator*() const { - assert( m_pos <= m_end ); - return addIndentAndSuffix( m_pos, m_len ); + std::string Column::const_iterator::operator*() const { + assert( m_lineStart <= m_parsedTo ); + return addIndentAndSuffix( m_lineStart, m_lineLength ); } - Column::iterator& Column::iterator::operator++() { - m_pos += m_len; + Column::const_iterator& Column::const_iterator::operator++() { + m_lineStart += m_lineLength; std::string const& current_line = m_column.m_string; - if ( m_pos < current_line.size() && current_line[m_pos] == '\n' ) { - m_pos += 1; + if ( m_lineStart < current_line.size() && current_line[m_lineStart] == '\n' ) { + m_lineStart += 1; } else { - while ( m_pos < current_line.size() && - isWhitespace( current_line[m_pos] ) ) { - ++m_pos; + while ( m_lineStart < current_line.size() && + isWhitespace( current_line[m_lineStart] ) ) { + ++m_lineStart; } } - if ( m_pos != current_line.size() ) { + if ( m_lineStart != current_line.size() ) { calcLength(); } return *this; } - Column::iterator Column::iterator::operator++( int ) { - iterator prev( *this ); + Column::const_iterator Column::const_iterator::operator++( int ) { + const_iterator prev( *this ); operator++(); return prev; } @@ -6100,7 +6830,9 @@ namespace Catch { } - +// Note: swapping these two includes around causes MSVC to error out +// while in /permissive- mode. No, I don't know why. +// Tested on VS 2019, 18.{3, 4}.x #include <iomanip> #include <type_traits> @@ -6168,7 +6900,7 @@ namespace { } - XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + XmlEncode::XmlEncode( StringRef str, ForWhat forWhat ) : m_str( str ), m_forWhat( forWhat ) {} @@ -6178,7 +6910,7 @@ namespace { // (see: http://www.w3.org/TR/xml/#syntax) for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { - unsigned char c = m_str[idx]; + unsigned char c = static_cast<unsigned char>(m_str[idx]); switch (c) { case '<': os << "<"; break; case '&': os << "&"; break; @@ -6238,7 +6970,7 @@ namespace { bool valid = true; uint32_t value = headerValue(c); for (std::size_t n = 1; n < encBytes; ++n) { - unsigned char nc = m_str[idx + n]; + unsigned char nc = static_cast<unsigned char>(m_str[idx + n]); valid &= ((nc & 0xC0) == 0x80); value = (value << 6) | (nc & 0x3F); } @@ -6302,11 +7034,20 @@ namespace { } } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, XmlFormatting fmt ) { + XmlWriter::ScopedElement& + XmlWriter::ScopedElement::writeText( StringRef text, XmlFormatting fmt ) { m_writer->writeText( text, fmt ); return *this; } + XmlWriter::ScopedElement& + XmlWriter::ScopedElement::writeAttribute( StringRef name, + StringRef attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) { writeDeclaration(); @@ -6350,7 +7091,7 @@ namespace { if (shouldIndent(fmt)) { m_os << m_indent; } - m_os << "</" << m_tags.back() << ">"; + m_os << "</" << m_tags.back() << '>'; } m_os << std::flush; applyFormatting(fmt); @@ -6358,48 +7099,50 @@ namespace { return *this; } - XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + XmlWriter& XmlWriter::writeAttribute( StringRef name, + StringRef attribute ) { if( !name.empty() && !attribute.empty() ) m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; return *this; } - XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { - m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + XmlWriter& XmlWriter::writeAttribute( StringRef name, bool attribute ) { + writeAttribute(name, (attribute ? "true"_sr : "false"_sr)); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( StringRef name, + char const* attribute ) { + writeAttribute( name, StringRef( attribute ) ); return *this; } - XmlWriter& XmlWriter::writeText( std::string const& text, XmlFormatting fmt) { + XmlWriter& XmlWriter::writeText( StringRef text, XmlFormatting fmt ) { + CATCH_ENFORCE(!m_tags.empty(), "Cannot write text as top level element"); if( !text.empty() ){ bool tagWasOpen = m_tagIsOpen; ensureTagClosed(); if (tagWasOpen && shouldIndent(fmt)) { m_os << m_indent; } - m_os << XmlEncode( text ); + m_os << XmlEncode( text, XmlEncode::ForTextNodes ); applyFormatting(fmt); } return *this; } - XmlWriter& XmlWriter::writeComment( std::string const& text, XmlFormatting fmt) { + XmlWriter& XmlWriter::writeComment( StringRef text, XmlFormatting fmt ) { ensureTagClosed(); if (shouldIndent(fmt)) { m_os << m_indent; } - m_os << "<!--" << text << "-->"; + m_os << "<!-- " << text << " -->"; applyFormatting(fmt); return *this; } - void XmlWriter::writeStylesheetRef( std::string const& url ) { - m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; - } - - XmlWriter& XmlWriter::writeBlankLine() { - ensureTagClosed(); - m_os << '\n'; - return *this; + void XmlWriter::writeStylesheetRef( StringRef url ) { + m_os << R"(<?xml-stylesheet type="text/xsl" href=")" << url << R"("?>)" << '\n'; } void XmlWriter::ensureTagClosed() { @@ -6415,12 +7158,12 @@ namespace { } void XmlWriter::writeDeclaration() { - m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + m_os << R"(<?xml version="1.0" encoding="UTF-8"?>)" << '\n'; } void XmlWriter::newlineIfNecessary() { if( m_needsNewline ) { - m_os << std::endl; + m_os << '\n' << std::flush; m_needsNewline = false; } } @@ -6432,7 +7175,6 @@ namespace { #include <cmath> #include <cstdlib> #include <cstdint> -#include <cstring> #include <sstream> #include <iomanip> #include <limits> @@ -6441,20 +7183,6 @@ namespace { namespace Catch { namespace { - int32_t convert(float f) { - static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); - int32_t i; - std::memcpy(&i, &f, sizeof(f)); - return i; - } - - int64_t convert(double d) { - static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); - int64_t i; - std::memcpy(&i, &d, sizeof(d)); - return i; - } - template <typename FP> bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { // Comparison with NaN should always be false. @@ -6463,16 +7191,10 @@ namespace { return false; } - auto lc = convert(lhs); - auto rc = convert(rhs); - - if ((lc < 0) != (rc < 0)) { - // Potentially we can have +0 and -0 - return lhs == rhs; - } + // This should also handle positive and negative zeros, infinities + const auto ulpDist = ulpDistance(lhs, rhs); - auto ulpDiff = std::abs(lc - rc); - return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff; + return ulpDist <= maxUlpDiff; } #if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) @@ -6547,6 +7269,9 @@ namespace Detail { CATCH_ENFORCE(m_type == Detail::FloatingPointKind::Double || m_ulps < (std::numeric_limits<uint32_t>::max)(), "Provided ULP is impossibly large for a float comparison."); + CATCH_ENFORCE( std::numeric_limits<double>::is_iec559, + "WithinUlp matcher only supports platforms with " + "IEEE-754 compatible floating point representation" ); } #if defined(__clang__) @@ -6584,14 +7309,26 @@ namespace Detail { ret << " (["; if (m_type == Detail::FloatingPointKind::Double) { - write(ret, step(m_target, static_cast<double>(-INFINITY), m_ulps)); + write( ret, + step( m_target, + -std::numeric_limits<double>::infinity(), + m_ulps ) ); ret << ", "; - write(ret, step(m_target, static_cast<double>( INFINITY), m_ulps)); + write( ret, + step( m_target, + std::numeric_limits<double>::infinity(), + m_ulps ) ); } else { // We have to cast INFINITY to float because of MinGW, see #1782 - write(ret, step(static_cast<float>(m_target), static_cast<float>(-INFINITY), m_ulps)); + write( ret, + step( static_cast<float>( m_target ), + -std::numeric_limits<float>::infinity(), + m_ulps ) ); ret << ", "; - write(ret, step(static_cast<float>(m_target), static_cast<float>( INFINITY), m_ulps)); + write( ret, + step( static_cast<float>( m_target ), + std::numeric_limits<float>::infinity(), + m_ulps ) ); } ret << "])"; @@ -6673,7 +7410,7 @@ namespace Matchers { } - StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + StringMatcherBase::StringMatcherBase( StringRef operation, CasedString const& comparator ) : m_comparator( comparator ), m_operation( operation ) { } @@ -6685,33 +7422,33 @@ namespace Matchers { description += m_operation; description += ": \""; description += m_comparator.m_str; - description += "\""; + description += '"'; description += m_comparator.caseSensitivitySuffix(); return description; } - StringEqualsMatcher::StringEqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + StringEqualsMatcher::StringEqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals"_sr, comparator ) {} bool StringEqualsMatcher::match( std::string const& source ) const { return m_comparator.adjustString( source ) == m_comparator.m_str; } - StringContainsMatcher::StringContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + StringContainsMatcher::StringContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains"_sr, comparator ) {} bool StringContainsMatcher::match( std::string const& source ) const { return contains( m_comparator.adjustString( source ), m_comparator.m_str ); } - StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with"_sr, comparator ) {} bool StartsWithMatcher::match( std::string const& source ) const { return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); } - EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with"_sr, comparator ) {} bool EndsWithMatcher::match( std::string const& source ) const { return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); @@ -6719,7 +7456,7 @@ namespace Matchers { - RegexMatcher::RegexMatcher(std::string regex, CaseSensitive caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive caseSensitivity): m_regex(CATCH_MOVE(regex)), m_caseSensitivity(caseSensitivity) {} bool RegexMatcher::match(std::string const& matchee) const { auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway @@ -6738,7 +7475,7 @@ namespace Matchers { StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity ) { return StringEqualsMatcher( CasedString( str, caseSensitivity) ); } - StringContainsMatcher Contains( std::string const& str, CaseSensitive caseSensitivity ) { + StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity ) { return StringContainsMatcher( CasedString( str, caseSensitivity) ); } EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity ) { @@ -6769,7 +7506,7 @@ namespace Matchers { for ( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { combined_size += desc->size(); } - combined_size += (descriptions_end - descriptions_begin - 1) * combine.size(); + combined_size += static_cast<size_t>(descriptions_end - descriptions_begin - 1) * combine.size(); description.reserve(combined_size); @@ -6812,9 +7549,9 @@ namespace Catch { // This is the general overload that takes a any string matcher // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers // the Equals matcher (so the header does not mention matchers) - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ) { + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) { std::string exceptionMessage = Catch::translateActiveException(); - MatchExpr<std::string, StringMatcher const&> expr( std::move(exceptionMessage), matcher, matcherString ); + MatchExpr<std::string, StringMatcher const&> expr( CATCH_MOVE(exceptionMessage), matcher, matcherString ); handler.handleExpr( expr ); } @@ -6901,7 +7638,7 @@ bool ExceptionMessageMatcher::match(std::exception const& ex) const { } std::string ExceptionMessageMatcher::describe() const { - return "exception message matches \"" + m_message + "\""; + return "exception message matches \"" + m_message + '"'; } ExceptionMessageMatcher Message(std::string const& message) { @@ -6921,20 +7658,20 @@ namespace Catch { void AutomakeReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { // Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR. - stream << ":test-result: "; + m_stream << ":test-result: "; if (_testCaseStats.totals.assertions.allPassed()) { - stream << "PASS"; + m_stream << "PASS"; } else if (_testCaseStats.totals.assertions.allOk()) { - stream << "XFAIL"; + m_stream << "XFAIL"; } else { - stream << "FAIL"; + m_stream << "FAIL"; } - stream << ' ' << _testCaseStats.testInfo->name << '\n'; + m_stream << ' ' << _testCaseStats.testInfo->name << '\n'; StreamingReporterBase::testCaseEnded(_testCaseStats); } void AutomakeReporter::skipTest(TestCaseInfo const& testInfo) { - stream << ":test-result: SKIP " << testInfo.name << '\n'; + m_stream << ":test-result: SKIP " << testInfo.name << '\n'; } } // end namespace Catch @@ -6953,12 +7690,33 @@ namespace Catch { */ +#include <algorithm> #include <cfloat> #include <cstdio> #include <ostream> +#include <iomanip> namespace Catch { + namespace { + void listTestNamesOnly(std::ostream& out, + std::vector<TestCaseHandle> const& tests) { + for (auto const& test : tests) { + auto const& testCaseInfo = test.getTestCaseInfo(); + + if (startsWith(testCaseInfo.name, '#')) { + out << '"' << testCaseInfo.name << '"'; + } else { + out << testCaseInfo.name; + } + + out << '\n'; + } + out << std::flush; + } + } // end unnamed namespace + + // Because formatting using c++ streams is stateful, drop down to C is // required Alternatively we could use stringstream, but its performance // is... not good. @@ -6973,11 +7731,13 @@ namespace Catch { // Save previous errno, to prevent sprintf from overwriting it ErrnoGuard guard; #ifdef _MSC_VER - sprintf_s( buffer, "%.3f", duration ); + size_t printedLength = static_cast<size_t>( + sprintf_s( buffer, "%.3f", duration ) ); #else - std::sprintf( buffer, "%.3f", duration ); + size_t printedLength = static_cast<size_t>( + std::snprintf( buffer, maxDoubleSize, "%.3f", duration ) ); #endif - return std::string( buffer ); + return std::string( buffer, printedLength ); } bool shouldShowDuration( IConfig const& config, double duration ) { @@ -7020,31 +7780,160 @@ namespace Catch { return out; } + void + defaultListReporters( std::ostream& out, + std::vector<ReporterDescription> const& descriptions, + Verbosity verbosity ) { + out << "Available reporters:\n"; + const auto maxNameLen = + std::max_element( descriptions.begin(), + descriptions.end(), + []( ReporterDescription const& lhs, + ReporterDescription const& rhs ) { + return lhs.name.size() < rhs.name.size(); + } ) + ->name.size(); + + for ( auto const& desc : descriptions ) { + if ( verbosity == Verbosity::Quiet ) { + out << TextFlow::Column( desc.name ) + .indent( 2 ) + .width( 5 + maxNameLen ) + << '\n'; + } else { + out << TextFlow::Column( desc.name + ':' ) + .indent( 2 ) + .width( 5 + maxNameLen ) + + TextFlow::Column( desc.description ) + .initialIndent( 0 ) + .indent( 2 ) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 ) + << '\n'; + } + } + out << '\n' << std::flush; + } + + void defaultListListeners( std::ostream& out, + std::vector<ListenerDescription> const& descriptions ) { + const auto maxNameLen = + std::max_element( descriptions.begin(), + descriptions.end(), + []( ListenerDescription const& lhs, + ListenerDescription const& rhs ) { + return lhs.name.size() < rhs.name.size(); + } ) + ->name.size(); + + out << "Registered listeners:\n"; + for ( auto const& desc : descriptions ) { + out << TextFlow::Column( static_cast<std::string>( desc.name ) + + ':' ) + .indent( 2 ) + .width( maxNameLen + 5 ) + + TextFlow::Column( desc.description ) + .initialIndent( 0 ) + .indent( 2 ) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 ) + << '\n'; + } + + out << '\n' << std::flush; + } + + void defaultListTags( std::ostream& out, + std::vector<TagInfo> const& tags, + bool isFiltered ) { + if ( isFiltered ) { + out << "Tags for matching test cases:\n"; + } else { + out << "All available tags:\n"; + } + + for ( auto const& tagCount : tags ) { + ReusableStringStream rss; + rss << " " << std::setw( 2 ) << tagCount.count << " "; + auto str = rss.str(); + auto wrapper = TextFlow::Column( tagCount.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH - 10 ); + out << str << wrapper << '\n'; + } + out << pluralise(tags.size(), "tag"_sr) << "\n\n" << std::flush; + } + + void defaultListTests(std::ostream& out, ColourImpl* streamColour, std::vector<TestCaseHandle> const& tests, bool isFiltered, Verbosity verbosity) { + // We special case this to provide the equivalent of old + // `--list-test-names-only`, which could then be used by the + // `--input-file` option. + if (verbosity == Verbosity::Quiet) { + listTestNamesOnly(out, tests); + return; + } + + if (isFiltered) { + out << "Matching test cases:\n"; + } else { + out << "All available test cases:\n"; + } + + for (auto const& test : tests) { + auto const& testCaseInfo = test.getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + auto colourGuard = streamColour->guardColour( colour ).engage( out ); + + out << TextFlow::Column(testCaseInfo.name).indent(2) << '\n'; + if (verbosity >= Verbosity::High) { + out << TextFlow::Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << '\n'; + } + if (!testCaseInfo.tags.empty() && + verbosity > Verbosity::Quiet) { + out << TextFlow::Column(testCaseInfo.tagsAsString()).indent(6) << '\n'; + } + } + + if (isFiltered) { + out << pluralise(tests.size(), "matching test case"_sr); + } else { + out << pluralise(tests.size(), "test case"_sr); + } + out << "\n\n" << std::flush; + } + } // namespace Catch namespace Catch { + + void EventListenerBase::fatalErrorEncountered( StringRef ) {} + + void EventListenerBase::benchmarkPreparing( StringRef ) {} + void EventListenerBase::benchmarkStarting( BenchmarkInfo const& ) {} + void EventListenerBase::benchmarkEnded( BenchmarkStats<> const& ) {} + void EventListenerBase::benchmarkFailed( StringRef ) {} + void EventListenerBase::assertionStarting( AssertionInfo const& ) {} - bool EventListenerBase::assertionEnded( AssertionStats const& ) { - return false; - } - void - EventListenerBase::listReporters( std::vector<ReporterDescription> const&, - IConfig const& ) {} - void EventListenerBase::listTests( std::vector<TestCaseHandle> const&, - IConfig const& ) {} - void EventListenerBase::listTags( std::vector<TagInfo> const&, - IConfig const& ) {} - void EventListenerBase::noMatchingTestCases( std::string const& ) {} + void EventListenerBase::assertionEnded( AssertionStats const& ) {} + void EventListenerBase::listReporters( + std::vector<ReporterDescription> const& ) {} + void EventListenerBase::listListeners( + std::vector<ListenerDescription> const& ) {} + void EventListenerBase::listTests( std::vector<TestCaseHandle> const& ) {} + void EventListenerBase::listTags( std::vector<TagInfo> const& ) {} + void EventListenerBase::noMatchingTestCases( StringRef ) {} + void EventListenerBase::reportInvalidTestSpec( StringRef ) {} void EventListenerBase::testRunStarting( TestRunInfo const& ) {} - void EventListenerBase::testGroupStarting( GroupInfo const& ) {} void EventListenerBase::testCaseStarting( TestCaseInfo const& ) {} + void EventListenerBase::testCasePartialStarting(TestCaseInfo const&, uint64_t) {} void EventListenerBase::sectionStarting( SectionInfo const& ) {} void EventListenerBase::sectionEnded( SectionStats const& ) {} + void EventListenerBase::testCasePartialEnded(TestCaseStats const&, uint64_t) {} void EventListenerBase::testCaseEnded( TestCaseStats const& ) {} - void EventListenerBase::testGroupEnded( TestGroupStats const& ) {} void EventListenerBase::testRunEnded( TestRunStats const& ) {} void EventListenerBase::skipTest( TestCaseInfo const& ) {} } // namespace Catch @@ -7052,14 +7941,51 @@ namespace Catch { + + +namespace Catch { + ReporterBase::ReporterBase( ReporterConfig&& config ): + IEventListener( config.fullConfig() ), + m_wrapped_stream( CATCH_MOVE(config).takeStream() ), + m_stream( m_wrapped_stream->stream() ), + m_colour( makeColourImpl( config.colourMode(), m_wrapped_stream.get() ) ), + m_customOptions( config.customOptions() ) + {} + + ReporterBase::~ReporterBase() = default; + + void ReporterBase::listReporters( + std::vector<ReporterDescription> const& descriptions ) { + defaultListReporters(m_stream, descriptions, m_config->verbosity()); + } + + void ReporterBase::listListeners( + std::vector<ListenerDescription> const& descriptions ) { + defaultListListeners( m_stream, descriptions ); + } + + void ReporterBase::listTests(std::vector<TestCaseHandle> const& tests) { + defaultListTests(m_stream, + m_colour.get(), + tests, + m_config->hasTestFilters(), + m_config->verbosity()); + } + + void ReporterBase::listTags(std::vector<TagInfo> const& tags) { + defaultListTags( m_stream, tags, m_config->hasTestFilters() ); + } + +} // namespace Catch + + + + #include <ostream> namespace { - // Colour::LightGrey - Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } - - Catch::StringRef bothOrAll( std::size_t count ) { + constexpr Catch::StringRef bothOrAll( std::uint64_t count ) { switch (count) { case 1: return Catch::StringRef{}; @@ -7076,6 +8002,9 @@ namespace { namespace Catch { namespace { + // Colour::LightGrey + static constexpr Colour::Code compactDimColour = Colour::FileName; + #ifdef CATCH_PLATFORM_MAC static constexpr Catch::StringRef compactFailedString = "FAILED"_sr; static constexpr Catch::StringRef compactPassedString = "PASSED"_sr; @@ -7090,35 +8019,33 @@ namespace { // - white: Passed [both/all] N test cases (no assertions). // - red: Failed N tests cases, failed M assertions. // - green: Passed [both/all] N tests cases with M assertions. -void printTotals(std::ostream& out, const Totals& totals) { +void printTotals(std::ostream& out, const Totals& totals, ColourImpl* colourImpl) { if (totals.testCases.total() == 0) { out << "No tests ran."; } else if (totals.testCases.failed == totals.testCases.total()) { - Colour colour(Colour::ResultError); + auto guard = colourImpl->guardColour( Colour::ResultError ).engage( out ); const StringRef qualify_assertions_failed = totals.assertions.failed == totals.assertions.total() ? bothOrAll(totals.assertions.failed) : StringRef{}; out << "Failed " << bothOrAll(totals.testCases.failed) - << pluralise(totals.testCases.failed, "test case") << ", " + << pluralise(totals.testCases.failed, "test case"_sr) << ", " "failed " << qualify_assertions_failed << - pluralise(totals.assertions.failed, "assertion") << '.'; + pluralise(totals.assertions.failed, "assertion"_sr) << '.'; } else if (totals.assertions.total() == 0) { out << "Passed " << bothOrAll(totals.testCases.total()) - << pluralise(totals.testCases.total(), "test case") + << pluralise(totals.testCases.total(), "test case"_sr) << " (no assertions)."; } else if (totals.assertions.failed) { - Colour colour(Colour::ResultError); - out << - "Failed " << pluralise(totals.testCases.failed, "test case") << ", " - "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; + out << colourImpl->guardColour( Colour::ResultError ) << + "Failed " << pluralise(totals.testCases.failed, "test case"_sr) << ", " + "failed " << pluralise(totals.assertions.failed, "assertion"_sr) << '.'; } else { - Colour colour(Colour::ResultSuccess); - out << + out << colourImpl->guardColour( Colour::ResultSuccess ) << "Passed " << bothOrAll(totals.testCases.passed) - << pluralise(totals.testCases.passed, "test case") << - " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + << pluralise(totals.testCases.passed, "test case"_sr) << + " with " << pluralise(totals.assertions.passed, "assertion"_sr) << '.'; } } @@ -7127,12 +8054,14 @@ class AssertionPrinter { public: AssertionPrinter& operator= (AssertionPrinter const&) = delete; AssertionPrinter(AssertionPrinter const&) = delete; - AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages, ColourImpl* colourImpl_) : stream(_stream) , result(_stats.assertionResult) , messages(_stats.infoMessages) , itMessage(_stats.infoMessages.begin()) - , printInfoMessages(_printInfoMessages) {} + , printInfoMessages(_printInfoMessages) + , colourImpl(colourImpl_) + {} void print() { printSourceInfo(); @@ -7204,16 +8133,13 @@ public: private: void printSourceInfo() const { - Colour colourGuard(Colour::FileName); - stream << result.getSourceInfo() << ':'; + stream << colourImpl->guardColour( Colour::FileName ) + << result.getSourceInfo() << ':'; } void printResultType(Colour::Code colour, StringRef passOrFail) const { if (!passOrFail.empty()) { - { - Colour colourGuard(colour); - stream << ' ' << passOrFail; - } + stream << colourImpl->guardColour(colour) << ' ' << passOrFail; stream << ':'; } } @@ -7226,8 +8152,7 @@ private: if (result.hasExpression()) { stream << ';'; { - Colour colour(dimColour()); - stream << " expression was:"; + stream << colourImpl->guardColour(compactDimColour) << " expression was:"; } printOriginalExpression(); } @@ -7241,10 +8166,7 @@ private: void printReconstructedExpression() const { if (result.hasExpandedExpression()) { - { - Colour colour(dimColour()); - stream << " for: "; - } + stream << colourImpl->guardColour(compactDimColour) << " for: "; stream << result.getExpandedExpression(); } } @@ -7256,25 +8178,22 @@ private: } } - void printRemainingMessages(Colour::Code colour = dimColour()) { + void printRemainingMessages(Colour::Code colour = compactDimColour) { if (itMessage == messages.end()) return; const auto itEnd = messages.cend(); const auto N = static_cast<std::size_t>(std::distance(itMessage, itEnd)); - { - Colour colourGuard(colour); - stream << " with " << pluralise(N, "message") << ':'; - } + stream << colourImpl->guardColour( colour ) << " with " + << pluralise( N, "message"_sr ) << ':'; while (itMessage != itEnd) { // If this assertion is a warning ignore any INFO messages if (printInfoMessages || itMessage->type != ResultWas::Info) { printMessage(); if (itMessage != itEnd) { - Colour colourGuard(dimColour()); - stream << " and"; + stream << colourImpl->guardColour(compactDimColour) << " and"; } continue; } @@ -7288,6 +8207,7 @@ private: std::vector<MessageInfo> messages; std::vector<MessageInfo>::const_iterator itMessage; bool printInfoMessages; + ColourImpl* colourImpl; }; } // anon namespace @@ -7296,13 +8216,21 @@ private: return "Reports test results on a single line, suitable for IDEs"; } - void CompactReporter::noMatchingTestCases( std::string const& spec ) { - stream << "No test cases matched '" << spec << '\'' << std::endl; + void CompactReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; } - void CompactReporter::assertionStarting( AssertionInfo const& ) {} + void CompactReporter::testRunStarting( TestRunInfo const& ) { + if ( m_config->testSpec().hasFilters() ) { + m_stream << m_colour->guardColour( Colour::BrightYellow ) + << "Filters: " + << serializeFilters( m_config->getTestsOrTags() ) + << '\n'; + } + m_stream << "RNG seed: " << m_config->rngSeed() << '\n'; + } - bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; @@ -7310,27 +8238,26 @@ private: // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) - return false; + return; printInfoMessages = false; } - AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + AssertionPrinter printer( m_stream, _assertionStats, printInfoMessages, m_colour.get() ); printer.print(); - stream << std::endl; - return true; + m_stream << '\n' << std::flush; } void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { double dur = _sectionStats.durationInSeconds; if ( shouldShowDuration( *m_config, dur ) ) { - stream << getFormattedDuration( dur ) << " s: " << _sectionStats.sectionInfo.name << std::endl; + m_stream << getFormattedDuration( dur ) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush; } } void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { - printTotals( stream, _testRunStats.totals ); - stream << '\n' << std::endl; + printTotals( m_stream, _testRunStats.totals, m_colour.get() ); + m_stream << "\n\n" << std::flush; StreamingReporterBase::testRunEnded( _testRunStats ); } @@ -7341,7 +8268,6 @@ private: -#include <cfloat> #include <cstdio> #if defined(_MSC_VER) @@ -7367,18 +8293,19 @@ class ConsoleAssertionPrinter { public: ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; - ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, ColourImpl* colourImpl_, bool _printInfoMessages) : stream(_stream), stats(_stats), result(_stats.assertionResult), colour(Colour::None), message(result.getMessage()), messages(_stats.infoMessages), + colourImpl(colourImpl_), printInfoMessages(_printInfoMessages) { switch (result.getResultType()) { case ResultWas::Ok: colour = Colour::Success; - passOrFail = "PASSED"; + passOrFail = "PASSED"_sr; //if( result.hasMessage() ) if (_stats.infoMessages.size() == 1) messageLabel = "with message"; @@ -7388,10 +8315,10 @@ public: case ResultWas::ExpressionFailed: if (result.isOk()) { colour = Colour::Success; - passOrFail = "FAILED - but was ok"; + passOrFail = "FAILED - but was ok"_sr; } else { colour = Colour::Error; - passOrFail = "FAILED"; + passOrFail = "FAILED"_sr; } if (_stats.infoMessages.size() == 1) messageLabel = "with message"; @@ -7400,7 +8327,7 @@ public: break; case ResultWas::ThrewException: colour = Colour::Error; - passOrFail = "FAILED"; + passOrFail = "FAILED"_sr; messageLabel = "due to unexpected exception with "; if (_stats.infoMessages.size() == 1) messageLabel += "message"; @@ -7409,12 +8336,12 @@ public: break; case ResultWas::FatalErrorCondition: colour = Colour::Error; - passOrFail = "FAILED"; + passOrFail = "FAILED"_sr; messageLabel = "due to a fatal error condition"; break; case ResultWas::DidntThrowException: colour = Colour::Error; - passOrFail = "FAILED"; + passOrFail = "FAILED"_sr; messageLabel = "because no exception was thrown where one was expected"; break; case ResultWas::Info: @@ -7424,7 +8351,7 @@ public: messageLabel = "warning"; break; case ResultWas::ExplicitFailure: - passOrFail = "FAILED"; + passOrFail = "FAILED"_sr; colour = Colour::Error; if (_stats.infoMessages.size() == 1) messageLabel = "explicitly with message"; @@ -7435,7 +8362,7 @@ public: case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: - passOrFail = "** internal error **"; + passOrFail = "** internal error **"_sr; colour = Colour::Error; break; } @@ -7456,23 +8383,22 @@ public: private: void printResultType() const { if (!passOrFail.empty()) { - Colour colourGuard(colour); - stream << passOrFail << ":\n"; + stream << colourImpl->guardColour(colour) << passOrFail << ":\n"; } } void printOriginalExpression() const { if (result.hasExpression()) { - Colour colourGuard(Colour::OriginalExpression); - stream << " "; - stream << result.getExpressionInMacro(); - stream << '\n'; + stream << colourImpl->guardColour( Colour::OriginalExpression ) + << " " << result.getExpressionInMacro() << '\n'; } } void printReconstructedExpression() const { if (result.hasExpandedExpression()) { stream << "with expansion:\n"; - Colour colourGuard(Colour::ReconstructedExpression); - stream << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n'; + stream << colourImpl->guardColour( Colour::ReconstructedExpression ) + << TextFlow::Column( result.getExpandedExpression() ) + .indent( 2 ) + << '\n'; } } void printMessage() const { @@ -7485,27 +8411,28 @@ private: } } void printSourceInfo() const { - Colour colourGuard(Colour::FileName); - stream << result.getSourceInfo() << ": "; + stream << colourImpl->guardColour( Colour::FileName ) + << result.getSourceInfo() << ": "; } std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; Colour::Code colour; - std::string passOrFail; + StringRef passOrFail; std::string messageLabel; std::string message; std::vector<MessageInfo> messages; + ColourImpl* colourImpl; bool printInfoMessages; }; -std::size_t makeRatio(std::size_t number, std::size_t total) { - std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; - return (ratio == 0 && number > 0) ? 1 : ratio; +std::size_t makeRatio( std::uint64_t number, std::uint64_t total ) { + const auto ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : static_cast<std::size_t>(ratio); } -std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { +std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { if (i > j && i > k) return i; else if (j > k) @@ -7518,7 +8445,7 @@ enum class Justification { Left, Right }; struct ColumnInfo { std::string name; - int width; + std::size_t width; Justification justification; }; struct ColumnBreak {}; @@ -7607,7 +8534,7 @@ class TablePrinter { public: TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos ) : m_os( os ), - m_columnInfos( std::move( columnInfos ) ) {} + m_columnInfos( CATCH_MOVE( columnInfos ) ) {} auto columnInfos() const -> std::vector<ColumnInfo> const& { return m_columnInfos; @@ -7621,7 +8548,8 @@ public: TextFlow::Columns headerCols; auto spacer = TextFlow::Spacer(2); for (auto const& info : m_columnInfos) { - headerCols += TextFlow::Column(info.name).width(static_cast<std::size_t>(info.width - 2)); + assert(info.width > 2); + headerCols += TextFlow::Column(info.name).width(info.width - 2); headerCols += spacer; } m_os << headerCols << '\n'; @@ -7632,7 +8560,7 @@ public: void close() { if (m_isOpen) { *this << RowBreak(); - m_os << std::endl; + m_os << '\n' << std::flush; m_isOpen = false; } } @@ -7655,7 +8583,7 @@ public: tp.m_currentColumn++; auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; - auto padding = (strSize + 1 < static_cast<std::size_t>(colInfo.width)) + auto padding = (strSize + 1 < colInfo.width) ? std::string(colInfo.width - (strSize + 1), ' ') : std::string(); if (colInfo.justification == Justification::Left) @@ -7674,9 +8602,9 @@ public: } }; -ConsoleReporter::ConsoleReporter(ReporterConfig const& config) - : StreamingReporterBase(config), - m_tablePrinter(new TablePrinter(config.stream(), +ConsoleReporter::ConsoleReporter(ReporterConfig&& config): + StreamingReporterBase( CATCH_MOVE( config ) ), + m_tablePrinter(Detail::make_unique<TablePrinter>(m_stream, [&config]() -> std::vector<ColumnInfo> { if (config.fullConfig()->benchmarkNoAnalysis()) { @@ -7703,31 +8631,30 @@ std::string ConsoleReporter::getDescription() { return "Reports test results as plain lines of text"; } -void ConsoleReporter::noMatchingTestCases(std::string const& spec) { - stream << "No test cases matched '" << spec << '\'' << std::endl; +void ConsoleReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; } -void ConsoleReporter::reportInvalidArguments(std::string const&arg){ - stream << "Invalid Filter: " << arg << std::endl; +void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) { + m_stream << "Invalid Filter: " << arg << '\n'; } void ConsoleReporter::assertionStarting(AssertionInfo const&) {} -bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { +void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { AssertionResult const& result = _assertionStats.assertionResult; bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); // Drop out if result was successful but we're not printing them. if (!includeResults && result.getResultType() != ResultWas::Warning) - return false; + return; lazyPrint(); - ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + ConsoleAssertionPrinter printer(m_stream, _assertionStats, m_colour.get(), includeResults); printer.print(); - stream << std::endl; - return true; + m_stream << '\n' << std::flush; } void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { @@ -7739,16 +8666,17 @@ void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { m_tablePrinter->close(); if (_sectionStats.missingAssertions) { lazyPrint(); - Colour colour(Colour::ResultError); + auto guard = + m_colour->guardColour( Colour::ResultError ).engage( m_stream ); if (m_sectionStack.size() > 1) - stream << "\nNo assertions in section"; + m_stream << "\nNo assertions in section"; else - stream << "\nNo assertions in test case"; - stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + m_stream << "\nNo assertions in test case"; + m_stream << " '" << _sectionStats.sectionInfo.name << "'\n\n" << std::flush; } double dur = _sectionStats.durationInSeconds; if (shouldShowDuration(*m_config, dur)) { - stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << std::endl; + m_stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush; } if (m_headerPrinted) { m_headerPrinted = false; @@ -7756,10 +8684,11 @@ void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { StreamingReporterBase::sectionEnded(_sectionStats); } -void ConsoleReporter::benchmarkPreparing(std::string const& name) { +void ConsoleReporter::benchmarkPreparing( StringRef name ) { lazyPrintWithoutClosingBenchmarkTable(); - auto nameCol = TextFlow::Column(name).width(static_cast<std::size_t>(m_tablePrinter->columnInfos()[0].width - 2)); + auto nameCol = TextFlow::Column( static_cast<std::string>( name ) ) + .width( m_tablePrinter->columnInfos()[0].width - 2 ); bool firstLine = true; for (auto line : nameCol) { @@ -7795,8 +8724,8 @@ void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) { } } -void ConsoleReporter::benchmarkFailed(std::string const& error) { - Colour colour(Colour::Red); +void ConsoleReporter::benchmarkFailed( StringRef error ) { + auto guard = m_colour->guardColour( Colour::Red ).engage( m_stream ); (*m_tablePrinter) << "Benchmark failed (" << error << ')' << ColumnBreak() << RowBreak(); @@ -7807,24 +8736,19 @@ void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { StreamingReporterBase::testCaseEnded(_testCaseStats); m_headerPrinted = false; } -void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { - if (currentGroupInfo.used) { - printSummaryDivider(); - stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; - printTotals(_testGroupStats.totals); - stream << '\n' << std::endl; - } - StreamingReporterBase::testGroupEnded(_testGroupStats); -} void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { printTotalsDivider(_testRunStats.totals); printTotals(_testRunStats.totals); - stream << std::endl; + m_stream << '\n' << std::flush; StreamingReporterBase::testRunEnded(_testRunStats); } void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) { StreamingReporterBase::testRunStarting(_testInfo); - printTestFilters(); + if ( m_config->testSpec().hasFilters() ) { + m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: " + << serializeFilters( m_config->getTestsOrTags() ) << '\n'; + } + m_stream << "Randomness seeded to: " << m_config->rngSeed() << '\n'; } void ConsoleReporter::lazyPrint() { @@ -7835,40 +8759,30 @@ void ConsoleReporter::lazyPrint() { void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { - if (!currentTestRunInfo.used) + if ( !m_testRunInfoPrinted ) { lazyPrintRunInfo(); - if (!currentGroupInfo.used) - lazyPrintGroupInfo(); - + } if (!m_headerPrinted) { printTestCaseAndSectionHeader(); m_headerPrinted = true; } } void ConsoleReporter::lazyPrintRunInfo() { - stream << '\n' << lineOfChars('~') << '\n'; - Colour colour(Colour::SecondaryText); - stream << currentTestRunInfo->name - << " is a Catch v" << libraryVersion() << " host application.\n" - << "Run with -? for options\n\n"; - - if (m_config->rngSeed() != 0) - stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; - - currentTestRunInfo.used = true; -} -void ConsoleReporter::lazyPrintGroupInfo() { - if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { - printClosedHeader("Group: " + currentGroupInfo->name); - currentGroupInfo.used = true; - } + m_stream << '\n' + << lineOfChars( '~' ) << '\n' + << m_colour->guardColour( Colour::SecondaryText ) + << currentTestRunInfo.name << " is a Catch2 v" << libraryVersion() + << " host application.\n" + << "Run with -? for options\n\n"; + + m_testRunInfoPrinted = true; } void ConsoleReporter::printTestCaseAndSectionHeader() { assert(!m_sectionStack.empty()); printOpenHeader(currentTestCaseInfo->name); if (m_sectionStack.size() > 1) { - Colour colourGuard(Colour::Headers); + auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); auto it = m_sectionStack.begin() + 1, // Skip first section (test case) @@ -7880,41 +8794,63 @@ void ConsoleReporter::printTestCaseAndSectionHeader() { SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; - stream << lineOfChars('-') << '\n'; - Colour colourGuard(Colour::FileName); - stream << lineInfo << '\n'; - stream << lineOfChars('.') << '\n' << std::endl; + m_stream << lineOfChars( '-' ) << '\n' + << m_colour->guardColour( Colour::FileName ) << lineInfo << '\n' + << lineOfChars( '.' ) << "\n\n" + << std::flush; } void ConsoleReporter::printClosedHeader(std::string const& _name) { printOpenHeader(_name); - stream << lineOfChars('.') << '\n'; + m_stream << lineOfChars('.') << '\n'; } void ConsoleReporter::printOpenHeader(std::string const& _name) { - stream << lineOfChars('-') << '\n'; + m_stream << lineOfChars('-') << '\n'; { - Colour colourGuard(Colour::Headers); + auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); printHeaderString(_name); } } -// if string has a : in first line will set indent to follow it on -// subsequent lines void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { - std::size_t i = _string.find(": "); - if (i != std::string::npos) - i += 2; - else - i = 0; - stream << TextFlow::Column(_string).indent(indent + i).initialIndent(indent) << '\n'; + // We want to get a bit fancy with line breaking here, so that subsequent + // lines start after ":" if one is present, e.g. + // ``` + // blablabla: Fancy + // linebreaking + // ``` + // but we also want to avoid problems with overly long indentation causing + // the text to take up too many lines, e.g. + // ``` + // blablabla: F + // a + // n + // c + // y + // . + // . + // . + // ``` + // So we limit the prefix indentation check to first quarter of the possible + // width + std::size_t idx = _string.find( ": " ); + if ( idx != std::string::npos && idx < CATCH_CONFIG_CONSOLE_WIDTH / 4 ) { + idx += 2; + } else { + idx = 0; + } + m_stream << TextFlow::Column( _string ) + .indent( indent + idx ) + .initialIndent( indent ) + << '\n'; } struct SummaryColumn { SummaryColumn( std::string _label, Colour::Code _colour ) - : label( std::move( _label ) ), + : label( CATCH_MOVE( _label ) ), colour( _colour ) {} - SummaryColumn addRow( std::size_t count ) { + SummaryColumn addRow( std::uint64_t count ) { ReusableStringStream rss; rss << count; std::string row = rss.str(); @@ -7936,12 +8872,14 @@ struct SummaryColumn { void ConsoleReporter::printTotals( Totals const& totals ) { if (totals.testCases.total() == 0) { - stream << Colour(Colour::Warning) << "No tests ran\n"; + m_stream << m_colour->guardColour( Colour::Warning ) + << "No tests ran\n"; } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { - stream << Colour(Colour::ResultSuccess) << "All tests passed"; - stream << " (" - << pluralise(totals.assertions.passed, "assertion") << " in " - << pluralise(totals.testCases.passed, "test case") << ')' + m_stream << m_colour->guardColour( Colour::ResultSuccess ) + << "All tests passed"; + m_stream << " (" + << pluralise(totals.assertions.passed, "assertion"_sr) << " in " + << pluralise(totals.testCases.passed, "test case"_sr) << ')' << '\n'; } else { @@ -7959,26 +8897,28 @@ void ConsoleReporter::printTotals( Totals const& totals ) { .addRow(totals.testCases.failedButOk) .addRow(totals.assertions.failedButOk)); - printSummaryRow("test cases", columns, 0); - printSummaryRow("assertions", columns, 1); + printSummaryRow("test cases"_sr, columns, 0); + printSummaryRow("assertions"_sr, columns, 1); } } -void ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) { +void ConsoleReporter::printSummaryRow(StringRef label, std::vector<SummaryColumn> const& cols, std::size_t row) { for (auto col : cols) { - std::string value = col.rows[row]; + std::string const& value = col.rows[row]; if (col.label.empty()) { - stream << label << ": "; - if (value != "0") - stream << value; - else - stream << Colour(Colour::Warning) << "- none -"; + m_stream << label << ": "; + if ( value != "0" ) { + m_stream << value; + } else { + m_stream << m_colour->guardColour( Colour::Warning ) + << "- none -"; + } } else if (value != "0") { - stream << Colour(Colour::LightGrey) << " | "; - stream << Colour(col.colour) - << value << ' ' << col.label; + m_stream << m_colour->guardColour( Colour::LightGrey ) << " | " + << m_colour->guardColour( col.colour ) << value << ' ' + << col.label; } } - stream << '\n'; + m_stream << '\n'; } void ConsoleReporter::printTotalsDivider(Totals const& totals) { @@ -7991,26 +8931,25 @@ void ConsoleReporter::printTotalsDivider(Totals const& totals) { while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) findMax(failedRatio, failedButOkRatio, passedRatio)--; - stream << Colour(Colour::Error) << std::string(failedRatio, '='); - stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); - if (totals.testCases.allPassed()) - stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); - else - stream << Colour(Colour::Success) << std::string(passedRatio, '='); + m_stream << m_colour->guardColour( Colour::Error ) + << std::string( failedRatio, '=' ) + << m_colour->guardColour( Colour::ResultExpectedFailure ) + << std::string( failedButOkRatio, '=' ); + if ( totals.testCases.allPassed() ) { + m_stream << m_colour->guardColour( Colour::ResultSuccess ) + << std::string( passedRatio, '=' ); + } else { + m_stream << m_colour->guardColour( Colour::Success ) + << std::string( passedRatio, '=' ); + } } else { - stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + m_stream << m_colour->guardColour( Colour::Warning ) + << std::string( CATCH_CONFIG_CONSOLE_WIDTH - 1, '=' ); } - stream << '\n'; + m_stream << '\n'; } void ConsoleReporter::printSummaryDivider() { - stream << lineOfChars('-') << '\n'; -} - -void ConsoleReporter::printTestFilters() { - if (m_config->testSpec().hasFilters()) { - Colour guard(Colour::BrightYellow); - stream << "Filters: " << serializeFilters(m_config->getTestsOrTags()) << '\n'; - } + m_stream << lineOfChars('-') << '\n'; } } // end namespace Catch @@ -8025,6 +8964,7 @@ void ConsoleReporter::printTestFilters() { + #include <algorithm> #include <cassert> @@ -8035,7 +8975,7 @@ namespace Catch { BySectionInfo( BySectionInfo const& other ): m_other( other.m_other ) {} bool operator()( - std::shared_ptr<CumulativeReporterBase::SectionNode> const& + Detail::unique_ptr<CumulativeReporterBase::SectionNode> const& node ) const { return ( ( node->stats.sectionInfo.name == m_other.name ) && @@ -8047,40 +8987,73 @@ namespace Catch { SectionInfo const& m_other; }; - void prepareExpandedExpression( AssertionResult& result ) { - result.getExpandedExpression(); - } } // namespace + namespace Detail { + AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( + AssertionStats const& assertion ): + m_assertion( assertion ) {} + + AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( + BenchmarkStats<> const& benchmark ): + m_benchmark( benchmark ) {} + + bool AssertionOrBenchmarkResult::isAssertion() const { + return m_assertion.some(); + } + bool AssertionOrBenchmarkResult::isBenchmark() const { + return m_benchmark.some(); + } + + AssertionStats const& AssertionOrBenchmarkResult::asAssertion() const { + assert(m_assertion.some()); + + return *m_assertion; + } + BenchmarkStats<> const& AssertionOrBenchmarkResult::asBenchmark() const { + assert(m_benchmark.some()); + + return *m_benchmark; + } + + } CumulativeReporterBase::~CumulativeReporterBase() = default; + void CumulativeReporterBase::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { + m_sectionStack.back()->assertionsAndBenchmarks.emplace_back(benchmarkStats); + } + void CumulativeReporterBase::sectionStarting( SectionInfo const& sectionInfo ) { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); - std::shared_ptr<SectionNode> node; + SectionNode* node; if ( m_sectionStack.empty() ) { - if ( !m_rootSection ) + if ( !m_rootSection ) { m_rootSection = - std::make_shared<SectionNode>( incompleteStats ); - node = m_rootSection; + Detail::make_unique<SectionNode>( incompleteStats ); + } + node = m_rootSection.get(); } else { SectionNode& parentNode = *m_sectionStack.back(); auto it = std::find_if( parentNode.childSections.begin(), parentNode.childSections.end(), BySectionInfo( sectionInfo ) ); if ( it == parentNode.childSections.end() ) { - node = std::make_shared<SectionNode>( incompleteStats ); - parentNode.childSections.push_back( node ); + auto newNode = + Detail::make_unique<SectionNode>( incompleteStats ); + node = newNode.get(); + parentNode.childSections.push_back( CATCH_MOVE( newNode ) ); } else { - node = *it; + node = it->get(); } } + + m_deepestSection = node; m_sectionStack.push_back( node ); - m_deepestSection = std::move( node ); } - bool CumulativeReporterBase::assertionEnded( + void CumulativeReporterBase::assertionEnded( AssertionStats const& assertionStats ) { assert( !m_sectionStack.empty() ); // AssertionResult holds a pointer to a temporary DecomposedExpression, @@ -8088,11 +9061,18 @@ namespace Catch { // Our section stack copy of the assertionResult will likely outlive the // temporary, so it must be expanded or discarded now to avoid calling // a destroyed object later. - prepareExpandedExpression( - const_cast<AssertionResult&>( assertionStats.assertionResult ) ); + if ( m_shouldStoreFailedAssertions && + !assertionStats.assertionResult.isOk() ) { + static_cast<void>( + assertionStats.assertionResult.getExpandedExpression() ); + } + if ( m_shouldStoreSuccesfulAssertions && + assertionStats.assertionResult.isOk() ) { + static_cast<void>( + assertionStats.assertionResult.getExpandedExpression() ); + } SectionNode& sectionNode = *m_sectionStack.back(); - sectionNode.assertions.push_back( assertionStats ); - return true; + sectionNode.assertionsAndBenchmarks.emplace_back( assertionStats ); } void CumulativeReporterBase::sectionEnded( SectionStats const& sectionStats ) { @@ -8104,31 +9084,33 @@ namespace Catch { void CumulativeReporterBase::testCaseEnded( TestCaseStats const& testCaseStats ) { - auto node = std::make_shared<TestCaseNode>( testCaseStats ); + auto node = Detail::make_unique<TestCaseNode>( testCaseStats ); assert( m_sectionStack.size() == 0 ); - node->children.push_back( m_rootSection ); - m_testCases.push_back( node ); - m_rootSection.reset(); + node->children.push_back( CATCH_MOVE(m_rootSection) ); + m_testCases.push_back( CATCH_MOVE(node) ); assert( m_deepestSection ); m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } - void CumulativeReporterBase::testGroupEnded( - TestGroupStats const& testGroupStats ) { - auto node = std::make_shared<TestGroupNode>( testGroupStats ); - node->children.swap( m_testCases ); - m_testGroups.push_back( node ); - } void CumulativeReporterBase::testRunEnded( TestRunStats const& testRunStats ) { - auto node = std::make_shared<TestRunNode>( testRunStats ); - node->children.swap( m_testGroups ); - m_testRuns.push_back( node ); + assert(!m_testRun && "CumulativeReporterBase assumes there can only be one test run"); + m_testRun = Detail::make_unique<TestRunNode>( testRunStats ); + m_testRun->children.swap( m_testCases ); testRunEndedCumulative(); } + bool CumulativeReporterBase::SectionNode::hasAnyAssertions() const { + return std::any_of( + assertionsAndBenchmarks.begin(), + assertionsAndBenchmarks.end(), + []( Detail::AssertionOrBenchmarkResult const& res ) { + return res.isAssertion(); + } ); + } + } // end namespace Catch @@ -8137,34 +9119,29 @@ namespace Catch { #include <cassert> #include <ctime> #include <algorithm> +#include <iomanip> namespace Catch { namespace { std::string getCurrentTimestamp() { - // Beware, this is not reentrant because of backward compatibility issues - // Also, UTC only, again because of backward compatibility (%z is C++11) time_t rawtime; std::time(&rawtime); - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); -#ifdef _MSC_VER std::tm timeInfo = {}; +#if defined (_MSC_VER) || defined (__MINGW32__) gmtime_s(&timeInfo, &rawtime); #else - std::tm* timeInfo; - timeInfo = std::gmtime(&rawtime); + gmtime_r(&rawtime, &timeInfo); #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); + + return std::string(timeStamp, timeStampSize - 1); } std::string fileNameTag(std::vector<Tag> const& tags) { @@ -8180,45 +9157,49 @@ namespace Catch { } return std::string(); } + + // Formats the duration in seconds to 3 decimal places. + // This is done because some genius defined Maven Surefire schema + // in a way that only accepts 3 decimal places, and tools like + // Jenkins use that schema for validation JUnit reporter output. + std::string formatDuration( double seconds ) { + ReusableStringStream rss; + rss << std::fixed << std::setprecision( 3 ) << seconds; + return rss.str(); + } + } // anonymous namespace - JunitReporter::JunitReporter( ReporterConfig const& _config ) - : CumulativeReporterBase( _config ), - xml( _config.stream() ) + JunitReporter::JunitReporter( ReporterConfig&& _config ) + : CumulativeReporterBase( CATCH_MOVE(_config) ), + xml( m_stream ) { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = true; + m_shouldStoreSuccesfulAssertions = false; } - JunitReporter::~JunitReporter() {} - std::string JunitReporter::getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } - void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} - void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); - } - - void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { suiteTimer.start(); stdOutForSuite.clear(); stdErrForSuite.clear(); unexpectedExceptions = 0; - CumulativeReporterBase::testGroupStarting( groupInfo ); } void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { m_okToFail = testCaseInfo.okToFail(); } - bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + void JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) unexpectedExceptions++; - return CumulativeReporterBase::assertionEnded( assertionStats ); + CumulativeReporterBase::assertionEnded( assertionStats ); } void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { @@ -8227,48 +9208,42 @@ namespace Catch { CumulativeReporterBase::testCaseEnded( testCaseStats ); } - void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { - double suiteTime = suiteTimer.getElapsedSeconds(); - CumulativeReporterBase::testGroupEnded( testGroupStats ); - writeGroup( *m_testGroups.back(), suiteTime ); - } - void JunitReporter::testRunEndedCumulative() { + const auto suiteTime = suiteTimer.getElapsedSeconds(); + writeRun( *m_testRun, suiteTime ); xml.endElement(); } - void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + void JunitReporter::writeRun( TestRunNode const& testRunNode, double suiteTime ) { XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); - TestGroupStats const& stats = groupNode.value; - xml.writeAttribute( "name", stats.groupInfo.name ); - xml.writeAttribute( "errors", unexpectedExceptions ); - xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); - xml.writeAttribute( "tests", stats.totals.assertions.total() ); - xml.writeAttribute( "hostname", "tbd" ); // !TBD + TestRunStats const& stats = testRunNode.value; + xml.writeAttribute( "name"_sr, stats.runInfo.name ); + xml.writeAttribute( "errors"_sr, unexpectedExceptions ); + xml.writeAttribute( "failures"_sr, stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests"_sr, stats.totals.assertions.total() ); + xml.writeAttribute( "hostname"_sr, "tbd"_sr ); // !TBD if( m_config->showDurations() == ShowDurations::Never ) - xml.writeAttribute( "time", "" ); + xml.writeAttribute( "time"_sr, ""_sr ); else - xml.writeAttribute( "time", suiteTime ); - xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + xml.writeAttribute( "time"_sr, formatDuration( suiteTime ) ); + xml.writeAttribute( "timestamp"_sr, getCurrentTimestamp() ); - // Write properties if there are any - if (m_config->hasTestFilters() || m_config->rngSeed() != 0) { + // Write properties + { auto properties = xml.scopedElement("properties"); + xml.scopedElement("property") + .writeAttribute("name"_sr, "random-seed"_sr) + .writeAttribute("value"_sr, m_config->rngSeed()); if (m_config->hasTestFilters()) { xml.scopedElement("property") - .writeAttribute("name", "filters") - .writeAttribute("value", serializeFilters(m_config->getTestsOrTags())); - } - if (m_config->rngSeed() != 0) { - xml.scopedElement("property") - .writeAttribute("name", "random-seed") - .writeAttribute("value", m_config->rngSeed()); + .writeAttribute("name"_sr, "filters"_sr) + .writeAttribute("value"_sr, serializeFilters(m_config->getTestsOrTags())); } } // Write test cases - for( auto const& child : groupNode.children ) + for( auto const& child : testRunNode.children ) writeTestCase( *child ); xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline ); @@ -8283,48 +9258,57 @@ namespace Catch { assert( testCaseNode.children.size() == 1 ); SectionNode const& rootSection = *testCaseNode.children.front(); - std::string className = stats.testInfo->className; + std::string className = + static_cast<std::string>( stats.testInfo->className ); if( className.empty() ) { className = fileNameTag(stats.testInfo->tags); - if ( className.empty() ) + if ( className.empty() ) { className = "global"; + } } if ( !m_config->name().empty() ) - className = m_config->name() + "." + className; + className = static_cast<std::string>(m_config->name()) + '.' + className; - writeSection( className, "", rootSection ); + writeSection( className, "", rootSection, stats.testInfo->okToFail() ); } - void JunitReporter::writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode ) { + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) name = rootName + '/' + name; - if( !sectionNode.assertions.empty() || - !sectionNode.stdOut.empty() || - !sectionNode.stdErr.empty() ) { + if( sectionNode.hasAnyAssertions() + || !sectionNode.stdOut.empty() + || !sectionNode.stdErr.empty() ) { XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); if( className.empty() ) { - xml.writeAttribute( "classname", name ); - xml.writeAttribute( "name", "root" ); + xml.writeAttribute( "classname"_sr, name ); + xml.writeAttribute( "name"_sr, "root"_sr ); } else { - xml.writeAttribute( "classname", className ); - xml.writeAttribute( "name", name ); + xml.writeAttribute( "classname"_sr, className ); + xml.writeAttribute( "name"_sr, name ); } - xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + xml.writeAttribute( "time"_sr, formatDuration( sectionNode.stats.durationInSeconds ) ); // This is not ideal, but it should be enough to mimic gtest's // junit output. // Ideally the JUnit reporter would also handle `skipTest` // events and write those out appropriately. - xml.writeAttribute( "status", "run" ); + xml.writeAttribute( "status"_sr, "run"_sr ); + + if (sectionNode.stats.assertions.failedButOk) { + xml.scopedElement("skipped") + .writeAttribute("message", "TEST_CASE tagged with !mayfail"); + } writeAssertions( sectionNode ); + if( !sectionNode.stdOut.empty() ) xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline ); if( !sectionNode.stdErr.empty() ) @@ -8332,14 +9316,17 @@ namespace Catch { } for( auto const& childNode : sectionNode.childSections ) if( className.empty() ) - writeSection( name, "", *childNode ); + writeSection( name, "", *childNode, testOkToFail ); else - writeSection( className, name, *childNode ); + writeSection( className, name, *childNode, testOkToFail ); } void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { - for( auto const& assertion : sectionNode.assertions ) - writeAssertion( assertion ); + for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { + if (assertionOrBenchmark.isAssertion()) { + writeAssertion(assertionOrBenchmark.asAssertion()); + } + } } void JunitReporter::writeAssertion( AssertionStats const& stats ) { @@ -8370,8 +9357,8 @@ namespace Catch { XmlWriter::ScopedElement e = xml.scopedElement( elementName ); - xml.writeAttribute( "message", result.getExpression() ); - xml.writeAttribute( "type", result.getTestMacroName() ); + xml.writeAttribute( "message"_sr, result.getExpression() ); + xml.writeAttribute( "type"_sr, result.getTestMacroName() ); ReusableStringStream rss; if (stats.totals.assertions.total() > 0) { @@ -8404,164 +9391,189 @@ namespace Catch { -#include <cassert> + +#include <ostream> namespace Catch { + void MultiReporter::updatePreferences(IEventListener const& reporterish) { + m_preferences.shouldRedirectStdOut |= + reporterish.getPreferences().shouldRedirectStdOut; + m_preferences.shouldReportAllAssertions |= + reporterish.getPreferences().shouldReportAllAssertions; + } - ListeningReporter::ListeningReporter() { - // We will assume that listeners will always want all assertions - m_preferences.shouldReportAllAssertions = true; + void MultiReporter::addListener( IEventListenerPtr&& listener ) { + updatePreferences(*listener); + m_reporterLikes.insert(m_reporterLikes.begin() + m_insertedListeners, CATCH_MOVE(listener) ); + ++m_insertedListeners; } - void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { - m_listeners.push_back( std::move( listener ) ); + void MultiReporter::addReporter( IEventListenerPtr&& reporter ) { + updatePreferences(*reporter); + + // We will need to output the captured stdout if there are reporters + // that do not want it captured. + // We do not consider listeners, because it is generally assumed that + // listeners are output-transparent, even though they can ask for stdout + // capture to do something with it. + m_haveNoncapturingReporters |= !reporter->getPreferences().shouldRedirectStdOut; + + // Reporters can always be placed to the back without breaking the + // reporting order + m_reporterLikes.push_back( CATCH_MOVE( reporter ) ); } - void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { - assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); - m_reporter = std::move( reporter ); - m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; + void MultiReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->noMatchingTestCases( unmatchedSpec ); + } } - void ListeningReporter::noMatchingTestCases( std::string const& spec ) { - for ( auto const& listener : m_listeners ) { - listener->noMatchingTestCases( spec ); + void MultiReporter::fatalErrorEncountered( StringRef error ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->fatalErrorEncountered( error ); } - m_reporter->noMatchingTestCases( spec ); } - void ListeningReporter::reportInvalidArguments(std::string const&arg){ - for ( auto const& listener : m_listeners ) { - listener->reportInvalidArguments( arg ); + void MultiReporter::reportInvalidTestSpec( StringRef arg ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->reportInvalidTestSpec( arg ); } - m_reporter->reportInvalidArguments( arg ); } - void ListeningReporter::benchmarkPreparing( std::string const& name ) { - for (auto const& listener : m_listeners) { - listener->benchmarkPreparing(name); + void MultiReporter::benchmarkPreparing( StringRef name ) { + for (auto& reporterish : m_reporterLikes) { + reporterish->benchmarkPreparing(name); } - m_reporter->benchmarkPreparing(name); } - void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { - for ( auto const& listener : m_listeners ) { - listener->benchmarkStarting( benchmarkInfo ); + void MultiReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->benchmarkStarting( benchmarkInfo ); } - m_reporter->benchmarkStarting( benchmarkInfo ); } - void ListeningReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) { - for ( auto const& listener : m_listeners ) { - listener->benchmarkEnded( benchmarkStats ); + void MultiReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->benchmarkEnded( benchmarkStats ); } - m_reporter->benchmarkEnded( benchmarkStats ); } - void ListeningReporter::benchmarkFailed( std::string const& error ) { - for (auto const& listener : m_listeners) { - listener->benchmarkFailed(error); + void MultiReporter::benchmarkFailed( StringRef error ) { + for (auto& reporterish : m_reporterLikes) { + reporterish->benchmarkFailed(error); } - m_reporter->benchmarkFailed(error); } - void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { - for ( auto const& listener : m_listeners ) { - listener->testRunStarting( testRunInfo ); + void MultiReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testRunStarting( testRunInfo ); } - m_reporter->testRunStarting( testRunInfo ); } - void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { - for ( auto const& listener : m_listeners ) { - listener->testGroupStarting( groupInfo ); + void MultiReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCaseStarting( testInfo ); } - m_reporter->testGroupStarting( groupInfo ); } - - void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { - for ( auto const& listener : m_listeners ) { - listener->testCaseStarting( testInfo ); + void + MultiReporter::testCasePartialStarting( TestCaseInfo const& testInfo, + uint64_t partNumber ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCasePartialStarting( testInfo, partNumber ); } - m_reporter->testCaseStarting( testInfo ); } - void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { - for ( auto const& listener : m_listeners ) { - listener->sectionStarting( sectionInfo ); + void MultiReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->sectionStarting( sectionInfo ); } - m_reporter->sectionStarting( sectionInfo ); } - void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { - for ( auto const& listener : m_listeners ) { - listener->assertionStarting( assertionInfo ); + void MultiReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->assertionStarting( assertionInfo ); } - m_reporter->assertionStarting( assertionInfo ); } // The return value indicates if the messages buffer should be cleared: - bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { - for( auto const& listener : m_listeners ) { - static_cast<void>( listener->assertionEnded( assertionStats ) ); + void MultiReporter::assertionEnded( AssertionStats const& assertionStats ) { + const bool reportByDefault = + assertionStats.assertionResult.getResultType() != ResultWas::Ok || + m_config->includeSuccessfulResults(); + + for ( auto & reporterish : m_reporterLikes ) { + if ( reportByDefault || + reporterish->getPreferences().shouldReportAllAssertions ) { + reporterish->assertionEnded( assertionStats ); + } } - return m_reporter->assertionEnded( assertionStats ); } - void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { - for ( auto const& listener : m_listeners ) { - listener->sectionEnded( sectionStats ); + void MultiReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->sectionEnded( sectionStats ); } - m_reporter->sectionEnded( sectionStats ); } - void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { - for ( auto const& listener : m_listeners ) { - listener->testCaseEnded( testCaseStats ); + void MultiReporter::testCasePartialEnded( TestCaseStats const& testStats, + uint64_t partNumber ) { + if ( m_preferences.shouldRedirectStdOut && + m_haveNoncapturingReporters ) { + if ( !testStats.stdOut.empty() ) { + Catch::cout() << testStats.stdOut << std::flush; + } + if ( !testStats.stdErr.empty() ) { + Catch::cerr() << testStats.stdErr << std::flush; + } + } + + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCasePartialEnded( testStats, partNumber ); } - m_reporter->testCaseEnded( testCaseStats ); } - void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { - for ( auto const& listener : m_listeners ) { - listener->testGroupEnded( testGroupStats ); + void MultiReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCaseEnded( testCaseStats ); } - m_reporter->testGroupEnded( testGroupStats ); } - void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { - for ( auto const& listener : m_listeners ) { - listener->testRunEnded( testRunStats ); + void MultiReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testRunEnded( testRunStats ); } - m_reporter->testRunEnded( testRunStats ); } - void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { - for ( auto const& listener : m_listeners ) { - listener->skipTest( testInfo ); + void MultiReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->skipTest( testInfo ); } - m_reporter->skipTest( testInfo ); } - void ListeningReporter::listReporters(std::vector<ReporterDescription> const& descriptions, IConfig const& config) { - for (auto const& listener : m_listeners) { - listener->listReporters(descriptions, config); + void MultiReporter::listReporters(std::vector<ReporterDescription> const& descriptions) { + for (auto& reporterish : m_reporterLikes) { + reporterish->listReporters(descriptions); } - m_reporter->listReporters(descriptions, config); } - void ListeningReporter::listTests(std::vector<TestCaseHandle> const& tests, IConfig const& config) { - for (auto const& listener : m_listeners) { - listener->listTests(tests, config); + void MultiReporter::listListeners( + std::vector<ListenerDescription> const& descriptions ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->listListeners( descriptions ); } - m_reporter->listTests(tests, config); } - void ListeningReporter::listTags(std::vector<TagInfo> const& tags, IConfig const& config) { - for (auto const& listener : m_listeners) { - listener->listTags(tags, config); + void MultiReporter::listTests(std::vector<TestCaseHandle> const& tests) { + for (auto& reporterish : m_reporterLikes) { + reporterish->listTests(tests); + } + } + + void MultiReporter::listTags(std::vector<TagInfo> const& tags) { + for (auto& reporterish : m_reporterLikes) { + reporterish->listTags(tags); } - m_reporter->listTags(tags, config); } } // end namespace Catch @@ -8569,35 +9581,65 @@ namespace Catch { + +namespace Catch { + namespace Detail { + + void registerReporterImpl( std::string const& name, + IReporterFactoryPtr reporterPtr ) { + CATCH_TRY { + getMutableRegistryHub().registerReporter( + name, CATCH_MOVE( reporterPtr ) ); + } + CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead + // register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + } // namespace Detail +} // namespace Catch + + + + #include <map> namespace Catch { - SonarQubeReporter::~SonarQubeReporter() {} + namespace { + std::string createRngSeedString(uint32_t seed) { + ReusableStringStream sstr; + sstr << "rng-seed=" << seed; + return sstr.str(); + } + } void SonarQubeReporter::testRunStarting(TestRunInfo const& testRunInfo) { CumulativeReporterBase::testRunStarting(testRunInfo); + + xml.writeComment( createRngSeedString( m_config->rngSeed() ) ); xml.startElement("testExecutions"); - xml.writeAttribute("version", '1'); + xml.writeAttribute("version"_sr, '1'); } - void SonarQubeReporter::testGroupEnded(TestGroupStats const& testGroupStats) { - CumulativeReporterBase::testGroupEnded(testGroupStats); - writeGroup(*m_testGroups.back()); - } + void SonarQubeReporter::writeRun( TestRunNode const& runNode ) { + std::map<std::string, std::vector<TestCaseNode const*>> testsPerFile; - void SonarQubeReporter::writeGroup(TestGroupNode const& groupNode) { - std::map<std::string, TestGroupNode::ChildNodes> testsPerFile; - for (auto const& child : groupNode.children) - testsPerFile[child->value.testInfo->lineInfo.file].push_back(child); + for ( auto const& child : runNode.children ) { + testsPerFile[child->value.testInfo->lineInfo.file].push_back( + child.get() ); + } - for (auto const& kv : testsPerFile) - writeTestFile(kv.first, kv.second); + for ( auto const& kv : testsPerFile ) { + writeTestFile( kv.first, kv.second ); + } } - void SonarQubeReporter::writeTestFile(std::string const& filename, TestGroupNode::ChildNodes const& testCaseNodes) { + void SonarQubeReporter::writeTestFile(std::string const& filename, std::vector<TestCaseNode const*> const& testCaseNodes) { XmlWriter::ScopedElement e = xml.scopedElement("file"); - xml.writeAttribute("path", filename); + xml.writeAttribute("path"_sr, filename); for (auto const& child : testCaseNodes) writeTestCase(*child); @@ -8616,10 +9658,12 @@ namespace Catch { if (!rootName.empty()) name = rootName + '/' + name; - if (!sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty()) { + if ( sectionNode.hasAnyAssertions() + || !sectionNode.stdOut.empty() + || !sectionNode.stdErr.empty() ) { XmlWriter::ScopedElement e = xml.scopedElement("testCase"); - xml.writeAttribute("name", name); - xml.writeAttribute("duration", static_cast<long>(sectionNode.stats.durationInSeconds * 1000)); + xml.writeAttribute("name"_sr, name); + xml.writeAttribute("duration"_sr, static_cast<long>(sectionNode.stats.durationInSeconds * 1000)); writeAssertions(sectionNode, okToFail); } @@ -8629,8 +9673,11 @@ namespace Catch { } void SonarQubeReporter::writeAssertions(SectionNode const& sectionNode, bool okToFail) { - for (auto const& assertion : sectionNode.assertions) - writeAssertion(assertion, okToFail); + for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { + if (assertionOrBenchmark.isAssertion()) { + writeAssertion(assertionOrBenchmark.asAssertion(), okToFail); + } + } } void SonarQubeReporter::writeAssertion(AssertionStats const& stats, bool okToFail) { @@ -8670,26 +9717,26 @@ namespace Catch { XmlWriter::ScopedElement e = xml.scopedElement(elementName); ReusableStringStream messageRss; - messageRss << result.getTestMacroName() << "(" << result.getExpression() << ")"; - xml.writeAttribute("message", messageRss.str()); + messageRss << result.getTestMacroName() << '(' << result.getExpression() << ')'; + xml.writeAttribute("message"_sr, messageRss.str()); ReusableStringStream textRss; if (stats.totals.assertions.total() > 0) { textRss << "FAILED:\n"; if (result.hasExpression()) { - textRss << "\t" << result.getExpressionInMacro() << "\n"; + textRss << '\t' << result.getExpressionInMacro() << '\n'; } if (result.hasExpandedExpression()) { - textRss << "with expansion:\n\t" << result.getExpandedExpression() << "\n"; + textRss << "with expansion:\n\t" << result.getExpandedExpression() << '\n'; } } if (!result.getMessage().empty()) - textRss << result.getMessage() << "\n"; + textRss << result.getMessage() << '\n'; for (auto const& msg : stats.infoMessages) if (msg.type == ResultWas::Info) - textRss << msg.message << "\n"; + textRss << msg.message << '\n'; textRss << "at " << result.getSourceInfo(); xml.writeText(textRss.str(), XmlFormatting::Newline); @@ -8709,19 +9756,8 @@ namespace Catch { currentTestRunInfo = _testRunInfo; } - void - StreamingReporterBase::testGroupStarting( GroupInfo const& _groupInfo ) { - currentGroupInfo = _groupInfo; - } - - void StreamingReporterBase::testGroupEnded( TestGroupStats const& ) { - currentGroupInfo.reset(); - } - void StreamingReporterBase::testRunEnded( TestRunStats const& ) { currentTestCaseInfo = nullptr; - currentGroupInfo.reset(); - currentTestRunInfo.reset(); } } // end namespace Catch @@ -8729,6 +9765,7 @@ namespace Catch { #include <algorithm> +#include <iterator> #include <ostream> namespace Catch { @@ -8738,18 +9775,20 @@ namespace Catch { // Making older compiler happy is hard. static constexpr StringRef tapFailedString = "not ok"_sr; static constexpr StringRef tapPassedString = "ok"_sr; + static constexpr Colour::Code tapDimColour = Colour::FileName; class TapAssertionPrinter { public: TapAssertionPrinter& operator= (TapAssertionPrinter const&) = delete; TapAssertionPrinter(TapAssertionPrinter const&) = delete; - TapAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter) + TapAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter, ColourImpl* colour_) : stream(_stream) , result(_stats.assertionResult) , messages(_stats.infoMessages) , itMessage(_stats.infoMessages.begin()) , printInfoMessages(true) - , counter(_counter) {} + , counter(_counter) + , colourImpl( colour_ ) {} void print() { itMessage = messages.begin(); @@ -8822,13 +9861,6 @@ namespace Catch { } private: - static Colour::Code dimColour() { return Colour::FileName; } - - void printSourceInfo() const { - Colour colourGuard(dimColour()); - stream << result.getSourceInfo() << ':'; - } - void printResultType(StringRef passOrFail) const { if (!passOrFail.empty()) { stream << passOrFail << ' ' << counter << " -"; @@ -8842,10 +9874,8 @@ namespace Catch { void printExpressionWas() { if (result.hasExpression()) { stream << ';'; - { - Colour colour(dimColour()); - stream << " expression was:"; - } + stream << colourImpl->guardColour( tapDimColour ) + << " expression was:"; printOriginalExpression(); } } @@ -8858,10 +9888,8 @@ namespace Catch { void printReconstructedExpression() const { if (result.hasExpandedExpression()) { - { - Colour colour(dimColour()); - stream << " for: "; - } + stream << colourImpl->guardColour( tapDimColour ) << " for: "; + std::string expr = result.getExpandedExpression(); std::replace(expr.begin(), expr.end(), '\n', ' '); stream << expr; @@ -8875,7 +9903,7 @@ namespace Catch { } } - void printRemainingMessages(Colour::Code colour = dimColour()) { + void printRemainingMessages(Colour::Code colour = tapDimColour) { if (itMessage == messages.end()) { return; } @@ -8884,18 +9912,15 @@ namespace Catch { std::vector<MessageInfo>::const_iterator itEnd = messages.end(); const std::size_t N = static_cast<std::size_t>(std::distance(itMessage, itEnd)); - { - Colour colourGuard(colour); - stream << " with " << pluralise(N, "message") << ':'; - } + stream << colourImpl->guardColour( colour ) << " with " + << pluralise( N, "message"_sr ) << ':'; for (; itMessage != itEnd; ) { // If this assertion is a warning ignore any INFO messages if (printInfoMessages || itMessage->type != ResultWas::Info) { stream << " '" << itMessage->message << '\''; if (++itMessage != itEnd) { - Colour colourGuard(dimColour()); - stream << " and"; + stream << colourImpl->guardColour(tapDimColour) << " and"; } } } @@ -8908,33 +9933,35 @@ namespace Catch { std::vector<MessageInfo>::const_iterator itMessage; bool printInfoMessages; std::size_t counter; + ColourImpl* colourImpl; }; } // End anonymous namespace - TAPReporter::~TAPReporter() {} + void TAPReporter::testRunStarting( TestRunInfo const& ) { + m_stream << "# rng-seed: " << m_config->rngSeed() << '\n'; + } - void TAPReporter::noMatchingTestCases(std::string const& spec) { - stream << "# No test cases matched '" << spec << "'\n"; + void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + m_stream << "# No test cases matched '" << unmatchedSpec << "'\n"; } - bool TAPReporter::assertionEnded(AssertionStats const& _assertionStats) { + void TAPReporter::assertionEnded(AssertionStats const& _assertionStats) { ++counter; - stream << "# " << currentTestCaseInfo->name << '\n'; - TapAssertionPrinter printer(stream, _assertionStats, counter); + m_stream << "# " << currentTestCaseInfo->name << '\n'; + TapAssertionPrinter printer(m_stream, _assertionStats, counter, m_colour.get()); printer.print(); - stream << '\n' << std::flush; - return true; + m_stream << '\n' << std::flush; } void TAPReporter::testRunEnded(TestRunStats const& _testRunStats) { - stream << "1.." << _testRunStats.totals.assertions.total(); + m_stream << "1.." << _testRunStats.totals.assertions.total(); if (_testRunStats.totals.testCases.total() == 0) { - stream << " # Skipped: No tests ran."; + m_stream << " # Skipped: No tests ran."; } - stream << "\n\n" << std::flush; + m_stream << "\n\n" << std::flush; StreamingReporterBase::testRunEnded(_testRunStats); } @@ -8947,6 +9974,7 @@ namespace Catch { #include <cassert> +#include <ostream> namespace Catch { @@ -8964,8 +9992,8 @@ namespace Catch { .initialIndent(indent) << '\n'; } - std::string escape(std::string const& str) { - std::string escaped = str; + std::string escape(StringRef str) { + std::string escaped = static_cast<std::string>(str); replaceInPlace(escaped, "|", "||"); replaceInPlace(escaped, "'", "|'"); replaceInPlace(escaped, "\n", "|n"); @@ -8979,19 +10007,17 @@ namespace Catch { TeamCityReporter::~TeamCityReporter() {} - void TeamCityReporter::testGroupStarting(GroupInfo const& groupInfo) { - StreamingReporterBase::testGroupStarting(groupInfo); - stream << "##teamcity[testSuiteStarted name='" - << escape(groupInfo.name) << "']\n"; + void TeamCityReporter::testRunStarting( TestRunInfo const& runInfo ) { + m_stream << "##teamcity[testSuiteStarted name='" << escape( runInfo.name ) + << "']\n"; } - void TeamCityReporter::testGroupEnded(TestGroupStats const& testGroupStats) { - StreamingReporterBase::testGroupEnded(testGroupStats); - stream << "##teamcity[testSuiteFinished name='" - << escape(testGroupStats.groupInfo.name) << "']\n"; + void TeamCityReporter::testRunEnded( TestRunStats const& runStats ) { + m_stream << "##teamcity[testSuiteFinished name='" + << escape( runStats.runInfo.name ) << "']\n"; } - bool TeamCityReporter::assertionEnded(AssertionStats const& assertionStats) { + void TeamCityReporter::assertionEnded(AssertionStats const& assertionStats) { AssertionResult const& result = assertionStats.assertionResult; if (!result.isOk()) { @@ -9047,44 +10073,43 @@ namespace Catch { if (currentTestCaseInfo->okToFail()) { msg << "- failure ignore as test marked as 'ok to fail'\n"; - stream << "##teamcity[testIgnored" + m_stream << "##teamcity[testIgnored" << " name='" << escape(currentTestCaseInfo->name) << '\'' << " message='" << escape(msg.str()) << '\'' << "]\n"; } else { - stream << "##teamcity[testFailed" + m_stream << "##teamcity[testFailed" << " name='" << escape(currentTestCaseInfo->name) << '\'' << " message='" << escape(msg.str()) << '\'' << "]\n"; } } - stream.flush(); - return true; + m_stream.flush(); } void TeamCityReporter::testCaseStarting(TestCaseInfo const& testInfo) { m_testTimer.start(); StreamingReporterBase::testCaseStarting(testInfo); - stream << "##teamcity[testStarted name='" + m_stream << "##teamcity[testStarted name='" << escape(testInfo.name) << "']\n"; - stream.flush(); + m_stream.flush(); } void TeamCityReporter::testCaseEnded(TestCaseStats const& testCaseStats) { StreamingReporterBase::testCaseEnded(testCaseStats); auto const& testCaseInfo = *testCaseStats.testInfo; if (!testCaseStats.stdOut.empty()) - stream << "##teamcity[testStdOut name='" + m_stream << "##teamcity[testStdOut name='" << escape(testCaseInfo.name) << "' out='" << escape(testCaseStats.stdOut) << "']\n"; if (!testCaseStats.stdErr.empty()) - stream << "##teamcity[testStdErr name='" + m_stream << "##teamcity[testStdErr name='" << escape(testCaseInfo.name) << "' out='" << escape(testCaseStats.stdErr) << "']\n"; - stream << "##teamcity[testFinished name='" + m_stream << "##teamcity[testFinished name='" << escape(testCaseInfo.name) << "' duration='" << m_testTimer.getElapsedMilliseconds() << "']\n"; - stream.flush(); + m_stream.flush(); } void TeamCityReporter::printSectionHeader(std::ostream& os) { @@ -9120,9 +10145,9 @@ namespace Catch { #endif namespace Catch { - XmlReporter::XmlReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ), - m_xml(_config.stream()) + XmlReporter::XmlReporter( ReporterConfig&& _config ) + : StreamingReporterBase( CATCH_MOVE(_config) ), + m_xml(m_stream) { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = true; @@ -9140,12 +10165,8 @@ namespace Catch { void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { m_xml - .writeAttribute( "filename", sourceInfo.file ) - .writeAttribute( "line", sourceInfo.line ); - } - - void XmlReporter::noMatchingTestCases( std::string const& s ) { - StreamingReporterBase::noMatchingTestCases( s ); + .writeAttribute( "filename"_sr, sourceInfo.file ) + .writeAttribute( "line"_sr, sourceInfo.line ); } void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { @@ -9153,27 +10174,19 @@ namespace Catch { std::string stylesheetRef = getStylesheetRef(); if( !stylesheetRef.empty() ) m_xml.writeStylesheetRef( stylesheetRef ); - m_xml.startElement( "Catch" ); - if( !m_config->name().empty() ) - m_xml.writeAttribute( "name", m_config->name() ); + m_xml.startElement("Catch2TestRun") + .writeAttribute("name"_sr, m_config->name()) + .writeAttribute("rng-seed"_sr, m_config->rngSeed()) + .writeAttribute("catch2-version"_sr, libraryVersion()); if (m_config->testSpec().hasFilters()) - m_xml.writeAttribute( "filters", serializeFilters( m_config->getTestsOrTags() ) ); - if( m_config->rngSeed() != 0 ) - m_xml.scopedElement( "Randomness" ) - .writeAttribute( "seed", m_config->rngSeed() ); - } - - void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { - StreamingReporterBase::testGroupStarting( groupInfo ); - m_xml.startElement( "Group" ) - .writeAttribute( "name", groupInfo.name ); + m_xml.writeAttribute( "filters"_sr, serializeFilters( m_config->getTestsOrTags() ) ); } void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { StreamingReporterBase::testCaseStarting(testInfo); m_xml.startElement( "TestCase" ) - .writeAttribute( "name", trim( testInfo.name ) ) - .writeAttribute( "tags", testInfo.tagsAsString() ); + .writeAttribute( "name"_sr, trim( testInfo.name ) ) + .writeAttribute( "tags"_sr, testInfo.tagsAsString() ); writeSourceInfo( testInfo.lineInfo ); @@ -9186,7 +10199,7 @@ namespace Catch { StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) - .writeAttribute( "name", trim( sectionInfo.name ) ); + .writeAttribute( "name"_sr, trim( sectionInfo.name ) ); writeSourceInfo( sectionInfo.lineInfo ); m_xml.ensureTagClosed(); } @@ -9194,7 +10207,7 @@ namespace Catch { void XmlReporter::assertionStarting( AssertionInfo const& ) { } - bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { + void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { AssertionResult const& result = assertionStats.assertionResult; @@ -9215,14 +10228,14 @@ namespace Catch { // Drop out if result was successful but we're not printing them. if( !includeResults && result.getResultType() != ResultWas::Warning ) - return true; + return; // Print the expression if there is one. if( result.hasExpression() ) { m_xml.startElement( "Expression" ) - .writeAttribute( "success", result.succeeded() ) - .writeAttribute( "type", result.getTestMacroName() ); + .writeAttribute( "success"_sr, result.succeeded() ) + .writeAttribute( "type"_sr, result.getTestMacroName() ); writeSourceInfo( result.getSourceInfo() ); @@ -9248,7 +10261,7 @@ namespace Catch { break; case ResultWas::Info: m_xml.scopedElement( "Info" ) - .writeText( result.getMessage() ); + .writeText( result.getMessage() ); break; case ResultWas::Warning: // Warning will already have been written @@ -9265,20 +10278,18 @@ namespace Catch { if( result.hasExpression() ) m_xml.endElement(); - - return true; } void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { StreamingReporterBase::sectionEnded( sectionStats ); if( --m_sectionDepth > 0 ) { XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); - e.writeAttribute( "successes", sectionStats.assertions.passed ); - e.writeAttribute( "failures", sectionStats.assertions.failed ); - e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + e.writeAttribute( "successes"_sr, sectionStats.assertions.passed ); + e.writeAttribute( "failures"_sr, sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures"_sr, sectionStats.assertions.failedButOk ); if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + e.writeAttribute( "durationInSeconds"_sr, sectionStats.durationInSeconds ); m_xml.endElement(); } @@ -9287,10 +10298,10 @@ namespace Catch { void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { StreamingReporterBase::testCaseEnded( testCaseStats ); XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); - e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + e.writeAttribute( "success"_sr, testCaseStats.totals.assertions.allOk() ); if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + e.writeAttribute( "durationInSeconds"_sr, m_testCaseTimer.getElapsedSeconds() ); if( !testCaseStats.stdOut.empty() ) m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), XmlFormatting::Newline ); @@ -9300,77 +10311,63 @@ namespace Catch { m_xml.endElement(); } - void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { - StreamingReporterBase::testGroupEnded( testGroupStats ); - // TODO: Check testGroupStats.aborting and act accordingly. - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) - .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); - m_xml.scopedElement( "OverallResultsCases") - .writeAttribute( "successes", testGroupStats.totals.testCases.passed ) - .writeAttribute( "failures", testGroupStats.totals.testCases.failed ) - .writeAttribute( "expectedFailures", testGroupStats.totals.testCases.failedButOk ); - m_xml.endElement(); - } - void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { StreamingReporterBase::testRunEnded( testRunStats ); m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testRunStats.totals.assertions.passed ) - .writeAttribute( "failures", testRunStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + .writeAttribute( "successes"_sr, testRunStats.totals.assertions.passed ) + .writeAttribute( "failures"_sr, testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures"_sr, testRunStats.totals.assertions.failedButOk ); m_xml.scopedElement( "OverallResultsCases") - .writeAttribute( "successes", testRunStats.totals.testCases.passed ) - .writeAttribute( "failures", testRunStats.totals.testCases.failed ) - .writeAttribute( "expectedFailures", testRunStats.totals.testCases.failedButOk ); + .writeAttribute( "successes"_sr, testRunStats.totals.testCases.passed ) + .writeAttribute( "failures"_sr, testRunStats.totals.testCases.failed ) + .writeAttribute( "expectedFailures"_sr, testRunStats.totals.testCases.failedButOk ); m_xml.endElement(); } - void XmlReporter::benchmarkPreparing(std::string const& name) { + void XmlReporter::benchmarkPreparing( StringRef name ) { m_xml.startElement("BenchmarkResults") - .writeAttribute("name", name); + .writeAttribute("name"_sr, name); } void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { - m_xml.writeAttribute("samples", info.samples) - .writeAttribute("resamples", info.resamples) - .writeAttribute("iterations", info.iterations) - .writeAttribute("clockResolution", info.clockResolution) - .writeAttribute("estimatedDuration", info.estimatedDuration) - .writeComment("All values in nano seconds"); + m_xml.writeAttribute("samples"_sr, info.samples) + .writeAttribute("resamples"_sr, info.resamples) + .writeAttribute("iterations"_sr, info.iterations) + .writeAttribute("clockResolution"_sr, info.clockResolution) + .writeAttribute("estimatedDuration"_sr, info.estimatedDuration) + .writeComment("All values in nano seconds"_sr); } void XmlReporter::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { m_xml.startElement("mean") - .writeAttribute("value", benchmarkStats.mean.point.count()) - .writeAttribute("lowerBound", benchmarkStats.mean.lower_bound.count()) - .writeAttribute("upperBound", benchmarkStats.mean.upper_bound.count()) - .writeAttribute("ci", benchmarkStats.mean.confidence_interval); + .writeAttribute("value"_sr, benchmarkStats.mean.point.count()) + .writeAttribute("lowerBound"_sr, benchmarkStats.mean.lower_bound.count()) + .writeAttribute("upperBound"_sr, benchmarkStats.mean.upper_bound.count()) + .writeAttribute("ci"_sr, benchmarkStats.mean.confidence_interval); m_xml.endElement(); m_xml.startElement("standardDeviation") - .writeAttribute("value", benchmarkStats.standardDeviation.point.count()) - .writeAttribute("lowerBound", benchmarkStats.standardDeviation.lower_bound.count()) - .writeAttribute("upperBound", benchmarkStats.standardDeviation.upper_bound.count()) - .writeAttribute("ci", benchmarkStats.standardDeviation.confidence_interval); + .writeAttribute("value"_sr, benchmarkStats.standardDeviation.point.count()) + .writeAttribute("lowerBound"_sr, benchmarkStats.standardDeviation.lower_bound.count()) + .writeAttribute("upperBound"_sr, benchmarkStats.standardDeviation.upper_bound.count()) + .writeAttribute("ci"_sr, benchmarkStats.standardDeviation.confidence_interval); m_xml.endElement(); m_xml.startElement("outliers") - .writeAttribute("variance", benchmarkStats.outlierVariance) - .writeAttribute("lowMild", benchmarkStats.outliers.low_mild) - .writeAttribute("lowSevere", benchmarkStats.outliers.low_severe) - .writeAttribute("highMild", benchmarkStats.outliers.high_mild) - .writeAttribute("highSevere", benchmarkStats.outliers.high_severe); + .writeAttribute("variance"_sr, benchmarkStats.outlierVariance) + .writeAttribute("lowMild"_sr, benchmarkStats.outliers.low_mild) + .writeAttribute("lowSevere"_sr, benchmarkStats.outliers.low_severe) + .writeAttribute("highMild"_sr, benchmarkStats.outliers.high_mild) + .writeAttribute("highSevere"_sr, benchmarkStats.outliers.high_severe); m_xml.endElement(); m_xml.endElement(); } - void XmlReporter::benchmarkFailed(std::string const &error) { + void XmlReporter::benchmarkFailed(StringRef error) { m_xml.scopedElement("failed"). - writeAttribute("message", error); + writeAttribute("message"_sr, error); m_xml.endElement(); } - void XmlReporter::listReporters(std::vector<ReporterDescription> const& descriptions, IConfig const&) { + void XmlReporter::listReporters(std::vector<ReporterDescription> const& descriptions) { auto outerTag = m_xml.scopedElement("AvailableReporters"); for (auto const& reporter : descriptions) { auto inner = m_xml.scopedElement("Reporter"); @@ -9383,7 +10380,20 @@ namespace Catch { } } - void XmlReporter::listTests(std::vector<TestCaseHandle> const& tests, IConfig const&) { + void XmlReporter::listListeners(std::vector<ListenerDescription> const& descriptions) { + auto outerTag = m_xml.scopedElement( "RegisteredListeners" ); + for ( auto const& listener : descriptions ) { + auto inner = m_xml.scopedElement( "Listener" ); + m_xml.startElement( "Name", XmlFormatting::Indent ) + .writeText( listener.name, XmlFormatting::None ) + .endElement( XmlFormatting::Newline ); + m_xml.startElement( "Description", XmlFormatting::Indent ) + .writeText( listener.description, XmlFormatting::None ) + .endElement( XmlFormatting::Newline ); + } + } + + void XmlReporter::listTests(std::vector<TestCaseHandle> const& tests) { auto outerTag = m_xml.scopedElement("MatchingTests"); for (auto const& test : tests) { auto innerTag = m_xml.scopedElement("TestCase"); @@ -9408,7 +10418,7 @@ namespace Catch { } } - void XmlReporter::listTags(std::vector<TagInfo> const& tags, IConfig const&) { + void XmlReporter::listTags(std::vector<TagInfo> const& tags) { auto outerTag = m_xml.scopedElement("TagsFromMatchingTests"); for (auto const& tag : tags) { auto innerTag = m_xml.scopedElement("Tag"); @@ -9418,7 +10428,7 @@ namespace Catch { auto aliasTag = m_xml.scopedElement("Aliases"); for (auto const& alias : tag.spellings) { m_xml.startElement("Alias", XmlFormatting::Indent) - .writeText(static_cast<std::string>(alias), XmlFormatting::None) + .writeText(alias, XmlFormatting::None) .endElement(XmlFormatting::Newline); } } diff --git a/include/catch2/catch_amalgamated.hpp b/include/catch2/catch_amalgamated.hpp index ddbbb4e..5c1caa3 100644 --- a/include/catch2/catch_amalgamated.hpp +++ b/include/catch2/catch_amalgamated.hpp @@ -5,8 +5,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.0.0-preview.3 -// Generated: 2020-10-08 13:59:26.309308 +// Catch v3.0.1 +// Generated: 2022-05-17 22:08:46.674860 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -86,6 +86,117 @@ namespace Catch { #endif // CATCH_NONCOPYABLE_HPP_INCLUDED + +#ifndef CATCH_STRINGREF_HPP_INCLUDED +#define CATCH_STRINGREF_HPP_INCLUDED + +#include <cstddef> +#include <string> +#include <iosfwd> +#include <cassert> + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef other ) const noexcept -> bool; + auto operator != (StringRef other) const noexcept -> bool { + return !(*this == other); + } + + constexpr auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + bool operator<(StringRef rhs) const noexcept; + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, start + size()). + // If start > size(), then the substring is empty. + constexpr StringRef substr(size_type start, size_type length) const noexcept { + if (start < m_size) { + const auto shortened_size = m_size - start; + return StringRef(m_start + start, (shortened_size < length) ? shortened_size : length); + } else { + return StringRef(); + } + } + + // Returns the current start pointer. May not be null-terminated. + constexpr char const* data() const noexcept { + return m_start; + } + + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + + + friend std::string& operator += (std::string& lhs, StringRef sr); + friend std::ostream& operator << (std::ostream& os, StringRef sr); + friend std::string operator+(StringRef lhs, StringRef rhs); + + /** + * Provides a three-way comparison with rhs + * + * Returns negative number if lhs < rhs, 0 if lhs == rhs, and a positive + * number if lhs > rhs + */ + int compare( StringRef rhs ) const; + }; + + + constexpr auto operator ""_sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator ""_catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +#endif // CATCH_STRINGREF_HPP_INCLUDED + #include <chrono> #include <iosfwd> #include <string> @@ -101,8 +212,10 @@ namespace Catch { struct WarnAbout { enum What { Nothing = 0x00, + //! A test case or leaf section did not run any assertions NoAssertions = 0x01, - NoTests = 0x02 + //! A command line test spec matched no test cases + UnmatchedTestSpec = 0x02, }; }; enum class ShowDurations { @@ -115,10 +228,15 @@ namespace Catch { LexicographicallySorted, Randomized }; - enum class UseColour { - Auto, - Yes, - No + enum class ColourMode : std::uint8_t { + //! Let Catch2 pick implementation based on platform detection + PlatformDefault, + //! Use ANSI colour code escapes + ANSI, + //! Use Win32 console colour API + Win32, + //! Don't use any colour + None }; struct WaitForKeypress { enum When { Never, @@ -128,18 +246,19 @@ namespace Catch { }; }; class TestSpec; + class IStream; - struct IConfig : Detail::NonCopyable { - + class IConfig : public Detail::NonCopyable { + public: virtual ~IConfig(); virtual bool allowThrows() const = 0; - virtual std::ostream& stream() const = 0; - virtual std::string name() const = 0; + virtual StringRef name() const = 0; virtual bool includeSuccessfulResults() const = 0; virtual bool shouldDebugBreak() const = 0; virtual bool warnAboutMissingAssertions() const = 0; - virtual bool warnAboutNoTests() const = 0; + virtual bool warnAboutUnmatchedTestSpecs() const = 0; + virtual bool zeroTestsCountAsSuccess() const = 0; virtual int abortAfter() const = 0; virtual bool showInvisibles() const = 0; virtual ShowDurations showDurations() const = 0; @@ -148,13 +267,16 @@ namespace Catch { virtual bool hasTestFilters() const = 0; virtual std::vector<std::string> const& getTestsOrTags() const = 0; virtual TestRunOrder runOrder() const = 0; - virtual unsigned int rngSeed() const = 0; - virtual UseColour useColour() const = 0; + virtual uint32_t rngSeed() const = 0; + virtual unsigned int shardCount() const = 0; + virtual unsigned int shardIndex() const = 0; + virtual ColourMode defaultColourMode() const = 0; virtual std::vector<std::string> const& getSectionsToRun() const = 0; virtual Verbosity verbosity() const = 0; + virtual bool skipBenchmarks() const = 0; virtual bool benchmarkNoAnalysis() const = 0; - virtual int benchmarkSamples() const = 0; + virtual unsigned int benchmarkSamples() const = 0; virtual double benchmarkConfidenceInterval() const = 0; virtual unsigned int benchmarkResamples() const = 0; virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; @@ -164,82 +286,12 @@ namespace Catch { #endif // CATCH_INTERFACES_CONFIG_HPP_INCLUDED -#ifndef CATCH_CONTEXT_HPP_INCLUDED -#define CATCH_CONTEXT_HPP_INCLUDED - -namespace Catch { - - struct IResultCapture; - struct IRunner; - struct IConfig; - - struct IContext - { - virtual ~IContext(); - - virtual IResultCapture* getResultCapture() = 0; - virtual IRunner* getRunner() = 0; - virtual IConfig const* getConfig() const = 0; - }; - - struct IMutableContext : IContext - { - virtual ~IMutableContext(); - virtual void setResultCapture( IResultCapture* resultCapture ) = 0; - virtual void setRunner( IRunner* runner ) = 0; - virtual void setConfig( IConfig const* config ) = 0; - - private: - static IMutableContext *currentContext; - friend IMutableContext& getCurrentMutableContext(); - friend void cleanUpContext(); - static void createContext(); - }; - - inline IMutableContext& getCurrentMutableContext() - { - if( !IMutableContext::currentContext ) - IMutableContext::createContext(); - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return *IMutableContext::currentContext; - } - - inline IContext& getCurrentContext() - { - return getCurrentMutableContext(); - } - - void cleanUpContext(); - - class SimplePcg32; - SimplePcg32& rng(); -} - -#endif // CATCH_CONTEXT_HPP_INCLUDED - - -#ifndef CATCH_INTERFACES_REPORTER_HPP_INCLUDED -#define CATCH_INTERFACES_REPORTER_HPP_INCLUDED - - - -#ifndef CATCH_SECTION_INFO_HPP_INCLUDED -#define CATCH_SECTION_INFO_HPP_INCLUDED - - - -#ifndef CATCH_COMMON_HPP_INCLUDED -#define CATCH_COMMON_HPP_INCLUDED - - - #ifndef CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED #define CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED // Detect a number of compiler features - by compiler // The following features are defined: // -// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? // CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? // CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? @@ -258,13 +310,16 @@ namespace Catch { #ifndef CATCH_PLATFORM_HPP_INCLUDED #define CATCH_PLATFORM_HPP_INCLUDED +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ -# include <TargetConditionals.h> -# if TARGET_OS_OSX == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif +# include <TargetConditionals.h> +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX @@ -287,9 +342,9 @@ namespace Catch { #endif -// We have to avoid both ICC and Clang, because they try to mask themselves -// as gcc, and we want only GCC in this block -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) @@ -305,7 +360,7 @@ namespace Catch { #endif -#if defined(__clang__) +#if defined(__clang__) && !defined(_MSC_VER) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) @@ -320,8 +375,13 @@ namespace Catch { // 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__) +# 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 @@ -359,14 +419,12 @@ namespace Catch { #ifdef __OS400__ # define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# define CATCH_CONFIG_COLOUR_NONE #endif //////////////////////////////////////////////////////////////////////////////// // Android somehow still does not support std::to_string #if defined(__ANDROID__) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE #endif //////////////////////////////////////////////////////////////////////////////// @@ -408,7 +466,7 @@ namespace Catch { // Universal Windows platform does not support SEH // Or console colours (or console at all...) # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -# define CATCH_CONFIG_COLOUR_NONE +# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 # else # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif @@ -435,11 +493,6 @@ namespace Catch { # define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED #endif -//////////////////////////////////////////////////////////////////////////////// -// DJGPP -#ifdef __DJGPP__ -# define CATCH_INTERNAL_CONFIG_NO_WCHAR -#endif // __DJGPP__ //////////////////////////////////////////////////////////////////////////////// // Embarcadero C++Build @@ -449,25 +502,13 @@ namespace Catch { //////////////////////////////////////////////////////////////////////////////// -// Use of __COUNTER__ is suppressed during code analysis in -// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly -// handled by it. -// Otherwise all supported compilers support COUNTER macro, -// but user still might want to turn it off -#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) - #define CATCH_INTERNAL_CONFIG_COUNTER -#endif - - -//////////////////////////////////////////////////////////////////////////////// - // RTX is a special version of Windows that is real time. // This means that it is detected as Windows, but does not provide // the same set of capabilities as real Windows does. #if defined(UNDER_RTSS) || defined(RTX64_BUILD) #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #define CATCH_INTERNAL_CONFIG_NO_ASYNC - #define CATCH_CONFIG_COLOUR_NONE + #define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 #endif #if !defined(_GLIBCXX_USE_C99_MATH_TR1) @@ -489,7 +530,7 @@ namespace Catch { // Check if byte is available and usable # if __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER) # include <cstddef> - # if __cpp_lib_byte > 0 + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) # define CATCH_INTERNAL_CONFIG_CPP17_BYTE # endif # endif // __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER) @@ -512,9 +553,6 @@ namespace Catch { #endif // defined(__has_include) -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) -# define CATCH_CONFIG_COUNTER -#endif #if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) # define CATCH_CONFIG_WINDOWS_SEH #endif @@ -522,10 +560,6 @@ namespace Catch { #if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) # define CATCH_CONFIG_POSIX_SIGNALS #endif -// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. -#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) -# define CATCH_CONFIG_WCHAR -#endif #if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) # define CATCH_CONFIG_CPP11_TO_STRING @@ -556,7 +590,9 @@ namespace Catch { # define CATCH_CONFIG_NEW_CAPTURE #endif -#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#if !defined( CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED ) && \ + !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) && \ + !defined( CATCH_CONFIG_NO_DISABLE_EXCEPTIONS ) # define CATCH_CONFIG_DISABLE_EXCEPTIONS #endif @@ -568,10 +604,6 @@ namespace Catch { # define CATCH_CONFIG_USE_ASYNC #endif -#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) -# define CATCH_CONFIG_ANDROID_LOGWRITE -#endif - #if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) # define CATCH_CONFIG_GLOBAL_NEXTAFTER #endif @@ -628,135 +660,98 @@ namespace Catch { #define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #endif -#endif // CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED +#if defined( CATCH_PLATFORM_WINDOWS ) && \ + !defined( CATCH_CONFIG_COLOUR_WIN32 ) && \ + !defined( CATCH_CONFIG_NO_COLOUR_WIN32 ) && \ + !defined( CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 ) +# define CATCH_CONFIG_COLOUR_WIN32 +#endif -#ifndef CATCH_STRINGREF_HPP_INCLUDED -#define CATCH_STRINGREF_HPP_INCLUDED +#endif // CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED -#include <cstddef> -#include <string> -#include <iosfwd> -#include <cassert> + +#ifndef CATCH_CONTEXT_HPP_INCLUDED +#define CATCH_CONTEXT_HPP_INCLUDED namespace Catch { - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. - class StringRef { + class IResultCapture; + class IConfig; + + class IContext { public: - using size_type = std::size_t; - using const_iterator = const char*; + virtual ~IContext(); // = default - private: - static constexpr char const* const s_empty = ""; + virtual IResultCapture* getResultCapture() = 0; + virtual IConfig const* getConfig() const = 0; + }; - char const* m_start = s_empty; - size_type m_size = 0; + class IMutableContext : public IContext { + public: + ~IMutableContext() override; // = default + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setConfig( IConfig const* config ) = 0; - public: // construction - constexpr StringRef() noexcept = default; + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); + }; - StringRef( char const* rawChars ) noexcept; + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return *IMutableContext::currentContext; + } - constexpr StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - {} + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } - StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} + void cleanUpContext(); - explicit operator std::string() const { - return std::string(m_start, m_size); - } + class SimplePcg32; + SimplePcg32& sharedRng(); +} - public: // operators - auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != (StringRef const& other) const noexcept -> bool { - return !(*this == other); - } +#endif // CATCH_CONTEXT_HPP_INCLUDED - constexpr auto operator[] ( size_type index ) const noexcept -> char { - assert(index < m_size); - return m_start[index]; - } - bool operator<(StringRef const& rhs) const noexcept; +#ifndef CATCH_INTERFACES_REPORTER_HPP_INCLUDED +#define CATCH_INTERFACES_REPORTER_HPP_INCLUDED - public: // named queries - constexpr auto empty() const noexcept -> bool { - return m_size == 0; - } - constexpr auto size() const noexcept -> size_type { - return m_size; - } - // Returns the current start pointer. If the StringRef is not - // null-terminated, throws std::domain_exception - auto c_str() const -> char const*; - public: // substrings and searches - // Returns a substring of [start, start + length). - // If start + length > size(), then the substring is [start, start + size()). - // If start > size(), then the substring is empty. - constexpr StringRef substr(size_type start, size_type length) const noexcept { - if (start < m_size) { - const auto shortened_size = m_size - start; - return StringRef(m_start + start, (shortened_size < length) ? shortened_size : length); - } else { - return StringRef(); - } - } +#ifndef CATCH_SECTION_INFO_HPP_INCLUDED +#define CATCH_SECTION_INFO_HPP_INCLUDED - // Returns the current start pointer. May not be null-terminated. - constexpr char const* data() const noexcept { - return m_start; - } - constexpr auto isNullTerminated() const noexcept -> bool { - return m_start[m_size] == '\0'; - } - public: // iterators - constexpr const_iterator begin() const { return m_start; } - constexpr const_iterator end() const { return m_start + m_size; } +#ifndef CATCH_MOVE_AND_FORWARD_HPP_INCLUDED +#define CATCH_MOVE_AND_FORWARD_HPP_INCLUDED +#include <type_traits> - friend std::string& operator += (std::string& lhs, StringRef const& sr); - friend std::ostream& operator << (std::ostream& os, StringRef const& sr); - friend std::string operator+(StringRef lhs, StringRef rhs); - }; +//! Replacement for std::move with better compile time performance +#define CATCH_MOVE(...) static_cast<std::remove_reference_t<decltype(__VA_ARGS__)>&&>(__VA_ARGS__) +//! Replacement for std::forward with better compile time performance +#define CATCH_FORWARD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__) - constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { - return StringRef( rawChars, size ); - } -} // namespace Catch +#endif // CATCH_MOVE_AND_FORWARD_HPP_INCLUDED -constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { - return Catch::StringRef( rawChars, size ); -} -#endif // CATCH_STRINGREF_HPP_INCLUDED - -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#ifdef CATCH_CONFIG_COUNTER -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) -#else -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) -#endif +#ifndef CATCH_SOURCE_LINE_INFO_HPP_INCLUDED +#define CATCH_SOURCE_LINE_INFO_HPP_INCLUDED +#include <cstddef> #include <iosfwd> -// We need a dummy global operator<< so we can bring it into Catch namespace later -struct Catch_global_namespace_dummy {}; -std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); - namespace Catch { struct SourceLineInfo { @@ -775,39 +770,18 @@ namespace Catch { friend std::ostream& operator << (std::ostream& os, SourceLineInfo const& info); }; - - - // Bring in operator<< from global namespace into Catch namespace - // This is necessary because the overload of operator<< above makes - // lookup stop at namespace Catch - using ::operator<<; - - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - StringRef operator+() const { - return StringRef(); - } - - template<typename T> - friend T const& operator + ( T const& value, StreamEndStop ) { - return value; - } - }; } #define CATCH_INTERNAL_LINEINFO \ ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) -#endif // CATCH_COMMON_HPP_INCLUDED +#endif // CATCH_SOURCE_LINE_INFO_HPP_INCLUDED #ifndef CATCH_TOTALS_HPP_INCLUDED #define CATCH_TOTALS_HPP_INCLUDED -#include <cstddef> +#include <cstdint> namespace Catch { @@ -815,13 +789,13 @@ namespace Catch { Counts operator - ( Counts const& other ) const; Counts& operator += ( Counts const& other ); - std::size_t total() const; + std::uint64_t total() const; bool allPassed() const; bool allOk() const; - std::size_t passed = 0; - std::size_t failed = 0; - std::size_t failedButOk = 0; + std::uint64_t passed = 0; + std::uint64_t failed = 0; + std::uint64_t failedButOk = 0; }; struct Totals { @@ -831,7 +805,6 @@ namespace Catch { Totals delta( Totals const& prevTotals ) const; - int error = 0; Counts assertions; Counts testCases; }; @@ -849,7 +822,7 @@ namespace Catch { // still use the `-c` flag comfortably. SectionInfo( SourceLineInfo const& _lineInfo, std::string _name, const char* const = nullptr ): - name(std::move(_name)), + name(CATCH_MOVE(_name)), lineInfo(_lineInfo) {} @@ -871,7 +844,6 @@ namespace Catch { #ifndef CATCH_ASSERTION_RESULT_HPP_INCLUDED #define CATCH_ASSERTION_RESULT_HPP_INCLUDED -#include <string> #ifndef CATCH_ASSERTION_INFO_HPP_INCLUDED @@ -950,8 +922,8 @@ namespace Catch { #include <iosfwd> namespace Catch { - - struct ITransientExpression; + + class ITransientExpression; class LazyExpression { friend class AssertionHandler; @@ -978,6 +950,8 @@ namespace Catch { #endif // CATCH_LAZY_EXPR_HPP_INCLUDED +#include <string> + namespace Catch { struct AssertionResultData @@ -1008,7 +982,7 @@ namespace Catch { std::string getExpressionInMacro() const; bool hasExpandedExpression() const; std::string getExpandedExpression() const; - std::string getMessage() const; + StringRef getMessage() const; SourceLineInfo getSourceInfo() const; StringRef getTestMacroName() const; @@ -1046,15 +1020,15 @@ namespace Catch { struct AssertionReaction; struct SourceLineInfo; - struct ITransientExpression; - struct IGeneratorTracker; + class ITransientExpression; + class IGeneratorTracker; struct BenchmarkInfo; template <typename Duration = std::chrono::duration<double, std::nano>> struct BenchmarkStats; - struct IResultCapture { - + class IResultCapture { + public: virtual ~IResultCapture(); virtual bool sectionStarted( SectionInfo const& sectionInfo, @@ -1064,10 +1038,10 @@ namespace Catch { virtual auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; - virtual void benchmarkPreparing( std::string const& name ) = 0; + virtual void benchmarkPreparing( StringRef name ) = 0; virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; - virtual void benchmarkFailed( std::string const& error ) = 0; + virtual void benchmarkFailed( StringRef error ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; @@ -1083,7 +1057,7 @@ namespace Catch { virtual void handleMessage ( AssertionInfo const& info, ResultWas::OfType resultType, - StringRef const& message, + StringRef message, AssertionReaction& reaction ) = 0; virtual void handleUnexpectedExceptionNotThrown ( AssertionInfo const& info, @@ -1120,7 +1094,7 @@ namespace Catch { namespace Catch { struct MessageInfo { - MessageInfo( StringRef const& _macroName, + MessageInfo( StringRef _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ); @@ -1151,11 +1125,14 @@ namespace Catch { #include <cassert> #include <type_traits> + namespace Catch { namespace Detail { - // reimplementation of unique_ptr for improved compilation times - // Does not support custom deleters (and thus does not require EBO) - // Does not support arrays + /** + * A reimplementation of `std::unique_ptr` for improved compilation performance + * + * Does not support arrays nor custom deleters. + */ template <typename T> class unique_ptr { T* m_ptr; @@ -1204,7 +1181,11 @@ namespace Detail { assert(m_ptr); return *m_ptr; } - T* operator->() const noexcept { + T* operator->() noexcept { + assert(m_ptr); + return m_ptr; + } + T const* operator->() const noexcept { assert(m_ptr); return m_ptr; } @@ -1234,20 +1215,13 @@ namespace Detail { } }; - // Purposefully doesn't exist - // We could also rely on compiler warning + werror for calling plain delete - // on a T[], but this seems better. - // Maybe add definition and a static assert? + //! Specialization to cause compile-time error for arrays template <typename T> class unique_ptr<T[]>; template <typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { - // static_cast<Args&&> does the same thing as std::forward in - // this case, but does not require including big header (<utility>) - // and compiles faster thanks to not requiring template instantiation - // and overload resolution - return unique_ptr<T>(new T(static_cast<Args&&>(args)...)); + return unique_ptr<T>(new T(CATCH_FORWARD(args)...)); } @@ -1257,7 +1231,6 @@ namespace Detail { #endif // CATCH_UNIQUE_PTR_HPP_INCLUDED - // Adapted from donated nonius code. #ifndef CATCH_ESTIMATE_HPP_INCLUDED @@ -1307,6 +1280,7 @@ namespace Catch { #endif // CATCH_OUTLIERS_CLASSIFICATION_HPP_INCLUDED +#include <map> #include <string> #include <vector> #include <iosfwd> @@ -1314,36 +1288,39 @@ namespace Catch { namespace Catch { struct ReporterDescription; + struct ListenerDescription; struct TagInfo; struct TestCaseInfo; class TestCaseHandle; - struct IConfig; + class IConfig; + class IStream; + enum class ColourMode : std::uint8_t; struct ReporterConfig { - explicit ReporterConfig( IConfig const* _fullConfig ); + ReporterConfig( IConfig const* _fullConfig, + Detail::unique_ptr<IStream> _stream, + ColourMode colourMode, + std::map<std::string, std::string> customOptions ); - ReporterConfig( IConfig const* _fullConfig, std::ostream& _stream ); + ReporterConfig( ReporterConfig&& ) = default; + ReporterConfig& operator=( ReporterConfig&& ) = default; + ~ReporterConfig(); // = default - std::ostream& stream() const; + Detail::unique_ptr<IStream> takeStream() &&; IConfig const* fullConfig() const; + ColourMode colourMode() const; + std::map<std::string, std::string> const& customOptions() const; private: - std::ostream* m_stream; + Detail::unique_ptr<IStream> m_stream; IConfig const* m_fullConfig; + ColourMode m_colourMode; + std::map<std::string, std::string> m_customOptions; }; struct TestRunInfo { - TestRunInfo( std::string const& _name ); - std::string name; - }; - struct GroupInfo { - GroupInfo( std::string const& _name, - std::size_t _groupIndex, - std::size_t _groupsCount ); - - std::string name; - std::size_t groupIndex; - std::size_t groupsCounts; + constexpr TestRunInfo(StringRef _name) : name(_name) {} + StringRef name; }; struct AssertionStats { @@ -1387,17 +1364,6 @@ namespace Catch { bool aborting; }; - struct TestGroupStats { - TestGroupStats( GroupInfo const& _groupInfo, - Totals const& _totals, - bool _aborting ); - TestGroupStats( GroupInfo const& _groupInfo ); - - GroupInfo groupInfo; - Totals totals; - bool aborting; - }; - struct TestRunStats { TestRunStats( TestRunInfo const& _runInfo, Totals const& _totals, @@ -1413,7 +1379,7 @@ namespace Catch { std::string name; double estimatedDuration; int iterations; - int samples; + unsigned int samples; unsigned int resamples; double clockResolution; double clockCost; @@ -1438,7 +1404,7 @@ namespace Catch { } return { info, - std::move(samples2), + CATCH_MOVE(samples2), mean, standardDeviation, outliers, @@ -1459,13 +1425,29 @@ namespace Catch { bool shouldReportAllAssertions = false; }; - - struct IStreamingReporter { + /** + * The common base for all reporters and event listeners + * + * Implementing classes must also implement: + * + * //! User-friendly description of the reporter/listener type + * static std::string getDescription() + * + * Generally shouldn't be derived from by users of Catch2 directly, + * instead they should derive from one of the utility bases that + * derive from this class. + */ + class IEventListener { protected: //! Derived classes can set up their preferences here ReporterPreferences m_preferences; + //! The test run's config as filled in from CLI and defaults + IConfig const* m_config; + public: - virtual ~IStreamingReporter() = default; + IEventListener( IConfig const* config ): m_config( config ) {} + + virtual ~IEventListener(); // = default; // Implementing class must also provide the following static methods: // static std::string getDescription(); @@ -1474,51 +1456,115 @@ namespace Catch { return m_preferences; } - virtual void noMatchingTestCases( std::string const& spec ) = 0; - - virtual void reportInvalidArguments(std::string const&) {} + //! Called when no test cases match provided test spec + virtual void noMatchingTestCases( StringRef unmatchedSpec ) = 0; + //! Called for all invalid test specs from the cli + virtual void reportInvalidTestSpec( StringRef invalidArgument ) = 0; + /** + * Called once in a testing run before tests are started + * + * Not called if tests won't be run (e.g. only listing will happen) + */ virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; - virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + //! Called _once_ for each TEST_CASE, no matter how many times it is entered virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections) + virtual void testCasePartialStarting( TestCaseInfo const& testInfo, uint64_t partNumber ) = 0; + //! Called when a `SECTION` is being entered. Not called for skipped sections virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; - virtual void benchmarkPreparing( std::string const& ) {} - virtual void benchmarkStarting( BenchmarkInfo const& ) {} - virtual void benchmarkEnded( BenchmarkStats<> const& ) {} - virtual void benchmarkFailed( std::string const& ) {} + //! Called when user-code is being probed before the actual benchmark runs + virtual void benchmarkPreparing( StringRef benchmarkName ) = 0; + //! Called after probe but before the user-code is being benchmarked + virtual void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) = 0; + //! Called with the benchmark results if benchmark successfully finishes + virtual void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) = 0; + //! Called if running the benchmarks fails for any reason + virtual void benchmarkFailed( StringRef benchmarkName ) = 0; + //! Called before assertion success/failure is evaluated virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; - // The return value indicates if the messages buffer should be cleared: - virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + //! Called after assertion was fully evaluated + virtual void assertionEnded( AssertionStats const& assertionStats ) = 0; + //! Called after a `SECTION` has finished running virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections) + virtual void testCasePartialEnded(TestCaseStats const& testCaseStats, uint64_t partNumber ) = 0; + //! Called _once_ for each TEST_CASE, no matter how many times it is entered virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + /** + * Called once after all tests in a testing run are finished + * + * Not called if tests weren't run (e.g. only listings happened) + */ virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + //! Called with test cases that are skipped due to the test run aborting virtual void skipTest( TestCaseInfo const& testInfo ) = 0; - // Default empty implementation provided - virtual void fatalErrorEncountered( StringRef name ); + //! Called if a fatal error (signal/structured exception) occured + virtual void fatalErrorEncountered( StringRef error ) = 0; //! Writes out information about provided reporters using reporter-specific format - virtual void listReporters(std::vector<ReporterDescription> const& descriptions, IConfig const& config); + virtual void listReporters(std::vector<ReporterDescription> const& descriptions) = 0; + //! Writes out the provided listeners descriptions using reporter-specific format + virtual void listListeners(std::vector<ListenerDescription> const& descriptions) = 0; //! Writes out information about provided tests using reporter-specific format - virtual void listTests(std::vector<TestCaseHandle> const& tests, IConfig const& config); + virtual void listTests(std::vector<TestCaseHandle> const& tests) = 0; //! Writes out information about the provided tags using reporter-specific format - virtual void listTags(std::vector<TagInfo> const& tags, IConfig const& config); - + virtual void listTags(std::vector<TagInfo> const& tags) = 0; }; - using IStreamingReporterPtr = Detail::unique_ptr<IStreamingReporter>; + using IEventListenerPtr = Detail::unique_ptr<IEventListener>; } // end namespace Catch #endif // CATCH_INTERFACES_REPORTER_HPP_INCLUDED +#ifndef CATCH_UNIQUE_NAME_HPP_INCLUDED +#define CATCH_UNIQUE_NAME_HPP_INCLUDED + + + + +/** \file + * Wrapper for the CONFIG configuration option + * + * When generating internal unique names, there are two options. Either + * we mix in the current line number, or mix in an incrementing number. + * We prefer the latter, using `__COUNTER__`, but users might want to + * use the former. + */ + +#ifndef CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define CATCH_CONFIG_COUNTER_HPP_INCLUDED + +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +#if defined( CATCH_INTERNAL_CONFIG_COUNTER ) && \ + !defined( CATCH_CONFIG_NO_COUNTER ) && \ + !defined( CATCH_CONFIG_COUNTER ) +# define CATCH_CONFIG_COUNTER +#endif + + +#endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#endif // CATCH_UNIQUE_NAME_HPP_INCLUDED + // Adapted from donated nonius code. @@ -1570,8 +1616,8 @@ namespace Catch { # include <atomic> // atomic_thread_fence #endif + #include <type_traits> -#include <utility> namespace Catch { namespace Benchmark { @@ -1612,13 +1658,13 @@ namespace Catch { } template <typename Fn, typename... Args> - inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> typename std::enable_if<!std::is_same<void, decltype(fn(args...))>::value>::type { - deoptimize_value(std::forward<Fn>(fn) (std::forward<Args...>(args...))); + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t<!std::is_same<void, decltype(fn(args...))>::value> { + deoptimize_value(CATCH_FORWARD(fn) (CATCH_FORWARD(args)...)); } template <typename Fn, typename... Args> - inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> typename std::enable_if<std::is_same<void, decltype(fn(args...))>::value>::type { - std::forward<Fn>(fn) (std::forward<Args...>(args...)); + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t<std::is_same<void, decltype(fn(args...))>::value> { + CATCH_FORWARD(fn) (CATCH_FORWARD(args)...); } } // namespace Benchmark } // namespace Catch @@ -1633,113 +1679,17 @@ namespace Catch { -#ifndef CATCH_ENFORCE_HPP_INCLUDED -#define CATCH_ENFORCE_HPP_INCLUDED - - - -#ifndef CATCH_STREAM_HPP_INCLUDED -#define CATCH_STREAM_HPP_INCLUDED - - -#include <iosfwd> -#include <cstddef> -#include <ostream> - -namespace Catch { - - std::ostream& cout(); - std::ostream& cerr(); - std::ostream& clog(); - - class StringRef; - - struct IStream { - virtual ~IStream(); - virtual std::ostream& stream() const = 0; - }; - - auto makeStream( StringRef const &filename ) -> IStream const*; - - class ReusableStringStream : Detail::NonCopyable { - std::size_t m_index; - std::ostream* m_oss; - public: - ReusableStringStream(); - ~ReusableStringStream(); - - //! Returns the serialized state - std::string str() const; - //! Sets internal state to `str` - void str(std::string const& str); - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -// Old versions of GCC do not understand -Wnonnull-compare -#pragma GCC diagnostic ignored "-Wpragmas" -// Streaming a function pointer triggers Waddress and Wnonnull-compare -// on GCC, because it implicitly converts it to bool and then decides -// that the check it uses (a? true : false) is tautological and cannot -// be null... -#pragma GCC diagnostic ignored "-Waddress" -#pragma GCC diagnostic ignored "-Wnonnull-compare" -#endif - - template<typename T> - auto operator << ( T const& value ) -> ReusableStringStream& { - *m_oss << value; - return *this; - } - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif - auto get() -> std::ostream& { return *m_oss; } - }; -} - -#endif // CATCH_STREAM_HPP_INCLUDED - -#include <exception> +#ifndef CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED +#define CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED namespace Catch { -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - template <typename Ex> - [[noreturn]] - void throw_exception(Ex const& e) { - throw e; - } -#else // ^^ Exceptions are enabled // Exceptions are disabled vv - [[noreturn]] - void throw_exception(std::exception const& e); -#endif - - [[noreturn]] - void throw_logic_error(std::string const& msg); - [[noreturn]] - void throw_domain_error(std::string const& msg); - [[noreturn]] - void throw_runtime_error(std::string const& msg); - -} // namespace Catch; - -#define CATCH_MAKE_MSG(...) \ - (Catch::ReusableStringStream() << __VA_ARGS__).str() - -#define CATCH_INTERNAL_ERROR(...) \ - Catch::throw_logic_error(CATCH_MAKE_MSG( CATCH_INTERNAL_LINEINFO << ": Internal Catch2 error: " << __VA_ARGS__)) - -#define CATCH_ERROR(...) \ - Catch::throw_domain_error(CATCH_MAKE_MSG( __VA_ARGS__ )) -#define CATCH_RUNTIME_ERROR(...) \ - Catch::throw_runtime_error(CATCH_MAKE_MSG( __VA_ARGS__ )) - -#define CATCH_ENFORCE( condition, ... ) \ - do{ if( !(condition) ) CATCH_ERROR( __VA_ARGS__ ); } while(false) + //! Used to signal that an assertion macro failed + struct TestFailureException{}; +} // namespace Catch -#endif // CATCH_ENFORCE_HPP_INCLUDED +#endif // CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED #ifndef CATCH_META_HPP_INCLUDED @@ -1795,22 +1745,24 @@ namespace Catch { class TestCaseHandle; struct TestCaseInfo; - struct ITestCaseRegistry; - struct IExceptionTranslatorRegistry; - struct IExceptionTranslator; - struct IReporterRegistry; - struct IReporterFactory; - struct ITagAliasRegistry; - struct ITestInvoker; - struct IMutableEnumValuesRegistry; + class ITestCaseRegistry; + class IExceptionTranslatorRegistry; + class IExceptionTranslator; + class IReporterRegistry; + class IReporterFactory; + class ITagAliasRegistry; + class ITestInvoker; + class IMutableEnumValuesRegistry; struct SourceLineInfo; class StartupExceptionRegistry; + class EventListenerFactory; using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>; - struct IRegistryHub { - virtual ~IRegistryHub(); + class IRegistryHub { + public: + virtual ~IRegistryHub(); // = default virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; @@ -1821,12 +1773,13 @@ namespace Catch { virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; }; - struct IMutableRegistryHub { - virtual ~IMutableRegistryHub(); + class IMutableRegistryHub { + public: + virtual ~IMutableRegistryHub(); // = default virtual void registerReporter( std::string const& name, IReporterFactoryPtr factory ) = 0; - virtual void registerListener( IReporterFactoryPtr factory ) = 0; + virtual void registerListener( Detail::unique_ptr<EventListenerFactory> factory ) = 0; virtual void registerTest(Detail::unique_ptr<TestCaseInfo>&& testInfo, Detail::unique_ptr<ITestInvoker>&& invoker) = 0; - virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) = 0; virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; virtual void registerStartupException() noexcept = 0; virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; @@ -1842,7 +1795,6 @@ namespace Catch { #endif // CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED #include <type_traits> -#include <utility> namespace Catch { namespace Benchmark { @@ -1859,14 +1811,14 @@ namespace Catch { struct CompleteInvoker { template <typename Fun, typename... Args> static Result invoke(Fun&& fun, Args&&... args) { - return std::forward<Fun>(fun)(std::forward<Args>(args)...); + return CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); } }; template <> struct CompleteInvoker<void> { template <typename Fun, typename... Args> static CompleteType_t<void> invoke(Fun&& fun, Args&&... args) { - std::forward<Fun>(fun)(std::forward<Args>(args)...); + CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); return {}; } }; @@ -1874,20 +1826,14 @@ namespace Catch { // invoke and not return void :( template <typename Fun, typename... Args> CompleteType_t<FunctionReturnType<Fun, Args...>> complete_invoke(Fun&& fun, Args&&... args) { - return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(std::forward<Fun>(fun), std::forward<Args>(args)...); + return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); } - extern const std::string benchmarkErrorMsg; } // namespace Detail template <typename Fun> Detail::CompleteType_t<FunctionReturnType<Fun>> user_code(Fun&& fun) { - CATCH_TRY{ - return Detail::complete_invoke(std::forward<Fun>(fun)); - } CATCH_CATCH_ALL{ - getResultCapture().benchmarkFailed(translateActiveException()); - CATCH_RUNTIME_ERROR(Detail::benchmarkErrorMsg); - } + return Detail::complete_invoke(CATCH_FORWARD(fun)); } } // namespace Benchmark } // namespace Catch @@ -1921,7 +1867,7 @@ namespace Catch { struct Chronometer { public: template <typename Fun> - void measure(Fun&& fun) { measure(std::forward<Fun>(fun), is_callable<Fun(int)>()); } + void measure(Fun&& fun) { measure(CATCH_FORWARD(fun), is_callable<Fun(int)>()); } int runs() const { return repeats; } @@ -1996,18 +1942,14 @@ namespace Catch { #define CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED -#include <cassert> #include <type_traits> -#include <utility> namespace Catch { namespace Benchmark { namespace Detail { - template <typename T> - using Decay = typename std::decay<T>::type; template <typename T, typename U> struct is_related - : std::is_same<Decay<T>, Decay<U>> {}; + : std::is_same<std::decay_t<T>, std::decay_t<U>> {}; /// 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 @@ -2020,7 +1962,7 @@ namespace Catch { private: struct callable { virtual void call(Chronometer meter) const = 0; - virtual callable* clone() const = 0; + virtual Catch::Detail::unique_ptr<callable> clone() const = 0; virtual ~callable(); // = default; callable() = default; @@ -2029,10 +1971,12 @@ namespace Catch { }; template <typename Fun> struct model : public callable { - model(Fun&& fun_) : fun(std::move(fun_)) {} + model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {} model(Fun const& fun_) : fun(fun_) {} - model<Fun>* clone() const override { return new model<Fun>(*this); } + Catch::Detail::unique_ptr<callable> clone() const override { + return Catch::Detail::make_unique<model<Fun>>( *this ); + } void call(Chronometer meter) const override { call(meter, is_callable<Fun(Chronometer)>()); @@ -2057,24 +2001,24 @@ namespace Catch { : f(new model<do_nothing>{ {} }) {} template <typename Fun, - typename std::enable_if<!is_related<Fun, BenchmarkFunction>::value, int>::type = 0> + std::enable_if_t<!is_related<Fun, BenchmarkFunction>::value, int> = 0> BenchmarkFunction(Fun&& fun) - : f(new model<typename std::decay<Fun>::type>(std::forward<Fun>(fun))) {} + : f(new model<std::decay_t<Fun>>(CATCH_FORWARD(fun))) {} BenchmarkFunction( BenchmarkFunction&& that ) noexcept: - f( std::move( that.f ) ) {} + f( CATCH_MOVE( that.f ) ) {} BenchmarkFunction(BenchmarkFunction const& that) : f(that.f->clone()) {} BenchmarkFunction& operator=( BenchmarkFunction&& that ) noexcept { - f = std::move( that.f ); + f = CATCH_MOVE( that.f ); return *this; } BenchmarkFunction& operator=(BenchmarkFunction const& that) { - f.reset(that.f->clone()); + f = that.f->clone(); return *this; } @@ -2096,7 +2040,6 @@ namespace Catch { #define CATCH_REPEAT_HPP_INCLUDED #include <type_traits> -#include <utility> namespace Catch { namespace Benchmark { @@ -2111,8 +2054,8 @@ namespace Catch { Fun fun; }; template <typename Fun> - repeater<typename std::decay<Fun>::type> repeat(Fun&& fun) { - return { std::forward<Fun>(fun) }; + repeater<std::decay_t<Fun>> repeat(Fun&& fun) { + return { CATCH_FORWARD(fun) }; } } // namespace Detail } // namespace Benchmark @@ -2158,18 +2101,16 @@ namespace Catch { #endif // CATCH_TIMING_HPP_INCLUDED -#include <utility> - namespace Catch { namespace Benchmark { namespace Detail { template <typename Clock, typename Fun, typename... Args> TimingOf<Clock, Fun, Args...> measure(Fun&& fun, Args&&... args) { auto start = Clock::now(); - auto&& r = Detail::complete_invoke(fun, std::forward<Args>(args)...); + auto&& r = Detail::complete_invoke(fun, CATCH_FORWARD(args)...); auto end = Clock::now(); auto delta = end - start; - return { delta, std::forward<decltype(r)>(r), 1 }; + return { delta, CATCH_FORWARD(r), 1 }; } } // namespace Detail } // namespace Benchmark @@ -2177,7 +2118,6 @@ namespace Catch { #endif // CATCH_MEASURE_HPP_INCLUDED -#include <utility> #include <type_traits> namespace Catch { @@ -2192,24 +2132,27 @@ namespace Catch { Detail::ChronometerModel<Clock> meter; auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters)); - return { meter.elapsed(), std::move(result), iters }; + return { meter.elapsed(), CATCH_MOVE(result), iters }; } template <typename Clock, typename Fun> - using run_for_at_least_argument_t = typename std::conditional<is_callable<Fun(Chronometer)>::value, Chronometer, int>::type; + using run_for_at_least_argument_t = std::conditional_t<is_callable<Fun(Chronometer)>::value, Chronometer, int>; [[noreturn]] void throw_optimized_away_error(); template <typename Clock, typename Fun> - TimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>> run_for_at_least(ClockDuration<Clock> how_long, int seed, Fun&& fun) { - auto iters = seed; + TimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>> + run_for_at_least(ClockDuration<Clock> how_long, + const int initial_iterations, + Fun&& fun) { + auto iters = initial_iterations; while (iters < (1 << 30)) { auto&& Timing = measure_one<Clock>(fun, iters, is_callable<Fun(Chronometer)>()); if (Timing.elapsed >= how_long) { - return { Timing.elapsed, std::move(Timing.result), iters }; + return { Timing.elapsed, CATCH_MOVE(Timing.result), iters }; } iters *= 2; } @@ -2222,6 +2165,7 @@ namespace Catch { #endif // CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED #include <algorithm> +#include <iterator> namespace Catch { namespace Benchmark { @@ -2279,13 +2223,16 @@ namespace Catch { #include <numeric> #include <tuple> #include <cmath> -#include <utility> namespace Catch { namespace Benchmark { namespace Detail { using sample = std::vector<double>; + // Used when we know we want == comparison of two doubles + // to centralize warning suppression + bool directCompare( double lhs, double rhs ); + double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last); template <typename Iterator> @@ -2316,12 +2263,12 @@ namespace Catch { double mean(Iterator first, Iterator last) { auto count = last - first; double sum = std::accumulate(first, last, 0.); - return sum / count; + return sum / static_cast<double>(count); } template <typename Estimator, typename Iterator> sample jackknife(Estimator&& estimator, Iterator first, Iterator last) { - auto n = last - first; + auto n = static_cast<size_t>(last - first); auto second = first; ++second; sample results; @@ -2362,23 +2309,26 @@ namespace Catch { }); double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5)); - int n = static_cast<int>(resample.size()); - double prob_n = std::count_if(resample.begin(), resample.end(), [point](double x) { return x < point; }) / (double)n; + long n = static_cast<long>(resample.size()); + double prob_n = std::count_if(resample.begin(), resample.end(), [point](double x) { return x < point; }) / static_cast<double>(n); // degenerate case with uniform samples - if (prob_n == 0) return { point, point, point, confidence_level }; + if ( directCompare( prob_n, 0. ) ) { + return { point, point, point, confidence_level }; + } double bias = normal_quantile(prob_n); double z1 = normal_quantile((1. - confidence_level) / 2.); - auto cumn = [n](double x) -> int { - return std::lround(normal_cdf(x) * n); }; + auto cumn = [n]( double x ) -> long { + return std::lround( normal_cdf( x ) * n ); + }; auto a = [bias, accel](double b) { return bias + b / (1. - accel * b); }; double b1 = bias + z1; double b2 = bias - z1; double a1 = a(b1); double a2 = a(b2); - auto lo = std::max(cumn(a1), 0); - auto hi = std::min(cumn(a2), n - 1); + auto lo = static_cast<size_t>((std::max)(cumn(a1), 0l)); + auto hi = static_cast<size_t>((std::min)(cumn(a2), n - 1)); return { point, resample[lo], resample[hi], confidence_level }; } @@ -2391,7 +2341,7 @@ namespace Catch { double outlier_variance; }; - bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last); + bootstrap_analysis analyse_samples(double confidence_level, unsigned int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last); } // namespace Detail } // namespace Benchmark } // namespace Catch @@ -2409,11 +2359,11 @@ namespace Catch { template <typename Clock> std::vector<double> resolution(int k) { std::vector<TimePoint<Clock>> times; - times.reserve(k + 1); + times.reserve(static_cast<size_t>(k + 1)); std::generate_n(std::back_inserter(times), k + 1, now<Clock>{}); std::vector<double> deltas; - deltas.reserve(k); + deltas.reserve(static_cast<size_t>(k)); std::transform(std::next(times.begin()), times.end(), times.begin(), std::back_inserter(deltas), [](TimePoint<Clock> a, TimePoint<Clock> b) { return static_cast<double>((a - b).count()); }); @@ -2447,7 +2397,9 @@ namespace Catch { } template <typename Clock> EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) { - auto time_limit = std::min(resolution * clock_cost_estimation_tick_limit, FloatDuration<Clock>(clock_cost_estimation_time_limit)); + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FloatDuration<Clock>(clock_cost_estimation_time_limit)); auto time_clock = [](int k) { return Detail::measure<Clock>([k] { for (int i = 0; i < k; ++i) { @@ -2461,7 +2413,7 @@ namespace Catch { auto&& r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_cost_estimation_time), iters, time_clock); std::vector<double> times; int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed)); - times.reserve(nsamples); + times.reserve(static_cast<size_t>(nsamples)); std::generate_n(std::back_inserter(times), nsamples, [time_clock, &r] { return static_cast<double>((time_clock(r.iterations) / r.iterations).count()); }); @@ -2473,7 +2425,14 @@ namespace Catch { template <typename Clock> Environment<FloatDuration<Clock>> measure_environment() { - static Environment<FloatDuration<Clock>>* env = nullptr; +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + static Catch::Detail::unique_ptr<Environment<FloatDuration<Clock>>> env; +#if defined(__clang__) +# pragma clang diagnostic pop +#endif if (env) { return *env; } @@ -2482,7 +2441,7 @@ namespace Catch { auto resolution = Detail::estimate_clock_resolution<Clock>(iters); auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean); - env = new Environment<FloatDuration<Clock>>{ resolution, cost }; + env = Catch::Detail::make_unique<Environment<FloatDuration<Clock>>>( Environment<FloatDuration<Clock>>{resolution, cost} ); return *env; } } // namespace Detail @@ -2507,7 +2466,6 @@ namespace Catch { #include <algorithm> #include <vector> -#include <string> #include <iterator> namespace Catch { @@ -2526,7 +2484,7 @@ namespace Catch { samples2.reserve(samples.size()); std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); }); return { - std::move(samples2), + CATCH_MOVE(samples2), mean, standard_deviation, outliers, @@ -2550,7 +2508,7 @@ namespace Catch { SampleAnalysis<Duration> analyse(const IConfig &cfg, Environment<Duration>, Iterator first, Iterator last) { if (!cfg.benchmarkNoAnalysis()) { std::vector<double> samples; - samples.reserve(last - first); + samples.reserve(static_cast<size_t>(last - first)); std::transform(first, last, std::back_inserter(samples), [](Duration d) { return d.count(); }); auto analysis = Catch::Benchmark::Detail::analyse_samples(cfg.benchmarkConfidenceInterval(), cfg.benchmarkResamples(), samples.begin(), samples.end()); @@ -2568,15 +2526,15 @@ namespace Catch { samples2.reserve(samples.size()); std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](double d) { return Duration(d); }); return { - std::move(samples2), + CATCH_MOVE(samples2), wrap_estimate(analysis.mean), wrap_estimate(analysis.standard_deviation), outliers, analysis.outlier_variance, }; } else { - std::vector<Duration> samples; - samples.reserve(last - first); + std::vector<Duration> samples; + samples.reserve(static_cast<size_t>(last - first)); Duration mean = Duration(0); int i = 0; @@ -2587,7 +2545,7 @@ namespace Catch { mean /= i; return { - std::move(samples), + CATCH_MOVE(samples), Estimate<Duration>{mean, mean, mean, 0.0}, Estimate<Duration>{Duration(0), Duration(0), Duration(0), 0.0}, OutlierClassification{}, @@ -2611,11 +2569,11 @@ namespace Catch { namespace Benchmark { struct Benchmark { Benchmark(std::string&& benchmarkName) - : name(std::move(benchmarkName)) {} + : name(CATCH_MOVE(benchmarkName)) {} template <class FUN> Benchmark(std::string&& benchmarkName , FUN &&func) - : fun(std::move(func)), name(std::move(benchmarkName)) {} + : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} template <typename Clock> ExecutionPlan<FloatDuration<Clock>> prepare(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const { @@ -2657,19 +2615,24 @@ namespace Catch { auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end()); BenchmarkStats<FloatDuration<Clock>> stats{ info, analysis.samples, analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; getResultCapture().benchmarkEnded(stats); - + } CATCH_CATCH_ANON (TestFailureException) { + getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); } CATCH_CATCH_ALL{ - if (translateActiveException() != Detail::benchmarkErrorMsg) // benchmark errors have been reported, otherwise rethrow. - std::rethrow_exception(std::current_exception()); + getResultCapture().benchmarkFailed(translateActiveException()); + // We let the exception go further up so that the + // test case is marked as failed. + std::rethrow_exception(std::current_exception()); } } // sets lambda to be used in fun *and* executes benchmark! - template <typename Fun, - typename std::enable_if<!Detail::is_related<Fun, Benchmark>::value, int>::type = 0> + template <typename Fun, std::enable_if_t<!Detail::is_related<Fun, Benchmark>::value, int> = 0> Benchmark & operator=(Fun func) { - fun = Detail::BenchmarkFunction(func); - run(); + auto const* cfg = getCurrentContext().getConfig(); + if (!cfg->skipBenchmarks()) { + fun = Detail::BenchmarkFunction(func); + run(); + } return *this; } @@ -2698,16 +2661,16 @@ namespace Catch { #if defined(CATCH_CONFIG_PREFIX_ALL) #define CATCH_BENCHMARK(...) \ - INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) #define CATCH_BENCHMARK_ADVANCED(name) \ - INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name) + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) #else #define BENCHMARK(...) \ - INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) #define BENCHMARK_ADVANCED(name) \ - INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name) + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) #endif @@ -2719,6 +2682,7 @@ namespace Catch { #ifndef CATCH_CONSTRUCTOR_HPP_INCLUDED #define CATCH_CONSTRUCTOR_HPP_INCLUDED + #include <type_traits> namespace Catch { @@ -2727,9 +2691,7 @@ namespace Catch { template <typename T, bool Destruct> struct ObjectStorage { - using TStorage = typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type; - - ObjectStorage() : data() {} + ObjectStorage() = default; ObjectStorage(const ObjectStorage& other) { @@ -2738,7 +2700,7 @@ namespace Catch { ObjectStorage(ObjectStorage&& other) { - new(&data) T(std::move(other.stored_object())); + new(data) T(CATCH_MOVE(other.stored_object())); } ~ObjectStorage() { destruct_on_exit<T>(); } @@ -2746,11 +2708,11 @@ namespace Catch { template <typename... Args> void construct(Args&&... args) { - new (&data) T(std::forward<Args>(args)...); + new (data) T(CATCH_FORWARD(args)...); } template <bool AllowManualDestruction = !Destruct> - typename std::enable_if<AllowManualDestruction>::type destruct() + std::enable_if_t<AllowManualDestruction> destruct() { stored_object().~T(); } @@ -2758,21 +2720,21 @@ namespace Catch { private: // If this is a constructor benchmark, destruct the underlying object template <typename U> - void destruct_on_exit(typename std::enable_if<Destruct, U>::type* = 0) { destruct<true>(); } + void destruct_on_exit(std::enable_if_t<Destruct, U>* = nullptr) { destruct<true>(); } // Otherwise, don't template <typename U> - void destruct_on_exit(typename std::enable_if<!Destruct, U>::type* = 0) { } + void destruct_on_exit(std::enable_if_t<!Destruct, U>* = nullptr) { } T& stored_object() { - return *static_cast<T*>(static_cast<void*>(&data)); + return *static_cast<T*>(static_cast<void*>(data)); } T const& stored_object() const { - return *static_cast<T*>(static_cast<void*>(&data)); + return *static_cast<T*>(static_cast<void*>(data)); } - TStorage data; + alignas( T ) unsigned char data[sizeof( T )]{}; }; } // namespace Detail @@ -2802,6 +2764,107 @@ namespace Catch { #include <cstddef> #include <type_traits> #include <string> +#include <string.h> + + + + +/** \file + * Wrapper for the WCHAR configuration option + * + * We want to support platforms that do not provide `wchar_t`, so we + * sometimes have to disable providing wchar_t overloads through Catch2, + * e.g. the StringMaker specialization for `std::wstring`. + */ + +#ifndef CATCH_CONFIG_WCHAR_HPP_INCLUDED +#define CATCH_CONFIG_WCHAR_HPP_INCLUDED + +// We assume that WCHAR should be enabled by default, and only disabled +// for a shortlist (so far only DJGPP) of compilers. + +#if defined(__DJGPP__) +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +#if !defined( CATCH_INTERNAL_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_WCHAR ) +# define CATCH_CONFIG_WCHAR +#endif + +#endif // CATCH_CONFIG_WCHAR_HPP_INCLUDED + + +#ifndef CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED +#define CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#include <iosfwd> +#include <cstddef> +#include <ostream> +#include <string> + +namespace Catch { + + class ReusableStringStream : Detail::NonCopyable { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + //! Returns the serialized state + std::string str() const; + //! Sets internal state to `str` + void str(std::string const& str); + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// Old versions of GCC do not understand -Wnonnull-compare +#pragma GCC diagnostic ignored "-Wpragmas" +// Streaming a function pointer triggers Waddress and Wnonnull-compare +// on GCC, because it implicitly converts it to bool and then decides +// that the check it uses (a? true : false) is tautological and cannot +// be null... +#pragma GCC diagnostic ignored "-Waddress" +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + + template<typename T> + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + auto get() -> std::ostream& { return *m_oss; } + }; +} + +#endif // CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#ifndef CATCH_VOID_TYPE_HPP_INCLUDED +#define CATCH_VOID_TYPE_HPP_INCLUDED + + +namespace Catch { + namespace Detail { + + template <typename...> + struct make_void { using type = void; }; + + template <typename... Ts> + using void_t = typename make_void<Ts...>::type; + + } // namespace Detail +} // namespace Catch + + +#endif // CATCH_VOID_TYPE_HPP_INCLUDED #ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED @@ -2823,8 +2886,9 @@ namespace Catch { }; } // namespace Detail - struct IMutableEnumValuesRegistry { - virtual ~IMutableEnumValuesRegistry(); + class IMutableEnumValuesRegistry { + public: + virtual ~IMutableEnumValuesRegistry(); // = default; virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values ) = 0; @@ -2852,10 +2916,26 @@ namespace Catch { #pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless #endif +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy{}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + namespace Catch { + // Bring in global namespace operator<< for ADL lookup in + // `IsStreamInsertable` below. + using ::operator<<; + namespace Detail { - extern const std::string unprintableString; + + constexpr StringRef unprintableString = "{?}"_sr; + + //! Encases `string in quotes, and optionally escapes invisibles + std::string convertIntoString( StringRef string, bool escapeInvisibles ); + + //! Encases `string` in quotes, and escapes invisibles if user requested + //! it via CLI + std::string convertIntoString( StringRef string ); std::string rawMemoryToString( const void *object, std::size_t size ); @@ -2884,7 +2964,7 @@ namespace Catch { std::enable_if_t< !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value, std::string> convertUnstreamable( T const& ) { - return Detail::unprintableString; + return std::string(Detail::unprintableString); } template<typename T> std::enable_if_t< @@ -2988,7 +3068,7 @@ namespace Catch { static std::string convert(char * str); }; -#ifdef CATCH_CONFIG_WCHAR +#if defined(CATCH_CONFIG_WCHAR) template<> struct StringMaker<std::wstring> { static std::string convert(const std::wstring& wstr); @@ -3009,26 +3089,33 @@ namespace Catch { struct StringMaker<wchar_t *> { static std::string convert(wchar_t * str); }; -#endif +#endif // CATCH_CONFIG_WCHAR - // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, - // while keeping string semantics? - template<int SZ> + template<size_t SZ> struct StringMaker<char[SZ]> { static std::string convert(char const* str) { - return ::Catch::Detail::stringify(std::string{ str }); + // Note that `strnlen` is not actually part of standard C++, + // but both POSIX and Windows cstdlib provide it. + return Detail::convertIntoString( + StringRef( str, strnlen( str, SZ ) ) ); } }; - template<int SZ> + template<size_t SZ> struct StringMaker<signed char[SZ]> { static std::string convert(signed char const* str) { - return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); + // See the plain `char const*` overload + auto reinterpreted = reinterpret_cast<char const*>(str); + return Detail::convertIntoString( + StringRef(reinterpreted, strnlen(reinterpreted, SZ))); } }; - template<int SZ> + template<size_t SZ> struct StringMaker<unsigned char[SZ]> { static std::string convert(unsigned char const* str) { - return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); + // See the plain `char const*` overload + auto reinterpreted = reinterpret_cast<char const*>(str); + return Detail::convertIntoString( + StringRef(reinterpreted, strnlen(reinterpreted, SZ))); } }; @@ -3189,13 +3276,11 @@ namespace Catch { template<typename T> struct StringMaker<std::optional<T> > { static std::string convert(const std::optional<T>& optional) { - ReusableStringStream rss; if (optional.has_value()) { - rss << ::Catch::Detail::stringify(*optional); + return ::Catch::Detail::stringify(*optional); } else { - rss << "{ }"; + return "{ }"; } - return rss.str(); } }; } @@ -3276,24 +3361,16 @@ namespace Catch { using std::begin; using std::end; - namespace detail { - template <typename...> - struct void_type { - using type = void; - }; - + namespace Detail { template <typename T, typename = void> - struct is_range_impl : std::false_type { - }; + struct is_range_impl : std::false_type {}; template <typename T> - struct is_range_impl<T, typename void_type<decltype(begin(std::declval<T>()))>::type> : std::true_type { - }; - } // namespace detail + struct is_range_impl<T, void_t<decltype(begin(std::declval<T>()))>> : std::true_type {}; + } // namespace Detail template <typename T> - struct is_range : detail::is_range_impl<T> { - }; + struct is_range : Detail::is_range_impl<T> {}; #if defined(_MANAGED) // Managed types are never ranges template <typename T> @@ -3331,7 +3408,7 @@ namespace Catch { } }; - template <typename T, int SZ> + template <typename T, size_t SZ> struct StringMaker<T[SZ]> { static std::string convert(T const(&arr)[SZ]) { return rangeToString(arr); @@ -3361,27 +3438,27 @@ struct ratio_string { template <> struct ratio_string<std::atto> { - static std::string symbol() { return "a"; } + static char symbol() { return 'a'; } }; template <> struct ratio_string<std::femto> { - static std::string symbol() { return "f"; } + static char symbol() { return 'f'; } }; template <> struct ratio_string<std::pico> { - static std::string symbol() { return "p"; } + static char symbol() { return 'p'; } }; template <> struct ratio_string<std::nano> { - static std::string symbol() { return "n"; } + static char symbol() { return 'n'; } }; template <> struct ratio_string<std::micro> { - static std::string symbol() { return "u"; } + static char symbol() { return 'u'; } }; template <> struct ratio_string<std::milli> { - static std::string symbol() { return "m"; } + static char symbol() { return 'm'; } }; //////////// @@ -3450,7 +3527,7 @@ struct ratio_string<std::milli> { #else std::strftime(timeStamp, timeStampSize, fmt, timeInfo); #endif - return std::string(timeStamp); + return std::string(timeStamp, timeStampSize - 1); } }; } @@ -3494,7 +3571,7 @@ namespace Catch { Approx operator-() const; template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> - Approx operator()( T const& value ) { + Approx operator()( T const& value ) const { Approx approx( static_cast<double>(value) ); approx.m_epsilon = m_epsilon; approx.m_margin = m_margin; @@ -3550,14 +3627,14 @@ namespace Catch { template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> Approx& epsilon( T const& newEpsilon ) { - double epsilonAsDouble = static_cast<double>(newEpsilon); + const auto epsilonAsDouble = static_cast<double>(newEpsilon); setEpsilon(epsilonAsDouble); return *this; } template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> Approx& margin( T const& newMargin ) { - double marginAsDouble = static_cast<double>(newMargin); + const auto marginAsDouble = static_cast<double>(newMargin); setMargin(marginAsDouble); return *this; } @@ -3578,8 +3655,8 @@ namespace Catch { }; namespace literals { - Approx operator "" _a(long double val); - Approx operator "" _a(unsigned long long val); + Approx operator ""_a(long double val); + Approx operator ""_a(unsigned long long val); } // end namespace literals template<> @@ -3638,8 +3715,7 @@ namespace Catch public: WildcardPattern( std::string const& pattern, CaseSensitive caseSensitivity ); - virtual ~WildcardPattern() = default; - virtual bool matches( std::string const& str ) const; + bool matches( std::string const& str ) const; private: std::string normaliseString( std::string const& str ) const; @@ -3656,7 +3732,7 @@ namespace Catch namespace Catch { - struct IConfig; + class IConfig; struct TestCaseInfo; class TestCaseHandle; @@ -3707,11 +3783,11 @@ namespace Catch { bool hasFilters() const; bool matches( TestCaseInfo const& testCase ) const; Matches matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const; - const vectorStrings & getInvalidArgs() const; + const vectorStrings & getInvalidSpecs() const; private: std::vector<Filter> m_filters; - std::vector<std::string> m_invalidArgs; + std::vector<std::string> m_invalidSpecs; friend class TestSpecParser; }; } @@ -3722,17 +3798,366 @@ namespace Catch { #endif // CATCH_TEST_SPEC_HPP_INCLUDED + +#ifndef CATCH_OPTIONAL_HPP_INCLUDED +#define CATCH_OPTIONAL_HPP_INCLUDED + +#include <cassert> + +namespace Catch { + + // An optional type + template<typename T> + class Optional { + public: + Optional() : nullableValue( nullptr ) {} + Optional( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Optional( Optional const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} + + ~Optional() { + reset(); + } + + Optional& operator= ( Optional const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Optional& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; + } + + T& operator*() { + assert(nullableValue); + return *nullableValue; + } + T const& operator*() const { + assert(nullableValue); + return *nullableValue; + } + T* operator->() { + assert(nullableValue); + return nullableValue; + } + const T* operator->() const { + assert(nullableValue); + return nullableValue; + } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + friend bool operator==(Optional const& a, Optional const& b) { + if (a.none() && b.none()) { + return true; + } else if (a.some() && b.some()) { + return *a == *b; + } else { + return false; + } + } + friend bool operator!=(Optional const& a, Optional const& b) { + return !( a == b ); + } + + private: + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +#endif // CATCH_OPTIONAL_HPP_INCLUDED + + +#ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED +#define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + +#include <cstdint> + +namespace Catch { + + enum class GenerateFrom { + Time, + RandomDevice, + //! Currently equivalent to RandomDevice, but can change at any point + Default + }; + + std::uint32_t generateRandomSeed(GenerateFrom from); + +} // end namespace Catch + +#endif // CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED +#define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + + + +#ifndef CATCH_CONSOLE_COLOUR_HPP_INCLUDED +#define CATCH_CONSOLE_COLOUR_HPP_INCLUDED + + +#include <iosfwd> +#include <cstdint> + +namespace Catch { + + enum class ColourMode : std::uint8_t; + class IStream; + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + }; + + class ColourImpl { + protected: + //! The associated stream of this ColourImpl instance + IStream* m_stream; + public: + ColourImpl( IStream* stream ): m_stream( stream ) {} + + //! RAII wrapper around writing specific colour of text using specific + //! colour impl into a stream. + class ColourGuard { + ColourImpl const* m_colourImpl; + Colour::Code m_code; + bool m_engaged = false; + + public: + //! Does **not** engage the guard/start the colour + ColourGuard( Colour::Code code, + ColourImpl const* colour ); + + ColourGuard( ColourGuard const& rhs ) = delete; + ColourGuard& operator=( ColourGuard const& rhs ) = delete; + + ColourGuard( ColourGuard&& rhs ) noexcept; + ColourGuard& operator=( ColourGuard&& rhs ) noexcept; + + //! Removes colour _if_ the guard was engaged + ~ColourGuard(); + + /** + * Explicitly engages colour for given stream. + * + * The API based on operator<< should be preferred. + */ + ColourGuard& engage( std::ostream& stream ) &; + /** + * Explicitly engages colour for given stream. + * + * The API based on operator<< should be preferred. + */ + ColourGuard&& engage( std::ostream& stream ) &&; + + private: + //! Engages the guard and starts using colour + friend std::ostream& operator<<( std::ostream& lhs, + ColourGuard& guard ) { + guard.engageImpl( lhs ); + return lhs; + } + //! Engages the guard and starts using colour + friend std::ostream& operator<<( std::ostream& lhs, + ColourGuard&& guard) { + guard.engageImpl( lhs ); + return lhs; + } + + void engageImpl( std::ostream& stream ); + + }; + + virtual ~ColourImpl(); // = default + /** + * Creates a guard object for given colour and this colour impl + * + * **Important:** + * the guard starts disengaged, and has to be engaged explicitly. + */ + ColourGuard guardColour( Colour::Code colourCode ); + + private: + virtual void use( Colour::Code colourCode ) const = 0; + }; + + //! Provides ColourImpl based on global config and target compilation platform + Detail::unique_ptr<ColourImpl> makeColourImpl( ColourMode colourSelection, + IStream* stream ); + + //! Checks if specific colour impl has been compiled into the binary + bool isColourImplAvailable( ColourMode colourSelection ); + +} // end namespace Catch + +#endif // CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +#include <map> +#include <string> #include <vector> + +namespace Catch { + + enum class ColourMode : std::uint8_t; + + namespace Detail { + //! Splits the reporter spec into reporter name and kv-pair options + std::vector<std::string> splitReporterSpec( StringRef reporterSpec ); + + Optional<ColourMode> stringToColourMode( StringRef colourMode ); + } + + /** + * Structured reporter spec that a reporter can be created from + * + * Parsing has been validated, but semantics have not. This means e.g. + * that the colour mode is known to Catch2, but it might not be + * compiled into the binary, and the output filename might not be + * openable. + */ + class ReporterSpec { + std::string m_name; + Optional<std::string> m_outputFileName; + Optional<ColourMode> m_colourMode; + std::map<std::string, std::string> m_customOptions; + + friend bool operator==( ReporterSpec const& lhs, + ReporterSpec const& rhs ); + friend bool operator!=( ReporterSpec const& lhs, + ReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + + public: + ReporterSpec( + std::string name, + Optional<std::string> outputFileName, + Optional<ColourMode> colourMode, + std::map<std::string, std::string> customOptions ); + + std::string const& name() const { return m_name; } + + Optional<std::string> const& outputFile() const { + return m_outputFileName; + } + + Optional<ColourMode> const& colourMode() const { return m_colourMode; } + + std::map<std::string, std::string> const& customOptions() const { + return m_customOptions; + } + }; + + /** + * Parses provided reporter spec string into + * + * Returns empty optional on errors, e.g. + * * field that is not first and not a key+value pair + * * duplicated keys in kv pair + * * unknown catch reporter option + * * empty key/value in an custom kv pair + * * ... + */ + Optional<ReporterSpec> parseReporterSpec( StringRef reporterSpec ); + +} + +#endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + +#include <chrono> +#include <map> #include <string> +#include <vector> namespace Catch { - struct IStream; + class IStream; + + /** + * `ReporterSpec` but with the defaults filled in. + * + * Like `ReporterSpec`, the semantics are unchecked. + */ + struct ProcessedReporterSpec { + std::string name; + std::string outputFilename; + ColourMode colourMode; + std::map<std::string, std::string> customOptions; + friend bool operator==( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ); + friend bool operator!=( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + }; struct ConfigData { + bool listTests = false; bool listTags = false; bool listReporters = false; + bool listListeners = false; bool showSuccessfulTests = false; bool shouldDebugBreak = false; @@ -3741,10 +4166,15 @@ namespace Catch { bool showInvisibles = false; bool filenamesAsTags = false; bool libIdentify = false; + bool allowZeroTests = false; int abortAfter = -1; - unsigned int rngSeed = 0; + uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default); + + unsigned int shardCount = 1; + unsigned int shardIndex = 0; + bool skipBenchmarks = false; bool benchmarkNoAnalysis = false; unsigned int benchmarkSamples = 100; double benchmarkConfidenceInterval = 0.95; @@ -3756,17 +4186,13 @@ namespace Catch { ShowDurations showDurations = ShowDurations::DefaultForReporter; double minDuration = -1; TestRunOrder runOrder = TestRunOrder::Declared; - UseColour useColour = UseColour::Auto; + ColourMode defaultColourMode = ColourMode::PlatformDefault; WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; - std::string outputFilename; + std::string defaultOutputFilename; std::string name; std::string processName; -#ifndef CATCH_CONFIG_DEFAULT_REPORTER -#define CATCH_CONFIG_DEFAULT_REPORTER "console" -#endif - std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; -#undef CATCH_CONFIG_DEFAULT_REPORTER + std::vector<ReporterSpec> reporterSpecifications; std::vector<std::string> testsOrTags; std::vector<std::string> sectionsToRun; @@ -3780,14 +4206,14 @@ namespace Catch { Config( ConfigData const& data ); ~Config() override; // = default in the cpp file - std::string const& getFilename() const; - bool listTests() const; bool listTags() const; bool listReporters() const; + bool listListeners() const; - std::string getProcessName() const; - std::string const& getReporterName() const; + std::vector<ReporterSpec> const& getReporterSpecs() const; + std::vector<ProcessedReporterSpec> const& + getProcessedReporterSpecs() const; std::vector<std::string> const& getTestsOrTags() const override; std::vector<std::string> const& getSectionsToRun() const override; @@ -3799,36 +4225,35 @@ namespace Catch { // IConfig interface bool allowThrows() const override; - std::ostream& stream() const override; - std::string name() const override; + StringRef name() const override; bool includeSuccessfulResults() const override; bool warnAboutMissingAssertions() const override; - bool warnAboutNoTests() const override; + bool warnAboutUnmatchedTestSpecs() const override; + bool zeroTestsCountAsSuccess() const override; ShowDurations showDurations() const override; double minDuration() const override; TestRunOrder runOrder() const override; - unsigned int rngSeed() const override; - UseColour useColour() const override; + uint32_t rngSeed() const override; + unsigned int shardCount() const override; + unsigned int shardIndex() const override; + ColourMode defaultColourMode() const override; bool shouldDebugBreak() const override; int abortAfter() const override; bool showInvisibles() const override; Verbosity verbosity() const override; + bool skipBenchmarks() const override; bool benchmarkNoAnalysis() const override; - int benchmarkSamples() const override; + unsigned int benchmarkSamples() const override; double benchmarkConfidenceInterval() const override; unsigned int benchmarkResamples() const override; std::chrono::milliseconds benchmarkWarmupTime() const override; private: - - IStream const* openStream(); ConfigData m_data; - - Detail::unique_ptr<IStream const> m_stream; + std::vector<ProcessedReporterSpec> m_processedReporterSpecs; TestSpec m_testSpec; bool m_hasTestFilters = false; }; - } // end namespace Catch #endif // CATCH_CONFIG_HPP_INCLUDED @@ -3838,11 +4263,37 @@ namespace Catch { #define CATCH_MESSAGE_HPP_INCLUDED + +#ifndef CATCH_STREAM_END_STOP_HPP_INCLUDED +#define CATCH_STREAM_END_STOP_HPP_INCLUDED + + +namespace Catch { + + // Use this in variadic streaming macros to allow + // << +StreamEndStop + // as well as + // << stuff +StreamEndStop + struct StreamEndStop { + StringRef operator+() const { return StringRef(); } + + template <typename T> + friend T const& operator+( T const& value, StreamEndStop ) { + return value; + } + }; + +} // namespace Catch + +#endif // CATCH_STREAM_END_STOP_HPP_INCLUDED + #include <string> #include <vector> namespace Catch { + struct SourceLineInfo; + struct MessageStream { template<typename T> @@ -3855,9 +4306,11 @@ namespace Catch { }; struct MessageBuilder : MessageStream { - MessageBuilder( StringRef const& macroName, + MessageBuilder( StringRef macroName, SourceLineInfo const& lineInfo, - ResultWas::OfType type ); + ResultWas::OfType type ): + m_info(macroName, lineInfo, type) {} + template<typename T> MessageBuilder& operator << ( T const& value ) { @@ -3965,97 +4418,6 @@ namespace Catch { #endif // CATCH_MESSAGE_HPP_INCLUDED -#ifndef CATCH_REPORTER_REGISTRARS_HPP_INCLUDED -#define CATCH_REPORTER_REGISTRARS_HPP_INCLUDED - - - -#ifndef CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED -#define CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED - -namespace Catch { - - struct ReporterConfig; - - struct IReporterFactory { - virtual ~IReporterFactory(); // = default - - virtual IStreamingReporterPtr - create( ReporterConfig const& config ) const = 0; - virtual std::string getDescription() const = 0; - }; - using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>; -} // namespace Catch - -#endif // CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED - -namespace Catch { - - template <typename T> - class ReporterFactory : public IReporterFactory { - - IStreamingReporterPtr create( ReporterConfig const& config ) const override { - return Detail::make_unique<T>( config ); - } - - std::string getDescription() const override { - return T::getDescription(); - } - }; - - - template<typename T> - class ReporterRegistrar { - public: - explicit ReporterRegistrar( std::string const& name ) { - getMutableRegistryHub().registerReporter( name, Detail::make_unique<ReporterFactory<T>>() ); - } - }; - - template<typename T> - class ListenerRegistrar { - - class ListenerFactory : public IReporterFactory { - - IStreamingReporterPtr create( ReporterConfig const& config ) const override { - return Detail::make_unique<T>(config); - } - std::string getDescription() const override { - return std::string(); - } - }; - - public: - - ListenerRegistrar() { - getMutableRegistryHub().registerListener( Detail::make_unique<ListenerFactory>() ); - } - }; -} - -#if !defined(CATCH_CONFIG_DISABLE) - -#define CATCH_REGISTER_REPORTER( name, reporterType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -#define CATCH_REGISTER_LISTENER( listenerType ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#else // CATCH_CONFIG_DISABLE - -#define CATCH_REGISTER_REPORTER(name, reporterType) -#define CATCH_REGISTER_LISTENER(listenerType) - -#endif // CATCH_CONFIG_DISABLE - -#endif // CATCH_REPORTER_REGISTRARS_HPP_INCLUDED - - #ifndef CATCH_SESSION_HPP_INCLUDED #define CATCH_SESSION_HPP_INCLUDED @@ -4092,11 +4454,11 @@ namespace Catch { #include <cassert> -#include <cctype> #include <memory> #include <ostream> #include <sstream> #include <string> +#include <type_traits> #include <vector> namespace Catch { @@ -4113,7 +4475,25 @@ namespace Catch { ShortCircuitSame }; + struct accept_many_t {}; + constexpr accept_many_t accept_many {}; + namespace Detail { + struct fake_arg { + template <typename T> + operator T(); + }; + + template <typename F, typename = void> + struct is_unary_function : std::false_type {}; + + template <typename F> + struct is_unary_function< + F, + Catch::Detail::void_t<decltype( + std::declval<F>()( fake_arg() ) ) + > + > : std::true_type {}; // Traits for extracting arg and return type of lambdas (for single // argument lambdas) @@ -4129,8 +4509,7 @@ namespace Catch { template <typename ClassT, typename ReturnT, typename ArgT> struct UnaryLambdaTraits<ReturnT ( ClassT::* )( ArgT ) const> { static const bool isValid = true; - using ArgType = typename std::remove_const< - typename std::remove_reference<ArgT>::type>::type; + using ArgType = std::remove_const_t<std::remove_reference_t<ArgT>>; using ReturnType = ReturnT; }; @@ -4264,20 +4643,20 @@ namespace Catch { return { ResultType::Ok, value }; } static auto ok() -> BasicResult { return { ResultType::Ok }; } - static auto logicError( std::string const& message ) + static auto logicError( std::string&& message ) -> BasicResult { - return { ResultType::LogicError, message }; + return { ResultType::LogicError, CATCH_MOVE(message) }; } - static auto runtimeError( std::string const& message ) + static auto runtimeError( std::string&& message ) -> BasicResult { - return { ResultType::RuntimeError, message }; + return { ResultType::RuntimeError, CATCH_MOVE(message) }; } explicit operator bool() const { return m_type == ResultType::Ok; } auto type() const -> ResultType { return m_type; } - auto errorMessage() const -> std::string { + auto errorMessage() const -> std::string const& { return m_errorMessage; } @@ -4296,8 +4675,8 @@ namespace Catch { m_errorMessage; // Only populated if resultType is an error BasicResult( ResultType type, - std::string const& message ): - ResultValueBase<T>( type ), m_errorMessage( message ) { + std::string&& message ): + ResultValueBase<T>( type ), m_errorMessage( CATCH_MOVE(message) ) { assert( m_type != ResultType::Ok ); } @@ -4353,7 +4732,7 @@ namespace Catch { T temp; auto result = convertInto( source, temp ); if ( result ) - target = std::move( temp ); + target = CATCH_MOVE( temp ); return result; } #endif // CLARA_CONFIG_OPTIONAL_TYPE @@ -4454,6 +4833,11 @@ namespace Catch { } }; + template <typename L> struct BoundManyLambda : BoundLambda<L> { + explicit BoundManyLambda( L const& lambda ): BoundLambda<L>( lambda ) {} + bool isContainer() const override { return true; } + }; + template <typename L> struct BoundFlagLambda : BoundFlagRefBase { L m_lambda; @@ -4508,12 +4892,23 @@ namespace Catch { m_ref( ref ) {} public: - template <typename T> + template <typename LambdaT> + ParserRefImpl( accept_many_t, + LambdaT const& ref, + std::string const& hint ): + m_ref( std::make_shared<BoundManyLambda<LambdaT>>( ref ) ), + m_hint( hint ) {} + + template <typename T, + typename = typename std::enable_if_t< + !Detail::is_unary_function<T>::value>> ParserRefImpl( T& ref, std::string const& hint ): m_ref( std::make_shared<BoundValueRef<T>>( ref ) ), m_hint( hint ) {} - template <typename LambdaT> + template <typename LambdaT, + typename = typename std::enable_if_t< + Detail::is_unary_function<LambdaT>::value>> ParserRefImpl( LambdaT const& ref, std::string const& hint ): m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ), m_hint( hint ) {} @@ -4554,6 +4949,7 @@ namespace Catch { class Arg : public Detail::ParserRefImpl<Arg> { public: using ParserRefImpl::ParserRefImpl; + using ParserBase::parse; Detail::InternalParseResult parse(std::string const&, @@ -4573,13 +4969,21 @@ namespace Catch { explicit Opt(bool& ref); + template <typename LambdaT, + typename = typename std::enable_if_t< + Detail::is_unary_function<LambdaT>::value>> + Opt( LambdaT const& ref, std::string const& hint ): + ParserRefImpl( ref, hint ) {} + template <typename LambdaT> - Opt(LambdaT const& ref, std::string const& hint) : - ParserRefImpl(ref, hint) {} + Opt( accept_many_t, LambdaT const& ref, std::string const& hint ): + ParserRefImpl( accept_many, ref, hint ) {} - template <typename T> - Opt(T& ref, std::string const& hint) : - ParserRefImpl(ref, hint) {} + template <typename T, + typename = typename std::enable_if_t< + !Detail::is_unary_function<T>::value>> + Opt( T& ref, std::string const& hint ): + ParserRefImpl( ref, hint ) {} auto operator[](std::string const& optName) -> Opt& { m_optNames.push_back(optName); @@ -4604,12 +5008,6 @@ namespace Catch { std::shared_ptr<std::string> m_name; std::shared_ptr<Detail::BoundValueRefBase> m_ref; - template <typename LambdaT> - static auto makeRef(LambdaT const& lambda) - -> std::shared_ptr<Detail::BoundValueRefBase> { - return std::make_shared<Detail::BoundLambda<LambdaT>>(lambda); - } - public: ExeName(); explicit ExeName(std::string& ref); @@ -4879,7 +5277,11 @@ namespace Catch { namespace Catch { - struct ITransientExpression { + class ITransientExpression { + bool m_isBinaryExpression; + bool m_result; + + public: auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } auto getResult() const -> bool { return m_result; } virtual void streamReconstructedExpression( std::ostream &os ) const = 0; @@ -4897,8 +5299,6 @@ namespace Catch { // complain if it's not here :-( virtual ~ITransientExpression(); // = default; - bool m_isBinaryExpression; - bool m_result; friend std::ostream& operator<<(std::ostream& out, ITransientExpression const& expr) { expr.streamReconstructedExpression(out); return out; @@ -5029,60 +5429,53 @@ namespace Catch { public: explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} - template<typename RhsT> - auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return { compareEqual( m_lhs, rhs ), m_lhs, "=="_sr, rhs }; + template<typename RhsT, std::enable_if_t<!std::is_arithmetic<std::remove_reference_t<RhsT>>::value, int> = 0> + friend auto operator == ( ExprLhs && lhs, RhsT && rhs ) -> BinaryExpr<LhsT, RhsT const&> { + return { compareEqual( lhs.m_lhs, rhs ), lhs.m_lhs, "=="_sr, rhs }; } - auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const { - return { m_lhs == rhs, m_lhs, "=="_sr, rhs }; + template<typename RhsT, std::enable_if_t<std::is_arithmetic<RhsT>::value, int> = 0> + friend auto operator == ( ExprLhs && lhs, RhsT rhs ) -> BinaryExpr<LhsT, RhsT> { + return { compareEqual( lhs.m_lhs, rhs ), lhs.m_lhs, "=="_sr, rhs }; } - template<typename RhsT> - auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return { compareNotEqual( m_lhs, rhs ), m_lhs, "!="_sr, rhs }; + template<typename RhsT, std::enable_if_t<!std::is_arithmetic<std::remove_reference_t<RhsT>>::value, int> = 0> + friend auto operator != ( ExprLhs && lhs, RhsT && rhs ) -> BinaryExpr<LhsT, RhsT const&> { + return { compareNotEqual( lhs.m_lhs, rhs ), lhs.m_lhs, "!="_sr, rhs }; } - auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const { - return { m_lhs != rhs, m_lhs, "!="_sr, rhs }; + template<typename RhsT, std::enable_if_t<std::is_arithmetic<RhsT>::value, int> = 0> + friend auto operator != ( ExprLhs && lhs, RhsT rhs ) -> BinaryExpr<LhsT, RhsT> { + return { compareNotEqual( lhs.m_lhs, rhs ), lhs.m_lhs, "!="_sr, rhs }; } - template<typename RhsT> - auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return { static_cast<bool>(m_lhs > rhs), m_lhs, ">"_sr, rhs }; - } - template<typename RhsT> - auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return { static_cast<bool>(m_lhs < rhs), m_lhs, "<"_sr, rhs }; - } - template<typename RhsT> - auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return { static_cast<bool>(m_lhs >= rhs), m_lhs, ">="_sr, rhs }; - } - template<typename RhsT> - auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return { static_cast<bool>(m_lhs <= rhs), m_lhs, "<="_sr, rhs }; - } - template <typename RhsT> - auto operator | (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const { - return { static_cast<bool>(m_lhs | rhs), m_lhs, "|"_sr, rhs }; - } - template <typename RhsT> - auto operator & (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const { - return { static_cast<bool>(m_lhs & rhs), m_lhs, "&"_sr, rhs }; - } - template <typename RhsT> - auto operator ^ (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const { - return { static_cast<bool>(m_lhs ^ rhs), m_lhs, "^"_sr, rhs }; + #define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(op) \ + template<typename RhsT, std::enable_if_t<!std::is_arithmetic<std::remove_reference_t<RhsT>>::value, int> = 0> \ + friend auto operator op ( ExprLhs && lhs, RhsT && rhs ) -> BinaryExpr<LhsT, RhsT const&> { \ + return { static_cast<bool>(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template<typename RhsT, std::enable_if_t<std::is_arithmetic<RhsT>::value, int> = 0> \ + friend auto operator op ( ExprLhs && lhs, RhsT rhs ) -> BinaryExpr<LhsT, RhsT> { \ + return { static_cast<bool>(lhs.m_lhs op rhs), lhs.m_lhs, #op##_sr, rhs }; \ } + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(<) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(>) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(<=) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(>=) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(|) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(&) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(^) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR + template<typename RhsT> - auto operator && ( RhsT const& ) -> BinaryExpr<LhsT, RhsT const&> const { + friend auto operator && ( ExprLhs &&, RhsT && ) -> BinaryExpr<LhsT, RhsT const&> { static_assert(always_false<RhsT>::value, "operator&& is not supported inside assertions, " "wrap the expression inside parentheses, or decompose it"); } template<typename RhsT> - auto operator || ( RhsT const& ) -> BinaryExpr<LhsT, RhsT const&> const { + friend auto operator || ( ExprLhs &&, RhsT && ) -> BinaryExpr<LhsT, RhsT const&> { static_assert(always_false<RhsT>::value, "operator|| is not supported inside assertions, " "wrap the expression inside parentheses, or decompose it"); @@ -5093,21 +5486,15 @@ namespace Catch { } }; - void handleExpression( ITransientExpression const& expr ); - - template<typename T> - void handleExpression( ExprLhs<T> const& expr ) { - handleExpression( expr.makeUnaryExpr() ); - } - struct Decomposer { - template<typename T> - auto operator <= ( T const& lhs ) -> ExprLhs<T const&> { - return ExprLhs<T const&>{ lhs }; + template<typename T, std::enable_if_t<!std::is_arithmetic<std::remove_reference_t<T>>::value, int> = 0> + friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs<T const&> { + return ExprLhs<const T&>{ lhs }; } - auto operator <=( bool value ) -> ExprLhs<bool> { - return ExprLhs<bool>{ value }; + template<typename T, std::enable_if_t<std::is_arithmetic<T>::value, int> = 0> + friend auto operator <= ( Decomposer &&, T value ) -> ExprLhs<T> { + return ExprLhs<T>{ value }; } }; @@ -5124,12 +5511,11 @@ namespace Catch { #endif // CATCH_DECOMPOSER_HPP_INCLUDED +#include <string> + namespace Catch { - struct TestFailureException{}; - struct AssertionResultData; - struct IResultCapture; - class RunContext; + class IResultCapture; struct AssertionReaction { bool shouldDebugBreak = false; @@ -5144,7 +5530,7 @@ namespace Catch { public: AssertionHandler - ( StringRef const& macroName, + ( StringRef macroName, SourceLineInfo const& lineInfo, StringRef capturedExpression, ResultDisposition::Flags resultDisposition ); @@ -5161,7 +5547,7 @@ namespace Catch { } void handleExpr( ITransientExpression const& expr ); - void handleMessage(ResultWas::OfType resultType, StringRef const& message); + void handleMessage(ResultWas::OfType resultType, StringRef message); void handleExceptionThrownAsExpected(); void handleUnexpectedExceptionNotThrown(); @@ -5176,7 +5562,7 @@ namespace Catch { auto allowThrows() const -> bool; }; - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ); + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); } // namespace Catch @@ -5215,7 +5601,7 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ - do { \ + do { /* NOLINT(bugprone-infinite-loop) */ \ /* The expression should not be evaluated, but warnings should hopefully be checked */ \ CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ @@ -5226,7 +5612,8 @@ namespace Catch { CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( (void)0, (false) && static_cast<bool>( !!(__VA_ARGS__) ) ) + } while( (void)0, (false) && static_cast<const bool&>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ @@ -5314,243 +5701,6 @@ namespace Catch { #endif // CATCH_TEST_MACRO_IMPL_HPP_INCLUDED -#ifndef CATCH_PREPROCESSOR_HPP_INCLUDED -#define CATCH_PREPROCESSOR_HPP_INCLUDED - - -#if defined(__GNUC__) -// We need to silence "empty __VA_ARGS__ warning", and using just _Pragma does not work -#pragma GCC system_header -#endif - - -#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__))) -#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) - -#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ -// MSVC needs more evaluations -#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) -#else -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) -#endif - -#define CATCH_REC_END(...) -#define CATCH_REC_OUT - -#define CATCH_EMPTY() -#define CATCH_DEFER(id) id CATCH_EMPTY() - -#define CATCH_REC_GET_END2() 0, CATCH_REC_END -#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 -#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 -#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT -#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) -#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) - -#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) - -#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) - -// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, -// and passes userdata as the first parameter to each invocation, -// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) -#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) -#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ -#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ -#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) -#else -// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) -#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) -#endif - -#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ -#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) - -#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>()) -#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<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) -#endif - -#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ - CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) - -#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) -#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) -#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) -#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) -#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) -#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) -#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) -#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) -#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) -#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) -#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) - -#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N - -#define INTERNAL_CATCH_TYPE_GEN\ - template<typename...> struct TypeList {};\ - template<typename...Ts>\ - constexpr auto get_wrapper() noexcept -> TypeList<Ts...> { return {}; }\ - template<template<typename...> class...> struct TemplateTypeList{};\ - template<template<typename...> class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList<Cs...> { return {}; }\ - template<typename...>\ - struct append;\ - template<typename...>\ - struct rewrap;\ - template<template<typename...> class, typename...>\ - struct create;\ - template<template<typename...> class, typename>\ - struct convert;\ - \ - template<typename T> \ - struct append<T> { using type = T; };\ - template< template<typename...> class L1, typename...E1, template<typename...> class L2, typename...E2, typename...Rest>\ - struct append<L1<E1...>, L2<E2...>, Rest...> { using type = typename append<L1<E1...,E2...>, Rest...>::type; };\ - template< template<typename...> class L1, typename...E1, typename...Rest>\ - struct append<L1<E1...>, TypeList<mpl_::na>, Rest...> { using type = L1<E1...>; };\ - \ - template< template<typename...> class Container, template<typename...> class List, typename...elems>\ - struct rewrap<TemplateTypeList<Container>, List<elems...>> { using type = TypeList<Container<elems...>>; };\ - template< template<typename...> class Container, template<typename...> class List, class...Elems, typename...Elements>\ - struct rewrap<TemplateTypeList<Container>, List<Elems...>, Elements...> { using type = typename append<TypeList<Container<Elems...>>, typename rewrap<TemplateTypeList<Container>, Elements...>::type>::type; };\ - \ - template<template <typename...> class Final, template< typename...> class...Containers, typename...Types>\ - struct create<Final, TemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<TemplateTypeList<Containers>, Types...>::type...>::type; };\ - template<template <typename...> class Final, template <typename...> class List, typename...Ts>\ - struct convert<Final, List<Ts...>> { using type = typename append<Final<>,TypeList<Ts>...>::type; }; - -#define INTERNAL_CATCH_NTTP_1(signature, ...)\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)> struct Nttp{};\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ - constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \ - template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...> struct NttpTemplateTypeList{};\ - template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Cs>\ - constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \ - \ - template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\ - struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> { using type = TypeList<Container<__VA_ARGS__>>; };\ - template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature), typename...Elements>\ - struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>, Elements...> { using type = typename append<TypeList<Container<__VA_ARGS__>>, typename rewrap<NttpTemplateTypeList<Container>, Elements...>::type>::type; };\ - template<template <typename...> class Final, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Containers, typename...Types>\ - struct create<Final, NttpTemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<NttpTemplateTypeList<Containers>, Types...>::type...>::type; }; - -#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName) -#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature)\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ - static void TestName() -#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...)\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ - static void TestName() - -#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName) -#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature)\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ - static void TestName() -#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature,...)\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ - static void TestName() - -#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\ - template<typename Type>\ - void reg_test(TypeList<Type>, Catch::NameAndTags nameAndTags)\ - {\ - Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Type>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ - } - -#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ - void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\ - {\ - Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ - } - -#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...)\ - template<typename Type>\ - void reg_test(TypeList<Type>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ - {\ - Catch::AutoReg( Catch::makeTestInvoker(&TestName<Type>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ - } - -#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...)\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ - void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ - {\ - Catch::AutoReg( Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ - } - -#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName) -#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature)\ - template<typename TestType> \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<TestType> { \ - void test();\ - } - -#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...)\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> { \ - void test();\ - } - -#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName) -#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature)\ - template<typename TestType> \ - void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<TestType>::test() -#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...)\ - template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ - void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test() - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_NTTP_0 -#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__),INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_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_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__) -#else -#define INTERNAL_CATCH_NTTP_0(signature) -#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)( __VA_ARGS__)) -#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_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__)) -#endif - -#endif // CATCH_PREPROCESSOR_HPP_INCLUDED - - #ifndef CATCH_SECTION_HPP_INCLUDED #define CATCH_SECTION_HPP_INCLUDED @@ -5563,9 +5713,6 @@ namespace Catch { namespace Catch { - auto getCurrentNanosecondsSinceEpoch() -> uint64_t; - auto getEstimatedClockResolution() -> uint64_t; - class Timer { uint64_t m_nanoseconds = 0; public: @@ -5580,8 +5727,6 @@ namespace Catch { #endif // CATCH_TIMER_HPP_INCLUDED -#include <string> - namespace Catch { class Section : Detail::NonCopyable { @@ -5632,16 +5777,18 @@ namespace Catch { class TestSpec; struct TestCaseInfo; - struct ITestInvoker { + class ITestInvoker { + public: virtual void invoke () const = 0; - virtual ~ITestInvoker(); + virtual ~ITestInvoker(); // = default }; class TestCaseHandle; - struct IConfig; + class IConfig; - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); + class ITestCaseRegistry { + public: + virtual ~ITestCaseRegistry(); // = default // TODO: this exists only for adding filenames to test cases -- let's expose this in a saner way later virtual std::vector<TestCaseInfo* > const& getAllInfos() const = 0; virtual std::vector<TestCaseHandle> const& getAllTests() const = 0; @@ -5657,6 +5804,20 @@ namespace Catch { #endif // CATCH_INTERFACES_TESTCASE_HPP_INCLUDED + +#ifndef CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED +#define CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +#define INTERNAL_CATCH_EXPAND1( param ) INTERNAL_CATCH_EXPAND2( param ) +#define INTERNAL_CATCH_EXPAND2( ... ) INTERNAL_CATCH_NO##__VA_ARGS__ +#define INTERNAL_CATCH_DEF( ... ) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#define INTERNAL_CATCH_REMOVE_PARENS( ... ) \ + INTERNAL_CATCH_EXPAND1( INTERNAL_CATCH_DEF __VA_ARGS__ ) + +#endif // CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + // GCC 5 and older do not properly handle disabling unused-variable warning // with a _Pragma. This means that we have to leak the suppression to the // user code as well :-( @@ -5684,19 +5845,19 @@ Detail::unique_ptr<ITestInvoker> makeTestInvoker( void(*testAsFunction)() ); template<typename C> Detail::unique_ptr<ITestInvoker> makeTestInvoker( void (C::*testAsMethod)() ) { - return Detail::unique_ptr<ITestInvoker>( new TestInvokerAsMethod<C>(testAsMethod) ); + return Detail::make_unique<TestInvokerAsMethod<C>>( testAsMethod ); } struct NameAndTags { - NameAndTags(StringRef const& name_ = StringRef(), - StringRef const& tags_ = StringRef()) noexcept: - name(name_), tags(tags_) {} + constexpr NameAndTags( StringRef name_ = StringRef(), + StringRef tags_ = StringRef() ) noexcept: + name( name_ ), tags( tags_ ) {} StringRef name; StringRef tags; }; struct AutoReg : Detail::NonCopyable { - AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept; + AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept; }; } // end namespace Catch @@ -5722,7 +5883,7 @@ struct AutoReg : Detail::NonCopyable { CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ @@ -5744,7 +5905,7 @@ struct AutoReg : Detail::NonCopyable { CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ void TestName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ - INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ @@ -5758,6 +5919,7 @@ struct AutoReg : Detail::NonCopyable { #endif // CATCH_TEST_REGISTRY_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. @@ -5774,8 +5936,8 @@ struct AutoReg : Detail::NonCopyable { #define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) - #define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) #define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) #define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) @@ -5796,9 +5958,13 @@ struct AutoReg : Detail::NonCopyable { #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) #define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) #define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) #else #define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) #define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) CATCH_CHECK( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) CATCH_CHECK_FALSE( __VA_ARGS__ ) #endif @@ -5831,8 +5997,8 @@ struct AutoReg : Detail::NonCopyable { #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) #define CATCH_CHECK_NOTHROW( ... ) (void)(0) - #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) - #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) #define CATCH_SECTION( ... ) @@ -5843,10 +6009,12 @@ struct AutoReg : Detail::NonCopyable { #define CATCH_STATIC_REQUIRE( ... ) (void)(0) #define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define CATCH_STATIC_CHECK( ... ) (void)(0) + #define CATCH_STATIC_CHECK_FALSE( ... ) (void)(0) // "BDD-style" convenience wrappers - #define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) - #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + #define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) #define CATCH_GIVEN( desc ) #define CATCH_AND_GIVEN( desc ) #define CATCH_WHEN( desc ) @@ -5865,8 +6033,8 @@ struct AutoReg : Detail::NonCopyable { #define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) - #define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) #define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) #define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) @@ -5887,9 +6055,13 @@ struct AutoReg : Detail::NonCopyable { #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) #define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) #define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) + #define STATIC_CHECK( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) #else #define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) #define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) + #define STATIC_CHECK( ... ) CHECK( __VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) CHECK_FALSE( __VA_ARGS__ ) #endif // "BDD-style" convenience wrappers @@ -5921,8 +6093,8 @@ struct AutoReg : Detail::NonCopyable { #define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) #define CHECK_NOTHROW( ... ) (void)(0) - #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__) - #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) #define METHOD_AS_TEST_CASE( method, ... ) #define REGISTER_TEST_CASE( Function, ... ) (void)(0) #define SECTION( ... ) @@ -5933,10 +6105,12 @@ struct AutoReg : Detail::NonCopyable { #define STATIC_REQUIRE( ... ) (void)(0) #define STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define STATIC_CHECK( ... ) (void)(0) + #define STATIC_CHECK_FALSE( ... ) (void)(0) // "BDD-style" convenience wrappers - #define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) - #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + #define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ) ) + #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) #define GIVEN( desc ) #define AND_GIVEN( desc ) @@ -5956,6 +6130,238 @@ struct AutoReg : Detail::NonCopyable { #define CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_PREPROCESSOR_HPP_INCLUDED +#define CATCH_PREPROCESSOR_HPP_INCLUDED + + +#if defined(__GNUC__) +// We need to silence "empty __VA_ARGS__ warning", and using just _Pragma does not work +#pragma GCC system_header +#endif + + +#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__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#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<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>()) +#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<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template<typename...> struct TypeList {};\ + template<typename...Ts>\ + constexpr auto get_wrapper() noexcept -> TypeList<Ts...> { return {}; }\ + template<template<typename...> class...> struct TemplateTypeList{};\ + template<template<typename...> class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList<Cs...> { return {}; }\ + template<typename...>\ + struct append;\ + template<typename...>\ + struct rewrap;\ + template<template<typename...> class, typename...>\ + struct create;\ + template<template<typename...> class, typename>\ + struct convert;\ + \ + template<typename T> \ + struct append<T> { using type = T; };\ + template< template<typename...> class L1, typename...E1, template<typename...> class L2, typename...E2, typename...Rest>\ + struct append<L1<E1...>, L2<E2...>, Rest...> { using type = typename append<L1<E1...,E2...>, Rest...>::type; };\ + template< template<typename...> class L1, typename...E1, typename...Rest>\ + struct append<L1<E1...>, TypeList<mpl_::na>, Rest...> { using type = L1<E1...>; };\ + \ + template< template<typename...> class Container, template<typename...> class List, typename...elems>\ + struct rewrap<TemplateTypeList<Container>, List<elems...>> { using type = TypeList<Container<elems...>>; };\ + template< template<typename...> class Container, template<typename...> class List, class...Elems, typename...Elements>\ + struct rewrap<TemplateTypeList<Container>, List<Elems...>, Elements...> { using type = typename append<TypeList<Container<Elems...>>, typename rewrap<TemplateTypeList<Container>, Elements...>::type>::type; };\ + \ + template<template <typename...> class Final, template< typename...> class...Containers, typename...Types>\ + struct create<Final, TemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<TemplateTypeList<Containers>, Types...>::type...>::type; };\ + template<template <typename...> class Final, template <typename...> class List, typename...Ts>\ + struct convert<Final, List<Ts...>> { using type = typename append<Final<>,TypeList<Ts>...>::type; }; + +#define INTERNAL_CATCH_NTTP_1(signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> struct Nttp{};\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \ + template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...> struct NttpTemplateTypeList{};\ + template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Cs>\ + constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \ + \ + template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> { using type = TypeList<Container<__VA_ARGS__>>; };\ + template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature), typename...Elements>\ + struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>, Elements...> { using type = typename append<TypeList<Container<__VA_ARGS__>>, typename rewrap<NttpTemplateTypeList<Container>, Elements...>::type>::type; };\ + template<template <typename...> class Final, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Containers, typename...Types>\ + struct create<Final, NttpTemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<NttpTemplateTypeList<Containers>, Types...>::type...>::type; }; + +#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() +#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() + +#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature,...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() + +#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\ + template<typename Type>\ + void reg_test(TypeList<Type>, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Type>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + } + +#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + } + +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...)\ + template<typename Type>\ + void reg_test(TypeList<Type>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestName<Type>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ + } + +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ + } + +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature)\ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<TestType> { \ + void test();\ + } + +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> { \ + void test();\ + } + +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature)\ + template<typename TestType> \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<TestType>::test() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_NTTP_0 +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__),INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_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_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__) +#else +#define INTERNAL_CATCH_NTTP_0(signature) +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)( __VA_ARGS__)) +#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_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__)) +#endif + +#endif // CATCH_PREPROCESSOR_HPP_INCLUDED + + // GCC 5 and older do not properly handle disabling unused-variable warning // with a _Pragma. This means that we have to leak the suppression to the // user code as well :-( @@ -5976,34 +6382,34 @@ struct AutoReg : Detail::NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) #endif #endif @@ -6024,9 +6430,9 @@ struct AutoReg : Detail::NonCopyable { template<typename...Types> \ struct TestName{\ TestName(){\ - int index = 0; \ + size_t index = 0; \ constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\ - using expander = int[];\ + using expander = size_t[];\ (void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ }\ };\ @@ -6041,18 +6447,18 @@ struct AutoReg : Detail::NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) ) #endif #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \ @@ -6069,12 +6475,12 @@ struct AutoReg : Detail::NonCopyable { template<typename... Types> \ struct TestName { \ void reg_tests() { \ - int index = 0; \ - using expander = int[]; \ + size_t index = 0; \ + using expander = size_t[]; \ 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<Types> ), 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<Types> ), 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 */\ } \ }; \ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ @@ -6091,18 +6497,18 @@ struct AutoReg : Detail::NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename T,__VA_ARGS__) + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename T,__VA_ARGS__) #else #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename T, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename T, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__) + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__) #else #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) ) #endif #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList)\ @@ -6117,8 +6523,8 @@ struct AutoReg : Detail::NonCopyable { template<typename... Types> \ struct TestName { \ void reg_tests() { \ - int index = 0; \ - using expander = int[]; \ + size_t index = 0; \ + using expander = size_t[]; \ (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\ } \ };\ @@ -6134,7 +6540,7 @@ struct AutoReg : Detail::NonCopyable { static void TestFunc() #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \ - INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, TmplList ) + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, TmplList ) #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \ @@ -6152,9 +6558,9 @@ struct AutoReg : Detail::NonCopyable { template<typename...Types> \ struct TestNameClass{\ TestNameClass(){\ - int index = 0; \ + size_t index = 0; \ constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\ - using expander = int[];\ + using expander = size_t[];\ (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ }\ };\ @@ -6169,18 +6575,18 @@ struct AutoReg : Detail::NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ - INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) #endif #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\ @@ -6200,12 +6606,12 @@ struct AutoReg : Detail::NonCopyable { template<typename...Types>\ struct TestNameClass{\ void reg_tests(){\ - int index = 0;\ - using expander = int[];\ + std::size_t index = 0;\ + using expander = std::size_t[];\ 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<Types>::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<Types>::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 */ \ }\ };\ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ @@ -6222,18 +6628,18 @@ struct AutoReg : Detail::NonCopyable { #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, typename T, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, typename T, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) ) #endif #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ - INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, Signature, __VA_ARGS__ ) + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, Signature, __VA_ARGS__ ) #else #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ - INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) ) + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) ) #endif #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \ @@ -6251,8 +6657,8 @@ struct AutoReg : Detail::NonCopyable { template<typename...Types>\ struct TestNameClass{\ void reg_tests(){\ - int index = 0;\ - using expander = int[];\ + size_t index = 0;\ + using expander = size_t[];\ (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \ }\ };\ @@ -6268,7 +6674,7 @@ struct AutoReg : Detail::NonCopyable { void TestName<TestType>::test() #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \ - INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, TmplList ) + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, TmplList ) #endif // CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED @@ -6391,14 +6797,24 @@ struct AutoReg : Detail::NonCopyable { namespace Catch { + /** + * A **view** of a tag string that provides case insensitive comparisons + * + * Note that in Catch2 internals, the square brackets around tags are + * not a part of tag's representation, so e.g. "[cool-tag]" is represented + * as "cool-tag" internally. + */ struct Tag { - Tag(StringRef original_, StringRef lowerCased_): - original(original_), lowerCased(lowerCased_) + constexpr Tag(StringRef original_): + original(original_) {} - StringRef original, lowerCased; + StringRef original; + + friend bool operator< ( Tag const& lhs, Tag const& rhs ); + friend bool operator==( Tag const& lhs, Tag const& rhs ); }; - struct ITestInvoker; + class ITestInvoker; enum class TestCaseProperties : uint8_t { None = 0, @@ -6410,10 +6826,18 @@ namespace Catch { Benchmark = 1 << 6 }; - + /** + * Various metadata about the test case. + * + * A test case is uniquely identified by its (class)name and tags + * combination, with source location being ignored, and other properties + * being determined from tags. + * + * Tags are kept sorted. + */ struct TestCaseInfo : Detail::NonCopyable { - TestCaseInfo(std::string const& _className, + TestCaseInfo(StringRef _className, NameAndTags const& _tags, SourceLineInfo const& _lineInfo); @@ -6425,13 +6849,17 @@ namespace Catch { // Adds the tag(s) with test's filename (for the -# flag) void addFilenameTag(); + //! Orders by name, classname and tags + friend bool operator<( TestCaseInfo const& lhs, + TestCaseInfo const& rhs ); + std::string tagsAsString() const; std::string name; - std::string className; + StringRef className; private: - std::string backingTags, backingLCaseTags; + std::string backingTags; // Internally we copy tags to the backing storage and then add // refs to this storage to the tags vector. void internalAppendTag(StringRef tagString); @@ -6441,6 +6869,12 @@ namespace Catch { TestCaseProperties properties = TestCaseProperties::None; }; + /** + * Wrapper over the test case information and the test case invoker + * + * Does not own either, and is specifically made to be cheap + * to copy around. + */ class TestCaseHandle { TestCaseInfo* m_info; ITestInvoker* m_invoker; @@ -6453,14 +6887,12 @@ namespace Catch { } TestCaseInfo const& getTestCaseInfo() const; - - bool operator== ( TestCaseHandle const& rhs ) const; - bool operator < ( TestCaseHandle const& rhs ) const; }; - Detail::unique_ptr<TestCaseInfo> makeTestCaseInfo( std::string const& className, - NameAndTags const& nameAndTags, - SourceLineInfo const& lineInfo ); + Detail::unique_ptr<TestCaseInfo> + makeTestCaseInfo( StringRef className, + NameAndTags const& nameAndTags, + SourceLineInfo const& lineInfo ); } #ifdef __clang__ @@ -6485,17 +6917,18 @@ namespace Catch { namespace Catch { using exceptionTranslateFunction = std::string(*)(); - struct IExceptionTranslator; + class IExceptionTranslator; using ExceptionTranslators = std::vector<Detail::unique_ptr<IExceptionTranslator const>>; - struct IExceptionTranslator { - virtual ~IExceptionTranslator(); + class IExceptionTranslator { + public: + virtual ~IExceptionTranslator(); // = default virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; }; - struct IExceptionTranslatorRegistry { - virtual ~IExceptionTranslatorRegistry(); - + class IExceptionTranslatorRegistry { + public: + virtual ~IExceptionTranslatorRegistry(); // = default virtual std::string translateActiveException() const = 0; }; @@ -6539,8 +6972,9 @@ namespace Catch { public: template<typename T> ExceptionTranslatorRegistrar( std::string(*translateFunction)( T const& ) ) { - getMutableRegistryHub().registerTranslator - ( new ExceptionTranslator<T>( translateFunction ) ); + getMutableRegistryHub().registerTranslator( + Detail::make_unique<ExceptionTranslator<T>>(translateFunction) + ); } }; @@ -6573,6 +7007,7 @@ namespace Catch { #endif // CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED + #ifndef CATCH_VERSION_HPP_INCLUDED #define CATCH_VERSION_HPP_INCLUDED @@ -6612,7 +7047,7 @@ namespace Catch { #define CATCH_VERSION_MAJOR 3 #define CATCH_VERSION_MINOR 0 -#define CATCH_VERSION_PATCH 0 +#define CATCH_VERSION_PATCH 1 #endif // CATCH_VERSION_MACROS_HPP_INCLUDED @@ -6670,10 +7105,30 @@ namespace Catch { #define CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED +#include <string> + namespace Catch { namespace Generators { class GeneratorUntypedBase { + // Caches result from `toStringImpl`, assume that when it is an + // empty string, the cache is invalidated. + mutable std::string m_stringReprCache; + + // Counts based on `next` returning true + std::size_t m_currentElementIndex = 0; + + /** + * Attempts to move the generator to the next element + * + * Returns true iff the move succeeded (and a valid element + * can be retrieved). + */ + virtual bool next() = 0; + + //! Customization point for `currentElementAsString` + virtual std::string stringifyImpl() const = 0; + public: GeneratorUntypedBase() = default; // Generation of copy ops is deprecated (and Clang will complain) @@ -6683,17 +7138,41 @@ namespace Catch { virtual ~GeneratorUntypedBase(); // = default; - // Attempts to move the generator to the next element - // - // Returns true iff the move succeeded (and a valid element - // can be retrieved). - virtual bool next() = 0; + /** + * Attempts to move the generator to the next element + * + * Serves as a non-virtual interface to `next`, so that the + * top level interface can provide sanity checking and shared + * features. + * + * As with `next`, returns true iff the move succeeded and + * the generator has new valid element to provide. + */ + bool countedNext(); + + std::size_t currentElementIndex() const { return m_currentElementIndex; } + + /** + * Returns generator's current element as user-friendly string. + * + * By default returns string equivalent to calling + * `Catch::Detail::stringify` on the current element, but generators + * can customize their implementation as needed. + * + * Not thread-safe due to internal caching. + * + * The returned ref is valid only until the generator instance + * is destructed, or it moves onto the next element, whichever + * comes first. + */ + StringRef currentElementAsString() const; }; using GeneratorBasePtr = Catch::Detail::unique_ptr<GeneratorUntypedBase>; } // namespace Generators - struct IGeneratorTracker { + class IGeneratorTracker { + public: virtual ~IGeneratorTracker(); // = default; virtual auto hasGenerator() const -> bool = 0; virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; @@ -6706,7 +7185,6 @@ namespace Catch { #include <vector> #include <tuple> -#include <utility> namespace Catch { @@ -6721,7 +7199,12 @@ namespace Detail { } // end namespace detail template<typename T> - struct IGenerator : GeneratorUntypedBase { + class IGenerator : public GeneratorUntypedBase { + std::string stringifyImpl() const override { + return ::Catch::Detail::stringify( get() ); + } + + public: ~IGenerator() override = default; IGenerator() = default; IGenerator(IGenerator const&) = default; @@ -6747,13 +7230,13 @@ namespace Detail { GeneratorWrapper(IGenerator<T>* generator): m_generator(generator) {} GeneratorWrapper(GeneratorPtr<T> generator): - m_generator(std::move(generator)) {} + m_generator(CATCH_MOVE(generator)) {} T const& get() const { return m_generator->get(); } bool next() { - return m_generator->next(); + return m_generator->countedNext(); } }; @@ -6762,8 +7245,11 @@ namespace Detail { class SingleValueGenerator final : public IGenerator<T> { T m_value; public: + SingleValueGenerator(T const& value) : + m_value(value) + {} SingleValueGenerator(T&& value): - m_value(std::forward<T>(value)) + m_value(CATCH_MOVE(value)) {} T const& get() const override { @@ -6793,9 +7279,11 @@ namespace Detail { } }; - template <typename T> - GeneratorWrapper<T> value(T&& value) { - return GeneratorWrapper<T>(Catch::Detail::make_unique<SingleValueGenerator<T>>(std::forward<T>(value))); + template <typename T, typename DecayedT = std::decay_t<T>> + GeneratorWrapper<DecayedT> value( T&& value ) { + return GeneratorWrapper<DecayedT>( + Catch::Detail::make_unique<SingleValueGenerator<DecayedT>>( + CATCH_FORWARD( value ) ) ); } template <typename T> GeneratorWrapper<T> values(std::initializer_list<T> values) { @@ -6807,27 +7295,36 @@ namespace Detail { std::vector<GeneratorWrapper<T>> m_generators; size_t m_current = 0; - void populate(GeneratorWrapper<T>&& generator) { - m_generators.emplace_back(std::move(generator)); + void add_generator( GeneratorWrapper<T>&& generator ) { + m_generators.emplace_back( CATCH_MOVE( generator ) ); } - void populate(T&& val) { - m_generators.emplace_back(value(std::forward<T>(val))); + void add_generator( T const& val ) { + m_generators.emplace_back( value( val ) ); } - template<typename U> - void populate(U&& val) { - populate(T(std::forward<U>(val))); + void add_generator( T&& val ) { + m_generators.emplace_back( value( CATCH_MOVE( val ) ) ); } - template<typename U, typename... Gs> - void populate(U&& valueOrGenerator, Gs &&... moreGenerators) { - populate(std::forward<U>(valueOrGenerator)); - populate(std::forward<Gs>(moreGenerators)...); + template <typename U> + std::enable_if_t<!std::is_same<std::decay_t<U>, T>::value> + add_generator( U&& val ) { + add_generator( T( CATCH_FORWARD( val ) ) ); + } + + template <typename U> void add_generators( U&& valueOrGenerator ) { + add_generator( CATCH_FORWARD( valueOrGenerator ) ); + } + + template <typename U, typename... Gs> + void add_generators( U&& valueOrGenerator, Gs&&... moreGenerators ) { + add_generator( CATCH_FORWARD( valueOrGenerator ) ); + add_generators( CATCH_FORWARD( moreGenerators )... ); } public: template <typename... Gs> Generators(Gs &&... moreGenerators) { m_generators.reserve(sizeof...(Gs)); - populate(std::forward<Gs>(moreGenerators)...); + add_generators(CATCH_FORWARD(moreGenerators)...); } T const& get() const override { @@ -6847,8 +7344,9 @@ namespace Detail { }; - template<typename... Ts> - GeneratorWrapper<std::tuple<Ts...>> table( std::initializer_list<std::tuple<std::decay_t<Ts>...>> tuples ) { + template <typename... Ts> + GeneratorWrapper<std::tuple<std::decay_t<Ts>...>> + table( std::initializer_list<std::tuple<std::decay_t<Ts>...>> tuples ) { return values<std::tuple<Ts...>>( tuples ); } @@ -6858,19 +7356,19 @@ namespace Detail { template<typename T, typename... Gs> auto makeGenerators( GeneratorWrapper<T>&& generator, Gs &&... moreGenerators ) -> Generators<T> { - return Generators<T>(std::move(generator), std::forward<Gs>(moreGenerators)...); + return Generators<T>(CATCH_MOVE(generator), CATCH_FORWARD(moreGenerators)...); } template<typename T> auto makeGenerators( GeneratorWrapper<T>&& generator ) -> Generators<T> { - return Generators<T>(std::move(generator)); + return Generators<T>(CATCH_MOVE(generator)); } template<typename T, typename... Gs> - auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<T> { - return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... ); + auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<std::decay_t<T>> { + return makeGenerators( value( CATCH_FORWARD( val ) ), CATCH_FORWARD( moreGenerators )... ); } template<typename T, typename U, typename... Gs> auto makeGenerators( as<T>, U&& val, Gs &&... moreGenerators ) -> Generators<T> { - return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... ); + return makeGenerators( value( T( CATCH_FORWARD( val ) ) ), CATCH_FORWARD( moreGenerators )... ); } auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; @@ -6914,6 +7412,8 @@ namespace Detail { #define CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED +#include <cassert> + namespace Catch { namespace Generators { @@ -6924,7 +7424,7 @@ namespace Generators { size_t m_target; public: TakeGenerator(size_t target, GeneratorWrapper<T>&& generator): - m_generator(std::move(generator)), + m_generator(CATCH_MOVE(generator)), m_target(target) { assert(target != 0 && "Empty generators are not allowed"); @@ -6950,7 +7450,7 @@ namespace Generators { template <typename T> GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) { - return GeneratorWrapper<T>(Catch::Detail::make_unique<TakeGenerator<T>>(target, std::move(generator))); + return GeneratorWrapper<T>(Catch::Detail::make_unique<TakeGenerator<T>>(target, CATCH_MOVE(generator))); } @@ -6961,8 +7461,8 @@ namespace Generators { public: template <typename P = Predicate> FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator): - m_generator(std::move(generator)), - m_predicate(std::forward<P>(pred)) + m_generator(CATCH_MOVE(generator)), + m_predicate(CATCH_FORWARD(pred)) { if (!m_predicate(m_generator.get())) { // It might happen that there are no values that pass the @@ -6991,7 +7491,7 @@ namespace Generators { template <typename T, typename Predicate> GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) { - return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, Predicate>>(std::forward<Predicate>(pred), std::move(generator))); + return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, Predicate>>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); } template <typename T> @@ -7006,7 +7506,7 @@ namespace Generators { size_t m_repeat_index = 0; public: RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator): - m_generator(std::move(generator)), + m_generator(CATCH_MOVE(generator)), m_target_repeats(repeats) { assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); @@ -7047,7 +7547,7 @@ namespace Generators { template <typename T> GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) { - return GeneratorWrapper<T>(Catch::Detail::make_unique<RepeatGenerator<T>>(repeats, std::move(generator))); + return GeneratorWrapper<T>(Catch::Detail::make_unique<RepeatGenerator<T>>(repeats, CATCH_MOVE(generator))); } template <typename T, typename U, typename Func> @@ -7060,8 +7560,8 @@ namespace Generators { public: template <typename F2 = Func> MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) : - m_generator(std::move(generator)), - m_function(std::forward<F2>(function)), + m_generator(CATCH_MOVE(generator)), + m_function(CATCH_FORWARD(function)), m_cache(m_function(m_generator.get())) {} @@ -7080,14 +7580,14 @@ namespace Generators { template <typename Func, typename U, typename T = FunctionReturnType<Func, U>> GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { return GeneratorWrapper<T>( - Catch::Detail::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) + Catch::Detail::make_unique<MapGenerator<T, U, Func>>(CATCH_FORWARD(function), CATCH_MOVE(generator)) ); } template <typename T, typename U, typename Func> GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { return GeneratorWrapper<T>( - Catch::Detail::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) + Catch::Detail::make_unique<MapGenerator<T, U, Func>>(CATCH_FORWARD(function), CATCH_MOVE(generator)) ); } @@ -7099,7 +7599,7 @@ namespace Generators { bool m_used_up = false; public: ChunkGenerator(size_t size, GeneratorWrapper<T> generator) : - m_chunk_size(size), m_generator(std::move(generator)) + m_chunk_size(size), m_generator(CATCH_MOVE(generator)) { m_chunk.reserve(m_chunk_size); if (m_chunk_size != 0) { @@ -7130,7 +7630,7 @@ namespace Generators { template <typename T> GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) { return GeneratorWrapper<std::vector<T>>( - Catch::Detail::make_unique<ChunkGenerator<T>>(size, std::move(generator)) + Catch::Detail::make_unique<ChunkGenerator<T>>(size, CATCH_MOVE(generator)) ); } @@ -7203,16 +7703,21 @@ namespace Catch { namespace Catch { namespace Generators { +namespace Detail { + // Returns a suitable seed for a random floating generator based off + // the primary internal rng. It does so by taking current value from + // the rng and returning it as the seed. + std::uint32_t getSeed(); +} template <typename Float> class RandomFloatingGenerator final : public IGenerator<Float> { - Catch::SimplePcg32& m_rng; + Catch::SimplePcg32 m_rng; std::uniform_real_distribution<Float> m_dist; Float m_current_number; public: - - RandomFloatingGenerator(Float a, Float b): - m_rng(rng()), + RandomFloatingGenerator( Float a, Float b, std::uint32_t seed ): + m_rng(seed), m_dist(a, b) { static_cast<void>(next()); } @@ -7228,13 +7733,12 @@ public: template <typename Integer> class RandomIntegerGenerator final : public IGenerator<Integer> { - Catch::SimplePcg32& m_rng; + Catch::SimplePcg32 m_rng; std::uniform_int_distribution<Integer> m_dist; Integer m_current_number; public: - - RandomIntegerGenerator(Integer a, Integer b): - m_rng(rng()), + RandomIntegerGenerator( Integer a, Integer b, std::uint32_t seed ): + m_rng(seed), m_dist(a, b) { static_cast<void>(next()); } @@ -7255,7 +7759,7 @@ std::enable_if_t<std::is_integral<T>::value && !std::is_same<T, bool>::value, GeneratorWrapper<T>> random(T a, T b) { return GeneratorWrapper<T>( - Catch::Detail::make_unique<RandomIntegerGenerator<T>>(a, b) + Catch::Detail::make_unique<RandomIntegerGenerator<T>>(a, b, Detail::getSeed()) ); } @@ -7264,7 +7768,7 @@ std::enable_if_t<std::is_floating_point<T>::value, GeneratorWrapper<T>> random(T a, T b) { return GeneratorWrapper<T>( - Catch::Detail::make_unique<RandomFloatingGenerator<T>>(a, b) + Catch::Detail::make_unique<RandomFloatingGenerator<T>>(a, b, Detail::getSeed()) ); } @@ -7401,29 +7905,94 @@ GeneratorWrapper<ResultType> from_range(Container const& cnt) { +#ifndef CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED +#define CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED + + +#include <string> + +namespace Catch { + + struct ReporterConfig; + class IConfig; + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr<IEventListener>; + + + class IReporterFactory { + public: + virtual ~IReporterFactory(); // = default + + virtual IEventListenerPtr + create( ReporterConfig&& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>; + + class EventListenerFactory { + public: + virtual ~EventListenerFactory(); // = default + virtual IEventListenerPtr create( IConfig const* config ) const = 0; + //! Return a meaningful name for the listener, e.g. its type name + virtual StringRef getName() const = 0; + //! Return listener's description if available + virtual std::string getDescription() const = 0; + }; +} // namespace Catch + +#endif // CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED + + #ifndef CATCH_INTERFACES_REPORTER_REGISTRY_HPP_INCLUDED #define CATCH_INTERFACES_REPORTER_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED +#define CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED + + +namespace Catch { + namespace Detail { + //! Provides case-insensitive `op<` semantics when called + struct CaseInsensitiveLess { + bool operator()( StringRef lhs, + StringRef rhs ) const; + }; + + //! Provides case-insensitive `op==` semantics when called + struct CaseInsensitiveEqualTo { + bool operator()( StringRef lhs, + StringRef rhs ) const; + }; + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED + #include <string> #include <vector> #include <map> namespace Catch { - struct IConfig; + class IConfig; - struct IStreamingReporter; - using IStreamingReporterPtr = Detail::unique_ptr<IStreamingReporter>; - struct IReporterFactory; + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr<IEventListener>; + class IReporterFactory; using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>; + struct ReporterConfig; + class EventListenerFactory; - struct IReporterRegistry { - using FactoryMap = std::map<std::string, IReporterFactoryPtr>; - using Listeners = std::vector<IReporterFactoryPtr>; + class IReporterRegistry { + public: + using FactoryMap = std::map<std::string, IReporterFactoryPtr, Detail::CaseInsensitiveLess>; + using Listeners = std::vector<Detail::unique_ptr<EventListenerFactory>>; - virtual ~IReporterRegistry(); - virtual IStreamingReporterPtr create( std::string const& name, IConfig const* config ) const = 0; + virtual ~IReporterRegistry(); // = default + virtual IEventListenerPtr create( std::string const& name, ReporterConfig&& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; virtual Listeners const& getListeners() const = 0; }; @@ -7433,20 +8002,6 @@ namespace Catch { #endif // CATCH_INTERFACES_REPORTER_REGISTRY_HPP_INCLUDED -#ifndef CATCH_INTERFACES_RUNNER_HPP_INCLUDED -#define CATCH_INTERFACES_RUNNER_HPP_INCLUDED - -namespace Catch { - - struct IRunner { - virtual ~IRunner(); - virtual bool aborting() const = 0; - }; -} - -#endif // CATCH_INTERFACES_RUNNER_HPP_INCLUDED - - #ifndef CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED #define CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED @@ -7456,8 +8011,9 @@ namespace Catch { struct TagAlias; - struct ITagAliasRegistry { - virtual ~ITagAliasRegistry(); + class ITagAliasRegistry { + public: + virtual ~ITagAliasRegistry(); // = default // Nullptr if not present virtual TagAlias const* find( std::string const& alias ) const = 0; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; @@ -7474,6 +8030,33 @@ namespace Catch { /** \file + * Wrapper for ANDROID_LOGWRITE configuration option + * + * We want to default to enabling it when compiled for android, but + * users of the library should also be able to disable it if they want + * to. + */ + +#ifndef CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED +#define CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED + + +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + + +#if defined( CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE ) && \ + !defined( CATCH_CONFIG_NO_ANDROID_LOGWRITE ) && \ + !defined( CATCH_CONFIG_ANDROID_LOGWRITE ) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#endif // CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED + + + +/** \file * Wrapper for UNCAUGHT_EXCEPTIONS configuration option * * For some functionality, Catch2 requires to know whether there is @@ -7481,8 +8064,8 @@ namespace Catch { * in C++17, we want to use `std::uncaught_exceptions` if possible. */ -#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP -#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP +#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED +#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED #if defined(_MSC_VER) # if _MSC_VER >= 1900 // Visual Studio 2015 or newer @@ -7508,71 +8091,7 @@ namespace Catch { #endif -#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP - - -#ifndef CATCH_CONSOLE_COLOUR_HPP_INCLUDED -#define CATCH_CONSOLE_COLOUR_HPP_INCLUDED - - -namespace Catch { - - struct Colour { - enum Code { - None = 0, - - White, - Red, - Green, - Blue, - Cyan, - Yellow, - Grey, - - Bright = 0x10, - - BrightRed = Bright | Red, - BrightGreen = Bright | Green, - LightGrey = Bright | Grey, - BrightWhite = Bright | White, - BrightYellow = Bright | Yellow, - - // By intention - FileName = LightGrey, - Warning = BrightYellow, - ResultError = BrightRed, - ResultSuccess = BrightGreen, - ResultExpectedFailure = Warning, - - Error = BrightRed, - Success = Green, - - OriginalExpression = Cyan, - ReconstructedExpression = BrightYellow, - - SecondaryText = LightGrey, - Headers = White - }; - - // Use constructed object for RAII guard - Colour( Code _colourCode ); - Colour( Colour&& other ) noexcept; - Colour& operator=( Colour&& other ) noexcept; - ~Colour(); - - // Use static method for one-shot changes - static void use( Code _colourCode ); - - private: - bool m_moved = false; - - friend std::ostream& operator << (std::ostream& os, Colour const&); - }; - - -} // end namespace Catch - -#endif // CATCH_CONSOLE_COLOUR_HPP_INCLUDED +#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED #ifndef CATCH_CONSOLE_WIDTH_HPP_INCLUDED @@ -7584,10 +8103,13 @@ namespace Catch { #endif // CATCH_CONSOLE_WIDTH_HPP_INCLUDED + #ifndef CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED #define CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED +#include <cstddef> +#include <initializer_list> // We want a simple polyfill over `std::empty`, `std::size` and so on // for C++14 or C++ libraries with incomplete support. @@ -7674,7 +8196,7 @@ namespace Catch { #if defined(__i386__) || defined(__x86_64__) #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ #elif defined(__aarch64__) - #define CATCH_TRAP() __asm__(".inst 0xd4200000") + #define CATCH_TRAP() __asm__(".inst 0xd43e0000") #endif #elif defined(CATCH_PLATFORM_IPHONE) @@ -7719,6 +8241,52 @@ namespace Catch { #endif // CATCH_DEBUGGER_HPP_INCLUDED +#ifndef CATCH_ENFORCE_HPP_INCLUDED +#define CATCH_ENFORCE_HPP_INCLUDED + + +#include <exception> + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + template <typename Ex> + [[noreturn]] + void throw_exception(Ex const& e) { + throw e; + } +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + [[noreturn]] + void throw_exception(std::exception const& e); +#endif + + [[noreturn]] + void throw_logic_error(std::string const& msg); + [[noreturn]] + void throw_domain_error(std::string const& msg); + [[noreturn]] + void throw_runtime_error(std::string const& msg); + +} // namespace Catch; + +#define CATCH_MAKE_MSG(...) \ + (Catch::ReusableStringStream() << __VA_ARGS__).str() + +#define CATCH_INTERNAL_ERROR(...) \ + Catch::throw_logic_error(CATCH_MAKE_MSG( CATCH_INTERNAL_LINEINFO << ": Internal Catch2 error: " << __VA_ARGS__)) + +#define CATCH_ERROR(...) \ + Catch::throw_domain_error(CATCH_MAKE_MSG( __VA_ARGS__ )) + +#define CATCH_RUNTIME_ERROR(...) \ + Catch::throw_runtime_error(CATCH_MAKE_MSG( __VA_ARGS__ )) + +#define CATCH_ENFORCE( condition, ... ) \ + do{ if( !(condition) ) CATCH_ERROR( __VA_ARGS__ ); } while(false) + + +#endif // CATCH_ENFORCE_HPP_INCLUDED + + #ifndef CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED #define CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED @@ -7772,6 +8340,7 @@ namespace Catch { #ifndef CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED #define CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + #include <vector> #include <string> @@ -7779,8 +8348,8 @@ namespace Catch { class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { public: - ~ExceptionTranslatorRegistry(); - virtual void registerTranslator( const IExceptionTranslator* translator ); + ~ExceptionTranslatorRegistry() override; + void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ); std::string translateActiveException() const override; std::string tryTranslators() const; @@ -7796,91 +8365,217 @@ namespace Catch { #define CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED +#include <cassert> -#ifndef CATCH_WINDOWS_H_PROXY_HPP_INCLUDED -#define CATCH_WINDOWS_H_PROXY_HPP_INCLUDED +namespace Catch { + /** + * Wrapper for platform-specific fatal error (signals/SEH) handlers + * + * Tries to be cooperative with other handlers, and not step over + * other handlers. This means that unknown structured exceptions + * are passed on, previous signal handlers are called, and so on. + * + * Can only be instantiated once, and assumes that once a signal + * is caught, the binary will end up terminating. Thus, there + */ + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform(); + void disengage_platform() noexcept; + public: + // Should also have platform-specific implementations as needed + FatalConditionHandler(); + ~FatalConditionHandler(); -#if defined(CATCH_PLATFORM_WINDOWS) + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINED_NOMINMAX -# define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif + void disengage() noexcept { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } + }; -#ifdef __AFXDLL -#include <AfxWin.h> -#else -#include <windows.h> -#endif + //! Simple RAII guard for (dis)engaging the FatalConditionHandler + class FatalConditionHandlerGuard { + FatalConditionHandler* m_handler; + public: + FatalConditionHandlerGuard(FatalConditionHandler* handler): + m_handler(handler) { + m_handler->engage(); + } + ~FatalConditionHandlerGuard() { + m_handler->disengage(); + } + }; -#ifdef CATCH_DEFINED_NOMINMAX -# undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif +} // end namespace Catch -#endif // defined(CATCH_PLATFORM_WINDOWS) +#endif // CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED -#endif // CATCH_WINDOWS_H_PROXY_HPP_INCLUDED + +#ifndef CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED +#define CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED -#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +#ifndef CATCH_POLYFILLS_HPP_INCLUDED +#define CATCH_POLYFILLS_HPP_INCLUDED namespace Catch { + bool isnan(float f); + bool isnan(double d); +} - struct FatalConditionHandler { +#endif // CATCH_POLYFILLS_HPP_INCLUDED - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); - FatalConditionHandler(); - static void reset(); - ~FatalConditionHandler() { reset(); } +#include <cassert> +#include <cmath> +#include <cstdint> +#include <utility> +#include <limits> - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; +namespace Catch { + namespace Detail { -} // namespace Catch + uint32_t convertToBits(float f); + uint64_t convertToBits(double d); -#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) + } // end namespace Detail -#include <signal.h> -namespace Catch { - struct FatalConditionHandler { +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic push + // We do a bunch of direct compensations of floating point numbers, + // because we know what we are doing and actually do want the direct + // comparison behaviour. +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + + /** + * Calculates the ULP distance between two floating point numbers + * + * The ULP distance of two floating point numbers is the count of + * valid floating point numbers representable between them. + * + * There are some exceptions between how this function counts the + * distance, and the interpretation of the standard as implemented. + * by e.g. `nextafter`. For this function it always holds that: + * * `(x == y) => ulpDistance(x, y) == 0` (so `ulpDistance(-0, 0) == 0`) + * * `ulpDistance(maxFinite, INF) == 1` + * * `ulpDistance(x, -x) == 2 * ulpDistance(x, 0)` + * + * \pre `!isnan( lhs )` + * \pre `!isnan( rhs )` + * \pre floating point numbers are represented in IEEE-754 format + */ + template <typename FP> + uint64_t ulpDistance( FP lhs, FP rhs ) { + assert( std::numeric_limits<FP>::is_iec559 && + "ulpDistance assumes IEEE-754 format for floating point types" ); + assert( !Catch::isnan( lhs ) && + "Distance between NaN and number is not meaningful" ); + assert( !Catch::isnan( rhs ) && + "Distance between NaN and number is not meaningful" ); + + // We want X == Y to imply 0 ULP distance even if X and Y aren't + // bit-equal (-0 and 0), or X - Y != 0 (same sign infinities). + if ( lhs == rhs ) { return 0; } + + // We need a properly typed positive zero for type inference. + static constexpr FP positive_zero{}; + + // We want to ensure that +/- 0 is always represented as positive zero + if ( lhs == positive_zero ) { lhs = positive_zero; } + if ( rhs == positive_zero ) { rhs = positive_zero; } + + // If arguments have different signs, we can handle them by summing + // how far are they from 0 each. + if ( std::signbit( lhs ) != std::signbit( rhs ) ) { + return ulpDistance( std::abs( lhs ), positive_zero ) + + ulpDistance( std::abs( rhs ), positive_zero ); + } + + // When both lhs and rhs are of the same sign, we can just + // read the numbers bitwise as integers, and then subtract them + // (assuming IEEE). + uint64_t lc = Detail::convertToBits( lhs ); + uint64_t rc = Detail::convertToBits( rhs ); + + // The ulp distance between two numbers is symmetric, so to avoid + // dealing with overflows we want the bigger converted number on the lhs + if ( lc < rc ) { + std::swap( lc, rc ); + } + + return lc - rc; + } + +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic pop +#endif - static bool isSet; - static struct sigaction oldSigActions[]; - static stack_t oldSigStack; - static char altStackMem[]; - static void handleSignal( int sig ); +} // end namespace Catch - FatalConditionHandler(); - ~FatalConditionHandler() { reset(); } - static void reset(); - }; +#endif // CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED -} // namespace Catch +#ifndef CATCH_ISTREAM_HPP_INCLUDED +#define CATCH_ISTREAM_HPP_INCLUDED -#else + +#include <iosfwd> +#include <cstddef> +#include <ostream> +#include <string> namespace Catch { - struct FatalConditionHandler {}; -} -#endif + class IStream { + public: + virtual ~IStream(); // = default + virtual std::ostream& stream() = 0; + /** + * Best guess on whether the instance is writing to a console (e.g. via stdout/stderr) + * + * This is useful for e.g. Win32 colour support, because the Win32 + * API manipulates console directly, unlike POSIX escape codes, + * that can be written anywhere. + * + * Due to variety of ways to change where the stdout/stderr is + * _actually_ being written, users should always assume that + * the answer might be wrong. + */ + virtual bool isConsole() const { return false; } + }; -#endif // CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED + /** + * Creates a stream wrapper that writes to specific file. + * + * Also recognizes 4 special filenames + * * `-` for stdout + * * `%stdout` for stdout + * * `%stderr` for stderr + * * `%debug` for platform specific debugging output + * + * \throws if passed an unrecognized %-prefixed stream + */ + auto makeStream( std::string const& filename ) -> Detail::unique_ptr<IStream>; + +} + +#endif // CATCH_STREAM_HPP_INCLUDED #ifndef CATCH_LEAK_DETECTOR_HPP_INCLUDED @@ -7907,13 +8602,17 @@ namespace Catch { namespace Catch { - struct IStreamingReporter; + class IEventListener; class Config; struct ReporterDescription { std::string name, description; }; + struct ListenerDescription { + StringRef name; + std::string description; + }; struct TagInfo { void add(StringRef spelling); @@ -7923,81 +8622,13 @@ namespace Catch { std::size_t count = 0; }; - bool list( IStreamingReporter& reporter, Config const& config ); + bool list( IEventListener& reporter, Config const& config ); } // end namespace Catch #endif // CATCH_LIST_HPP_INCLUDED -#ifndef CATCH_OPTION_HPP_INCLUDED -#define CATCH_OPTION_HPP_INCLUDED - -namespace Catch { - - // An optional type - template<typename T> - class Option { - public: - Option() : nullableValue( nullptr ) {} - Option( T const& _value ) - : nullableValue( new( storage ) T( _value ) ) - {} - Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) - {} - - ~Option() { - reset(); - } - - Option& operator= ( Option const& _other ) { - if( &_other != this ) { - reset(); - if( _other ) - nullableValue = new( storage ) T( *_other ); - } - return *this; - } - Option& operator = ( T const& _value ) { - reset(); - nullableValue = new( storage ) T( _value ); - return *this; - } - - void reset() { - if( nullableValue ) - nullableValue->~T(); - nullableValue = nullptr; - } - - T& operator*() { return *nullableValue; } - T const& operator*() const { return *nullableValue; } - T* operator->() { return nullableValue; } - const T* operator->() const { return nullableValue; } - - T valueOr( T const& defaultValue ) const { - return nullableValue ? *nullableValue : defaultValue; - } - - bool some() const { return nullableValue != nullptr; } - bool none() const { return nullableValue == nullptr; } - - bool operator !() const { return nullableValue == nullptr; } - explicit operator bool() const { - return some(); - } - - private: - T *nullableValue; - alignas(alignof(T)) char storage[sizeof(T)]; - }; - -} // end namespace Catch - -#endif // CATCH_OPTION_HPP_INCLUDED - - #ifndef CATCH_OUTPUT_REDIRECT_HPP_INCLUDED #define CATCH_OUTPUT_REDIRECT_HPP_INCLUDED @@ -8108,17 +8739,6 @@ namespace Catch { #endif // CATCH_OUTPUT_REDIRECT_HPP_INCLUDED -#ifndef CATCH_POLYFILLS_HPP_INCLUDED -#define CATCH_POLYFILLS_HPP_INCLUDED - -namespace Catch { - bool isnan(float f); - bool isnan(double d); -} - -#endif // CATCH_POLYFILLS_HPP_INCLUDED - - #ifndef CATCH_REPORTER_REGISTRY_HPP_INCLUDED #define CATCH_REPORTER_REGISTRY_HPP_INCLUDED @@ -8133,10 +8753,10 @@ namespace Catch { ReporterRegistry(); ~ReporterRegistry() override; // = default, out of line to allow fwd decl - IStreamingReporterPtr create( std::string const& name, IConfig const* config ) const override; + IEventListenerPtr create( std::string const& name, ReporterConfig&& config ) const override; void registerReporter( std::string const& name, IReporterFactoryPtr factory ); - void registerListener( IReporterFactoryPtr factory ); + void registerListener( Detail::unique_ptr<EventListenerFactory> factory ); FactoryMap const& getFactories() const override; Listeners const& getListeners() const override; @@ -8161,7 +8781,6 @@ namespace Catch { #include <string> #include <vector> -#include <memory> namespace Catch { namespace TestCaseTracking { @@ -8179,19 +8798,31 @@ namespace TestCaseTracking { class ITracker; - using ITrackerPtr = std::shared_ptr<ITracker>; + using ITrackerPtr = Catch::Detail::unique_ptr<ITracker>; - class ITracker { + class ITracker { NameAndLocation m_nameAndLocation; using Children = std::vector<ITrackerPtr>; protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + ITracker* m_parent = nullptr; Children m_children; + CycleState m_runState = NotStarted; public: - ITracker(NameAndLocation const& nameAndLoc) : - m_nameAndLocation(nameAndLoc) + ITracker( NameAndLocation const& nameAndLoc, ITracker* parent ): + m_nameAndLocation( nameAndLoc ), + m_parent( parent ) {} @@ -8199,42 +8830,60 @@ namespace TestCaseTracking { NameAndLocation const& nameAndLocation() const { return m_nameAndLocation; } + ITracker* parent() const { + return m_parent; + } - virtual ~ITracker(); + virtual ~ITracker(); // = default // dynamic queries - virtual bool isComplete() const = 0; // Successfully completed or failed - virtual bool isSuccessfullyCompleted() const = 0; - virtual bool isOpen() const = 0; // Started but not complete - virtual bool hasStarted() const = 0; - virtual ITracker& parent() = 0; + //! Returns true if tracker run to completion (successfully or not) + virtual bool isComplete() const = 0; + //! Returns true if tracker run to completion succesfully + bool isSuccessfullyCompleted() const; + //! Returns true if tracker has started but hasn't been completed + bool isOpen() const; + //! Returns true iff tracker has started + bool hasStarted() const; // actions virtual void close() = 0; // Successfully complete virtual void fail() = 0; - virtual void markAsNeedingAnotherRun() = 0; + void markAsNeedingAnotherRun(); //! Register a nested ITracker - void addChild( ITrackerPtr const& child ); + void addChild( ITrackerPtr&& child ); /** * Returns ptr to specific child if register with this tracker. * * Returns nullptr if not found. */ - ITrackerPtr findChild( NameAndLocation const& nameAndLocation ); + ITracker* findChild( NameAndLocation const& nameAndLocation ); //! Have any children been added? bool hasChildren() const { return !m_children.empty(); } - virtual void openChild() = 0; + //! Marks tracker as executing a child, doing se recursively up the tree + void openChild(); - // Debug/ checking - virtual bool isSectionTracker() const = 0; - virtual bool isGeneratorTracker() const = 0; + /** + * Returns true if the instance is a section tracker + * + * Subclasses should override to true if they are, replaces RTTI + * for internal debug checks. + */ + virtual bool isSectionTracker() const; + /** + * Returns true if the instance is a generator tracker + * + * Subclasses should override to true if they are, replaces RTTI + * for internal debug checks. + */ + virtual bool isGeneratorTracker() const; }; class TrackerContext { @@ -8264,41 +8913,18 @@ namespace TestCaseTracking { class TrackerBase : public ITracker { protected: - enum CycleState { - NotStarted, - Executing, - ExecutingChildren, - NeedsAnotherRun, - CompletedSuccessfully, - Failed - }; TrackerContext& m_ctx; - ITracker* m_parent; - CycleState m_runState = NotStarted; public: TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); bool isComplete() const override; - bool isSuccessfullyCompleted() const override; - bool isOpen() const override; - bool hasStarted() const override { - return m_runState != NotStarted; - } - - ITracker& parent() override; - - void openChild() override; - - bool isSectionTracker() const override; - bool isGeneratorTracker() const override; void open(); void close() override; void fail() override; - void markAsNeedingAnotherRun() override; private: void moveToParent(); @@ -8306,7 +8932,7 @@ namespace TestCaseTracking { }; class SectionTracker : public TrackerBase { - std::vector<std::string> m_filters; + std::vector<StringRef> m_filters; std::string m_trimmed_name; public: SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); @@ -8320,7 +8946,11 @@ namespace TestCaseTracking { void tryOpen(); void addInitialFilters( std::vector<std::string> const& filters ); - void addNextFilters( std::vector<std::string> const& filters ); + void addNextFilters( std::vector<StringRef> const& filters ); + //! Returns filters active in this tracker + std::vector<StringRef> const& getFilters() const; + //! Returns whitespace-trimmed name of the tracked section + StringRef trimmedName() const; }; } // namespace TestCaseTracking @@ -8337,25 +8967,22 @@ using TestCaseTracking::SectionTracker; namespace Catch { - struct IMutableContext; - struct IGeneratorTracker; - struct IConfig; + class IMutableContext; + class IGeneratorTracker; + class IConfig; /////////////////////////////////////////////////////////////////////////// - class RunContext : public IResultCapture, public IRunner { + class RunContext : public IResultCapture { public: RunContext( RunContext const& ) = delete; RunContext& operator =( RunContext const& ) = delete; - explicit RunContext( IConfig const* _config, IStreamingReporterPtr&& reporter ); + explicit RunContext( IConfig const* _config, IEventListenerPtr&& reporter ); ~RunContext() override; - void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); - void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); - Totals runTest(TestCaseHandle const& testCase); public: // IResultCapture @@ -8368,7 +8995,7 @@ namespace Catch { void handleMessage ( AssertionInfo const& info, ResultWas::OfType resultType, - StringRef const& message, + StringRef message, AssertionReaction& reaction ) override; void handleUnexpectedExceptionNotThrown ( AssertionInfo const& info, @@ -8391,10 +9018,10 @@ namespace Catch { auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; - void benchmarkPreparing( std::string const& name ) override; + void benchmarkPreparing( StringRef name ) override; void benchmarkStarting( BenchmarkInfo const& info ) override; void benchmarkEnded( BenchmarkStats<> const& stats ) override; - void benchmarkFailed( std::string const& error ) override; + void benchmarkFailed( StringRef error ) override; void pushScopedMessage( MessageInfo const& message ) override; void popScopedMessage( MessageInfo const& message ) override; @@ -8415,7 +9042,7 @@ namespace Catch { public: // !TBD We need to do this another way! - bool aborting() const override; + bool aborting() const; private: @@ -8442,17 +9069,18 @@ namespace Catch { IMutableContext& m_context; TestCaseHandle const* m_activeTestCase = nullptr; ITracker* m_testCaseTracker = nullptr; - Option<AssertionResult> m_lastResult; + Optional<AssertionResult> m_lastResult; IConfig const* m_config; Totals m_totals; - IStreamingReporterPtr m_reporter; + IEventListenerPtr m_reporter; std::vector<MessageInfo> m_messages; std::vector<ScopedMessage> m_messageScopes; /* Keeps owners of so-called unscoped messages. */ AssertionInfo m_lastAssertionInfo; std::vector<SectionEndInfo> m_unfinishedSections; std::vector<ITracker*> m_activeSections; TrackerContext m_trackerContext; + FatalConditionHandler m_fatalConditionhandler; bool m_lastAssertionPassed = false; bool m_shouldReportUnexpected = true; bool m_includeSuccessfulResults; @@ -8465,13 +9093,48 @@ namespace Catch { #endif // CATCH_RUN_CONTEXT_HPP_INCLUDED +#ifndef CATCH_SHARDING_HPP_INCLUDED +#define CATCH_SHARDING_HPP_INCLUDED + + +#include <cmath> + +namespace Catch { + + template<typename Container> + Container createShard(Container const& container, std::size_t const shardCount, std::size_t const shardIndex) { + assert(shardCount > shardIndex); + + if (shardCount == 1) { + return container; + } + + const std::size_t totalTestCount = container.size(); + + const std::size_t shardSize = totalTestCount / shardCount; + const std::size_t leftoverTests = totalTestCount % shardCount; + + const std::size_t startIndex = shardIndex * shardSize + (std::min)(shardIndex, leftoverTests); + const std::size_t endIndex = (shardIndex + 1) * shardSize + (std::min)(shardIndex + 1, leftoverTests); + + auto startIterator = std::next(container.begin(), static_cast<std::ptrdiff_t>(startIndex)); + auto endIterator = std::next(container.begin(), static_cast<std::ptrdiff_t>(endIndex)); + + return Container(startIterator, endIterator); + } + +} + +#endif // CATCH_SHARDING_HPP_INCLUDED + + #ifndef CATCH_SINGLETONS_HPP_INCLUDED #define CATCH_SINGLETONS_HPP_INCLUDED namespace Catch { struct ISingleton { - virtual ~ISingleton(); + virtual ~ISingleton(); // = default }; @@ -8529,6 +9192,23 @@ namespace Catch { #endif // CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED + +#ifndef CATCH_STDSTREAMS_HPP_INCLUDED +#define CATCH_STDSTREAMS_HPP_INCLUDED + +#include <iosfwd> + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + +} // namespace Catch + +#endif + + #ifndef CATCH_STRING_MANIP_HPP_INCLUDED #define CATCH_STRING_MANIP_HPP_INCLUDED @@ -8540,12 +9220,13 @@ namespace Catch { namespace Catch { bool startsWith( std::string const& s, std::string const& prefix ); - bool startsWith( std::string const& s, char prefix ); + bool startsWith( StringRef s, char prefix ); bool endsWith( std::string const& s, std::string const& suffix ); bool endsWith( std::string const& s, char suffix ); bool contains( std::string const& s, std::string const& infix ); void toLowerInPlace( std::string& s ); std::string toLower( std::string const& s ); + char toLower( char c ); //! 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! @@ -8555,13 +9236,27 @@ namespace Catch { std::vector<StringRef> splitStringRef( StringRef str, char delimiter ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); - struct pluralise { - pluralise( std::size_t count, std::string const& label ); + /** + * Helper for streaming a "count [maybe-plural-of-label]" human-friendly string + * + * Usage example: + * ```cpp + * std::cout << "Found " << pluralise(count, "error") << '\n'; + * ``` + * + * **Important:** The provided string must outlive the instance + */ + class pluralise { + std::uint64_t m_count; + StringRef m_label; - friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + public: + constexpr pluralise(std::uint64_t count, StringRef label): + m_count(count), + m_label(label) + {} - std::size_t m_count; - std::string m_label; + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); }; } @@ -8576,6 +9271,7 @@ namespace Catch { #include <string> namespace Catch { + struct SourceLineInfo; class TagAliasRegistry : public ITagAliasRegistry { public: @@ -8593,6 +9289,30 @@ namespace Catch { #endif // CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED +#ifndef CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED +#define CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED + +#include <cstdint> + +namespace Catch { + + struct TestCaseInfo; + + class TestCaseInfoHasher { + public: + using hash_t = std::uint64_t; + TestCaseInfoHasher( hash_t seed ); + uint32_t operator()( TestCaseInfo const& t ) const; + + private: + hash_t m_seed; + }; + +} // namespace Catch + +#endif /* CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED */ + + #ifndef CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED #define CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED @@ -8602,7 +9322,7 @@ namespace Catch { namespace Catch { class TestCaseHandle; - struct IConfig; + class IConfig; class TestSpec; std::vector<TestCaseHandle> sortTests( IConfig const& config, std::vector<TestCaseHandle> const& unsortedTestCases ); @@ -8649,9 +9369,6 @@ namespace Catch { void invoke() const override; }; - - std::string extractClassName( StringRef const& classOrQualifiedMethodName ); - /////////////////////////////////////////////////////////////////////////// @@ -8675,7 +9392,7 @@ namespace Catch { namespace Catch { - struct ITagAliasRegistry; + class ITagAliasRegistry; class TestSpecParser { enum Mode{ None, Name, QuotedName, Tag, EscapedName }; @@ -8749,31 +9466,49 @@ namespace Catch { class Columns; + /** + * Represents a column of text with specific width and indentation + * + * When written out to a stream, it will perform linebreaking + * of the provided text so that the written lines fit within + * target width. + */ class Column { + // String to be written out std::string m_string; + // Width of the column for linebreaking size_t m_width = CATCH_CONFIG_CONSOLE_WIDTH - 1; + // Indentation of other lines (including first if initial indent is unset) size_t m_indent = 0; + // Indentation of the first line size_t m_initialIndent = std::string::npos; public: - class iterator { + /** + * Iterates "lines" in `Column` and return sthem + */ + class const_iterator { friend Column; struct EndTag {}; Column const& m_column; - size_t m_pos = 0; - - size_t m_len = 0; - size_t m_end = 0; - bool m_suffix = false; - - iterator( Column const& column, EndTag ): - m_column( column ), m_pos( m_column.m_string.size() ) {} - + // Where does the current line start? + size_t m_lineStart = 0; + // How long should the current line be? + size_t m_lineLength = 0; + // How far have we checked the string to iterate? + size_t m_parsedTo = 0; + // Should a '-' be appended to the line? + bool m_addHyphen = false; + + const_iterator( Column const& column, EndTag ): + m_column( column ), m_lineStart( m_column.m_string.size() ) {} + + // Calculates the length of the current line void calcLength(); // Returns current indention width - size_t indent() const; + size_t indentSize() const; // Creates an indented and (optionally) suffixed string from // current iterator position, indentation and length. @@ -8787,21 +9522,21 @@ namespace Catch { using reference = value_type&; using iterator_category = std::forward_iterator_tag; - explicit iterator( Column const& column ); + explicit const_iterator( Column const& column ); std::string operator*() const; - iterator& operator++(); - iterator operator++( int ); + const_iterator& operator++(); + const_iterator operator++( int ); - bool operator==( iterator const& other ) const { - return m_pos == other.m_pos && &m_column == &other.m_column; + bool operator==( const_iterator const& other ) const { + return m_lineStart == other.m_lineStart && &m_column == &other.m_column; } - bool operator!=( iterator const& other ) const { + bool operator!=( const_iterator const& other ) const { return !operator==( other ); } }; - using const_iterator = iterator; + using iterator = const_iterator; explicit Column( std::string const& text ): m_string( text ) {} @@ -8820,8 +9555,8 @@ namespace Catch { } size_t width() const { return m_width; } - iterator begin() const { return iterator( *this ); } - iterator end() const { return { *this, iterator::EndTag{} }; } + const_iterator begin() const { return const_iterator( *this ); } + const_iterator end() const { return { *this, const_iterator::EndTag{} }; } friend std::ostream& operator<<( std::ostream& os, Column const& col ); @@ -8841,7 +9576,7 @@ namespace Catch { struct EndTag {}; std::vector<Column> const& m_columns; - std::vector<Column::iterator> m_iterators; + std::vector<Column::const_iterator> m_iterators; size_t m_activeIterators; iterator( Columns const& columns, EndTag ); @@ -8914,16 +9649,37 @@ namespace Catch { #endif // CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED -#ifndef CATCH_XMLWRITER_HPP_INCLUDED -#define CATCH_XMLWRITER_HPP_INCLUDED +#ifndef CATCH_WINDOWS_H_PROXY_HPP_INCLUDED +#define CATCH_WINDOWS_H_PROXY_HPP_INCLUDED -// FixMe: Without this include (and something inside it), MSVC goes crazy -// and reports that calls to XmlEncode's op << are ambiguous between -// the declaration and definition. -// It also has to be in the header. +#if defined(CATCH_PLATFORM_WINDOWS) +// We might end up with the define made globally through the compiler, +// and we don't want to trigger warnings for this +#if !defined(NOMINMAX) +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +#endif +#ifdef __AFXDLL +#include <AfxWin.h> +#else +#include <windows.h> +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +#endif // CATCH_WINDOWS_H_PROXY_HPP_INCLUDED + + +#ifndef CATCH_XMLWRITER_HPP_INCLUDED +#define CATCH_XMLWRITER_HPP_INCLUDED + + +#include <iosfwd> #include <vector> namespace Catch { @@ -8936,18 +9692,24 @@ namespace Catch { XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs); XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs); + /** + * Helper for XML-encoding text (escaping angle brackets, quotes, etc) + * + * Note: doesn't take ownership of passed strings, and thus the + * encoded string must outlive the encoding instance. + */ class XmlEncode { public: enum ForWhat { ForTextNodes, ForAttributes }; - XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + XmlEncode( StringRef str, ForWhat forWhat = ForTextNodes ); void encodeTo( std::ostream& os ) const; friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); private: - std::string m_str; + StringRef m_str; ForWhat m_forWhat; }; @@ -8963,20 +9725,32 @@ namespace Catch { ~ScopedElement(); - ScopedElement& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent ); - - template<typename T> - ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + ScopedElement& + writeText( StringRef text, + XmlFormatting fmt = XmlFormatting::Newline | + XmlFormatting::Indent ); + + ScopedElement& writeAttribute( StringRef name, + StringRef attribute ); + template <typename T, + // Without this SFINAE, this overload is a better match + // for `std::string`, `char const*`, `char const[N]` args. + // While it would still work, it would cause code bloat + // and multiple iteration over the strings + typename = typename std::enable_if_t< + !std::is_convertible<T, StringRef>::value>> + ScopedElement& writeAttribute( StringRef name, + T const& attribute ) { m_writer->writeAttribute( name, attribute ); return *this; } private: - mutable XmlWriter* m_writer = nullptr; + XmlWriter* m_writer = nullptr; XmlFormatting m_fmt; }; - XmlWriter( std::ostream& os = Catch::cout() ); + XmlWriter( std::ostream& os ); ~XmlWriter(); XmlWriter( XmlWriter const& ) = delete; @@ -8988,24 +9762,41 @@ namespace Catch { XmlWriter& endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); - - XmlWriter& writeAttribute( std::string const& name, bool attribute ); - - template<typename T> - XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + //! The attribute content is XML-encoded + XmlWriter& writeAttribute( StringRef name, StringRef attribute ); + + //! Writes the attribute as "true/false" + XmlWriter& writeAttribute( StringRef name, bool attribute ); + + //! The attribute content is XML-encoded + XmlWriter& writeAttribute( StringRef name, char const* attribute ); + + //! The attribute value must provide op<<(ostream&, T). The resulting + //! serialization is XML-encoded + template <typename T, + // Without this SFINAE, this overload is a better match + // for `std::string`, `char const*`, `char const[N]` args. + // While it would still work, it would cause code bloat + // and multiple iteration over the strings + typename = typename std::enable_if_t< + !std::is_convertible<T, StringRef>::value>> + XmlWriter& writeAttribute( StringRef name, T const& attribute ) { ReusableStringStream rss; rss << attribute; return writeAttribute( name, rss.str() ); } - XmlWriter& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); - - XmlWriter& writeComment(std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + //! Writes escaped `text` in a element + XmlWriter& writeText( StringRef text, + XmlFormatting fmt = XmlFormatting::Newline | + XmlFormatting::Indent ); - void writeStylesheetRef( std::string const& url ); + //! Writes XML comment as "<!-- text -->" + XmlWriter& writeComment( StringRef text, + XmlFormatting fmt = XmlFormatting::Newline | + XmlFormatting::Indent ); - XmlWriter& writeBlankLine(); + void writeStylesheetRef( StringRef url ); void ensureTagClosed(); @@ -9063,35 +9854,32 @@ namespace Catch { MatcherT const& m_matcher; StringRef m_matcherString; public: - MatchExpr( ArgT && arg, MatcherT const& matcher, StringRef const& matcherString ) + MatchExpr( ArgT && arg, MatcherT const& matcher, StringRef matcherString ) : ITransientExpression{ true, matcher.match( arg ) }, // not forwarding arg here on purpose - m_arg( std::forward<ArgT>(arg) ), + m_arg( CATCH_FORWARD(arg) ), m_matcher( matcher ), m_matcherString( matcherString ) {} - void streamReconstructedExpression( std::ostream &os ) const override { - auto matcherAsString = m_matcher.toString(); - os << Catch::Detail::stringify( m_arg ) << ' '; - if( matcherAsString == Detail::unprintableString ) - os << m_matcherString; - else - os << matcherAsString; + void streamReconstructedExpression( std::ostream& os ) const override { + os << Catch::Detail::stringify( m_arg ) + << ' ' + << m_matcher.toString(); } }; namespace Matchers { template <typename ArgT> - struct MatcherBase; + class MatcherBase; } using StringMatcher = Matchers::MatcherBase<std::string>; - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ); + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ); template<typename ArgT, typename MatcherT> - auto makeMatchExpr( ArgT && arg, MatcherT const& matcher, StringRef const& matcherString ) -> MatchExpr<ArgT, MatcherT> { - return MatchExpr<ArgT, MatcherT>( std::forward<ArgT>(arg), matcher, matcherString ); + auto makeMatchExpr( ArgT && arg, MatcherT const& matcher, StringRef matcherString ) -> MatchExpr<ArgT, MatcherT> { + return MatchExpr<ArgT, MatcherT>( CATCH_FORWARD(arg), matcher, matcherString ); } } // namespace Catch @@ -9155,27 +9943,20 @@ namespace Matchers { mutable std::string m_cachedToString; }; -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wnon-virtual-dtor" -#endif - - template<typename ObjectT> - struct MatcherMethod { - virtual bool match(ObjectT const& arg) const = 0; - }; - -#ifdef __clang__ -# pragma clang diagnostic pop -#endif template<typename T> - struct MatcherBase : MatcherUntypedBase, MatcherMethod<T> {}; + class MatcherBase : public MatcherUntypedBase { + public: + virtual bool match( T const& arg ) const = 0; + }; namespace Detail { template<typename ArgT> - struct MatchAllOf final : MatcherBase<ArgT> { + class MatchAllOf final : public MatcherBase<ArgT> { + std::vector<MatcherBase<ArgT> const*> m_matchers; + + public: MatchAllOf() = default; MatchAllOf(MatchAllOf const&) = delete; MatchAllOf& operator=(MatchAllOf const&) = delete; @@ -9208,15 +9989,12 @@ namespace Matchers { friend MatchAllOf operator&& (MatchAllOf&& lhs, MatcherBase<ArgT> const& rhs) { lhs.m_matchers.push_back(&rhs); - return std::move(lhs); + return CATCH_MOVE(lhs); } friend MatchAllOf operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf&& rhs) { rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); - return std::move(rhs); + return CATCH_MOVE(rhs); } - - private: - std::vector<MatcherBase<ArgT> const*> m_matchers; }; //! lvalue overload is intentionally deleted, users should @@ -9229,7 +10007,9 @@ namespace Matchers { MatchAllOf<ArgT> operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf<ArgT> const& rhs) = delete; template<typename ArgT> - struct MatchAnyOf final : MatcherBase<ArgT> { + class MatchAnyOf final : public MatcherBase<ArgT> { + std::vector<MatcherBase<ArgT> const*> m_matchers; + public: MatchAnyOf() = default; MatchAnyOf(MatchAnyOf const&) = delete; MatchAnyOf& operator=(MatchAnyOf const&) = delete; @@ -9261,15 +10041,12 @@ namespace Matchers { friend MatchAnyOf operator|| (MatchAnyOf&& lhs, MatcherBase<ArgT> const& rhs) { lhs.m_matchers.push_back(&rhs); - return std::move(lhs); + return CATCH_MOVE(lhs); } friend MatchAnyOf operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf&& rhs) { rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); - return std::move(rhs); + return CATCH_MOVE(rhs); } - - private: - std::vector<MatcherBase<ArgT> const*> m_matchers; }; //! lvalue overload is intentionally deleted, users should @@ -9282,8 +10059,10 @@ namespace Matchers { MatchAnyOf<ArgT> operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf<ArgT> const& rhs) = delete; template<typename ArgT> - struct MatchNotOf final : MatcherBase<ArgT> { + class MatchNotOf final : public MatcherBase<ArgT> { + MatcherBase<ArgT> const& m_underlyingMatcher; + public: explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ): m_underlyingMatcher( underlyingMatcher ) {} @@ -9295,9 +10074,6 @@ namespace Matchers { std::string describe() const override { return "not " + m_underlyingMatcher.toString(); } - - private: - MatcherBase<ArgT> const& m_underlyingMatcher; }; } // namespace Detail @@ -9382,13 +10158,13 @@ namespace Matchers { #include <algorithm> #include <string> #include <type_traits> -#include <utility> namespace Catch { namespace Matchers { - struct MatcherGenericBase : MatcherUntypedBase { + class MatcherGenericBase : public MatcherUntypedBase { + public: MatcherGenericBase() = default; - virtual ~MatcherGenericBase(); // = default; + ~MatcherGenericBase() override; // = default; MatcherGenericBase(MatcherGenericBase&) = default; MatcherGenericBase(MatcherGenericBase&&) = default; @@ -9422,11 +10198,11 @@ namespace Matchers { return arr; } - #ifdef CATCH_CPP17_OR_GREATER +#if defined( __cpp_lib_logical_traits ) && __cpp_lib_logical_traits >= 201510 using std::conjunction; - #else // CATCH_CPP17_OR_GREATER +#else // __cpp_lib_logical_traits template<typename... Cond> struct conjunction : std::true_type {}; @@ -9434,7 +10210,7 @@ namespace Matchers { template<typename Cond, typename... Rest> struct conjunction<Cond, Rest...> : std::integral_constant<bool, Cond::value && conjunction<Rest...>::value> {}; - #endif // CATCH_CPP17_OR_GREATER +#endif // __cpp_lib_logical_traits template<typename T> using is_generic_matcher = std::is_base_of< @@ -9486,7 +10262,8 @@ namespace Matchers { template<typename... MatcherTs> - struct MatchAllOfGeneric final : MatcherGenericBase { + class MatchAllOfGeneric final : public MatcherGenericBase { + public: MatchAllOfGeneric(MatchAllOfGeneric const&) = delete; MatchAllOfGeneric& operator=(MatchAllOfGeneric const&) = delete; MatchAllOfGeneric(MatchAllOfGeneric&&) = default; @@ -9504,7 +10281,11 @@ namespace Matchers { return describe_multi_matcher<MatcherTs...>(" and "_sr, m_matchers, std::index_sequence_for<MatcherTs...>{}); } - std::array<void const*, sizeof...(MatcherTs)> m_matchers; + // Has to be public to enable the concatenating operators + // below, because they are not friend of the RHS, only LHS, + // and thus cannot access private fields of RHS + std::array<void const*, sizeof...( MatcherTs )> m_matchers; + //! Avoids type nesting for `GenericAllOf && GenericAllOf` case template<typename... MatchersRHS> @@ -9512,7 +10293,7 @@ namespace Matchers { MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && ( MatchAllOfGeneric<MatcherTs...>&& lhs, MatchAllOfGeneric<MatchersRHS...>&& rhs) { - return MatchAllOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(std::move(lhs.m_matchers), std::move(rhs.m_matchers))}; + return MatchAllOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; } //! Avoids type nesting for `GenericAllOf && some matcher` case @@ -9521,7 +10302,7 @@ namespace Matchers { MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && ( MatchAllOfGeneric<MatcherTs...>&& lhs, MatcherRHS const& rhs) { - return MatchAllOfGeneric<MatcherTs..., MatcherRHS>{array_cat(std::move(lhs.m_matchers), static_cast<void const*>(&rhs))}; + return MatchAllOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(&rhs))}; } //! Avoids type nesting for `some matcher && GenericAllOf` case @@ -9530,13 +10311,14 @@ namespace Matchers { MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && ( MatcherLHS const& lhs, MatchAllOfGeneric<MatcherTs...>&& rhs) { - return MatchAllOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), std::move(rhs.m_matchers))}; + return MatchAllOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; } }; template<typename... MatcherTs> - struct MatchAnyOfGeneric final : MatcherGenericBase { + class MatchAnyOfGeneric final : public MatcherGenericBase { + public: MatchAnyOfGeneric(MatchAnyOfGeneric const&) = delete; MatchAnyOfGeneric& operator=(MatchAnyOfGeneric const&) = delete; MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default; @@ -9554,14 +10336,18 @@ namespace Matchers { return describe_multi_matcher<MatcherTs...>(" or "_sr, m_matchers, std::index_sequence_for<MatcherTs...>{}); } - std::array<void const*, sizeof...(MatcherTs)> m_matchers; + + // Has to be public to enable the concatenating operators + // below, because they are not friend of the RHS, only LHS, + // and thus cannot access private fields of RHS + std::array<void const*, sizeof...( MatcherTs )> m_matchers; //! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case template<typename... MatchersRHS> friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || ( MatchAnyOfGeneric<MatcherTs...>&& lhs, MatchAnyOfGeneric<MatchersRHS...>&& rhs) { - return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(std::move(lhs.m_matchers), std::move(rhs.m_matchers))}; + return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; } //! Avoids type nesting for `GenericAnyOf || some matcher` case @@ -9570,7 +10356,7 @@ namespace Matchers { MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || ( MatchAnyOfGeneric<MatcherTs...>&& lhs, MatcherRHS const& rhs) { - return MatchAnyOfGeneric<MatcherTs..., MatcherRHS>{array_cat(std::move(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))}; + return MatchAnyOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))}; } //! Avoids type nesting for `some matcher || GenericAnyOf` case @@ -9579,13 +10365,16 @@ namespace Matchers { MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || ( MatcherLHS const& lhs, MatchAnyOfGeneric<MatcherTs...>&& rhs) { - return MatchAnyOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), std::move(rhs.m_matchers))}; + return MatchAnyOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; } }; template<typename MatcherT> - struct MatchNotOfGeneric final : MatcherGenericBase { + class MatchNotOfGeneric final : public MatcherGenericBase { + MatcherT const& m_matcher; + + public: MatchNotOfGeneric(MatchNotOfGeneric const&) = delete; MatchNotOfGeneric& operator=(MatchNotOfGeneric const&) = delete; MatchNotOfGeneric(MatchNotOfGeneric&&) = default; @@ -9606,8 +10395,6 @@ namespace Matchers { friend MatcherT const& operator ! (MatchNotOfGeneric<MatcherT> const& matcher) { return matcher.m_matcher; } - private: - MatcherT const& m_matcher; }; } // namespace Detail @@ -9707,7 +10494,7 @@ namespace Catch { Matcher m_matcher; public: explicit SizeMatchesMatcher(Matcher m): - m_matcher(std::move(m)) + m_matcher(CATCH_MOVE(m)) {} template <typename RangeLike> @@ -9733,7 +10520,7 @@ namespace Catch { template <typename Matcher> std::enable_if_t<Detail::is_matcher<Matcher>::value, SizeMatchesMatcher<Matcher>> SizeIs(Matcher&& m) { - return SizeMatchesMatcher<Matcher>{std::forward<Matcher>(m)}; + return SizeMatchesMatcher<Matcher>{CATCH_FORWARD(m)}; } } // end namespace Matchers @@ -9748,7 +10535,6 @@ namespace Catch { #include <algorithm> #include <functional> -#include <utility> namespace Catch { namespace Matchers { @@ -9760,8 +10546,8 @@ namespace Catch { public: template <typename T2, typename Equality2> ContainsElementMatcher(T2&& target, Equality2&& predicate): - m_desired(std::forward<T2>(target)), - m_eq(std::forward<Equality2>(predicate)) + m_desired(CATCH_FORWARD(target)), + m_eq(CATCH_FORWARD(predicate)) {} std::string describe() const override { @@ -9788,12 +10574,11 @@ namespace Catch { // constructor (and also avoid some perfect forwarding failure // cases) ContainsMatcherMatcher(Matcher matcher): - m_matcher(std::move(matcher)) + m_matcher(CATCH_MOVE(matcher)) {} template <typename RangeLike> bool match(RangeLike&& rng) const { - using std::begin; using std::endl; for (auto&& elem : rng) { if (m_matcher.match(elem)) { return true; @@ -9815,14 +10600,14 @@ namespace Catch { template <typename T> std::enable_if_t<!Detail::is_matcher<T>::value, ContainsElementMatcher<T, std::equal_to<>>> Contains(T&& elem) { - return { std::forward<T>(elem), std::equal_to<>{} }; + return { CATCH_FORWARD(elem), std::equal_to<>{} }; } //! Creates a matcher that checks whether a range contains element matching a matcher template <typename Matcher> std::enable_if_t<Detail::is_matcher<Matcher>::value, ContainsMatcherMatcher<Matcher>> Contains(Matcher&& matcher) { - return { std::forward<Matcher>(matcher) }; + return { CATCH_FORWARD(matcher) }; } /** @@ -9832,7 +10617,7 @@ namespace Catch { */ template <typename T, typename Equality> ContainsElementMatcher<T, Equality> Contains(T&& elem, Equality&& eq) { - return { std::forward<T>(elem), std::forward<Equality>(eq) }; + return { CATCH_FORWARD(elem), CATCH_FORWARD(eq) }; } } @@ -9870,8 +10655,8 @@ ExceptionMessageMatcher Message(std::string const& message); #endif // CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED -#ifndef CATCH_MATCHERS_FLOATING_HPP_INCLUDED -#define CATCH_MATCHERS_FLOATING_HPP_INCLUDED +#ifndef CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED +#define CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED namespace Catch { @@ -9881,7 +10666,8 @@ namespace Matchers { enum class FloatingPointKind : uint8_t; } - struct WithinAbsMatcher final : MatcherBase<double> { + class WithinAbsMatcher final : public MatcherBase<double> { + public: WithinAbsMatcher(double target, double margin); bool match(double const& matchee) const override; std::string describe() const override; @@ -9890,8 +10676,11 @@ namespace Matchers { double m_margin; }; - struct WithinUlpsMatcher final : MatcherBase<double> { - WithinUlpsMatcher(double target, uint64_t ulps, Detail::FloatingPointKind baseType); + class WithinUlpsMatcher final : public MatcherBase<double> { + public: + WithinUlpsMatcher( double target, + uint64_t ulps, + Detail::FloatingPointKind baseType ); bool match(double const& matchee) const override; std::string describe() const override; private: @@ -9906,8 +10695,9 @@ namespace Matchers { // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get // the same result if we do this for floats, as if we do this for // doubles that were promoted from floats. - struct WithinRelMatcher final : MatcherBase<double> { - WithinRelMatcher(double target, double epsilon); + class WithinRelMatcher final : public MatcherBase<double> { + public: + WithinRelMatcher( double target, double epsilon ); bool match(double const& matchee) const override; std::string describe() const override; private: @@ -9934,7 +10724,7 @@ namespace Matchers { } // namespace Matchers } // namespace Catch -#endif // CATCH_MATCHERS_FLOATING_HPP_INCLUDED +#endif // CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED #ifndef CATCH_MATCHERS_PREDICATE_HPP_INCLUDED @@ -9942,7 +10732,6 @@ namespace Matchers { #include <string> -#include <utility> namespace Catch { namespace Matchers { @@ -9958,7 +10747,7 @@ class PredicateMatcher final : public MatcherBase<T> { public: PredicateMatcher(Predicate&& elem, std::string const& descr) - :m_predicate(std::forward<Predicate>(elem)), + :m_predicate(CATCH_FORWARD(elem)), m_description(Detail::finalizeDescription(descr)) {} @@ -9980,7 +10769,7 @@ public: PredicateMatcher<T, Pred> Predicate(Pred&& predicate, std::string const& description = "") { static_assert(is_callable<Pred(T)>::value, "Predicate not callable with argument T"); static_assert(std::is_same<bool, FunctionReturnType<Pred, T>>::value, "Predicate does not return bool"); - return PredicateMatcher<T, Pred>(std::forward<Pred>(predicate), description); + return PredicateMatcher<T, Pred>(CATCH_FORWARD(predicate), description); } } // namespace Matchers @@ -9989,6 +10778,107 @@ public: #endif // CATCH_MATCHERS_PREDICATE_HPP_INCLUDED +#ifndef CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED +#define CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED + + +namespace Catch { + namespace Matchers { + // Matcher for checking that all elements in range matches a given matcher. + template <typename Matcher> + class AllMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + AllMatchMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + std::string describe() const override { + return "all match " + m_matcher.describe(); + } + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (!m_matcher.match(elem)) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that no element in range matches a given matcher. + template <typename Matcher> + class NoneMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + NoneMatchMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + std::string describe() const override { + return "none match " + m_matcher.describe(); + } + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (m_matcher.match(elem)) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that at least one element in range matches a given matcher. + template <typename Matcher> + class AnyMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + AnyMatchMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + std::string describe() const override { + return "any match " + m_matcher.describe(); + } + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (m_matcher.match(elem)) { + return true; + } + } + return false; + } + }; + + // Creates a matcher that checks whether a range contains element matching a matcher + template <typename Matcher> + AllMatchMatcher<Matcher> AllMatch(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + // Creates a matcher that checks whether no element in a range matches a matcher. + template <typename Matcher> + NoneMatchMatcher<Matcher> NoneMatch(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + // Creates a matcher that checks whether any element in a range matches a matcher. + template <typename Matcher> + AnyMatchMatcher<Matcher> AnyMatch(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + } +} + +#endif // CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED + + #ifndef CATCH_MATCHERS_STRING_HPP_INCLUDED #define CATCH_MATCHERS_STRING_HPP_INCLUDED @@ -10007,45 +10897,52 @@ namespace Matchers { std::string m_str; }; - struct StringMatcherBase : MatcherBase<std::string> { - StringMatcherBase( std::string const& operation, CasedString const& comparator ); - std::string describe() const override; - + class StringMatcherBase : public MatcherBase<std::string> { + protected: CasedString m_comparator; - std::string m_operation; + StringRef m_operation; + + public: + StringMatcherBase( StringRef operation, + CasedString const& comparator ); + std::string describe() const override; }; - struct StringEqualsMatcher final : StringMatcherBase { + class StringEqualsMatcher final : public StringMatcherBase { + public: StringEqualsMatcher( CasedString const& comparator ); bool match( std::string const& source ) const override; }; - struct StringContainsMatcher final : StringMatcherBase { + class StringContainsMatcher final : public StringMatcherBase { + public: StringContainsMatcher( CasedString const& comparator ); bool match( std::string const& source ) const override; }; - struct StartsWithMatcher final : StringMatcherBase { + class StartsWithMatcher final : public StringMatcherBase { + public: StartsWithMatcher( CasedString const& comparator ); bool match( std::string const& source ) const override; }; - struct EndsWithMatcher final : StringMatcherBase { + class EndsWithMatcher final : public StringMatcherBase { + public: EndsWithMatcher( CasedString const& comparator ); bool match( std::string const& source ) const override; }; - struct RegexMatcher final : MatcherBase<std::string> { + class RegexMatcher final : public MatcherBase<std::string> { + std::string m_regex; + CaseSensitive m_caseSensitivity; + + public: RegexMatcher( std::string regex, CaseSensitive caseSensitivity ); bool match( std::string const& matchee ) const override; std::string describe() const override; - - private: - std::string m_regex; - CaseSensitive m_caseSensitivity; }; //! Creates matcher that accepts strings that are exactly equal to `str` StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); //! Creates matcher that accepts strings that contain `str` - StringContainsMatcher Contains( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); //! Creates matcher that accepts strings that _end_ with `str` EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); //! Creates matcher that accepts strings that _start_ with `str` @@ -10069,8 +10966,10 @@ namespace Catch { namespace Matchers { template<typename T, typename Alloc> - struct VectorContainsElementMatcher final : MatcherBase<std::vector<T, Alloc>> { + class VectorContainsElementMatcher final : public MatcherBase<std::vector<T, Alloc>> { + T const& m_comparator; + public: VectorContainsElementMatcher(T const& comparator): m_comparator(comparator) {} @@ -10087,13 +10986,13 @@ namespace Matchers { std::string describe() const override { return "Contains: " + ::Catch::Detail::stringify( m_comparator ); } - - T const& m_comparator; }; template<typename T, typename AllocComp, typename AllocMatch> - struct ContainsMatcher final : MatcherBase<std::vector<T, AllocMatch>> { + class ContainsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_comparator; + public: ContainsMatcher(std::vector<T, AllocComp> const& comparator): m_comparator( comparator ) {} @@ -10119,13 +11018,13 @@ namespace Matchers { std::string describe() const override { return "Contains: " + ::Catch::Detail::stringify( m_comparator ); } - - std::vector<T, AllocComp> const& m_comparator; }; template<typename T, typename AllocComp, typename AllocMatch> - struct EqualsMatcher final : MatcherBase<std::vector<T, AllocMatch>> { + class EqualsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_comparator; + public: EqualsMatcher(std::vector<T, AllocComp> const& comparator): m_comparator( comparator ) {} @@ -10145,12 +11044,14 @@ namespace Matchers { std::string describe() const override { return "Equals: " + ::Catch::Detail::stringify( m_comparator ); } - std::vector<T, AllocComp> const& m_comparator; }; template<typename T, typename AllocComp, typename AllocMatch> - struct ApproxMatcher final : MatcherBase<std::vector<T, AllocMatch>> { + class ApproxMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_comparator; + mutable Catch::Approx approx = Catch::Approx::custom(); + public: ApproxMatcher(std::vector<T, AllocComp> const& comparator): m_comparator( comparator ) {} @@ -10181,13 +11082,13 @@ namespace Matchers { approx.scale(static_cast<double>(newScale)); return *this; } - - std::vector<T, AllocComp> const& m_comparator; - mutable Catch::Approx approx = Catch::Approx::custom(); }; template<typename T, typename AllocComp, typename AllocMatch> - struct UnorderedEqualsMatcher final : MatcherBase<std::vector<T, AllocMatch>> { + class UnorderedEqualsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_target; + + public: UnorderedEqualsMatcher(std::vector<T, AllocComp> const& target): m_target(target) {} @@ -10201,15 +11102,12 @@ namespace Matchers { std::string describe() const override { return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); } - private: - std::vector<T, AllocComp> const& m_target; }; // The following functions create the actual matcher objects. // This allows the types to be inferred - //! Creates a matcher that matches vectors that contain all elements in `comparator` template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) { @@ -10276,58 +11174,116 @@ namespace Matchers { -#include <iosfwd> +#ifndef CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED +#define CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED + + +#include <map> #include <string> -#include <vector> namespace Catch { + class ColourImpl; - template<typename T> - struct LazyStat : Option<T> { - LazyStat& operator=(T const& _value) { - Option<T>::operator=(_value); - used = false; - return *this; - } - void reset() { - Option<T>::reset(); - used = false; - } - bool used = false; - }; + /** + * This is the base class for all reporters. + * + * If are writing a reporter, you must derive from this type, or one + * of the helper reporter bases that are derived from this type. + * + * ReporterBase centralizes handling of various common tasks in reporters, + * like storing the right stream for the reporters to write to, and + * providing the default implementation of the different listing events. + */ + class ReporterBase : public IEventListener { + protected: + //! The stream wrapper as passed to us by outside code + Detail::unique_ptr<IStream> m_wrapped_stream; + //! Cached output stream from `m_wrapped_stream` to reduce + //! number of indirect calls needed to write output. + std::ostream& m_stream; + //! Colour implementation this reporter was configured for + Detail::unique_ptr<ColourImpl> m_colour; + //! The custom reporter options user passed down to the reporter + std::map<std::string, std::string> m_customOptions; + public: + ReporterBase( ReporterConfig&& config ); + ~ReporterBase() override; // = default; + + /** + * Provides a simple default listing of reporters. + * + * Should look roughly like the reporter listing in v2 and earlier + * versions of Catch2. + */ + void listReporters( + std::vector<ReporterDescription> const& descriptions ) override; + /** + * Provides a simple default listing of listeners + * + * Looks similarly to listing of reporters, but with listener type + * instead of reporter name. + */ + void listListeners( + std::vector<ListenerDescription> const& descriptions ) override; + /** + * Provides a simple default listing of tests. + * + * Should look roughly like the test listing in v2 and earlier versions + * of Catch2. Especially supports low-verbosity listing that mimics the + * old `--list-test-names-only` output. + */ + void listTests( std::vector<TestCaseHandle> const& tests ) override; + /** + * Provides a simple default listing of tags. + * + * Should look roughly like the tag listing in v2 and earlier versions + * of Catch2. + */ + void listTags( std::vector<TagInfo> const& tags ) override; + }; +} // namespace Catch - struct StreamingReporterBase : IStreamingReporter { +#endif // CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED - StreamingReporterBase( ReporterConfig const& _config ): - m_config( _config.fullConfig() ), stream( _config.stream() ) { - } +#include <vector> +namespace Catch { + class StreamingReporterBase : public ReporterBase { + public: + using ReporterBase::ReporterBase; ~StreamingReporterBase() override; - void noMatchingTestCases(std::string const&) override {} + void benchmarkPreparing( StringRef ) override {} + void benchmarkStarting( BenchmarkInfo const& ) override {} + void benchmarkEnded( BenchmarkStats<> const& ) override {} + void benchmarkFailed( StringRef ) override {} - void reportInvalidArguments(std::string const&) override {} + void fatalErrorEncountered( StringRef /*error*/ ) override {} + void noMatchingTestCases( StringRef /*unmatchedSpec*/ ) override {} + void reportInvalidTestSpec( StringRef /*invalidArgument*/ ) override {} void testRunStarting( TestRunInfo const& _testRunInfo ) override; - void testGroupStarting( GroupInfo const& _groupInfo ) override; - void testCaseStarting(TestCaseInfo const& _testInfo) override { currentTestCaseInfo = &_testInfo; } + void testCasePartialStarting( TestCaseInfo const&, uint64_t ) override {} void sectionStarting(SectionInfo const& _sectionInfo) override { m_sectionStack.push_back(_sectionInfo); } + void assertionStarting( AssertionInfo const& ) override {} + void assertionEnded( AssertionStats const& ) override {} + void sectionEnded(SectionStats const& /* _sectionStats */) override { m_sectionStack.pop_back(); } + void testCasePartialEnded( TestCaseStats const&, uint64_t ) override {} void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { currentTestCaseInfo = nullptr; } - void testGroupEnded( TestGroupStats const& ) override; void testRunEnded( TestRunStats const& /* _testRunStats */ ) override; void skipTest(TestCaseInfo const&) override { @@ -10335,13 +11291,11 @@ namespace Catch { // It can optionally be overridden in the derived class. } - IConfig const* m_config; - std::ostream& stream; - - LazyStat<TestRunInfo> currentTestRunInfo; - LazyStat<GroupInfo> currentGroupInfo; + protected: + TestRunInfo currentTestRunInfo{ "test run has not started yet"_sr }; TestCaseInfo const* currentTestCaseInfo = nullptr; + //! Stack of all _active_ sections in the _current_ test case std::vector<SectionInfo> m_sectionStack; }; @@ -10349,13 +11303,13 @@ namespace Catch { #endif // CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED -namespace Catch { +#include <string> - struct AutomakeReporter : StreamingReporterBase { - AutomakeReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ) - {} +namespace Catch { + class AutomakeReporter final : public StreamingReporterBase { + public: + using StreamingReporterBase::StreamingReporterBase; ~AutomakeReporter() override; static std::string getDescription() { @@ -10363,14 +11317,8 @@ namespace Catch { return "Reports test results in the format of Automake .trs files"s; } - void assertionStarting( AssertionInfo const& ) override {} - - bool assertionEnded( AssertionStats const& /*_assertionStats*/ ) override { return true; } - void testCaseEnded(TestCaseStats const& _testCaseStats) override; - void skipTest(TestCaseInfo const& testInfo) override; - }; } // end namespace Catch @@ -10386,19 +11334,19 @@ namespace Catch { namespace Catch { - struct CompactReporter : StreamingReporterBase { - + class CompactReporter final : public StreamingReporterBase { + public: using StreamingReporterBase::StreamingReporterBase; ~CompactReporter() override; static std::string getDescription(); - void noMatchingTestCases(std::string const& spec) override; + void noMatchingTestCases( StringRef unmatchedSpec ) override; - void assertionStarting(AssertionInfo const&) override; + void testRunStarting( TestRunInfo const& _testInfo ) override; - bool assertionEnded(AssertionStats const& _assertionStats) override; + void assertionEnded(AssertionStats const& _assertionStats) override; void sectionEnded(SectionStats const& _sectionStats) override; @@ -10415,53 +11363,43 @@ namespace Catch { #define CATCH_REPORTER_CONSOLE_HPP_INCLUDED -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch - // Note that 4062 (not all labels are handled - // and default is missing) is enabled -#endif - - namespace Catch { // Fwd decls struct SummaryColumn; class TablePrinter; - struct ConsoleReporter : StreamingReporterBase { + class ConsoleReporter final : public StreamingReporterBase { Detail::unique_ptr<TablePrinter> m_tablePrinter; - ConsoleReporter(ReporterConfig const& config); + public: + ConsoleReporter(ReporterConfig&& config); ~ConsoleReporter() override; static std::string getDescription(); - void noMatchingTestCases(std::string const& spec) override; - - void reportInvalidArguments(std::string const&arg) override; + void noMatchingTestCases( StringRef unmatchedSpec ) override; + void reportInvalidTestSpec( StringRef arg ) override; void assertionStarting(AssertionInfo const&) override; - bool assertionEnded(AssertionStats const& _assertionStats) override; + void assertionEnded(AssertionStats const& _assertionStats) override; void sectionStarting(SectionInfo const& _sectionInfo) override; void sectionEnded(SectionStats const& _sectionStats) override; - void benchmarkPreparing(std::string const& name) override; + void benchmarkPreparing( StringRef name ) override; void benchmarkStarting(BenchmarkInfo const& info) override; void benchmarkEnded(BenchmarkStats<> const& stats) override; - void benchmarkFailed(std::string const& error) override; + void benchmarkFailed( StringRef error ) override; void testCaseEnded(TestCaseStats const& _testCaseStats) override; - void testGroupEnded(TestGroupStats const& _testGroupStats) override; void testRunEnded(TestRunStats const& _testRunStats) override; void testRunStarting(TestRunInfo const& _testRunInfo) override; - private: + private: void lazyPrint(); void lazyPrintWithoutClosingBenchmarkTable(); void lazyPrintRunInfo(); - void lazyPrintGroupInfo(); void printTestCaseAndSectionHeader(); void printClosedHeader(std::string const& _name); @@ -10473,22 +11411,17 @@ namespace Catch { void printTotals(Totals const& totals); - void printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row); + void printSummaryRow(StringRef label, std::vector<SummaryColumn> const& cols, std::size_t row); void printTotalsDivider(Totals const& totals); void printSummaryDivider(); - void printTestFilters(); - private: bool m_headerPrinted = false; + bool m_testRunInfoPrinted = false; }; } // end namespace Catch -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - #endif // CATCH_REPORTER_CONSOLE_HPP_INCLUDED @@ -10496,20 +11429,59 @@ namespace Catch { #define CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED -#include <iosfwd> -#include <memory> #include <string> #include <vector> namespace Catch { - struct CumulativeReporterBase : IStreamingReporter { + namespace Detail { + + //! Represents either an assertion or a benchmark result to be handled by cumulative reporter later + class AssertionOrBenchmarkResult { + // This should really be a variant, but this is much faster + // to write and the data layout here is already terrible + // enough that we do not have to care about the object size. + Optional<AssertionStats> m_assertion; + Optional<BenchmarkStats<>> m_benchmark; + public: + AssertionOrBenchmarkResult(AssertionStats const& assertion); + AssertionOrBenchmarkResult(BenchmarkStats<> const& benchmark); + + bool isAssertion() const; + bool isBenchmark() const; + + AssertionStats const& asAssertion() const; + BenchmarkStats<> const& asBenchmark() const; + }; + } + + /** + * Utility base for reporters that need to handle all results at once + * + * It stores tree of all test cases, sections and assertions, and after the + * test run is finished, calls into `testRunEndedCumulative` to pass the + * control to the deriving class. + * + * If you are deriving from this class and override any testing related + * member functions, you should first call into the base's implementation to + * avoid breaking the tree construction. + * + * Due to the way this base functions, it has to expand assertions up-front, + * even if they are later unused (e.g. because the deriving reporter does + * not report successful assertions, or because the deriving reporter does + * not use assertion expansion at all). Derived classes can use two + * customization points, `m_shouldStoreSuccesfulAssertions` and + * `m_shouldStoreFailedAssertions`, to disable the expansion and gain extra + * performance. **Accessing the assertion expansions if it wasn't stored is + * UB.** + */ + class CumulativeReporterBase : public ReporterBase { + public: template<typename T, typename ChildNodeT> struct Node { explicit Node( T const& _value ) : value( _value ) {} - virtual ~Node() {} - using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>; + using ChildNodes = std::vector<Detail::unique_ptr<ChildNodeT>>; T value; ChildNodes children; }; @@ -10520,54 +11492,70 @@ namespace Catch { return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; } + bool hasAnyAssertions() const; + SectionStats stats; - using ChildSections = std::vector<std::shared_ptr<SectionNode>>; - using Assertions = std::vector<AssertionStats>; - ChildSections childSections; - Assertions assertions; + std::vector<Detail::unique_ptr<SectionNode>> childSections; + std::vector<Detail::AssertionOrBenchmarkResult> assertionsAndBenchmarks; std::string stdOut; std::string stdErr; }; using TestCaseNode = Node<TestCaseStats, SectionNode>; - using TestGroupNode = Node<TestGroupStats, TestCaseNode>; - using TestRunNode = Node<TestRunStats, TestGroupNode>; + using TestRunNode = Node<TestRunStats, TestCaseNode>; - CumulativeReporterBase( ReporterConfig const& _config ): - m_config( _config.fullConfig() ), stream( _config.stream() ) {} + using ReporterBase::ReporterBase; ~CumulativeReporterBase() override; + void benchmarkPreparing( StringRef ) override {} + void benchmarkStarting( BenchmarkInfo const& ) override {} + void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override; + void benchmarkFailed( StringRef ) override {} + + void noMatchingTestCases( StringRef ) override {} + void reportInvalidTestSpec( StringRef ) override {} + void fatalErrorEncountered( StringRef /*error*/ ) override {} + void testRunStarting( TestRunInfo const& ) override {} - void testGroupStarting( GroupInfo const& ) override {} void testCaseStarting( TestCaseInfo const& ) override {} - + void testCasePartialStarting( TestCaseInfo const&, uint64_t ) override {} void sectionStarting( SectionInfo const& sectionInfo ) override; void assertionStarting( AssertionInfo const& ) override {} - bool assertionEnded( AssertionStats const& assertionStats ) override; + void assertionEnded( AssertionStats const& assertionStats ) override; void sectionEnded( SectionStats const& sectionStats ) override; + void testCasePartialEnded( TestCaseStats const&, uint64_t ) override {} void testCaseEnded( TestCaseStats const& testCaseStats ) override; - void testGroupEnded( TestGroupStats const& testGroupStats ) override; void testRunEnded( TestRunStats const& testRunStats ) override; + //! Customization point: called after last test finishes (testRunEnded has been handled) virtual void testRunEndedCumulative() = 0; void skipTest(TestCaseInfo const&) override {} - IConfig const* m_config; - std::ostream& stream; - std::vector<AssertionStats> m_assertions; - std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections; - std::vector<std::shared_ptr<TestCaseNode>> m_testCases; - std::vector<std::shared_ptr<TestGroupNode>> m_testGroups; + protected: + //! Should the cumulative base store the assertion expansion for succesful assertions? + bool m_shouldStoreSuccesfulAssertions = true; + //! Should the cumulative base store the assertion expansion for failed assertions? + bool m_shouldStoreFailedAssertions = true; - std::vector<std::shared_ptr<TestRunNode>> m_testRuns; + // We need lazy construction here. We should probably refactor it + // later, after the events are redone. + //! The root node of the test run tree. + Detail::unique_ptr<TestRunNode> m_testRun; - std::shared_ptr<SectionNode> m_rootSection; - std::shared_ptr<SectionNode> m_deepestSection; - std::vector<std::shared_ptr<SectionNode>> m_sectionStack; + private: + // Note: We rely on pointer identity being stable, which is why + // we store pointers to the nodes rather than the values. + std::vector<Detail::unique_ptr<TestCaseNode>> m_testCases; + // Root section of the _current_ test case + Detail::unique_ptr<SectionNode> m_rootSection; + // Deepest section of the _current_ test case + SectionNode* m_deepestSection = nullptr; + // Stack of _active_ sections in the _current_ test case + std::vector<SectionNode*> m_sectionStack; }; } // end namespace Catch @@ -10582,38 +11570,44 @@ namespace Catch { namespace Catch { /** - * Base class identifying listeners. + * Base class to simplify implementing listeners. * - * Provides default implementation for all IStreamingReporter member - * functions, so that listeners implementations can pick which + * Provides empty default implementation for all IEventListener member + * functions, so that a listener implementation can pick which * member functions it actually cares about. */ - class EventListenerBase : public IStreamingReporter { - IConfig const* m_config; - + class EventListenerBase : public IEventListener { public: - EventListenerBase( ReporterConfig const& config ): - m_config( config.fullConfig() ) {} + using IEventListener::IEventListener; + + void reportInvalidTestSpec( StringRef unmatchedSpec ) override; + void fatalErrorEncountered( StringRef error ) override; + + void benchmarkPreparing( StringRef name ) override; + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override; + void benchmarkFailed( StringRef error ) override; void assertionStarting( AssertionInfo const& assertionInfo ) override; - bool assertionEnded( AssertionStats const& assertionStats ) override; + void assertionEnded( AssertionStats const& assertionStats ) override; - void - listReporters( std::vector<ReporterDescription> const& descriptions, - IConfig const& config ) override; - void listTests( std::vector<TestCaseHandle> const& tests, - IConfig const& config ) override; - void listTags( std::vector<TagInfo> const& tagInfos, - IConfig const& config ) override; + void listReporters( + std::vector<ReporterDescription> const& descriptions ) override; + void listListeners( + std::vector<ListenerDescription> const& descriptions ) override; + void listTests( std::vector<TestCaseHandle> const& tests ) override; + void listTags( std::vector<TagInfo> const& tagInfos ) override; - void noMatchingTestCases( std::string const& spec ) override; + void noMatchingTestCases( StringRef unmatchedSpec ) override; void testRunStarting( TestRunInfo const& testRunInfo ) override; - void testGroupStarting( GroupInfo const& groupInfo ) override; void testCaseStarting( TestCaseInfo const& testInfo ) override; + void testCasePartialStarting( TestCaseInfo const& testInfo, + uint64_t partNumber ) override; void sectionStarting( SectionInfo const& sectionInfo ) override; void sectionEnded( SectionStats const& sectionStats ) override; + void testCasePartialEnded( TestCaseStats const& testCaseStats, + uint64_t partNumber ) override; void testCaseEnded( TestCaseStats const& testCaseStats ) override; - void testGroupEnded( TestGroupStats const& testGroupStats ) override; void testRunEnded( TestRunStats const& testRunStats ) override; void skipTest( TestCaseInfo const& testInfo ) override; }; @@ -10630,9 +11624,12 @@ namespace Catch { #include <string> #include <vector> + namespace Catch { - struct IConfig; + class IConfig; + class TestCaseHandle; + class ColourImpl; // Returns double formatted as %.3f (format expected on output) std::string getFormattedDuration( double duration ); @@ -10649,6 +11646,50 @@ namespace Catch { friend std::ostream& operator<<( std::ostream& out, lineOfChars value ); }; + /** + * Lists reporter descriptions to the provided stream in user-friendly + * format + * + * Used as the default listing implementation by the first party reporter + * bases. The output should be backwards compatible with the output of + * Catch2 v2 binaries. + */ + void + defaultListReporters( std::ostream& out, + std::vector<ReporterDescription> const& descriptions, + Verbosity verbosity ); + + /** + * Lists listeners descriptions to the provided stream in user-friendly + * format + */ + void defaultListListeners( std::ostream& out, + std::vector<ListenerDescription> const& descriptions ); + + /** + * Lists tag information to the provided stream in user-friendly format + * + * Used as the default listing implementation by the first party reporter + * bases. The output should be backwards compatible with the output of + * Catch2 v2 binaries. + */ + void defaultListTags( std::ostream& out, std::vector<TagInfo> const& tags, bool isFiltered ); + + /** + * Lists test case information to the provided stream in user-friendly + * format + * + * Used as the default listing implementation by the first party reporter + * bases. The output is backwards compatible with the output of Catch2 + * v2 binaries, and also supports the format specific to the old + * `--list-test-names-only` option, for people who used it in integrations. + */ + void defaultListTests( std::ostream& out, + ColourImpl* streamColour, + std::vector<TestCaseHandle> const& tests, + bool isFiltered, + Verbosity verbosity ); + } // end namespace Catch #endif // CATCH_REPORTER_HELPERS_HPP_INCLUDED @@ -10661,36 +11702,32 @@ namespace Catch { namespace Catch { - class JunitReporter : public CumulativeReporterBase { + class JunitReporter final : public CumulativeReporterBase { public: - JunitReporter(ReporterConfig const& _config); + JunitReporter(ReporterConfig&& _config); - ~JunitReporter() override; + ~JunitReporter() override = default; static std::string getDescription(); - void noMatchingTestCases(std::string const& /*spec*/) override; - void testRunStarting(TestRunInfo const& runInfo) override; - void testGroupStarting(GroupInfo const& groupInfo) override; - void testCaseStarting(TestCaseInfo const& testCaseInfo) override; - bool assertionEnded(AssertionStats const& assertionStats) override; + void assertionEnded(AssertionStats const& assertionStats) override; void testCaseEnded(TestCaseStats const& testCaseStats) override; - void testGroupEnded(TestGroupStats const& testGroupStats) override; - void testRunEndedCumulative() override; - void writeGroup(TestGroupNode const& groupNode, double suiteTime); + private: + void writeRun(TestRunNode const& testRunNode, double suiteTime); void writeTestCase(TestCaseNode const& testCaseNode); - void writeSection(std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode); + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail ); void writeAssertions(SectionNode const& sectionNode); void writeAssertion(AssertionStats const& stats); @@ -10708,59 +11745,189 @@ namespace Catch { #endif // CATCH_REPORTER_JUNIT_HPP_INCLUDED -#ifndef CATCH_REPORTER_LISTENING_HPP_INCLUDED -#define CATCH_REPORTER_LISTENING_HPP_INCLUDED +#ifndef CATCH_REPORTER_MULTI_HPP_INCLUDED +#define CATCH_REPORTER_MULTI_HPP_INCLUDED namespace Catch { - class ListeningReporter final : public IStreamingReporter { - using Reporters = std::vector<IStreamingReporterPtr>; - Reporters m_listeners; - IStreamingReporterPtr m_reporter = nullptr; + class MultiReporter final : public IEventListener { + /* + * Stores all added reporters and listeners + * + * All Listeners are stored before all reporters, and individual + * listeners/reporters are stored in order of insertion. + */ + std::vector<IEventListenerPtr> m_reporterLikes; + bool m_haveNoncapturingReporters = false; - public: - ListeningReporter(); + // Keep track of how many listeners we have already inserted, + // so that we can insert them into the main vector at the right place + size_t m_insertedListeners = 0; - void addListener( IStreamingReporterPtr&& listener ); - void addReporter( IStreamingReporterPtr&& reporter ); + void updatePreferences(IEventListener const& reporterish); - public: // IStreamingReporter + public: + using IEventListener::IEventListener; - void noMatchingTestCases( std::string const& spec ) override; + void addListener( IEventListenerPtr&& listener ); + void addReporter( IEventListenerPtr&& reporter ); - void reportInvalidArguments(std::string const&arg) override; + public: // IEventListener - void benchmarkPreparing(std::string const& name) override; + void noMatchingTestCases( StringRef unmatchedSpec ) override; + void fatalErrorEncountered( StringRef error ) override; + void reportInvalidTestSpec( StringRef arg ) override; + + void benchmarkPreparing( StringRef name ) override; void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override; - void benchmarkFailed(std::string const&) override; + void benchmarkFailed( StringRef error ) override; void testRunStarting( TestRunInfo const& testRunInfo ) override; - void testGroupStarting( GroupInfo const& groupInfo ) override; void testCaseStarting( TestCaseInfo const& testInfo ) override; + void testCasePartialStarting(TestCaseInfo const& testInfo, uint64_t partNumber) override; void sectionStarting( SectionInfo const& sectionInfo ) override; void assertionStarting( AssertionInfo const& assertionInfo ) override; - // The return value indicates if the messages buffer should be cleared: - bool assertionEnded( AssertionStats const& assertionStats ) override; + void assertionEnded( AssertionStats const& assertionStats ) override; void sectionEnded( SectionStats const& sectionStats ) override; + void testCasePartialEnded(TestCaseStats const& testInfo, uint64_t partNumber) override; void testCaseEnded( TestCaseStats const& testCaseStats ) override; - void testGroupEnded( TestGroupStats const& testGroupStats ) override; void testRunEnded( TestRunStats const& testRunStats ) override; void skipTest( TestCaseInfo const& testInfo ) override; - void listReporters(std::vector<ReporterDescription> const& descriptions, IConfig const& config) override; - void listTests(std::vector<TestCaseHandle> const& tests, IConfig const& config) override; - void listTags(std::vector<TagInfo> const& tags, IConfig const& config) override; + void listReporters(std::vector<ReporterDescription> const& descriptions) override; + void listListeners(std::vector<ListenerDescription> const& descriptions) override; + void listTests(std::vector<TestCaseHandle> const& tests) override; + void listTags(std::vector<TagInfo> const& tags) override; }; } // end namespace Catch -#endif // CATCH_REPORTER_LISTENING_HPP_INCLUDED +#endif // CATCH_REPORTER_MULTI_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_REGISTRARS_HPP_INCLUDED +#define CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + + +#include <type_traits> + +namespace Catch { + + namespace Detail { + + template <typename T, typename = void> + struct has_description : std::false_type {}; + + template <typename T> + struct has_description< + T, + void_t<decltype( T::getDescription() )>> + : std::true_type {}; + + //! Indirection for reporter registration, so that the error handling is + //! independent on the reporter's concrete type + void registerReporterImpl( std::string const& name, + IReporterFactoryPtr reporterPtr ); + + } // namespace Detail + + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr<IEventListener>; + + template <typename T> + class ReporterFactory : public IReporterFactory { + + IEventListenerPtr create( ReporterConfig&& config ) const override { + return Detail::make_unique<T>( CATCH_MOVE(config) ); + } + + std::string getDescription() const override { + return T::getDescription(); + } + }; + + + template<typename T> + class ReporterRegistrar { + public: + explicit ReporterRegistrar( std::string const& name ) { + registerReporterImpl( name, + Detail::make_unique<ReporterFactory<T>>() ); + } + }; + + template<typename T> + class ListenerRegistrar { + + class TypedListenerFactory : public EventListenerFactory { + StringRef m_listenerName; + + std::string getDescriptionImpl( std::true_type ) const { + return T::getDescription(); + } + + std::string getDescriptionImpl( std::false_type ) const { + return "(No description provided)"; + } + + public: + TypedListenerFactory( StringRef listenerName ): + m_listenerName( listenerName ) {} + + IEventListenerPtr create( IConfig const* config ) const override { + return Detail::make_unique<T>( config ); + } + + StringRef getName() const override { + return m_listenerName; + } + + std::string getDescription() const override { + return getDescriptionImpl( Detail::has_description<T>{} ); + } + }; + + public: + ListenerRegistrar(StringRef listenerName) { + getMutableRegistryHub().registerListener( Detail::make_unique<TypedListenerFactory>(listenerName) ); + } + }; +} + +#if !defined(CATCH_CONFIG_DISABLE) + +# define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + Catch::ReporterRegistrar<reporterType> 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<listenerType> INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_RegistrarFor )( #listenerType ); \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +#endif // CATCH_REPORTER_REGISTRARS_HPP_INCLUDED #ifndef CATCH_REPORTER_SONARQUBE_HPP_INCLUDED @@ -10770,35 +11937,33 @@ namespace Catch { namespace Catch { - struct SonarQubeReporter : CumulativeReporterBase { - - SonarQubeReporter(ReporterConfig const& config) - : CumulativeReporterBase(config) - , xml(config.stream()) { + class SonarQubeReporter final : public CumulativeReporterBase { + public: + SonarQubeReporter(ReporterConfig&& config) + : CumulativeReporterBase(CATCH_MOVE(config)) + , xml(m_stream) { m_preferences.shouldRedirectStdOut = true; m_preferences.shouldReportAllAssertions = true; + m_shouldStoreSuccesfulAssertions = false; } - ~SonarQubeReporter() override; + ~SonarQubeReporter() override = default; static std::string getDescription() { using namespace std::string_literals; return "Reports test results in the Generic Test Data SonarQube XML format"s; } - void noMatchingTestCases(std::string const& /*spec*/) override {} - - void testRunStarting(TestRunInfo const& testRunInfo) override; - - void testGroupEnded(TestGroupStats const& testGroupStats) override; + void testRunStarting( TestRunInfo const& testRunInfo ) override; void testRunEndedCumulative() override { + writeRun( *m_testRun ); xml.endElement(); } - void writeGroup(TestGroupNode const& groupNode); + void writeRun( TestRunNode const& groupNode ); - void writeTestFile(std::string const& filename, TestGroupNode::ChildNodes const& testCaseNodes); + void writeTestFile(std::string const& filename, std::vector<TestCaseNode const*> const& testCaseNodes); void writeTestCase(TestCaseNode const& testCaseNode); @@ -10824,24 +11989,24 @@ namespace Catch { namespace Catch { - struct TAPReporter : StreamingReporterBase { - - TAPReporter( ReporterConfig const& config ): - StreamingReporterBase( config ) { + class TAPReporter final : public StreamingReporterBase { + public: + TAPReporter( ReporterConfig&& config ): + StreamingReporterBase( CATCH_MOVE(config) ) { m_preferences.shouldReportAllAssertions = true; } - ~TAPReporter() override; + ~TAPReporter() override = default; static std::string getDescription() { using namespace std::string_literals; return "Reports test results in TAP format, suitable for test harnesses"s; } - void noMatchingTestCases(std::string const& spec) override; + void testRunStarting( TestRunInfo const& testInfo ) override; - void assertionStarting( AssertionInfo const& ) override {} + void noMatchingTestCases( StringRef unmatchedSpec ) override; - bool assertionEnded(AssertionStats const& _assertionStats) override; + void assertionEnded(AssertionStats const& _assertionStats) override; void testRunEnded(TestRunStats const& _testRunStats) override; @@ -10867,9 +12032,10 @@ namespace Catch { namespace Catch { - struct TeamCityReporter : StreamingReporterBase { - TeamCityReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ) + class TeamCityReporter final : public StreamingReporterBase { + public: + TeamCityReporter( ReporterConfig&& _config ) + : StreamingReporterBase( CATCH_MOVE(_config) ) { m_preferences.shouldRedirectStdOut = true; } @@ -10881,17 +12047,11 @@ namespace Catch { return "Reports test results as TeamCity service messages"s; } - void skipTest( TestCaseInfo const& /* testInfo */ ) override {} + void testRunStarting( TestRunInfo const& groupInfo ) override; + void testRunEnded( TestRunStats const& testGroupStats ) override; - void noMatchingTestCases( std::string const& /* spec */ ) override {} - void testGroupStarting(GroupInfo const& groupInfo) override; - void testGroupEnded(TestGroupStats const& testGroupStats) override; - - - void assertionStarting(AssertionInfo const&) override {} - - bool assertionEnded(AssertionStats const& assertionStats) override; + void assertionEnded(AssertionStats const& assertionStats) override; void sectionStarting(SectionInfo const& sectionInfo) override { m_headerPrintedForThisSection = false; @@ -10905,7 +12065,6 @@ namespace Catch { private: void printSectionHeader(std::ostream& os); - private: bool m_headerPrintedForThisSection = false; Timer m_testTimer; }; @@ -10928,7 +12087,7 @@ namespace Catch { namespace Catch { class XmlReporter : public StreamingReporterBase { public: - XmlReporter(ReporterConfig const& _config); + XmlReporter(ReporterConfig&& _config); ~XmlReporter() override; @@ -10940,36 +12099,31 @@ namespace Catch { public: // StreamingReporterBase - void noMatchingTestCases(std::string const& s) override; - void testRunStarting(TestRunInfo const& testInfo) override; - void testGroupStarting(GroupInfo const& groupInfo) override; - void testCaseStarting(TestCaseInfo const& testInfo) override; void sectionStarting(SectionInfo const& sectionInfo) override; void assertionStarting(AssertionInfo const&) override; - bool assertionEnded(AssertionStats const& assertionStats) override; + void assertionEnded(AssertionStats const& assertionStats) override; void sectionEnded(SectionStats const& sectionStats) override; void testCaseEnded(TestCaseStats const& testCaseStats) override; - void testGroupEnded(TestGroupStats const& testGroupStats) override; - void testRunEnded(TestRunStats const& testRunStats) override; - void benchmarkPreparing(std::string const& name) override; + void benchmarkPreparing( StringRef name ) override; void benchmarkStarting(BenchmarkInfo const&) override; void benchmarkEnded(BenchmarkStats<> const&) override; - void benchmarkFailed(std::string const&) override; + void benchmarkFailed( StringRef error ) override; - void listReporters(std::vector<ReporterDescription> const& descriptions, IConfig const& config) override; - void listTests(std::vector<TestCaseHandle> const& tests, IConfig const& config) override; - void listTags(std::vector<TagInfo> const& tags, IConfig const& config) override; + void listReporters(std::vector<ReporterDescription> const& descriptions) override; + void listListeners(std::vector<ListenerDescription> const& descriptions) override; + void listTests(std::vector<TestCaseHandle> const& tests) override; + void listTags(std::vector<TagInfo> const& tags) override; private: Timer m_testCaseTimer; @@ -10984,9 +12138,4 @@ namespace Catch { #endif // CATCH_REPORTERS_ALL_HPP_INCLUDED #endif // CATCH_ALL_HPP_INCLUDED - -#if defined(CATCH_CONFIG_MAIN) -#include <catch2/catch_main.cpp> -#endif /* defined(CATCH_CONFIG_MAIN) */ - #endif // CATCH_AMALGAMATED_HPP_INCLUDED |