Hidden tests now require positive filter match to be selected

This also required some refactoring of how the pattern matching
works. This means that the concepts of include and exclude patterns
are no longer unified, with exclusion patterns working as just
negation of an inclusion patterns (which led to including hidden
tags by default, as they did not match the exclusion), but rather
both include and exclude patterns are handled separately.

The new logic is that given a filter and a test case, the test
case must match _all_ include patterns and _no_ exclude patterns
to be included by the filter. Furthermore, if the test case is
hidden, then the filter must have at least one include pattern
for the test case to be used.

Closes #1184
This commit is contained in:
Martin Hořeňovský
2019-06-22 20:26:03 +02:00
parent 2bcf1b3db6
commit 4f47d1c6c1
15 changed files with 63 additions and 69 deletions

View File

@@ -48,25 +48,30 @@ namespace Catch {
m_tag) != end(testCase.lcaseTags);
}
TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern )
: Pattern( underlyingPattern->name() )
, m_underlyingPattern( underlyingPattern )
{}
bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const {
return !m_underlyingPattern->matches( testCase );
}
bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {
return std::all_of( m_patterns.begin(), m_patterns.end(), [&]( PatternPtr const& p ){ return p->matches( testCase ); } );
bool should_use = !testCase.isHidden();
for (auto const& pattern : m_required) {
should_use = true;
if (!pattern->matches(testCase)) {
return false;
}
}
for (auto const& pattern : m_forbidden) {
if (pattern->matches(testCase)) {
return false;
}
}
return should_use;
}
std::string TestSpec::Filter::name() const {
std::string name;
for( auto const& p : m_patterns )
for (auto const& p : m_required) {
name += p->name();
}
for (auto const& p : m_forbidden) {
name += p->name();
}
return name;
}
@@ -91,7 +96,7 @@ namespace Catch {
} );
return matches;
}
const TestSpec::vectorStrings& TestSpec::getInvalidArgs() const{
return (m_invalidArgs);
}

View File

@@ -52,16 +52,9 @@ namespace Catch {
std::string m_tag;
};
class ExcludedPattern : public Pattern {
public:
explicit ExcludedPattern( PatternPtr const& underlyingPattern );
bool matches( TestCaseInfo const& testCase ) const override;
private:
PatternPtr m_underlyingPattern;
};
struct Filter {
std::vector<PatternPtr> m_patterns;
std::vector<PatternPtr> m_required;
std::vector<PatternPtr> m_forbidden;
bool matches( TestCaseInfo const& testCase ) const;
std::string name() const;

View File

@@ -147,7 +147,7 @@ namespace Catch {
}
void TestSpecParser::addFilter() {
if( !m_currentFilter.m_patterns.empty() ) {
if( !m_currentFilter.m_required.empty() || !m_currentFilter.m_forbidden.empty() ) {
m_testSpec.m_filters.push_back( m_currentFilter );
m_currentFilter = TestSpec::Filter();
}

View File

@@ -53,7 +53,7 @@ namespace Catch {
void revertBackToLastMode();
void addFilter();
bool separate();
template<typename T>
void addPattern() {
std::string token = m_patternName;
@@ -66,22 +66,24 @@ namespace Catch {
}
if( !token.empty() ) {
TestSpec::PatternPtr pattern = std::make_shared<T>( token, m_substring );
if( m_exclusion )
pattern = std::make_shared<TestSpec::ExcludedPattern>( pattern );
m_currentFilter.m_patterns.push_back( pattern );
if (m_exclusion) {
m_currentFilter.m_forbidden.push_back(pattern);
} else {
m_currentFilter.m_required.push_back(pattern);
}
}
m_substring.clear();
m_patternName.clear();
m_exclusion = false;
m_mode = None;
}
inline void addCharToPattern(char c) {
m_substring += c;
m_patternName += c;
m_realPatternPos++;
}
};
TestSpec parseTestSpec( std::string const& arg );