PageRenderTime 51ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/python/python3/cxxtest/cxxtest_parser.py

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