catch_test_spec_parser.cpp 6.9 KB

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