mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-25 23:06:10 +01:00
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:
parent
8ac86495de
commit
a51fd07bd0
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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" ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user