mirror of
https://github.com/catchorg/Catch2.git
synced 2025-08-03 05:45:39 +02:00
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:

committed by
Martin Hořeňovský

parent
6b55f5d780
commit
ccd67b293d
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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 ) {}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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" )
|
||||
|
@@ -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;
|
||||
}
|
||||
};
|
||||
|
@@ -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 ) {
|
||||
|
@@ -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 );
|
||||
|
@@ -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
|
||||
|
@@ -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 ):
|
||||
|
Reference in New Issue
Block a user