Add helper for parsing the new reporter specs

The new reporter spec generalizes key-value options that can be
passed to the reporter, looking like this
`reporterName[::key=value]*`. A key can be either Catch2-recognized,
which currently means either `out` or `colour`, or reporter-specific
which is anything prefixed with `X`, e.g. `Xfoo`.
This commit is contained in:
Martin Hořeňovský 2022-04-04 17:06:47 +02:00
parent 8ac86495de
commit a51fd07bd0
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
3 changed files with 196 additions and 1 deletions

View File

@ -9,9 +9,28 @@
#include <catch2/internal/catch_reporter_spec_parser.hpp> #include <catch2/internal/catch_reporter_spec_parser.hpp>
#include <catch2/interfaces/catch_interfaces_config.hpp> #include <catch2/interfaces/catch_interfaces_config.hpp>
#include <catch2/internal/catch_move_and_forward.hpp>
#include <algorithm>
#include <ostream>
namespace Catch { namespace Catch {
namespace {
struct kvPair {
StringRef key, value;
};
kvPair splitKVPair(StringRef kvString) {
auto splitPos = static_cast<size_t>( std::distance(
kvString.begin(),
std::find( kvString.begin(), kvString.end(), '=' ) ) );
return { kvString.substr( 0, splitPos ),
kvString.substr( splitPos + 1, kvString.size() ) };
}
}
namespace Detail { namespace Detail {
std::vector<std::string> splitReporterSpec( StringRef reporterSpec ) { std::vector<std::string> splitReporterSpec( StringRef reporterSpec ) {
static constexpr auto separator = "::"; static constexpr auto separator = "::";
@ -78,4 +97,78 @@ namespace Catch {
} // namespace Detail } // namespace Detail
bool operator==( ReporterSpec const& lhs, ReporterSpec const& rhs ) {
return lhs.m_name == rhs.m_name &&
lhs.m_outputFileName == rhs.m_outputFileName &&
lhs.m_colourMode == rhs.m_colourMode &&
lhs.m_customOptions == rhs.m_customOptions;
}
Optional<ReporterSpec> parseReporterSpec( StringRef reporterSpec ) {
auto parts = Detail::splitReporterSpec( reporterSpec );
assert( parts.size() > 0 && "Split should never return empty vector" );
std::map<std::string, std::string> kvPairs;
Optional<std::string> outputFileName;
Optional<ColourMode> colourMode;
// First part is always reporter name, so we skip it
for ( size_t i = 1; i < parts.size(); ++i ) {
auto kv = splitKVPair( parts[i] );
auto key = kv.key, value = kv.value;
if ( key.empty() || value.empty() ) {
return {};
} else if ( key[0] == 'X' ) {
// This is a reporter-specific option, we don't check these
// apart from basic sanity checks
if ( key.size() == 1 ) {
return {};
}
auto ret = kvPairs.emplace( kv.key, kv.value );
if ( !ret.second ) {
// Duplicated key. We might want to handle this differently,
// e.g. by overwriting the existing value?
return {};
}
} else if ( key == "out" ) {
// Duplicated key
if ( outputFileName ) {
return {};
}
outputFileName = static_cast<std::string>( value );
} else if ( key == "colour" ) {
// Duplicated key
if ( colourMode ) {
return {};
}
colourMode = Detail::stringToColourMode( value );
// Parsing failed
if ( !colourMode ) {
return {};
}
} else {
// Unrecognized option
return {};
}
}
return ReporterSpec{ CATCH_MOVE( parts[0] ),
CATCH_MOVE( outputFileName ),
CATCH_MOVE( colourMode ),
CATCH_MOVE( kvPairs ) };
}
ReporterSpec::ReporterSpec(
std::string name,
Optional<std::string> outputFileName,
Optional<ColourMode> colourMode,
std::map<std::string, std::string> customOptions ):
m_name( CATCH_MOVE( name ) ),
m_outputFileName( CATCH_MOVE( outputFileName ) ),
m_colourMode( CATCH_MOVE( colourMode ) ),
m_customOptions( CATCH_MOVE( customOptions ) ) {}
} // namespace Catch } // namespace Catch

View File

@ -8,11 +8,13 @@
#ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED #ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED
#define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED #define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED
#include <catch2/internal/catch_console_colour.hpp>
#include <catch2/internal/catch_optional.hpp> #include <catch2/internal/catch_optional.hpp>
#include <catch2/internal/catch_stringref.hpp> #include <catch2/internal/catch_stringref.hpp>
#include <vector> #include <map>
#include <string> #include <string>
#include <vector>
namespace Catch { namespace Catch {
@ -25,6 +27,59 @@ namespace Catch {
Optional<ColourMode> stringToColourMode( StringRef colourMode ); Optional<ColourMode> stringToColourMode( StringRef colourMode );
} }
/**
* Structured reporter spec that a reporter can be created from
*
* Parsing has been validated, but semantics have not. This means e.g.
* that the colour mode is known to Catch2, but it might not be
* compiled into the binary, and the output filename might not be
* openable.
*/
class ReporterSpec {
std::string m_name;
Optional<std::string> m_outputFileName;
Optional<ColourMode> m_colourMode;
std::map<std::string, std::string> m_customOptions;
friend bool operator==( ReporterSpec const& lhs,
ReporterSpec const& rhs );
friend bool operator!=( ReporterSpec const& lhs,
ReporterSpec const& rhs ) {
return !( lhs == rhs );
}
public:
ReporterSpec(
std::string name,
Optional<std::string> outputFileName,
Optional<ColourMode> colourMode,
std::map<std::string, std::string> customOptions );
std::string const& name() const { return m_name; }
Optional<std::string> const& outputFile() const {
return m_outputFileName;
}
Optional<ColourMode> const& colourMode() const { return m_colourMode; }
std::map<std::string, std::string> const& customOptions() const {
return m_customOptions;
}
};
/**
* Parses provided reporter spec string into
*
* Returns empty optional on errors, e.g.
* * field that is not first and not a key+value pair
* * duplicated keys in kv pair
* * unknown catch reporter option
* * empty key/value in an custom kv pair
* * ...
*/
Optional<ReporterSpec> parseReporterSpec( StringRef reporterSpec );
} }
#endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED #endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED

View File

@ -62,3 +62,50 @@ TEST_CASE( "Parsing colour mode", "[cli][colour][approvals]" ) {
REQUIRE_FALSE( stringToColourMode( "asdbjsdb kasbd" ) ); REQUIRE_FALSE( stringToColourMode( "asdbjsdb kasbd" ) );
} }
} }
TEST_CASE("Parsing reporter specs", "[cli][reporter-spec][approvals]") {
using Catch::parseReporterSpec;
using Catch::ReporterSpec;
using namespace std::string_literals;
SECTION( "Correct specs" ) {
REQUIRE( parseReporterSpec( "someReporter" ) ==
ReporterSpec( "someReporter"s, {}, {}, {} ) );
REQUIRE( parseReporterSpec( "otherReporter::Xk=v::out=c:\\blah" ) ==
ReporterSpec(
"otherReporter"s, "c:\\blah"s, {}, { { "Xk"s, "v"s } } ) );
REQUIRE( parseReporterSpec( "diffReporter::Xk1=v1::Xk2==v2" ) ==
ReporterSpec( "diffReporter",
{},
{},
{ { "Xk1"s, "v1"s }, { "Xk2"s, "=v2"s } } ) );
REQUIRE( parseReporterSpec(
"Foo:bar:reporter::colour=ansi::Xk 1=v 1::Xk2=v:3" ) ==
ReporterSpec( "Foo:bar:reporter",
{},
Catch::ColourMode::ANSI,
{ { "Xk 1"s, "v 1"s }, { "Xk2"s, "v:3"s } } ) );
}
SECTION( "Bad specs" ) {
REQUIRE_FALSE( parseReporterSpec( "::" ) );
// Unknown Catch2 arg (should be "out")
REQUIRE_FALSE( parseReporterSpec( "reporter::output=filename" ) );
// Wrong colour spec
REQUIRE_FALSE( parseReporterSpec( "reporter::colour=custom" ) );
// Duplicated colour spec
REQUIRE_FALSE( parseReporterSpec( "reporter::colour=ansi::colour=ansi" ) );
// Duplicated out arg
REQUIRE_FALSE( parseReporterSpec( "reporter::out=f.txt::out=z.txt" ) );
// Duplicated custom arg
REQUIRE_FALSE( parseReporterSpec( "reporter::Xa=foo::Xa=bar" ) );
// Empty key
REQUIRE_FALSE( parseReporterSpec( "reporter::X=foo" ) );
REQUIRE_FALSE( parseReporterSpec( "reporter::=foo" ) );
// Empty value
REQUIRE_FALSE( parseReporterSpec( "reporter::Xa=" ) );
// non-key value later field
REQUIRE_FALSE( parseReporterSpec( "reporter::Xab" ) );
}
}