/cxxtest/python/python3/cxxtest/cxxtest_parser.py
Python | 242 lines | 201 code | 13 blank | 28 comment | 15 complexity | f587019e0cdefc4df0a7a584a8632c0b MD5 | raw file
Possible License(s): ISC, Apache-2.0
- #-------------------------------------------------------------------------
- # CxxTest: A lightweight C++ unit testing library.
- # Copyright (c) 2008 Sandia Corporation.
- # This software is distributed under the LGPL License v2.1
- # For more information, see the COPYING file in the top CxxTest directory.
- # Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
- # the U.S. Government retains certain rights in this software.
- #-------------------------------------------------------------------------
- import codecs
- import re
- #import sys
- #import getopt
- #import glob
- from cxxtest.cxxtest_misc import abort
- # Global variables
- suites = []
- suite = None
- inBlock = 0
- options=None
- def scanInputFiles(files, _options):
- '''Scan all input files for test suites'''
- global options
- options=_options
- for file in files:
- scanInputFile(file)
- global suites
- if len(suites) is 0 and not options.root:
- abort( 'No tests defined' )
- return [options,suites]
- lineCont_re = re.compile('(.*)\\\s*$')
- def scanInputFile(fileName):
- '''Scan single input file for test suites'''
- # mode 'rb' is problematic in python3 - byte arrays don't behave the same as
- # strings.
- # As far as the choice of the default encoding: utf-8 chews through
- # everything that the previous ascii codec could, plus most of new code.
- # TODO: figure out how to do this properly - like autodetect encoding from
- # file header.
- file = codecs.open(fileName, mode='r', encoding='utf-8')
- prev = ""
- lineNo = 0
- contNo = 0
- while 1:
- line = file.readline()
- if not line:
- break
- lineNo += 1
- m = lineCont_re.match(line)
- if m:
- prev += m.group(1) + " "
- contNo += 1
- else:
- scanInputLine( fileName, lineNo - contNo, prev + line )
- contNo = 0
- prev = ""
- if contNo:
- scanInputLine( fileName, lineNo - contNo, prev + line )
-
- closeSuite()
- file.close()
- def scanInputLine( fileName, lineNo, line ):
- '''Scan single input line for interesting stuff'''
- scanLineForExceptionHandling( line )
- scanLineForStandardLibrary( line )
- scanLineForSuiteStart( fileName, lineNo, line )
- global suite
- if suite:
- scanLineInsideSuite( suite, lineNo, line )
- def scanLineInsideSuite( suite, lineNo, line ):
- '''Analyze line which is part of a suite'''
- global inBlock
- if lineBelongsToSuite( suite, lineNo, line ):
- scanLineForTest( suite, lineNo, line )
- scanLineForCreate( suite, lineNo, line )
- scanLineForDestroy( suite, lineNo, line )
- def lineBelongsToSuite( suite, lineNo, line ):
- '''Returns whether current line is part of the current suite.
- This can be false when we are in a generated suite outside of CXXTEST_CODE() blocks
- If the suite is generated, adds the line to the list of lines'''
- if not suite['generated']:
- return 1
- global inBlock
- if not inBlock:
- inBlock = lineStartsBlock( line )
- if inBlock:
- inBlock = addLineToBlock( suite, lineNo, line )
- return inBlock
- std_re = re.compile( r"\b(std\s*::|CXXTEST_STD|using\s+namespace\s+std\b|^\s*\#\s*include\s+<[a-z0-9]+>)" )
- def scanLineForStandardLibrary( line ):
- '''Check if current line uses standard library'''
- global options
- if not options.haveStandardLibrary and std_re.search(line):
- if not options.noStandardLibrary:
- options.haveStandardLibrary = 1
- exception_re = re.compile( r"\b(throw|try|catch|TSM?_ASSERT_THROWS[A-Z_]*)\b" )
- def scanLineForExceptionHandling( line ):
- '''Check if current line uses exception handling'''
- global options
- if not options.haveExceptionHandling and exception_re.search(line):
- if not options.noExceptionHandling:
- options.haveExceptionHandling = 1
- classdef = '(?:::\s*)?(?:\w+\s*::\s*)*\w+'
- baseclassdef = '(?:public|private|protected)\s+%s' % (classdef,)
- general_suite = r"\bclass\s+(%s)\s*:(?:\s*%s\s*,)*\s*public\s+" \
- % (classdef, baseclassdef,)
- testsuite = '(?:(?:::)?\s*CxxTest\s*::\s*)?TestSuite'
- suites_re = { re.compile( general_suite + testsuite ) : None }
- generatedSuite_re = re.compile( r'\bCXXTEST_SUITE\s*\(\s*(\w*)\s*\)' )
- def scanLineForSuiteStart( fileName, lineNo, line ):
- '''Check if current line starts a new test suite'''
- for i in list(suites_re.items()):
- m = i[0].search( line )
- if m:
- suite = startSuite( m.group(1), fileName, lineNo, 0 )
- if i[1] is not None:
- for test in i[1]['tests']:
- addTest(suite, test['name'], test['line'])
- break
- m = generatedSuite_re.search( line )
- if m:
- sys.stdout.write( "%s:%s: Warning: Inline test suites are deprecated.\n" % (fileName, lineNo) )
- startSuite( m.group(1), fileName, lineNo, 1 )
- def startSuite( name, file, line, generated ):
- '''Start scanning a new suite'''
- global suite
- closeSuite()
- object_name = name.replace(':',"_")
- suite = { 'name' : name,
- 'file' : file,
- 'cfile' : cstr(file),
- 'line' : line,
- 'generated' : generated,
- 'object' : 'suite_%s' % object_name,
- 'dobject' : 'suiteDescription_%s' % object_name,
- 'tlist' : 'Tests_%s' % object_name,
- 'tests' : [],
- 'lines' : [] }
- suites_re[re.compile( general_suite + name )] = suite
- return suite
- def lineStartsBlock( line ):
- '''Check if current line starts a new CXXTEST_CODE() block'''
- return re.search( r'\bCXXTEST_CODE\s*\(', line ) is not None
- test_re = re.compile( r'^([^/]|/[^/])*\bvoid\s+([Tt]est\w+)\s*\(\s*(void)?\s*\)' )
- def scanLineForTest( suite, lineNo, line ):
- '''Check if current line starts a test'''
- m = test_re.search( line )
- if m:
- addTest( suite, m.group(2), lineNo )
- def addTest( suite, name, line ):
- '''Add a test function to the current suite'''
- test = { 'name' : name,
- 'suite' : suite,
- 'class' : 'TestDescription_%s_%s' % (suite['object'], name),
- 'object' : 'testDescription_%s_%s' % (suite['object'], name),
- 'line' : line,
- }
- suite['tests'].append( test )
- def addLineToBlock( suite, lineNo, line ):
- '''Append the line to the current CXXTEST_CODE() block'''
- line = fixBlockLine( suite, lineNo, line )
- line = re.sub( r'^.*\{\{', '', line )
-
- e = re.search( r'\}\}', line )
- if e:
- line = line[:e.start()]
- suite['lines'].append( line )
- return e is None
- def fixBlockLine( suite, lineNo, line):
- '''Change all [E]TS_ macros used in a line to _[E]TS_ macros with the correct file/line'''
- return re.sub( r'\b(E?TSM?_(ASSERT[A-Z_]*|FAIL))\s*\(',
- r'_\1(%s,%s,' % (suite['cfile'], lineNo),
- line, 0 )
- create_re = re.compile( r'\bstatic\s+\w+\s*\*\s*createSuite\s*\(\s*(void)?\s*\)' )
- def scanLineForCreate( suite, lineNo, line ):
- '''Check if current line defines a createSuite() function'''
- if create_re.search( line ):
- addSuiteCreateDestroy( suite, 'create', lineNo )
- destroy_re = re.compile( r'\bstatic\s+void\s+destroySuite\s*\(\s*\w+\s*\*\s*\w*\s*\)' )
- def scanLineForDestroy( suite, lineNo, line ):
- '''Check if current line defines a destroySuite() function'''
- if destroy_re.search( line ):
- addSuiteCreateDestroy( suite, 'destroy', lineNo )
- def cstr( s ):
- '''Convert a string to its C representation'''
- return '"' + s.replace( '\\', '\\\\' ) + '"'
- def addSuiteCreateDestroy( suite, which, line ):
- '''Add createSuite()/destroySuite() to current suite'''
- if which in suite:
- abort( '%s:%s: %sSuite() already declared' % ( suite['file'], str(line), which ) )
- suite[which] = line
- def closeSuite():
- '''Close current suite and add it to the list if valid'''
- global suite
- if suite is not None:
- if len(suite['tests']) is not 0:
- verifySuite(suite)
- rememberSuite(suite)
- suite = None
- def verifySuite(suite):
- '''Verify current suite is legal'''
- if 'create' in suite and 'destroy' not in suite:
- abort( '%s:%s: Suite %s has createSuite() but no destroySuite()' %
- (suite['file'], suite['create'], suite['name']) )
- elif 'destroy' in suite and 'create' not in suite:
- abort( '%s:%s: Suite %s has destroySuite() but no createSuite()' %
- (suite['file'], suite['destroy'], suite['name']) )
- def rememberSuite(suite):
- '''Add current suite to list'''
- global suites
- suites.append( suite )