From 3820593bb8dfdca4e2e17c70b278065b7f7894d9 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Wed, 29 Mar 2006 10:25:48 +0000 Subject: bringing cxxtest-3.10.1 to ScummVM's main branch svn-id: r21488 --- test/cxxtest/cxxtestgen.py | 593 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 593 insertions(+) create mode 100755 test/cxxtest/cxxtestgen.py (limited to 'test/cxxtest/cxxtestgen.py') 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] +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+\s*$" ) +world_re = re.compile( r"^\s*\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 \n" ) + output.write( "#include \n" ) + output.write( "#include \n" ) + output.write( "#include \n" ) + if runner: + output.write( "#include \n" % runner ) + if gui: + output.write( "#include \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( 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 \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() -- cgit v1.2.3