1 /* 2 * Created by Martin on 19/07/2017. 3 * 4 * Distributed under the Boost Software License, Version 1.0. (See accompanying 5 * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 */ 7 8 #include "catch_test_spec_parser.h" 9 10 11 namespace Catch { 12 TestSpecParser(ITagAliasRegistry const & tagAliases)13 TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} 14 parse(std::string const & arg)15 TestSpecParser& TestSpecParser::parse( std::string const& arg ) { 16 m_mode = None; 17 m_exclusion = false; 18 m_arg = m_tagAliases->expandAliases( arg ); 19 m_escapeChars.clear(); 20 m_substring.reserve(m_arg.size()); 21 m_patternName.reserve(m_arg.size()); 22 m_realPatternPos = 0; 23 24 for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) 25 //if visitChar fails 26 if( !visitChar( m_arg[m_pos] ) ){ 27 m_testSpec.m_invalidArgs.push_back(arg); 28 break; 29 } 30 endMode(); 31 return *this; 32 } testSpec()33 TestSpec TestSpecParser::testSpec() { 34 addFilter(); 35 return m_testSpec; 36 } visitChar(char c)37 bool TestSpecParser::visitChar( char c ) { 38 if( (m_mode != EscapedName) && (c == '\\') ) { 39 escape(); 40 addCharToPattern(c); 41 return true; 42 }else if((m_mode != EscapedName) && (c == ',') ) { 43 return separate(); 44 } 45 46 switch( m_mode ) { 47 case None: 48 if( processNoneChar( c ) ) 49 return true; 50 break; 51 case Name: 52 processNameChar( c ); 53 break; 54 case EscapedName: 55 endMode(); 56 addCharToPattern(c); 57 return true; 58 default: 59 case Tag: 60 case QuotedName: 61 if( processOtherChar( c ) ) 62 return true; 63 break; 64 } 65 66 m_substring += c; 67 if( !isControlChar( c ) ) { 68 m_patternName += c; 69 m_realPatternPos++; 70 } 71 return true; 72 } 73 // Two of the processing methods return true to signal the caller to return 74 // without adding the given character to the current pattern strings processNoneChar(char c)75 bool TestSpecParser::processNoneChar( char c ) { 76 switch( c ) { 77 case ' ': 78 return true; 79 case '~': 80 m_exclusion = true; 81 return false; 82 case '[': 83 startNewMode( Tag ); 84 return false; 85 case '"': 86 startNewMode( QuotedName ); 87 return false; 88 default: 89 startNewMode( Name ); 90 return false; 91 } 92 } processNameChar(char c)93 void TestSpecParser::processNameChar( char c ) { 94 if( c == '[' ) { 95 if( m_substring == "exclude:" ) 96 m_exclusion = true; 97 else 98 endMode(); 99 startNewMode( Tag ); 100 } 101 } processOtherChar(char c)102 bool TestSpecParser::processOtherChar( char c ) { 103 if( !isControlChar( c ) ) 104 return false; 105 m_substring += c; 106 endMode(); 107 return true; 108 } startNewMode(Mode mode)109 void TestSpecParser::startNewMode( Mode mode ) { 110 m_mode = mode; 111 } endMode()112 void TestSpecParser::endMode() { 113 switch( m_mode ) { 114 case Name: 115 case QuotedName: 116 return addNamePattern(); 117 case Tag: 118 return addTagPattern(); 119 case EscapedName: 120 revertBackToLastMode(); 121 return; 122 case None: 123 default: 124 return startNewMode( None ); 125 } 126 } escape()127 void TestSpecParser::escape() { 128 saveLastMode(); 129 m_mode = EscapedName; 130 m_escapeChars.push_back(m_realPatternPos); 131 } isControlChar(char c) const132 bool TestSpecParser::isControlChar( char c ) const { 133 switch( m_mode ) { 134 default: 135 return false; 136 case None: 137 return c == '~'; 138 case Name: 139 return c == '['; 140 case EscapedName: 141 return true; 142 case QuotedName: 143 return c == '"'; 144 case Tag: 145 return c == '[' || c == ']'; 146 } 147 } 148 addFilter()149 void TestSpecParser::addFilter() { 150 if( !m_currentFilter.m_patterns.empty() ) { 151 m_testSpec.m_filters.push_back( m_currentFilter ); 152 m_currentFilter = TestSpec::Filter(); 153 } 154 } 155 saveLastMode()156 void TestSpecParser::saveLastMode() { 157 lastMode = m_mode; 158 } 159 revertBackToLastMode()160 void TestSpecParser::revertBackToLastMode() { 161 m_mode = lastMode; 162 } 163 separate()164 bool TestSpecParser::separate() { 165 if( (m_mode==QuotedName) || (m_mode==Tag) ){ 166 //invalid argument, signal failure to previous scope. 167 m_mode = None; 168 m_pos = m_arg.size(); 169 m_substring.clear(); 170 m_patternName.clear(); 171 return false; 172 } 173 endMode(); 174 addFilter(); 175 return true; //success 176 } 177 preprocessPattern()178 std::string TestSpecParser::preprocessPattern() { 179 std::string token = m_patternName; 180 for (std::size_t i = 0; i < m_escapeChars.size(); ++i) 181 token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1); 182 m_escapeChars.clear(); 183 if (startsWith(token, "exclude:")) { 184 m_exclusion = true; 185 token = token.substr(8); 186 } 187 188 m_patternName.clear(); 189 190 return token; 191 } 192 addNamePattern()193 void TestSpecParser::addNamePattern() { 194 auto token = preprocessPattern(); 195 196 if (!token.empty()) { 197 TestSpec::PatternPtr pattern = std::make_shared<TestSpec::NamePattern>(token, m_substring); 198 if (m_exclusion) 199 pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern); 200 m_currentFilter.m_patterns.push_back(pattern); 201 } 202 m_substring.clear(); 203 m_exclusion = false; 204 m_mode = None; 205 } 206 addTagPattern()207 void TestSpecParser::addTagPattern() { 208 auto token = preprocessPattern(); 209 210 if (!token.empty()) { 211 // If the tag pattern is the "hide and tag" shorthand (e.g. [.foo]) 212 // we have to create a separate hide tag and shorten the real one 213 if (token.size() > 1 && token[0] == '.') { 214 token.erase(token.begin()); 215 TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(".", m_substring); 216 if (m_exclusion) { 217 pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern); 218 } 219 m_currentFilter.m_patterns.push_back(pattern); 220 } 221 222 TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(token, m_substring); 223 224 if (m_exclusion) { 225 pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern); 226 } 227 m_currentFilter.m_patterns.push_back(pattern); 228 } 229 m_substring.clear(); 230 m_exclusion = false; 231 m_mode = None; 232 } 233 parseTestSpec(std::string const & arg)234 TestSpec parseTestSpec( std::string const& arg ) { 235 return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); 236 } 237 238 } // namespace Catch 239