Provide round-tripping serialization for TestSpec

This commit is contained in:
Martin Hořeňovský 2022-10-27 17:10:28 +02:00
parent c6dfeb5e7d
commit e19ed221bd
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
4 changed files with 112 additions and 10 deletions

View File

@ -6,12 +6,14 @@
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
#include <catch2/catch_test_spec.hpp> #include <catch2/catch_test_spec.hpp>
#include <catch2/internal/catch_reusable_string_stream.hpp>
#include <catch2/internal/catch_string_manip.hpp> #include <catch2/internal/catch_string_manip.hpp>
#include <catch2/catch_test_case_info.hpp> #include <catch2/catch_test_case_info.hpp>
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <vector> #include <vector>
#include <ostream>
namespace Catch { namespace Catch {
@ -35,6 +37,10 @@ namespace Catch {
return m_wildcardPattern.matches( testCase.name ); return m_wildcardPattern.matches( testCase.name );
} }
void TestSpec::NamePattern::serializeTo( std::ostream& out ) const {
out << '"' << name() << '"';
}
TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString ) TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString )
: Pattern( filterString ) : Pattern( filterString )
@ -47,6 +53,10 @@ namespace Catch {
Tag( m_tag ) ) != end( testCase.tags ); Tag( m_tag ) ) != end( testCase.tags );
} }
void TestSpec::TagPattern::serializeTo( std::ostream& out ) const {
out << name();
}
bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {
bool should_use = !testCase.isHidden(); bool should_use = !testCase.isHidden();
for (auto const& pattern : m_required) { for (auto const& pattern : m_required) {
@ -63,18 +73,31 @@ namespace Catch {
return should_use; return should_use;
} }
std::string TestSpec::Filter::name() const { void TestSpec::Filter::serializeTo( std::ostream& out ) const {
std::string name; bool first = true;
for (auto const& p : m_required) { for ( auto const& pattern : m_required ) {
name += p->name(); if ( !first ) {
out << ' ';
}
out << *pattern;
first = false;
} }
for (auto const& p : m_forbidden) { for ( auto const& pattern : m_forbidden ) {
name += p->name(); if ( !first ) {
out << ' ';
}
out << *pattern;
first = false;
} }
return name;
} }
std::string TestSpec::extractFilterName( Filter const& filter ) {
Catch::ReusableStringStream sstr;
sstr << filter;
return sstr.str();
}
bool TestSpec::hasFilters() const { bool TestSpec::hasFilters() const {
return !m_filters.empty(); return !m_filters.empty();
} }
@ -91,7 +114,7 @@ namespace Catch {
for( auto const& test : testCases ) for( auto const& test : testCases )
if( isThrowSafe( test, config ) && filter.matches( test.getTestCaseInfo() ) ) if( isThrowSafe( test, config ) && filter.matches( test.getTestCaseInfo() ) )
currentMatches.emplace_back( &test ); currentMatches.emplace_back( &test );
return FilterMatch{ filter.name(), currentMatches }; return FilterMatch{ extractFilterName(filter), currentMatches };
} ); } );
return matches; return matches;
} }
@ -100,4 +123,15 @@ namespace Catch {
return m_invalidSpecs; return m_invalidSpecs;
} }
void TestSpec::serializeTo( std::ostream& out ) const {
bool first = true;
for ( auto const& filter : m_filters ) {
if ( !first ) {
out << ',';
}
out << filter;
first = false;
}
}
} }

View File

