Add support for multiple parallel reporters

This requires a bunch of different changes across the reporter
subsystem.

* We need to handle multiple reporters and their differing
  preferences in `ListeningReporter`, e.g. what to do when
  we mix reporters that capture and don't capture stdout.
* We need to change how the reporter is given output and
  how we parse reporter's output destination from CLI.
* Approval tests need to handle multireporter option
This commit is contained in:
Martin Jeřábek
2021-02-06 20:12:07 +01:00
committed by Martin Hořeňovský
parent 6b55f5d780
commit ccd67b293d
33 changed files with 51234 additions and 224 deletions

View File

@@ -13,13 +13,39 @@
#include <catch2/internal/catch_test_spec_parser.hpp>
#include <catch2/interfaces/catch_interfaces_tag_alias_registry.hpp>
#include <ostream>
namespace Catch {
namespace Detail {
namespace {
class RDBufStream : public IStream {
mutable std::ostream m_os;
Config::Config( ConfigData const& data )
: m_data( data ),
m_stream( Catch::makeStream(m_data.outputFilename) )
{
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 << " }";
}
Config::Config( ConfigData const& data ):
m_data( data ),
m_defaultStream( openStream( data.defaultOutputFilename ) ) {
// We need to trim filter specs to avoid trouble with superfluous
// whitespace (esp. important for bdd macros, as those are manually
// aligned with whitespace).
@@ -39,24 +65,37 @@ 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 ) );
}
}
}
Config::~Config() = default;
std::string const& Config::getFilename() const {
return m_data.outputFilename ;
}
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 const& Config::getReporterName() const { return m_data.reporterName; }
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 {
return m_data.reporterSpecifications;
}
std::ostream& Config::getReporterOutputStream(std::size_t reporterIdx) const {
return m_reporterStreams.at(reporterIdx)->stream();
}
TestSpec const& Config::testSpec() const { return m_testSpec; }
bool Config::hasTestFilters() const { return m_hasTestFilters; }
@@ -64,8 +103,8 @@ namespace Catch {
// IConfig interface
bool Config::allowThrows() const { return !m_data.noThrow; }
std::ostream& Config::stream() const { return m_stream->stream(); }
StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
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 {
return !!( m_data.warnings & WarnAbout::NoAssertions );
@@ -92,4 +131,8 @@ 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

View File

@@ -14,6 +14,7 @@
#include <catch2/internal/catch_optional.hpp>
#include <catch2/internal/catch_random_seed_generation.hpp>
#include <iosfwd>
#include <vector>
#include <string>
@@ -22,6 +23,18 @@ namespace Catch {
struct IStream;
struct ConfigData {
struct ReporterAndFile {
std::string reporterName;
// If none, the output goes to the default output.
Optional<std::string> outputFileName;
friend bool operator==(ReporterAndFile const& lhs, ReporterAndFile const& rhs) {
return lhs.reporterName == rhs.reporterName && lhs.outputFileName == rhs.outputFileName;
}
friend std::ostream& operator<<(std::ostream &os, ReporterAndFile const& reporter);
};
bool listTests = false;
bool listTags = false;
bool listReporters = false;
@@ -55,13 +68,17 @@ namespace Catch {
UseColour useColour = UseColour::Auto;
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;
std::vector<ReporterAndFile> reporterSpecifications = {
{CATCH_CONFIG_DEFAULT_REPORTER, {}}
};
// Internal: used as parser state
bool _nonDefaultReporterSpecifications = false;
#undef CATCH_CONFIG_DEFAULT_REPORTER
std::vector<std::string> testsOrTags;
@@ -76,13 +93,12 @@ 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;
std::string const& getReporterName() const;
std::vector<ConfigData::ReporterAndFile> const& getReportersAndOutputFiles() const;
std::ostream& getReporterOutputStream(std::size_t reporterIdx) const;
std::vector<std::string> const& getTestsOrTags() const override;
std::vector<std::string> const& getSectionsToRun() const override;
@@ -94,7 +110,7 @@ namespace Catch {
// IConfig interface
bool allowThrows() const override;
std::ostream& stream() const override;
std::ostream& defaultStream() const override;
StringRef name() const override;
bool includeSuccessfulResults() const override;
bool warnAboutMissingAssertions() const override;
@@ -118,13 +134,14 @@ namespace Catch {
std::chrono::milliseconds benchmarkWarmupTime() const override;
private:
Detail::unique_ptr<IStream const> openStream(std::string const& outputFileName);
ConfigData m_data;
Detail::unique_ptr<IStream const> m_stream;
Detail::unique_ptr<IStream const> m_defaultStream;
std::vector<Detail::unique_ptr<IStream const>> m_reporterStreams;
TestSpec m_testSpec;
bool m_hasTestFilters = false;
};
} // end namespace Catch
#endif // CATCH_CONFIG_HPP_INCLUDED

View File

@@ -34,7 +34,7 @@ namespace Catch {
namespace {
const int MaxExitCode = 255;
IStreamingReporterPtr createReporter(std::string const& reporterName, IConfig const* config) {
IStreamingReporterPtr createReporter(std::string const& reporterName, ReporterConfig const& config) {
auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config);
CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\'');
@@ -42,16 +42,26 @@ namespace Catch {
}
IStreamingReporterPtr makeReporter(Config const* config) {
if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) {
return createReporter(config->getReporterName(), 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));
}
auto multi = Detail::make_unique<ListeningReporter>(config);
auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners();
for (auto const& listener : listeners) {
multi->addListener(listener->create(Catch::ReporterConfig(config)));
multi->addListener(listener->create(Catch::ReporterConfig(config, config->defaultStream())));
}
multi->addReporter(createReporter(config->getReporterName(), 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)));
reporterIdx++;
}
return multi;
}

View File

@@ -61,7 +61,7 @@ namespace Catch {
virtual ~IConfig();
virtual bool allowThrows() const = 0;
virtual std::ostream& stream() const = 0;
virtual std::ostream& defaultStream() const = 0;
virtual StringRef name() const = 0;
virtual bool includeSuccessfulResults() const = 0;
virtual bool shouldDebugBreak() const = 0;

View File

@@ -20,9 +20,6 @@
namespace Catch {
ReporterConfig::ReporterConfig( IConfig const* _fullConfig )
: m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {}
ReporterConfig::ReporterConfig( IConfig const* _fullConfig, std::ostream& _stream )
: m_stream( &_stream ), m_fullConfig( _fullConfig ) {}

View File

@@ -32,8 +32,6 @@ namespace Catch {
struct IConfig;
struct ReporterConfig {
explicit ReporterConfig( IConfig const* _fullConfig );
ReporterConfig( IConfig const* _fullConfig, std::ostream& _stream );
std::ostream& stream() const;

View File

@@ -23,13 +23,14 @@ namespace Catch {
using IStreamingReporterPtr = Detail::unique_ptr<IStreamingReporter>;
struct IReporterFactory;
using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>;
struct ReporterConfig;
struct IReporterRegistry {
using FactoryMap = std::map<std::string, IReporterFactoryPtr, Detail::CaseInsensitiveLess>;
using Listeners = std::vector<IReporterFactoryPtr>;
virtual ~IReporterRegistry(); // = default
virtual IStreamingReporterPtr create( std::string const& name, IConfig const* config ) const = 0;
virtual IStreamingReporterPtr create( std::string const& name, ReporterConfig const& config ) const = 0;
virtual FactoryMap const& getFactories() const = 0;
virtual Listeners const& getListeners() const = 0;
};

View File

@@ -14,6 +14,7 @@
#include <catch2/interfaces/catch_interfaces_reporter_registry.hpp>
#include <catch2/interfaces/catch_interfaces_reporter.hpp>
#include <algorithm>
#include <fstream>
#include <string>
@@ -133,15 +134,56 @@ namespace Catch {
return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + '\'' );
return ParserResult::ok( ParseResultType::Matched );
};
auto const setReporter = [&]( std::string const& reporter ) {
auto const setReporter = [&]( std::string const& reporterSpec ) {
IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
auto result = factories.find( reporter );
// clear the default reporter
if (!config._nonDefaultReporterSpecifications) {
config.reporterSpecifications.clear();
config._nonDefaultReporterSpecifications = true;
}
// 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 separatorPos = reporterSpec.find( separator );
const bool containsFileName = separatorPos != reporterSpec.npos;
std::string reporterName;
Optional<std::string> outputFileName;
if (!containsFileName) {
reporterName = reporterSpec;
} else {
reporterName = reporterSpec.substr( 0, separatorPos );
outputFileName = reporterSpec.substr(
separatorPos + 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) {
int n_reporters_without_file = 0;
for (auto const& spec : config.reporterSpecifications) {
if (spec.outputFileName.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 = reporter;
else
return ParserResult::runtimeError( "Unrecognized reporter, '" + reporter + "'. Check available with --list-reporters" );
return ParserResult::ok( ParseResultType::Matched );
};
auto const setShardCount = [&]( std::string const& shardCount ) {
@@ -202,10 +244,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[:output-file]" )
["-r"]["--reporter"]
( "reporter to use (defaults to console)" )
| Opt( config.name, "name" )

View File

@@ -159,7 +159,7 @@ namespace {
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()
getCurrentContext().getConfig()->defaultStream()
<< '\033' << _escapeCode << std::flush;
}
};

View File

@@ -36,11 +36,11 @@ namespace Catch {
ReporterRegistry::~ReporterRegistry() = default;
IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfig const* config ) const {
IStreamingReporterPtr ReporterRegistry::create( std::string const& name, ReporterConfig const& 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( config );
}
void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr factory ) {

View File

@@ -21,7 +21,7 @@ namespace Catch {
ReporterRegistry();
~ReporterRegistry() override; // = default, out of line to allow fwd decl
IStreamingReporterPtr create( std::string const& name, IConfig const* config ) const override;
IStreamingReporterPtr create( std::string const& name, ReporterConfig const& config ) const override;
void registerReporter( std::string const& name, IReporterFactoryPtr factory );
void registerListener( IReporterFactoryPtr factory );

View File

@@ -6,170 +6,185 @@
// SPDX-License-Identifier: BSL-1.0
#include <catch2/reporters/catch_reporter_listening.hpp>
#include <catch2/catch_config.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <catch2/internal/catch_stream.hpp>
#include <cassert>
namespace Catch {
void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) {
m_listeners.push_back( CATCH_MOVE( listener ) );
void ListeningReporter::updatePreferences(IStreamingReporter const& reporterish) {
m_preferences.shouldRedirectStdOut |=
reporterish.getPreferences().shouldRedirectStdOut;
m_preferences.shouldReportAllAssertions |=
reporterish.getPreferences().shouldReportAllAssertions;
}
void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) {
assert(!m_reporter && "Listening reporter can wrap only 1 real reporter");
m_reporter = CATCH_MOVE( reporter );
m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut;
void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) {
updatePreferences(*listener);
m_reporterLikes.insert(m_reporterLikes.begin() + m_insertedListeners, CATCH_MOVE(listener) );
++m_insertedListeners;
}
void ListeningReporter::addReporter( IStreamingReporterPtr&& 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::noMatchingTestCases( StringRef unmatchedSpec ) {
for ( auto& listener : m_listeners ) {
listener->noMatchingTestCases( unmatchedSpec );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->noMatchingTestCases( unmatchedSpec );
}
m_reporter->noMatchingTestCases( unmatchedSpec );
}
void ListeningReporter::fatalErrorEncountered( StringRef error ) {
for ( auto& listener : m_listeners ) {
listener->fatalErrorEncountered( error );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->fatalErrorEncountered( error );
}
m_reporter->fatalErrorEncountered( error );
}
void ListeningReporter::reportInvalidTestSpec( StringRef arg ) {
for ( auto& listener : m_listeners ) {
listener->reportInvalidTestSpec( arg );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->reportInvalidTestSpec( arg );
}
m_reporter->reportInvalidTestSpec( arg );
}
void ListeningReporter::benchmarkPreparing( StringRef name ) {
for (auto& listener : m_listeners) {
listener->benchmarkPreparing(name);
for (auto& reporterish : m_reporterLikes) {
reporterish->benchmarkPreparing(name);
}
m_reporter->benchmarkPreparing(name);
}
void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) {
for ( auto& listener : m_listeners ) {
listener->benchmarkStarting( benchmarkInfo );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->benchmarkStarting( benchmarkInfo );
}
m_reporter->benchmarkStarting( benchmarkInfo );
}
void ListeningReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) {
for ( auto& listener : m_listeners ) {
listener->benchmarkEnded( benchmarkStats );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->benchmarkEnded( benchmarkStats );
}
m_reporter->benchmarkEnded( benchmarkStats );
}
void ListeningReporter::benchmarkFailed( StringRef error ) {
for (auto& listener : m_listeners) {
listener->benchmarkFailed(error);
for (auto& reporterish : m_reporterLikes) {
reporterish->benchmarkFailed(error);
}
m_reporter->benchmarkFailed(error);
}
void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) {
for ( auto& listener : m_listeners ) {
listener->testRunStarting( testRunInfo );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->testRunStarting( testRunInfo );
}
m_reporter->testRunStarting( testRunInfo );
}
void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) {
for ( auto& listener : m_listeners ) {
listener->testCaseStarting( testInfo );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->testCaseStarting( testInfo );
}
m_reporter->testCaseStarting( testInfo );
}
void
ListeningReporter::testCasePartialStarting( TestCaseInfo const& testInfo,
uint64_t partNumber ) {
for ( auto& listener : m_listeners ) {
listener->testCasePartialStarting( testInfo, partNumber );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->testCasePartialStarting( testInfo, partNumber );
}
m_reporter->testCasePartialStarting( testInfo, partNumber );
}
void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) {
for ( auto& listener : m_listeners ) {
listener->sectionStarting( sectionInfo );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->sectionStarting( sectionInfo );
}
m_reporter->sectionStarting( sectionInfo );
}
void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) {
for ( auto& listener : m_listeners ) {
listener->assertionStarting( assertionInfo );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->assertionStarting( assertionInfo );
}
m_reporter->assertionStarting( assertionInfo );
}
// The return value indicates if the messages buffer should be cleared:
void ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) {
for( auto& listener : m_listeners ) {
listener->assertionEnded( 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 );
}
}
m_reporter->assertionEnded( assertionStats );
}
void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) {
for ( auto& listener : m_listeners ) {
listener->sectionEnded( sectionStats );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->sectionEnded( sectionStats );
}
m_reporter->sectionEnded( sectionStats );
}
void ListeningReporter::testCasePartialEnded( TestCaseStats const& testInfo,
void ListeningReporter::testCasePartialEnded( TestCaseStats const& testStats,
uint64_t partNumber ) {
for ( auto& listener : m_listeners ) {
listener->testCasePartialEnded( testInfo, partNumber );
// TODO: Fix handling of stderr/stdout?
for ( auto& reporterish : m_reporterLikes ) {
reporterish->testCasePartialEnded( testStats, partNumber );
}
m_reporter->testCasePartialEnded( testInfo, partNumber );
}
void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
for ( auto& listener : m_listeners ) {
listener->testCaseEnded( testCaseStats );
if ( m_preferences.shouldRedirectStdOut && m_haveNoncapturingReporters ) {
if ( !testCaseStats.stdOut.empty() ) {
Catch::cout() << testCaseStats.stdOut << std::flush;
}
if ( !testCaseStats.stdErr.empty() ) {
Catch::cerr() << testCaseStats.stdErr << std::flush;
}
}
for ( auto& reporterish : m_reporterLikes ) {
reporterish->testCaseEnded( testCaseStats );
}
m_reporter->testCaseEnded( testCaseStats );
}
void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) {
for ( auto& listener : m_listeners ) {
listener->testRunEnded( testRunStats );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->testRunEnded( testRunStats );
}
m_reporter->testRunEnded( testRunStats );
}
void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) {
for ( auto& listener : m_listeners ) {
listener->skipTest( testInfo );
for ( auto& reporterish : m_reporterLikes ) {
reporterish->skipTest( testInfo );
}
m_reporter->skipTest( testInfo );
}
void ListeningReporter::listReporters(std::vector<ReporterDescription> const& descriptions) {
for (auto& listener : m_listeners) {
listener->listReporters(descriptions);
for (auto& reporterish : m_reporterLikes) {
reporterish->listReporters(descriptions);
}
m_reporter->listReporters(descriptions);
}
void ListeningReporter::listTests(std::vector<TestCaseHandle> const& tests) {
for (auto& listener : m_listeners) {
listener->listTests(tests);
for (auto& reporterish : m_reporterLikes) {
reporterish->listTests(tests);
}
m_reporter->listTests(tests);
}
void ListeningReporter::listTags(std::vector<TagInfo> const& tags) {
for (auto& listener : m_listeners) {
listener->listTags(tags);
for (auto& reporterish : m_reporterLikes) {
reporterish->listTags(tags);
}
m_reporter->listTags(tags);
}
} // end namespace Catch

View File

@@ -13,9 +13,20 @@
namespace Catch {
class ListeningReporter final : public IStreamingReporter {
using Reporters = std::vector<IStreamingReporterPtr>;
Reporters m_listeners;
IStreamingReporterPtr m_reporter = nullptr;
/*
* 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<IStreamingReporterPtr> m_reporterLikes;
bool m_haveNoncapturingReporters = false;
// 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 updatePreferences(IStreamingReporter const& reporterish);
public:
ListeningReporter( IConfig const* config ):