mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-04 05:09:53 +01:00
4f47d1c6c1
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
183 lines
5.0 KiB
C++
183 lines
5.0 KiB
C++
/*
|
|
* Created by Martin on 19/07/2017.
|
|
*
|
|
* Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
*/
|
|
|
|
#include "catch_test_spec_parser.h"
|
|
|
|
|
|
namespace Catch {
|
|
|
|
TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}
|
|
|
|
TestSpecParser& TestSpecParser::parse( std::string const& arg ) {
|
|
m_mode = None;
|
|
m_exclusion = false;
|
|
m_arg = m_tagAliases->expandAliases( arg );
|
|
m_escapeChars.clear();
|
|
m_substring.reserve(m_arg.size());
|
|
m_patternName.reserve(m_arg.size());
|
|
m_realPatternPos = 0;
|
|
|
|
for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )
|
|
//if visitChar fails
|
|
if( !visitChar( m_arg[m_pos] ) ){
|
|
m_testSpec.m_invalidArgs.push_back(arg);
|
|
break;
|
|
}
|
|
endMode();
|
|
return *this;
|
|
}
|
|
TestSpec TestSpecParser::testSpec() {
|
|
addFilter();
|
|
return m_testSpec;
|
|
}
|
|
bool TestSpecParser::visitChar( char c ) {
|
|
if( (m_mode != EscapedName) && (c == '\\') ) {
|
|
escape();
|
|
addCharToPattern(c);
|
|
return true;
|
|
}else if((m_mode != EscapedName) && (c == ',') ) {
|
|
return separate();
|
|
}
|
|
|
|
switch( m_mode ) {
|
|
case None:
|
|
if( processNoneChar( c ) )
|
|
return true;
|
|
break;
|
|
case Name:
|
|
processNameChar( c );
|
|
break;
|
|
case EscapedName:
|
|
endMode();
|
|
addCharToPattern(c);
|
|
return true;
|
|
default:
|
|
case Tag:
|
|
case QuotedName:
|
|
if( processOtherChar( c ) )
|
|
return true;
|
|
break;
|
|
}
|
|
|
|
m_substring += c;
|
|
if( !isControlChar( c ) ) {
|
|
m_patternName += c;
|
|
m_realPatternPos++;
|
|
}
|
|
return true;
|
|
}
|
|
// Two of the processing methods return true to signal the caller to return
|
|
// without adding the given character to the current pattern strings
|
|
bool TestSpecParser::processNoneChar( char c ) {
|
|
switch( c ) {
|
|
case ' ':
|
|
return true;
|
|
case '~':
|
|
m_exclusion = true;
|
|
return false;
|
|
case '[':
|
|
startNewMode( Tag );
|
|
return false;
|
|
case '"':
|
|
startNewMode( QuotedName );
|
|
return false;
|
|
default:
|
|
startNewMode( Name );
|
|
return false;
|
|
}
|
|
}
|
|
void TestSpecParser::processNameChar( char c ) {
|
|
if( c == '[' ) {
|
|
if( m_substring == "exclude:" )
|
|
m_exclusion = true;
|
|
else
|
|
endMode();
|
|
startNewMode( Tag );
|
|
}
|
|
}
|
|
bool TestSpecParser::processOtherChar( char c ) {
|
|
if( !isControlChar( c ) )
|
|
return false;
|
|
m_substring += c;
|
|
endMode();
|
|
return true;
|
|
}
|
|
void TestSpecParser::startNewMode( Mode mode ) {
|
|
m_mode = mode;
|
|
}
|
|
void TestSpecParser::endMode() {
|
|
switch( m_mode ) {
|
|
case Name:
|
|
case QuotedName:
|
|
return addPattern<TestSpec::NamePattern>();
|
|
case Tag:
|
|
return addPattern<TestSpec::TagPattern>();
|
|
case EscapedName:
|
|
revertBackToLastMode();
|
|
return;
|
|
case None:
|
|
default:
|
|
return startNewMode( None );
|
|
}
|
|
}
|
|
void TestSpecParser::escape() {
|
|
saveLastMode();
|
|
m_mode = EscapedName;
|
|
m_escapeChars.push_back(m_realPatternPos);
|
|
}
|
|
bool TestSpecParser::isControlChar( char c ) const {
|
|
switch( m_mode ) {
|
|
default:
|
|
return false;
|
|
case None:
|
|
return c == '~';
|
|
case Name:
|
|
return c == '[';
|
|
case EscapedName:
|
|
return true;
|
|
case QuotedName:
|
|
return c == '"';
|
|
case Tag:
|
|
return c == '[' || c == ']';
|
|
}
|
|
}
|
|
|
|
void TestSpecParser::addFilter() {
|
|
if( !m_currentFilter.m_required.empty() || !m_currentFilter.m_forbidden.empty() ) {
|
|
m_testSpec.m_filters.push_back( m_currentFilter );
|
|
m_currentFilter = TestSpec::Filter();
|
|
}
|
|
}
|
|
|
|
void TestSpecParser::saveLastMode() {
|
|
lastMode = m_mode;
|
|
}
|
|
|
|
void TestSpecParser::revertBackToLastMode() {
|
|
m_mode = lastMode;
|
|
}
|
|
|
|
bool TestSpecParser::separate() {
|
|
if( (m_mode==QuotedName) || (m_mode==Tag) ){
|
|
//invalid argument, signal failure to previous scope.
|
|
m_mode = None;
|
|
m_pos = m_arg.size();
|
|
m_substring.clear();
|
|
m_patternName.clear();
|
|
return false;
|
|
}
|
|
endMode();
|
|
addFilter();
|
|
return true; //success
|
|
}
|
|
|
|
TestSpec parseTestSpec( std::string const& arg ) {
|
|
return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();
|
|
}
|
|
|
|
} // namespace Catch
|