@ -16,6 +16,7 @@
#include <catch2/internal/catch_unique_ptr.hpp> #include <catch2/internal/catch_unique_ptr.hpp>
#include <catch2/internal/catch_wildcard_pattern.hpp> #include <catch2/internal/catch_wildcard_pattern.hpp>
#include <iosfwd>
#include <string> #include <string>
#include <vector> #include <vector>
@ -34,6 +35,14 @@ namespace Catch {
virtual bool matches( TestCaseInfo const& testCase ) const = 0; virtual bool matches( TestCaseInfo const& testCase ) const = 0;
std::string const& name() const; std::string const& name() const;
private: private:
virtual void serializeTo( std::ostream& out ) const = 0;
// Writes string that would be reparsed into the pattern
friend std::ostream& operator<<(std::ostream& out,
Pattern const& pattern) {
pattern.serializeTo( out );
return out;
}
std::string const m_name; std::string const m_name;
}; };
@ -42,6 +51,8 @@ namespace Catch {
explicit NamePattern( std::string const& name, std::string const& filterString ); explicit NamePattern( std::string const& name, std::string const& filterString );
bool matches( TestCaseInfo const& testCase ) const override; bool matches( TestCaseInfo const& testCase ) const override;
private: private:
void serializeTo( std::ostream& out ) const override;
WildcardPattern m_wildcardPattern; WildcardPattern m_wildcardPattern;
}; };
@ -50,6 +61,8 @@ namespace Catch {
explicit TagPattern( std::string const& tag, std::string const& filterString ); explicit TagPattern( std::string const& tag, std::string const& filterString );
bool matches( TestCaseInfo const& testCase ) const override; bool matches( TestCaseInfo const& testCase ) const override;
private: private:
void serializeTo( std::ostream& out ) const override;
std::string m_tag; std::string m_tag;
}; };
@ -57,10 +70,19 @@ namespace Catch {
std::vector<Detail::unique_ptr<Pattern>> m_required; std::vector<Detail::unique_ptr<Pattern>> m_required;
std::vector<Detail::unique_ptr<Pattern>> m_forbidden; std::vector<Detail::unique_ptr<Pattern>> m_forbidden;
//! Serializes this filter into a string that would be parsed into
//! an equivalent filter
void serializeTo( std::ostream& out ) const;
friend std::ostream& operator<<(std::ostream& out, Filter const& f) {
f.serializeTo( out );
return out;
}
bool matches( TestCaseInfo const& testCase ) const; bool matches( TestCaseInfo const& testCase ) const;
std::string name() const;
}; };
static std::string extractFilterName( Filter const& filter );
public: public:
struct FilterMatch { struct FilterMatch {
std::string name; std::string name;
@ -77,7 +99,16 @@ namespace Catch {
private: private:
std::vector<Filter> m_filters; std::vector<Filter> m_filters;
std::vector<std::string> m_invalidSpecs; std::vector<std::string> m_invalidSpecs;
friend class TestSpecParser; friend class TestSpecParser;
//! Serializes this test spec into a string that would be parsed into
//! equivalent test spec
void serializeTo( std::ostream& out ) const;
friend std::ostream& operator<<(std::ostream& out,
TestSpec const& spec) {
spec.serializeTo( out );
return out;
}
}; };
} }

View File

@ -249,7 +249,7 @@ add_test(NAME TestSpecs::CombiningMatchingAndNonMatchingIsOk-1 COMMAND $<TARGET_
add_test(NAME TestSpecs::CombiningMatchingAndNonMatchingIsOk-2 COMMAND $<TARGET_FILE:SelfTest> Tracker, "___nonexistent_test___") add_test(NAME TestSpecs::CombiningMatchingAndNonMatchingIsOk-2 COMMAND $<TARGET_FILE:SelfTest> Tracker, "___nonexistent_test___")
set_tests_properties(TestSpecs::CombiningMatchingAndNonMatchingIsOk-2 PROPERTIES set_tests_properties(TestSpecs::CombiningMatchingAndNonMatchingIsOk-2 PROPERTIES
PASS_REGULAR_EXPRESSION "No test cases matched '___nonexistent_test___'" PASS_REGULAR_EXPRESSION "No test cases matched '\"___nonexistent_test___\"'"
FAIL_REGULAR_EXPRESSION "No tests ran" FAIL_REGULAR_EXPRESSION "No tests ran"
) )

View File

@ -325,3 +325,40 @@ TEST_CASE("#1912 -- test spec parser handles escaping", "[command-line][test-spe
REQUIRE(spec.matches(*fakeTestCase(R"(spec \ char)"))); REQUIRE(spec.matches(*fakeTestCase(R"(spec \ char)")));
} }
} }
TEST_CASE("Test spec serialization is round-trippable", "[test-spec][serialization][approvals]") {
using Catch::parseTestSpec;
using Catch::TestSpec;
auto serializedTestSpec = []( std::string const& spec ) {
Catch::ReusableStringStream sstr;
sstr << parseTestSpec( spec );
return sstr.str();
};
SECTION("Spaces are normalized") {
CHECK( serializedTestSpec( "[abc][def]" ) == "[abc] [def]" );
CHECK( serializedTestSpec( "[def] [abc]" ) == "[def] [abc]" );
CHECK( serializedTestSpec( "[def] [abc]" ) == "[def] [abc]" );
}
SECTION("Output is order dependent") {
CHECK( serializedTestSpec( "[abc][def]" ) == "[abc] [def]" );
CHECK( serializedTestSpec( "[def][abc]" ) == "[def] [abc]" );
}
SECTION("Multiple disjunct filters") {
CHECK( serializedTestSpec( "[abc],[def]" ) == "[abc],[def]" );
CHECK( serializedTestSpec( "[def],[abc],[idkfa]" ) == "[def],[abc],[idkfa]" );
}
SECTION("Test names are enclosed in string") {
CHECK( serializedTestSpec( "Some test" ) == "\"Some test\"" );
CHECK( serializedTestSpec( "*Some test" ) == "\"*Some test\"" );
CHECK( serializedTestSpec( "* Some test" ) == "\"* Some test\"" );
CHECK( serializedTestSpec( "* Some test *" ) == "\"* Some test *\"" );
}
SECTION( "Mixing test names and tags" ) {
CHECK( serializedTestSpec( "some test[abcd]" ) ==
"\"some test\" [abcd]" );
CHECK( serializedTestSpec( "[ab]some test[cd]" ) ==
"[ab] \"some test\" [cd]" );
}
}