aboutsummaryrefslogtreecommitdiff
path: root/test/cxxtest/cxxtestgen.py
diff options
context:
space:
mode:
authorMax Horn2006-03-29 10:25:48 +0000
committerMax Horn2006-03-29 10:25:48 +0000
commit3820593bb8dfdca4e2e17c70b278065b7f7894d9 (patch)
treedf3e8bebda3abcd99d0882dc9e8c6410f86850fb /test/cxxtest/cxxtestgen.py
parent9c94670e22cb68e32fd38c0d2b009539e35d4a71 (diff)
downloadscummvm-rg350-3820593bb8dfdca4e2e17c70b278065b7f7894d9.tar.gz
scummvm-rg350-3820593bb8dfdca4e2e17c70b278065b7f7894d9.tar.bz2
scummvm-rg350-3820593bb8dfdca4e2e17c70b278065b7f7894d9.zip
bringing cxxtest-3.10.1 to ScummVM's main branch
svn-id: r21488
Diffstat (limited to 'test/cxxtest/cxxtestgen.py')
-rwxr-xr-xtest/cxxtest/cxxtestgen.py593
1 files changed, 593 insertions, 0 deletions
diff --git a/test/cxxtest/cxxtestgen.py b/test/cxxtest/cxxtestgen.py
new file mode 100755
index 0000000000..831d23ab41
--- /dev/null
+++ b/test/cxxtest/cxxtestgen.py
@@ -0,0 +1,593 @@
+#!/usr/bin/python
+'''Usage: %s [OPTIONS] <input file(s)>
+Generate test source file for CxxTest.
+
+ -v, --version Write CxxTest version
+ -o, --output=NAME Write output to file NAME
+ --runner=CLASS Create a main() function that runs CxxTest::CLASS
+ --gui=CLASS Like --runner, with GUI component
+ --error-printer Same as --runner=ErrorPrinter
+ --abort-on-fail Abort tests on failed asserts (like xUnit)
+ --have-std Use standard library (even if not found in tests)
+ --no-std Don\'t use standard library (even if found in tests)
+ --have-eh Use exception handling (even if not found in tests)
+ --no-eh Don\'t use exception handling (even if found in tests)
+ --longlong=[TYPE] Use TYPE (default: long long) as long long
+ --template=TEMPLATE Use TEMPLATE file to generate the test runner
+ --include=HEADER Include HEADER in test runner before other headers
+ --root Write CxxTest globals
+ --part Don\'t write CxxTest globals
+ --no-static-init Don\'t rely on static initialization
+'''
+
+import re
+import sys
+import getopt
+import glob
+import string
+
+# Global variables
+suites = []
+suite = None
+inBlock = 0
+
+outputFileName = None
+runner = None
+gui = None
+root = None
+part = None
+noStaticInit = None
+templateFileName = None
+headers = []
+
+haveExceptionHandling = 0
+noExceptionHandling = 0
+haveStandardLibrary = 0
+noStandardLibrary = 0
+abortOnFail = 0
+factor = 0
+longlong = 0
+
+def main():
+ '''The main program'''
+ files = parseCommandline()
+ scanInputFiles( files )
+ writeOutput()
+
+def usage( problem = None ):
+ '''Print usage info and exit'''
+ if problem is None:
+ print usageString()
+ sys.exit(0)
+ else:
+ sys.stderr.write( usageString() )
+ abort( problem )
+
+def usageString():
+ '''Construct program usage string'''
+ return __doc__ % sys.argv[0]
+
+def abort( problem ):
+ '''Print error message and exit'''
+ sys.stderr.write( '\n' )
+ sys.stderr.write( problem )
+ sys.stderr.write( '\n\n' )
+ sys.exit(2)
+
+def parseCommandline():
+ '''Analyze command line arguments'''
+ try:
+ options, patterns = getopt.getopt( sys.argv[1:], 'o:r:',
+ ['version', 'output=', 'runner=', 'gui=',
+ 'error-printer', 'abort-on-fail', 'have-std', 'no-std',
+ 'have-eh', 'no-eh', 'template=', 'include=',
+ 'root', 'part', 'no-static-init', 'factor', 'longlong='] )
+ except getopt.error, problem:
+ usage( problem )
+ setOptions( options )
+ return setFiles( patterns )
+
+def setOptions( options ):
+ '''Set options specified on command line'''
+ global outputFileName, templateFileName, runner, gui, haveStandardLibrary, factor, longlong
+ global haveExceptionHandling, noExceptionHandling, abortOnFail, headers, root, part, noStaticInit
+ for o, a in options:
+ if o in ('-v', '--version'):
+ printVersion()
+ elif o in ('-o', '--output'):
+ outputFileName = a
+ elif o == '--template':
+ templateFileName = a
+ elif o == '--runner':
+ runner = a
+ elif o == '--gui':
+ gui = a
+ elif o == '--include':
+ if not re.match( r'^["<].*[>"]$', a ):
+ a = ('"%s"' % a)
+ headers.append( a )
+ elif o == '--error-printer':
+ runner = 'ErrorPrinter'
+ haveStandardLibrary = 1
+ elif o == '--abort-on-fail':
+ abortOnFail = 1
+ elif o == '--have-std':
+ haveStandardLibrary = 1
+ elif o == '--no-std':
+ noStandardLibrary = 1
+ elif o == '--have-eh':
+ haveExceptionHandling = 1
+ elif o == '--no-eh':
+ noExceptionHandling = 1
+ elif o == '--root':
+ root = 1
+ elif o == '--part':
+ part = 1
+ elif o == '--no-static-init':
+ noStaticInit = 1
+ elif o == '--factor':
+ factor = 1
+ elif o == '--longlong':
+ if a:
+ longlong = a
+ else:
+ longlong = 'long long'
+
+ if noStaticInit and (root or part):
+ abort( '--no-static-init cannot be used with --root/--part' )
+
+ if gui and not runner:
+ runner = 'StdioPrinter'
+
+def printVersion():
+ '''Print CxxTest version and exit'''
+ sys.stdout.write( "This is CxxTest version 3.10.1.\n" )
+ sys.exit(0)
+
+def setFiles( patterns ):
+ '''Set input files specified on command line'''
+ files = expandWildcards( patterns )
+ if len(files) is 0 and not root:
+ usage( "No input files found" )
+ return files
+
+def expandWildcards( patterns ):
+ '''Expand all wildcards in an array (glob)'''
+ fileNames = []
+ for pathName in patterns:
+ patternFiles = glob.glob( pathName )
+ for fileName in patternFiles:
+ fileNames.append( fixBackslashes( fileName ) )
+ return fileNames
+
+def fixBackslashes( fileName ):
+ '''Convert backslashes to slashes in file name'''
+ return re.sub( r'\\', '/', fileName, 0 )
+
+def scanInputFiles(files):
+ '''Scan all input files for test suites'''
+ for file in files:
+ scanInputFile(file)
+ global suites
+ if len(suites) is 0 and not root:
+ abort( 'No tests defined' )
+
+def scanInputFile(fileName):
+ '''Scan single input file for test suites'''
+ file = open(fileName)
+ lineNo = 0
+ while 1:
+ line = file.readline()
+ if not line:
+ break
+ lineNo = lineNo + 1
+
+ scanInputLine( fileName, lineNo, 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 haveStandardLibrary, noStandardLibrary
+ if not haveStandardLibrary and std_re.search(line):
+ if not noStandardLibrary:
+ 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 haveExceptionHandling, noExceptionHandling
+ if not haveExceptionHandling and exception_re.search(line):
+ if not noExceptionHandling:
+ haveExceptionHandling = 1
+
+suite_re = re.compile( r'\bclass\s+(\w+)\s*:\s*public\s+((::)?\s*CxxTest\s*::\s*)?TestSuite\b' )
+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'''
+ m = suite_re.search( line )
+ if m:
+ startSuite( m.group(1), fileName, lineNo, 0 )
+ 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()
+ suite = { 'name' : name,
+ 'file' : file,
+ 'cfile' : cstr(file),
+ 'line' : line,
+ 'generated' : generated,
+ 'object' : 'suite_%s' % name,
+ 'dobject' : 'suiteDescription_%s' % name,
+ 'tlist' : 'Tests_%s' % name,
+ 'tests' : [],
+ 'lines' : [] }
+
+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['name'], name),
+ 'object' : 'testDescription_%s_%s' % (suite['name'], 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( str ):
+ '''Convert a string to its C representation'''
+ return '"' + string.replace( str, '\\', '\\\\' ) + '"'
+
+
+def addSuiteCreateDestroy( suite, which, line ):
+ '''Add createSuite()/destroySuite() to current suite'''
+ if suite.has_key(which):
+ 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 suite.has_key('create') and not suite.has_key('destroy'):
+ abort( '%s:%s: Suite %s has createSuite() but no destroySuite()' %
+ (suite['file'], suite['create'], suite['name']) )
+ if suite.has_key('destroy') and not suite.has_key('create'):
+ 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 )
+
+def writeOutput():
+ '''Create output file'''
+ if templateFileName:
+ writeTemplateOutput()
+ else:
+ writeSimpleOutput()
+
+def writeSimpleOutput():
+ '''Create output not based on template'''
+ output = startOutputFile()
+ writePreamble( output )
+ writeMain( output )
+ writeWorld( output )
+ output.close()
+
+include_re = re.compile( r"\s*\#\s*include\s+<cxxtest/" )
+preamble_re = re.compile( r"^\s*<CxxTest\s+preamble>\s*$" )
+world_re = re.compile( r"^\s*<CxxTest\s+world>\s*$" )
+def writeTemplateOutput():
+ '''Create output based on template file'''
+ template = open(templateFileName)
+ output = startOutputFile()
+ while 1:
+ line = template.readline()
+ if not line:
+ break;
+ if include_re.search( line ):
+ writePreamble( output )
+ output.write( line )
+ elif preamble_re.search( line ):
+ writePreamble( output )
+ elif world_re.search( line ):
+ writeWorld( output )
+ else:
+ output.write( line )
+ template.close()
+ output.close()
+
+def startOutputFile():
+ '''Create output file and write header'''
+ if outputFileName is not None:
+ output = open( outputFileName, 'w' )
+ else:
+ output = sys.stdout
+ output.write( "/* Generated file, do not edit */\n\n" )
+ return output
+
+wrotePreamble = 0
+def writePreamble( output ):
+ '''Write the CxxTest header (#includes and #defines)'''
+ global wrotePreamble, headers, longlong
+ if wrotePreamble: return
+ output.write( "#ifndef CXXTEST_RUNNING\n" )
+ output.write( "#define CXXTEST_RUNNING\n" )
+ output.write( "#endif\n" )
+ output.write( "\n" )
+ if haveStandardLibrary:
+ output.write( "#define _CXXTEST_HAVE_STD\n" )
+ if haveExceptionHandling:
+ output.write( "#define _CXXTEST_HAVE_EH\n" )
+ if abortOnFail:
+ output.write( "#define _CXXTEST_ABORT_TEST_ON_FAIL\n" )
+ if longlong:
+ output.write( "#define _CXXTEST_LONGLONG %s\n" % longlong )
+ if factor:
+ output.write( "#define _CXXTEST_FACTOR\n" )
+ for header in headers:
+ output.write( "#include %s\n" % header )
+ output.write( "#include <cxxtest/TestListener.h>\n" )
+ output.write( "#include <cxxtest/TestTracker.h>\n" )
+ output.write( "#include <cxxtest/TestRunner.h>\n" )
+ output.write( "#include <cxxtest/RealDescriptions.h>\n" )
+ if runner:
+ output.write( "#include <cxxtest/%s.h>\n" % runner )
+ if gui:
+ output.write( "#include <cxxtest/%s.h>\n" % gui )
+ output.write( "\n" )
+ wrotePreamble = 1
+
+def writeMain( output ):
+ '''Write the main() function for the test runner'''
+ if gui:
+ output.write( 'int main( int argc, char *argv[] ) {\n' )
+ if noStaticInit:
+ output.write( ' CxxTest::initialize();\n' )
+ output.write( ' return CxxTest::GuiTuiRunner<CxxTest::%s, CxxTest::%s>( argc, argv ).run();\n' % (gui, runner) )
+ output.write( '}\n' )
+ elif runner:
+ output.write( 'int main() {\n' )
+ if noStaticInit:
+ output.write( ' CxxTest::initialize();\n' )
+ output.write( ' return CxxTest::%s().run();\n' % runner )
+ output.write( '}\n' )
+
+wroteWorld = 0
+def writeWorld( output ):
+ '''Write the world definitions'''
+ global wroteWorld, part
+ if wroteWorld: return
+ writePreamble( output )
+ writeSuites( output )
+ if root or not part:
+ writeRoot( output )
+ if noStaticInit:
+ writeInitialize( output )
+ wroteWorld = 1
+
+def writeSuites(output):
+ '''Write all TestDescriptions and SuiteDescriptions'''
+ for suite in suites:
+ writeInclude( output, suite['file'] )
+ if isGenerated(suite):
+ generateSuite( output, suite )
+ if isDynamic(suite):
+ writeSuitePointer( output, suite )
+ else:
+ writeSuiteObject( output, suite )
+ writeTestList( output, suite )
+ writeSuiteDescription( output, suite )
+ writeTestDescriptions( output, suite )
+
+def isGenerated(suite):
+ '''Checks whether a suite class should be created'''
+ return suite['generated']
+
+def isDynamic(suite):
+ '''Checks whether a suite is dynamic'''
+ return suite.has_key('create')
+
+lastIncluded = ''
+def writeInclude(output, file):
+ '''Add #include "file" statement'''
+ global lastIncluded
+ if file == lastIncluded: return
+ output.writelines( [ '#include "', file, '"\n\n' ] )
+ lastIncluded = file
+
+def generateSuite( output, suite ):
+ '''Write a suite declared with CXXTEST_SUITE()'''
+ output.write( 'class %s : public CxxTest::TestSuite {\n' % suite['name'] )
+ output.write( 'public:\n' )
+ for line in suite['lines']:
+ output.write(line)
+ output.write( '};\n\n' )
+
+def writeSuitePointer( output, suite ):
+ '''Create static suite pointer object for dynamic suites'''
+ if noStaticInit:
+ output.write( 'static %s *%s;\n\n' % (suite['name'], suite['object']) )
+ else:
+ output.write( 'static %s *%s = 0;\n\n' % (suite['name'], suite['object']) )
+
+def writeSuiteObject( output, suite ):
+ '''Create static suite object for non-dynamic suites'''
+ output.writelines( [ "static ", suite['name'], " ", suite['object'], ";\n\n" ] )
+
+def writeTestList( output, suite ):
+ '''Write the head of the test linked list for a suite'''
+ if noStaticInit:
+ output.write( 'static CxxTest::List %s;\n' % suite['tlist'] )
+ else:
+ output.write( 'static CxxTest::List %s = { 0, 0 };\n' % suite['tlist'] )
+
+def writeTestDescriptions( output, suite ):
+ '''Write all test descriptions for a suite'''
+ for test in suite['tests']:
+ writeTestDescription( output, suite, test )
+
+def writeTestDescription( output, suite, test ):
+ '''Write test description object'''
+ output.write( 'static class %s : public CxxTest::RealTestDescription {\n' % test['class'] )
+ output.write( 'public:\n' )
+ if not noStaticInit:
+ output.write( ' %s() : CxxTest::RealTestDescription( %s, %s, %s, "%s" ) {}\n' %
+ (test['class'], suite['tlist'], suite['dobject'], test['line'], test['name']) )
+ output.write( ' void runTest() { %s }\n' % runBody( suite, test ) )
+ output.write( '} %s;\n\n' % test['object'] )
+
+def runBody( suite, test ):
+ '''Body of TestDescription::run()'''
+ if isDynamic(suite): return dynamicRun( suite, test )
+ else: return staticRun( suite, test )
+
+def dynamicRun( suite, test ):
+ '''Body of TestDescription::run() for test in a dynamic suite'''
+ return 'if ( ' + suite['object'] + ' ) ' + suite['object'] + '->' + test['name'] + '();'
+
+def staticRun( suite, test ):
+ '''Body of TestDescription::run() for test in a non-dynamic suite'''
+ return suite['object'] + '.' + test['name'] + '();'
+
+def writeSuiteDescription( output, suite ):
+ '''Write SuiteDescription object'''
+ if isDynamic( suite ):
+ writeDynamicDescription( output, suite )
+ else:
+ writeStaticDescription( output, suite )
+
+def writeDynamicDescription( output, suite ):
+ '''Write SuiteDescription for a dynamic suite'''
+ output.write( 'CxxTest::DynamicSuiteDescription<%s> %s' % (suite['name'], suite['dobject']) )
+ if not noStaticInit:
+ output.write( '( %s, %s, "%s", %s, %s, %s, %s )' %
+ (suite['cfile'], suite['line'], suite['name'], suite['tlist'],
+ suite['object'], suite['create'], suite['destroy']) )
+ output.write( ';\n\n' )
+
+def writeStaticDescription( output, suite ):
+ '''Write SuiteDescription for a static suite'''
+ output.write( 'CxxTest::StaticSuiteDescription %s' % suite['dobject'] )
+ if not noStaticInit:
+ output.write( '( %s, %s, "%s", %s, %s )' %
+ (suite['cfile'], suite['line'], suite['name'], suite['object'], suite['tlist']) )
+ output.write( ';\n\n' )
+
+def writeRoot(output):
+ '''Write static members of CxxTest classes'''
+ output.write( '#include <cxxtest/Root.cpp>\n' )
+
+def writeInitialize(output):
+ '''Write CxxTest::initialize(), which replaces static initialization'''
+ output.write( 'namespace CxxTest {\n' )
+ output.write( ' void initialize()\n' )
+ output.write( ' {\n' )
+ for suite in suites:
+ output.write( ' %s.initialize();\n' % suite['tlist'] )
+ if isDynamic(suite):
+ output.write( ' %s = 0;\n' % suite['object'] )
+ output.write( ' %s.initialize( %s, %s, "%s", %s, %s, %s, %s );\n' %
+ (suite['dobject'], suite['cfile'], suite['line'], suite['name'],
+ suite['tlist'], suite['object'], suite['create'], suite['destroy']) )
+ else:
+ output.write( ' %s.initialize( %s, %s, "%s", %s, %s );\n' %
+ (suite['dobject'], suite['cfile'], suite['line'], suite['name'],
+ suite['object'], suite['tlist']) )
+
+ for test in suite['tests']:
+ output.write( ' %s.initialize( %s, %s, %s, "%s" );\n' %
+ (test['object'], suite['tlist'], suite['dobject'], test['line'], test['name']) )
+
+ output.write( ' }\n' )
+ output.write( '}\n' )
+
+main()