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          m_realPatternPos = 0;
172          return false;
173       }
174       endMode();
175       addFilter();
176       return true; //success
177     }
178 
preprocessPattern()179     std::string TestSpecParser::preprocessPattern() {
180         std::string token = m_patternName;
181         for (std::size_t i = 0; i < m_escapeChars.size(); ++i)
182             token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1);
183         m_escapeChars.clear();
184         if (startsWith(token, "exclude:")) {
185             m_exclusion = true;
186             token = token.substr(8);
187         }
188 
189         m_patternName.clear();
190         m_realPatternPos = 0;
191 
192         return token;
193     }
194 
addNamePattern()195     void TestSpecParser::addNamePattern() {
196         auto token = preprocessPattern();
197 
198         if (!token.empty()) {
199             TestSpec::PatternPtr pattern = std::make_shared<TestSpec::NamePattern>(token, m_substring);
200             if (m_exclusion)
201                 pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern);
202             m_currentFilter.m_patterns.push_back(pattern);
203         }
204         m_substring.clear();
205         m_exclusion = false;
206         m_mode = None;
207     }
208 
addTagPattern()209     void TestSpecParser::addTagPattern() {
210         auto token = preprocessPattern();
211 
212         if (!token.empty()) {
213             // If the tag pattern is the "hide and tag" shorthand (e.g. [.foo])
214             // we have to create a separate hide tag and shorten the real one
215             if (token.size() > 1 && token[0] == '.') {
216                 token.erase(token.begin());
217                 TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(".", m_substring);
218                 if (m_exclusion) {
219                     pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern);
220                 }
221                 m_currentFilter.m_patterns.push_back(pattern);
222             }
223 
224             TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(token, m_substring);
225 
226             if (m_exclusion) {
227                 pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern);
228             }
229             m_currentFilter.m_patterns.push_back(pattern);
230         }
231         m_substring.clear();
232         m_exclusion = false;
233         m_mode = None;
234     }
235 
parseTestSpec(std::string const & arg)236     TestSpec parseTestSpec( std::string const& arg ) {
237         return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();
238     }
239 
240 } // namespace Catch
241