mirror of
https://github.com/catchorg/Catch2.git
synced 2025-08-03 13:55:39 +02:00
Integrate the new reporter specs into Catch2
This means that the CLI interface now uses the new key-value oriented reporter spec, the common reporter base creates the colour implementation based on the reporter-specific configuration, and it also stores the custom configuration options for each reporter instance. Closes #339 as it allows per-reporter forcing of ansi colour codes.
This commit is contained in:
@@ -33,17 +33,6 @@ namespace Catch {
|
||||
} // 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 ) {
|
||||
// We need to trim filter specs to avoid trouble with superfluous
|
||||
@@ -76,14 +65,14 @@ namespace Catch {
|
||||
#else
|
||||
"console",
|
||||
#endif
|
||||
{}
|
||||
{}, {}, {}
|
||||
} );
|
||||
}
|
||||
|
||||
bool defaultOutputUsed = false;
|
||||
m_reporterStreams.reserve( m_data.reporterSpecifications.size() );
|
||||
for ( auto const& reporterAndFile : m_data.reporterSpecifications ) {
|
||||
if ( reporterAndFile.outputFileName.none() ) {
|
||||
for ( auto const& reporterSpec : m_data.reporterSpecifications ) {
|
||||
if ( reporterSpec.outputFile().none() ) {
|
||||
CATCH_ENFORCE( !defaultOutputUsed,
|
||||
"Internal error: cannot use default output for "
|
||||
"multiple reporters" );
|
||||
@@ -93,7 +82,7 @@ namespace Catch {
|
||||
openStream( data.defaultOutputFilename ) );
|
||||
} else {
|
||||
m_reporterStreams.push_back(
|
||||
openStream( *reporterAndFile.outputFileName ) );
|
||||
openStream( *reporterSpec.outputFile() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +97,7 @@ 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;
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,7 @@
|
||||
#include <catch2/internal/catch_unique_ptr.hpp>
|
||||
#include <catch2/internal/catch_optional.hpp>
|
||||
#include <catch2/internal/catch_random_seed_generation.hpp>
|
||||
#include <catch2/internal/catch_reporter_spec_parser.hpp>
|
||||
|
||||
#include <iosfwd>
|
||||
#include <vector>
|
||||
@@ -24,17 +25,6 @@ namespace Catch {
|
||||
class 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;
|
||||
@@ -72,7 +62,7 @@ namespace Catch {
|
||||
std::string defaultOutputFilename;
|
||||
std::string name;
|
||||
std::string processName;
|
||||
std::vector<ReporterAndFile> reporterSpecifications;
|
||||
std::vector<ReporterSpec> reporterSpecifications;
|
||||
|
||||
std::vector<std::string> testsOrTags;
|
||||
std::vector<std::string> sectionsToRun;
|
||||
@@ -90,7 +80,7 @@ namespace Catch {
|
||||
bool listTags() const;
|
||||
bool listReporters() const;
|
||||
|
||||
std::vector<ConfigData::ReporterAndFile> const& getReportersAndOutputFiles() const;
|
||||
std::vector<ReporterSpec> const& getReporterSpecs() const;
|
||||
IStream const* getReporterOutputStream(std::size_t reporterIdx) const;
|
||||
|
||||
std::vector<std::string> const& getTestsOrTags() const override;
|
||||
|
@@ -41,11 +41,18 @@ namespace Catch {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
IStreamingReporterPtr makeReporter(Config const* config) {
|
||||
IStreamingReporterPtr prepareReporters(Config const* config) {
|
||||
if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()
|
||||
&& config->getReportersAndOutputFiles().size() == 1) {
|
||||
&& config->getReporterSpecs().size() == 1) {
|
||||
auto const& spec = config->getReporterSpecs()[0];
|
||||
auto stream = config->getReporterOutputStream(0);
|
||||
return createReporter(config->getReportersAndOutputFiles()[0].reporterName, ReporterConfig(config, stream));
|
||||
return createReporter(
|
||||
config->getReporterSpecs()[0].name(),
|
||||
ReporterConfig(
|
||||
config,
|
||||
stream,
|
||||
spec.colourMode().valueOr( config->colourMode() ),
|
||||
spec.customOptions() ) );
|
||||
}
|
||||
|
||||
auto multi = Detail::make_unique<MultiReporter>(config);
|
||||
@@ -56,9 +63,15 @@ namespace Catch {
|
||||
}
|
||||
|
||||
std::size_t reporterIdx = 0;
|
||||
for (auto const& reporterAndFile : config->getReportersAndOutputFiles()) {
|
||||
for (auto const& reporterSpec : config->getReporterSpecs()) {
|
||||
auto stream = config->getReporterOutputStream(reporterIdx);
|
||||
multi->addReporter(createReporter(reporterAndFile.reporterName, ReporterConfig(config, stream)));
|
||||
multi->addReporter( createReporter(
|
||||
reporterSpec.name(),
|
||||
ReporterConfig( config,
|
||||
stream,
|
||||
reporterSpec.colourMode().valueOr(
|
||||
config->colourMode() ),
|
||||
reporterSpec.customOptions() ) ) );
|
||||
reporterIdx++;
|
||||
}
|
||||
|
||||
@@ -304,7 +317,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() ) {
|
||||
|
@@ -14,17 +14,31 @@
|
||||
#include <catch2/internal/catch_string_manip.hpp>
|
||||
#include <catch2/catch_test_case_info.hpp>
|
||||
#include <catch2/reporters/catch_reporter_helpers.hpp>
|
||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
ReporterConfig::ReporterConfig( IConfig const* _fullConfig, IStream const* _stream )
|
||||
: m_stream( _stream ), m_fullConfig( _fullConfig ) {}
|
||||
ReporterConfig::ReporterConfig(
|
||||
IConfig const* _fullConfig,
|
||||
IStream const* _stream,
|
||||
ColourMode colourMode,
|
||||
std::map<std::string, std::string> customOptions ):
|
||||
m_stream( _stream ),
|
||||
m_fullConfig( _fullConfig ),
|
||||
m_colourMode( colourMode ),
|
||||
m_customOptions( CATCH_MOVE( customOptions ) ) {}
|
||||
|
||||
IStream const* ReporterConfig::stream() const { return 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;
|
||||
}
|
||||
|
||||
AssertionStats::AssertionStats( AssertionResult const& _assertionResult,
|
||||
std::vector<MessageInfo> const& _infoMessages,
|
||||
|
@@ -19,6 +19,7 @@
|
||||
#include <catch2/benchmark/catch_outlier_classification.hpp>
|
||||
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iosfwd>
|
||||
@@ -31,16 +32,24 @@ namespace Catch {
|
||||
class TestCaseHandle;
|
||||
struct IConfig;
|
||||
class IStream;
|
||||
enum class ColourMode : std::uint8_t;
|
||||
|
||||
struct ReporterConfig {
|
||||
ReporterConfig( IConfig const* _fullConfig, IStream const* _stream );
|
||||
ReporterConfig( IConfig const* _fullConfig,
|
||||
IStream const* _stream,
|
||||
ColourMode colourMode,
|
||||
std::map<std::string, std::string> customOptions );
|
||||
|
||||
IStream const* stream() const;
|
||||
IConfig const* fullConfig() const;
|
||||
ColourMode colourMode() const;
|
||||
std::map<std::string, std::string> const& customOptions() const;
|
||||
|
||||
private:
|
||||
IStream const* m_stream;
|
||||
IConfig const* m_fullConfig;
|
||||
ColourMode m_colourMode;
|
||||
std::map<std::string, std::string> m_customOptions;
|
||||
};
|
||||
|
||||
struct TestRunInfo {
|
||||
|
@@ -141,57 +141,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." );
|
||||
}
|
||||
|
||||
// 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 + '\'' );
|
||||
}
|
||||
Optional<ReporterSpec> parsed =
|
||||
parseReporterSpec( userReporterSpec );
|
||||
if ( !parsed ) {
|
||||
return ParserResult::runtimeError(
|
||||
"Could not parse reporter spec '" + userReporterSpec +
|
||||
"'" );
|
||||
}
|
||||
|
||||
std::string reporterName;
|
||||
Optional<std::string> outputFileName;
|
||||
reporterName = reporterSpec.substr( 0, fileNameSeparatorPos );
|
||||
if ( reporterName.empty() ) {
|
||||
return ParserResult::runtimeError( "Reporter name cannot be empty." );
|
||||
}
|
||||
|
||||
if ( containsFileName ) {
|
||||
outputFileName = reporterSpec.substr(
|
||||
fileNameSeparatorPos + separatorSize, reporterSpec.size() );
|
||||
}
|
||||
auto const& reporterSpec = *parsed;
|
||||
|
||||
IReporterRegistry::FactoryMap const& factories =
|
||||
getRegistryHub().getReporterRegistry().getFactories();
|
||||
auto result = factories.find( reporterName );
|
||||
auto result = factories.find( reporterSpec.name() );
|
||||
|
||||
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." );
|
||||
if ( result == factories.end() ) {
|
||||
return ParserResult::runtimeError(
|
||||
"Unrecognized reporter, '" + reporterSpec.name() +
|
||||
"'. Check available with --list-reporters" );
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,9 @@ namespace Catch {
|
||||
IEventListener( config.fullConfig() ),
|
||||
m_wrapped_stream( config.stream() ),
|
||||
m_stream( m_wrapped_stream->stream() ),
|
||||
m_colour( makeColourImpl( m_config->colourMode(), m_wrapped_stream ) ) {}
|
||||
m_colour( makeColourImpl( config.colourMode(), m_wrapped_stream ) ),
|
||||
m_customOptions( config.customOptions() )
|
||||
{}
|
||||
|
||||
ReporterBase::~ReporterBase() = default;
|
||||
|
||||
|
@@ -10,6 +10,9 @@
|
||||
|
||||
#include <catch2/interfaces/catch_interfaces_reporter.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace Catch {
|
||||
class ColourImpl;
|
||||
|
||||
@@ -30,7 +33,10 @@ namespace Catch {
|
||||
//! 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 const& config );
|
||||
|
Reference in New Issue
Block a user