PageRenderTime 24ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/python/cxxtest/cxxtest_parser.py

https://github.com/kindkaktus/cxxtest
Python | 253 lines | 212 code | 12 blank | 29 comment | 15 complexity | 26ac6148fc3c752a56d991c8aa4f8e5e MD5 | raw file
Possible License(s): LGPL-3.0
  1. #-------------------------------------------------------------------------
  2. # CxxTest: A lightweight C++ unit testing library.
  3. # Copyright (c) 2008 Sandia Corporation.
  4. # This software is distributed under the LGPL License v3
  5. # For more information, see the COPYING file in the top CxxTest directory.
  6. # Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
  7. # the U.S. Government retains certain rights in this software.
  8. #-------------------------------------------------------------------------
  9. from __future__ import division
  10. import codecs
  11. import re
  12. from cxxtest.cxxtest_misc import abort
  13. # Global variables
  14. suites = []
  15. suite = None
  16. inBlock = 0
  17. options=None
  18. def scanInputFiles(files, _options):
  19. '''Scan all input files for test suites'''
  20. #
  21. # Reset global data
  22. #
  23. global options
  24. options=_options
  25. global suites
  26. suites = []
  27. global suite
  28. suite = None
  29. global inBlock
  30. inBlock = 0
  31. #
  32. for file in files:
  33. scanInputFile(file)
  34. if len(suites) is 0 and not options.root:
  35. abort( 'No tests defined' )
  36. return [options,suites]
  37. lineCont_re = re.compile('(.*)\\\s*$')
  38. def scanInputFile(fileName):
  39. '''Scan single input file for test suites'''
  40. # mode 'rb' is problematic in python3 - byte arrays don't behave the same as
  41. # strings.
  42. # As far as the choice of the default encoding: utf-8 chews through
  43. # everything that the previous ascii codec could, plus most of new code.
  44. # TODO: figure out how to do this properly - like autodetect encoding from
  45. # file header.
  46. file = codecs.open(fileName, mode='r', encoding='utf-8')
  47. prev = ""
  48. lineNo = 0
  49. contNo = 0
  50. while 1:
  51. try:
  52. line = file.readline()
  53. except UnicodeDecodeError:
  54. sys.stderr.write("Could not decode unicode character at %s:%s\n" % (fileName, lineNo + 1));
  55. raise
  56. if not line:
  57. break
  58. lineNo += 1
  59. m = lineCont_re.match(line)
  60. if m:
  61. prev += m.group(1) + " "
  62. contNo += 1
  63. else:
  64. scanInputLine( fileName, lineNo - contNo, prev + line )
  65. contNo = 0
  66. prev = ""
  67. if contNo:
  68. scanInputLine( fileName, lineNo - contNo, prev + line )
  69. closeSuite()
  70. file.close()
  71. def scanInputLine( fileName, lineNo, line ):
  72. '''Scan single input line for interesting stuff'''
  73. scanLineForExceptionHandling( line )
  74. scanLineForStandardLibrary( line )
  75. scanLineForSuiteStart( fileName, lineNo, line )
  76. global suite
  77. if suite:
  78. scanLineInsideSuite( suite, lineNo, line )
  79. def scanLineInsideSuite( suite, lineNo, line ):
  80. '''Analyze line which is part of a suite'''
  81. global inBlock
  82. if lineBelongsToSuite( suite, lineNo, line ):
  83. scanLineForTest( suite, lineNo, line )
  84. scanLineForCreate( suite, lineNo, line )
  85. scanLineForDestroy( suite, lineNo, line )
  86. def lineBelongsToSuite( suite, lineNo, line ):
  87. '''Returns whether current line is part of the current suite.
  88. This can be false when we are in a generated suite outside of CXXTEST_CODE() blocks
  89. If the suite is generated, adds the line to the list of lines'''
  90. if not suite['generated']:
  91. return 1
  92. global inBlock
  93. if not inBlock:
  94. inBlock = lineStartsBlock( line )
  95. if inBlock:
  96. inBlock = addLineToBlock( suite, lineNo, line )
  97. return inBlock
  98. std_re = re.compile( r"\b(std\s*::|CXXTEST_STD|using\s+namespace\s+std\b|^\s*\#\s*include\s+<[a-z0-9]+>)" )
  99. def scanLineForStandardLibrary( line ):
  100. '''Check if current line uses standard library'''
  101. global options
  102. if not options.haveStandardLibrary and std_re.search(line):
  103. if not options.noStandardLibrary:
  104. options.haveStandardLibrary = 1
  105. exception_re = re.compile( r"\b(throw|try|catch|TSM?_ASSERT_THROWS[A-Z_]*)\b" )
  106. def scanLineForExceptionHandling( line ):
  107. '''Check if current line uses exception handling'''
  108. global options
  109. if not options.haveExceptionHandling and exception_re.search(line):
  110. if not options.noExceptionHandling:
  111. options.haveExceptionHandling = 1
  112. classdef = '(?:::\s*)?(?:\w+\s*::\s*)*\w+'
  113. baseclassdef = '(?:public|private|protected)\s+%s' % (classdef,)
  114. general_suite = r"\bclass\s+(%s)\s*:(?:\s*%s\s*,)*\s*public\s+" \
  115. % (classdef, baseclassdef,)
  116. testsuite = '(?:(?:::)?\s*CxxTest\s*::\s*)?TestSuite'
  117. suites_re = { re.compile( general_suite + testsuite ) : None }
  118. generatedSuite_re = re.compile( r'\bCXXTEST_SUITE\s*\(\s*(\w*)\s*\)' )
  119. def scanLineForSuiteStart( fileName, lineNo, line ):
  120. '''Check if current line starts a new test suite'''
  121. for i in list(suites_re.items()):
  122. m = i[0].search( line )
  123. if m:
  124. suite = startSuite( m.group(1), fileName, lineNo, 0 )
  125. if i[1] is not None:
  126. for test in i[1]['tests']:
  127. addTest(suite, test['name'], test['line'])
  128. break
  129. m = generatedSuite_re.search( line )
  130. if m:
  131. sys.stdout.write( "%s:%s: Warning: Inline test suites are deprecated.\n" % (fileName, lineNo) )
  132. startSuite( m.group(1), fileName, lineNo, 1 )
  133. def startSuite( name, file, line, generated ):
  134. '''Start scanning a new suite'''
  135. global suite
  136. closeSuite()
  137. object_name = name.replace(':',"_")
  138. suite = { 'fullname' : name,
  139. 'name' : name,
  140. 'file' : file,
  141. 'cfile' : cstr(file),
  142. 'line' : line,
  143. 'generated' : generated,
  144. 'object' : 'suite_%s' % object_name,
  145. 'dobject' : 'suiteDescription_%s' % object_name,
  146. 'tlist' : 'Tests_%s' % object_name,
  147. 'tests' : [],
  148. 'lines' : [] }
  149. suites_re[re.compile( general_suite + name )] = suite
  150. return suite
  151. def lineStartsBlock( line ):
  152. '''Check if current line starts a new CXXTEST_CODE() block'''
  153. return re.search( r'\bCXXTEST_CODE\s*\(', line ) is not None
  154. test_re = re.compile( r'^([^/]|/[^/])*\bvoid\s+([Tt]est\w+)\s*\(\s*(void)?\s*\)' )
  155. def scanLineForTest( suite, lineNo, line ):
  156. '''Check if current line starts a test'''
  157. m = test_re.search( line )
  158. if m:
  159. addTest( suite, m.group(2), lineNo )
  160. def addTest( suite, name, line ):
  161. '''Add a test function to the current suite'''
  162. test = { 'name' : name,
  163. 'suite' : suite,
  164. 'class' : 'TestDescription_%s_%s' % (suite['object'], name),
  165. 'object' : 'testDescription_%s_%s' % (suite['object'], name),
  166. 'line' : line,
  167. }
  168. suite['tests'].append( test )
  169. def addLineToBlock( suite, lineNo, line ):
  170. '''Append the line to the current CXXTEST_CODE() block'''
  171. line = fixBlockLine( suite, lineNo, line )
  172. line = re.sub( r'^.*\{\{', '', line )
  173. e = re.search( r'\}\}', line )
  174. if e:
  175. line = line[:e.start()]
  176. suite['lines'].append( line )
  177. return e is None
  178. def fixBlockLine( suite, lineNo, line):
  179. '''Change all [E]TS_ macros used in a line to _[E]TS_ macros with the correct file/line'''
  180. return re.sub( r'\b(E?TSM?_(ASSERT[A-Z_]*|FAIL))\s*\(',
  181. r'_\1(%s,%s,' % (suite['cfile'], lineNo),
  182. line, 0 )
  183. create_re = re.compile( r'\bstatic\s+\w+\s*\*\s*createSuite\s*\(\s*(void)?\s*\)' )
  184. def scanLineForCreate( suite, lineNo, line ):
  185. '''Check if current line defines a createSuite() function'''
  186. if create_re.search( line ):
  187. addSuiteCreateDestroy( suite, 'create', lineNo )
  188. destroy_re = re.compile( r'\bstatic\s+void\s+destroySuite\s*\(\s*\w+\s*\*\s*\w*\s*\)' )
  189. def scanLineForDestroy( suite, lineNo, line ):
  190. '''Check if current line defines a destroySuite() function'''
  191. if destroy_re.search( line ):
  192. addSuiteCreateDestroy( suite, 'destroy', lineNo )
  193. def cstr( s ):
  194. '''Convert a string to its C representation'''
  195. return '"' + s.replace( '\\', '\\\\' ) + '"'
  196. def addSuiteCreateDestroy( suite, which, line ):
  197. '''Add createSuite()/destroySuite() to current suite'''
  198. if which in suite:
  199. abort( '%s:%s: %sSuite() already declared' % ( suite['file'], str(line), which ) )
  200. suite[which] = line
  201. def closeSuite():
  202. '''Close current suite and add it to the list if valid'''
  203. global suite
  204. if suite is not None:
  205. if len(suite['tests']) is not 0:
  206. verifySuite(suite)
  207. rememberSuite(suite)
  208. suite = None
  209. def verifySuite(suite):
  210. '''Verify current suite is legal'''
  211. if 'create' in suite and 'destroy' not in suite:
  212. abort( '%s:%s: Suite %s has createSuite() but no destroySuite()' %
  213. (suite['file'], suite['create'], suite['name']) )
  214. elif 'destroy' in suite and 'create' not in suite:
  215. abort( '%s:%s: Suite %s has destroySuite() but no createSuite()' %
  216. (suite['file'], suite['destroy'], suite['name']) )
  217. def rememberSuite(suite):
  218. '''Add current suite to list'''
  219. global suites
  220. suites.append( suite )