mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 12:17:11 +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:
		| @@ -9,9 +9,28 @@ | ||||
| #include <catch2/internal/catch_reporter_spec_parser.hpp> | ||||
|  | ||||
| #include <catch2/interfaces/catch_interfaces_config.hpp> | ||||
| #include <catch2/internal/catch_move_and_forward.hpp> | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <ostream> | ||||
|  | ||||
| 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 { | ||||
|         std::vector<std::string> splitReporterSpec( StringRef reporterSpec ) { | ||||
|             static constexpr auto separator = "::"; | ||||
| @@ -78,4 +97,78 @@ namespace Catch { | ||||
|     } // 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 | ||||
|   | ||||
| @@ -8,11 +8,13 @@ | ||||
| #ifndef 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_stringref.hpp> | ||||
|  | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <string> | ||||
| #include <vector> | ||||
|  | ||||
| namespace Catch { | ||||
|  | ||||
| @@ -25,6 +27,59 @@ namespace Catch { | ||||
|         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 | ||||
|   | ||||
| @@ -62,3 +62,50 @@ TEST_CASE( "Parsing colour mode", "[cli][colour][approvals]" ) { | ||||
|         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" ) ); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský