diff options
| author | Martin Hořeňovský | 2022-04-20 23:46:07 +0200 |
|---|---|---|
| committer | Martin Hořeňovský | 2022-04-20 23:46:07 +0200 |
| commit | f526ff0fc37ae00ff2c0dc8f6550bf8209c30afc (patch) | |
| tree | e3e57df2fe5e860d97044e19ba8b241417b7b012 /extras/catch_amalgamated.cpp | |
| parent | 17a04f88d90d3687bd561e89dab40e4ba6a55b83 (diff) | |
| download | catch3-3.0.0-preview5.tar.gz | |
Bump version to v3.0.0 preview 5v3.0.0-preview5
Diffstat (limited to 'extras/catch_amalgamated.cpp')
| -rw-r--r-- | extras/catch_amalgamated.cpp | 1645 |
1 files changed, 988 insertions, 657 deletions
diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp index e42faf64..58a1c7d5 100644 --- a/extras/catch_amalgamated.cpp +++ b/extras/catch_amalgamated.cpp @@ -5,8 +5,8 @@ // SPDX-License-Identifier: BSL-1.0 -// Catch v3.0.0-preview.4 -// Generated: 2022-01-03 23:14:23.198909 +// Catch v3.0.0-preview.5 +// Generated: 2022-04-20 23:45:15.004945 // ---------------------------------------------------------- // 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> @@ -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 ); } } @@ -491,39 +496,18 @@ namespace Catch { -#include <ostream> - namespace Catch { - namespace Detail { - namespace { - class RDBufStream : public IStream { - mutable std::ostream m_os; - - public: - //! The streambuf `sb` must outlive the constructed object. - RDBufStream( std::streambuf* sb ): m_os( sb ) {} - ~RDBufStream() override = default; - public: // IStream - std::ostream& stream() const override { return m_os; } - }; - } // unnamed namespace - } // namespace Detail - - std::ostream& operator<<( std::ostream& os, - ConfigData::ReporterAndFile const& reporter ) { - os << "{ " << reporter.reporterName << ", "; - if ( reporter.outputFileName ) { - os << *reporter.outputFileName; - } else { - os << "<default-output>"; - } - return os << " }"; + 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 ), - m_defaultStream( openStream( data.defaultOutputFilename ) ) { + 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). @@ -535,6 +519,7 @@ namespace Catch { elem = trim(elem); } + TestSpecParser parser(ITagAliasRegistry::get()); if (!m_data.testsOrTags.empty()) { m_hasTestFilters = true; @@ -544,15 +529,62 @@ namespace Catch { } m_testSpec = parser.testSpec(); - m_reporterStreams.reserve( m_data.reporterSpecifications.size() ); - for ( auto const& reporterAndFile : m_data.reporterSpecifications ) { - if ( reporterAndFile.outputFileName.none() ) { - m_reporterStreams.emplace_back( new Detail::RDBufStream( - m_defaultStream->stream().rdbuf() ) ); - } else { - m_reporterStreams.emplace_back( - openStream( *reporterAndFile.outputFileName ) ); + + // 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; } + + m_processedReporterSpecs.push_back( ProcessedReporterSpec{ + reporterSpec.name(), + reporterSpec.outputFile() ? *reporterSpec.outputFile() + : data.defaultOutputFilename, + reporterSpec.colourMode().valueOr( data.defaultColourMode ), + reporterSpec.customOptions() } ); } } @@ -566,12 +598,13 @@ namespace Catch { 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<ConfigData::ReporterAndFile> const& Config::getReportersAndOutputFiles() const { + std::vector<ReporterSpec> const& Config::getReporterSpecs() const { return m_data.reporterSpecifications; } - std::ostream& Config::getReporterOutputStream(std::size_t reporterIdx) const { - return m_reporterStreams.at(reporterIdx)->stream(); + std::vector<ProcessedReporterSpec> const& + Config::getProcessedReporterSpecs() const { + return m_processedReporterSpecs; } TestSpec const& Config::testSpec() const { return m_testSpec; } @@ -581,7 +614,6 @@ namespace Catch { // IConfig interface bool Config::allowThrows() const { return !m_data.noThrow; } - std::ostream& Config::defaultStream() const { return m_defaultStream->stream(); } 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 { @@ -597,7 +629,7 @@ namespace Catch { 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; } - UseColour Config::useColour() const { return m_data.useColour; } + 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; } @@ -609,10 +641,6 @@ namespace Catch { unsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; } std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); } - Detail::unique_ptr<IStream const> Config::openStream(std::string const& outputFileName) { - return Catch::makeStream(outputFileName); - } - } // end namespace Catch @@ -750,7 +778,7 @@ namespace Catch { void registerReporter( std::string const& name, IReporterFactoryPtr factory ) override { m_reporterRegistry.registerReporter( name, CATCH_MOVE(factory) ); } - void registerListener( IReporterFactoryPtr factory ) override { + 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 { @@ -814,31 +842,40 @@ namespace Catch { namespace { const int MaxExitCode = 255; - IStreamingReporterPtr createReporter(std::string const& reporterName, ReporterConfig const& config) { - auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + 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) { + IEventListenerPtr prepareReporters(Config const* config) { if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty() - && config->getReportersAndOutputFiles().size() == 1) { - auto& stream = config->getReporterOutputStream(0); - return createReporter(config->getReportersAndOutputFiles()[0].reporterName, ReporterConfig(config, stream)); + && config->getProcessedReporterSpecs().size() == 1) { + auto const& spec = config->getProcessedReporterSpecs()[0]; + return createReporter( + spec.name, + ReporterConfig( config, + makeStream( spec.outputFilename ), + spec.colourMode, + spec.customOptions ) ); } - auto multi = Detail::make_unique<ListeningReporter>(config); + 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, config->defaultStream()))); + multi->addListener(listener->create(config)); } std::size_t reporterIdx = 0; - for (auto const& reporterAndFile : config->getReportersAndOutputFiles()) { - auto& stream = config->getReporterOutputStream(reporterIdx); - multi->addReporter(createReporter(reporterAndFile.reporterName, ReporterConfig(config, stream))); + for ( auto const& reporterSpec : config->getProcessedReporterSpecs() ) { + multi->addReporter( createReporter( + reporterSpec.name, + ReporterConfig( config, + makeStream( reporterSpec.outputFilename ), + reporterSpec.colourMode, + reporterSpec.customOptions ) ) ); reporterIdx++; } @@ -847,7 +884,7 @@ namespace Catch { 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, CATCH_MOVE(reporter)} { @@ -900,7 +937,7 @@ namespace Catch { private: - IStreamingReporter* m_reporter; + IEventListener* m_reporter; Config const* m_config; RunContext m_context; std::set<TestCaseHandle const*> m_tests; @@ -931,14 +968,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'; } } } @@ -953,7 +993,7 @@ namespace Catch { void Session::showHelp() const { Catch::cout() - << "\nCatch v" << libraryVersion() << '\n' + << "\nCatch2 v" << libraryVersion() << '\n' << m_cli << '\n' << "For more detailed usage please see the project docs\n\n" << std::flush; } @@ -961,7 +1001,7 @@ namespace Catch { 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) << "framework: " << "Catch2\n" << std::left << std::setw(16) << "version: " << libraryVersion() << '\n' << std::flush; } @@ -974,12 +1014,15 @@ namespace Catch { 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\n" << std::flush; + errStream->stream() << "Run with -? for usage\n\n" << std::flush; return MaxExitCode; } @@ -1078,7 +1121,7 @@ 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() ) { @@ -1110,7 +1153,7 @@ namespace Catch { // 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 ) { @@ -1817,7 +1860,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 3, 0, 0, "preview", 4 ); + static Version version( 3, 0, 0, "preview", 5 ); return version; } @@ -1953,20 +1996,40 @@ namespace Catch { namespace Catch { IReporterFactory::~IReporterFactory() = default; + EventListenerFactory::~EventListenerFactory() = default; } #include <algorithm> +#include <cassert> #include <iomanip> namespace Catch { - ReporterConfig::ReporterConfig( IConfig const* _fullConfig, std::ostream& _stream ) - : m_stream( &_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 ) ) {} - 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; + } + + ReporterConfig::~ReporterConfig() = default; AssertionStats::AssertionStats( AssertionResult const& _assertionResult, std::vector<MessageInfo> const& _infoMessages, @@ -2020,7 +2083,7 @@ namespace Catch { aborting( _aborting ) {} - IStreamingReporter::~IStreamingReporter() = default; + IEventListener::~IEventListener() = default; } // end namespace Catch @@ -2126,7 +2189,10 @@ namespace Catch { } // namespace Catch + + #include <algorithm> +#include <ostream> namespace { bool isOptPrefix( char c ) { @@ -2778,7 +2844,6 @@ namespace Catch { -#include <algorithm> #include <fstream> #include <string> @@ -2859,19 +2924,23 @@ namespace Catch { return ParserResult::runtimeError("Could not parse '" + seed + "' as seed"); } }; - 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 ); - }; + 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") @@ -2898,64 +2967,42 @@ namespace Catch { return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + '\'' ); return ParserResult::ok( ParseResultType::Matched ); }; - auto const setReporter = [&]( std::string const& reporterSpec ) { - if ( reporterSpec.empty() ) { + auto const setReporter = [&]( std::string const& userReporterSpec ) { + if ( userReporterSpec.empty() ) { return ParserResult::runtimeError( "Received empty reporter spec." ); } - IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); - - // clear the default reporter - if (!config._nonDefaultReporterSpecifications) { - config.reporterSpecifications.clear(); - config._nonDefaultReporterSpecifications = true; + Optional<ReporterSpec> parsed = + parseReporterSpec( userReporterSpec ); + if ( !parsed ) { + return ParserResult::runtimeError( + "Could not parse reporter spec '" + userReporterSpec + + "'" ); } + auto const& reporterSpec = *parsed; - // Exactly one of the reporters may be specified without an output - // file, in which case it defaults to the output specified by "-o" - // (or standard output). - static constexpr auto separator = "::"; - static constexpr size_t separatorSize = 2; - auto fileNameSeparatorPos = reporterSpec.find( separator ); - const bool containsFileName = fileNameSeparatorPos != reporterSpec.npos; - if ( containsFileName ) { - auto nextSeparatorPos = reporterSpec.find( - separator, fileNameSeparatorPos + separatorSize ); - if ( nextSeparatorPos != reporterSpec.npos ) { - return ParserResult::runtimeError( - "Too many separators in reporter spec '" + reporterSpec + '\'' ); - } - } + IReporterRegistry::FactoryMap const& factories = + getRegistryHub().getReporterRegistry().getFactories(); + auto result = factories.find( reporterSpec.name() ); - std::string reporterName; - Optional<std::string> outputFileName; - reporterName = reporterSpec.substr( 0, fileNameSeparatorPos ); - if ( reporterName.empty() ) { - return ParserResult::runtimeError( "Reporter name cannot be empty." ); + if ( result == factories.end() ) { + return ParserResult::runtimeError( + "Unrecognized reporter, '" + reporterSpec.name() + + "'. Check available with --list-reporters" ); } - if ( containsFileName ) { - outputFileName = reporterSpec.substr( - fileNameSeparatorPos + separatorSize, reporterSpec.size() ); - } - auto result = factories.find( reporterName ); - - if( result == factories.end() ) - return ParserResult::runtimeError( "Unrecognized reporter, '" + reporterName + "'. Check available with --list-reporters" ); - if( containsFileName && outputFileName->empty() ) - return ParserResult::runtimeError( "Reporter '" + reporterName + "' has empty filename specified as its output. Supply a filename or remove the colons to use the default output." ); - - config.reporterSpecifications.push_back({ std::move(reporterName), std::move(outputFileName) }); - - // 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 (!containsFileName) { + 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.outputFileName.none()) { + if (spec.outputFile().none()) { n_reporters_without_file++; } } @@ -3021,7 +3068,7 @@ namespace Catch { | Opt( config.defaultOutputFilename, "filename" ) ["-o"]["--out"] ( "default output filename" ) - | Opt( accept_many, setReporter, "name[:output-file]" ) + | Opt( accept_many, setReporter, "name[::key=value]*" ) ["-r"]["--reporter"] ( "reporter to use (defaults to console)" ) | Opt( config.name, "name" ) @@ -3069,9 +3116,9 @@ namespace Catch { | 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" ) @@ -3118,53 +3165,107 @@ namespace Catch { +#include <cassert> #include <ostream> +#include <utility> namespace Catch { - namespace { - struct IColourImpl { - virtual ~IColourImpl() = default; - virtual void use( Colour::Code _colourCode ) = 0; - }; + ColourImpl::~ColourImpl() = default; - struct NoColourImpl : IColourImpl { - void use( Colour::Code ) override {} + ColourImpl::ColourGuard ColourImpl::guardColour( Colour::Code colourCode ) { + return ColourGuard(colourCode, this ); + } - static IColourImpl* instance() { - static NoColourImpl s_instance; - return &s_instance; - } + 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 ); + } + + 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; + } + + ColourImpl::ColourGuard&& + ColourImpl::ColourGuard::engage( std::ostream& stream ) && { + engageImpl( stream ); + return CATCH_MOVE(*this); + } + + 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 ) {} + static bool useColourOnPlatform() { return true; } + + private: + void use( Colour::Code ) const override {} }; + } // namespace - } // 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 +} // namespace Catch -#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 useColourOnPlatform(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 ); @@ -3188,45 +3289,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 useColourOnPlatform(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" ); @@ -3247,89 +3366,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()->defaultStream() |