From: Yurii R. <yr...@us...> - 2003-02-02 06:59:15
|
Update of /cvsroot/eas-dev/eas-dev/tools/test In directory sc8-pr-cvs1:/tmp/cvs-serv30198/tools/test Added Files: cxxtestgen.py Log Message: modified build process; moving to glib-2.2.0; pkg-config; unstable changes to `logger' component; minor changes to libsxmlstream; binarystream test added to libsxmlstream (both text and binary streams are broken now); per-platform INSTALL notes --- NEW FILE: cxxtestgen.py --- #!/usr/bin/python '''Usage: %s [OPTIONS] <input file(s)> Generate test source file for CxxTest. -o, --output=NAME Write output to file NAME -r, --runner=CLASS Create a main() function that runs CxxTest::CLASS --error-printer Same as --runner=ErrorPrinter --exit-code The generated main() returns error code -t, --template=TEMPLATE Use TEMPLATE file to generate the test runner ''' import re import sys import getopt import glob import string # Global variables suites = [] suite = None inBlock = 0 numTotalTests = 0 outputFileName = None runner = None templateFileName = None haveExceptionHandling = 0 haveStandardLibrary = 0 exitCode = 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:', ['output=', 'runner=', 'error-printer', 'exit-code', 'template='] ) except getopt.error, problem: usage( problem ) setOptions( options ) return setFiles( patterns ) def setOptions( options ): '''Set options specified on command line''' global outputFileName, templateFileName, runner, haveStandardLibrary, exitCode for o, a in options: if o in ('-o', '--output'): outputFileName = a if o in ('t', '--template'): templateFileName = a if o in ('-r', '--runner'): runner = a if o == '--error-printer': runner = 'ErrorPrinter' haveStandardLibrary = 1 if o == '--exit-code': exitCode = 1 def setFiles( patterns ): '''Set input files specified on command line''' files = expandWildcards( patterns ) if len(files) is 0: 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: 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*::|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 if not haveStandardLibrary: haveStandardLibrary = std_re.search(line) is not None exception_re = re.compile( r"\b\(throw|try|catch|TS_ASSERT_THROWS|TS_ASSERT_THROWS_ANYTHING|TS_ASSERT_THROWS_NOTHING\)\b" ) def scanLineForExceptionHandling( line ): '''Check if current line uses exception handling''' global haveExceptionHandling if not haveExceptionHandling: haveExceptionHandling = exception_re.search(line) is not None 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*\)\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: 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, 'dclass' : 'SuiteDescription_%s' % name, 'dobject' : 'suiteDescription_%s' % name, 'testBase' : 'TestDescriptionBase_%s' % name, 'descriptions' : 'testDescriptions_%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(1), 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 global numTotalTests suites.append( suite ) numTotalTests = numTotalTests + len(suite['tests']) def writeOutput(): '''Create output file''' if templateFileName: writeTemplateOutput() else: writeSimpleOutput() def writeSimpleOutput(): '''Create output not based on template''' output = startOutputFile() writeHeader( output ) writeMain( output ) writeWorld( output ) output.close() include_re = re.compile( r"\s*\#\s*include\s+<cxxtest/" ) 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 ): writeHeader( output ) output.write( line ) 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 wroteHeader = 0 def writeHeader( output ): '''Write the CxxTest header (#includes and #defines)''' global wroteHeader if wroteHeader: return if haveStandardLibrary: output.write( "#ifndef CXXTEST_HAVE_STD\n" ) output.write( "#define CXXTEST_HAVE_STD\n" ) output.write( "#endif\n" ) if haveExceptionHandling: output.write( "#ifndef CXXTEST_HAVE_EH\n" ) output.write( "#define CXXTEST_HAVE_EH\n" ) output.write( "#endif\n" ) output.write( "#define CXXTEST_RUNNING\n" ) output.write( "\n" ) output.write( "#include <cxxtest/TestListener.h>\n" ) output.write( "#include <cxxtest/CountingTracker.h>\n" ) output.write( "#include <cxxtest/TestRunner.h>\n" ) if runner: output.write( "#include <cxxtest/%s.h>\n" % runner ) output.write( "\n" ) wroteHeader = 1 def writeMain( output ): '''Write the main() function for the test runner''' if runner: output.write( 'int main() {\n' ) if exitCode: output.write( ' return CxxTest::%s().run();\n' % runner ) else: output.write( ' CxxTest::%s().run();\n' % runner ) output.write( ' return 0;\n' ) output.write( '}\n\n' ) wroteWorld = 0 def writeWorld( output ): '''Write the world definitions''' global wroteWorld if wroteWorld: return if not wroteHeader: writeHeader( output ) writeSuites( output ) writeWorldDescription( output ) writeClassStatics( output ) wroteWorld = 1 def writeSuites(output): '''Write all TestDescription's and SuiteDescription's''' for suite in suites: writeInclude( output, suite['file'] ) if isGenerated(suite): generateSuite( output, suite ) if isDynamic(suite): writeSuitePointer( output, suite ) else: writeSuiteObject( output, suite ) writeTestDescriptionsBase( output, suite ) writeTestDescriptions( output, suite ) writeTestPointers( output, suite ) writeSuiteDescription( output, suite ) writeSuitePointers( output ) 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''' output.writelines( [ "static ", suite['name'], " *", suite['object'], " = 0;\n\n" ] ) def writeSuiteObject( output, suite ): '''Create static suite object for non-dynamic suites''' output.writelines( [ "static ", suite['name'], " ", suite['object'], ";\n\n" ] ) def writeTestDescriptionsBase( output, suite ): '''Write common base class for one the tests of one suite''' output.writelines( [ 'class ', suite['testBase'], ' : public CxxTest::TestDescription {\n', 'public:\n', ' const char *file() const { return ', suite['cfile'], '; }\n', ' const char *suiteName() const { return "', suite['name'], '"; }\n' '};\n\n' ] ) 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.writelines( [ 'static class ', test['class'], ' : public ', suite['testBase'], ' {\n', 'public:\n', ' unsigned line() const { return ', str(test['line']), '; }\n', ' const char *testName() const { return "', test['name'], '"; }\n', ' void run() const { ', runBody( suite, test ), ' }\n', '} ', test['object'], ';\n\n' ] ) 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 writeTestPointers( output, suite ): '''Write array of test descripion pointers for a suite''' output.writelines( [ 'static const CxxTest::TestDescription *', suite['descriptions'], '[] = {\n' ] ) for test in suite['tests']: output.writelines( [ ' { &', test['object'], ' },\n' ] ) output.write( ' { &CxxTest::TestDescription::_dummy }\n' ) output.write( '};\n\n' ) def writeSuiteDescription( output, suite ): '''Write SuiteDescription object''' output.writelines( [ 'static class %s : public CxxTest::SuiteDescription {\n' % suite['dclass'], 'public:\n', ' const char *file() const { return %s; }\n' % suite['cfile'], ' unsigned line() const { return %s; }\n' % suite['line'], ' const char *suiteName() const { return "%s"; }\n' % suite['name'], ' unsigned numTests() const { return %s; }\n' % len(suite['tests']), ' CxxTest::TestSuite *suite() const { return %s; }\n' % suiteObjectPointer( suite ), ' const CxxTest::TestDescription &testDescription( unsigned i ) const ', '{ return *(%s[i]); }\n' % suite['descriptions'], '} %s;\n\n' % suite['dobject'] ] ) def suiteObjectPointer( suite ): '''Return a C expression which points to the suite object''' if isDynamic(suite): return suite['object'] else: return '&' + suite['object'] def writeSuitePointers( output ): '''Write array of suite descripion pointers''' output.write( 'static const CxxTest::SuiteDescription *suiteDescriptions_TheWorld[] = {\n' ) for suite in suites: output.write( ' { &%s },\n' % suite['dobject'] ) output.write( ' { &CxxTest::SuiteDescription::_dummy }\n' ) output.write( '};\n\n' ) def writeWorldDescription(output): '''Write WorldDescription object''' output.writelines( [ 'static class WorldDescription_TheWorld : public CxxTest::WorldDescription {\n', 'public:\n', ' unsigned numSuites() const { return %s; }\n' % len(suites), ' unsigned numTotalTests() const { return %s; }\n' % numTotalTests, ' const CxxTest::SuiteDescription &suiteDescription( unsigned i ) const\n', ' { return *(suiteDescriptions_TheWorld[i]); }\n', 'protected:\n' ] ) writeWorldSetUp( output ) writeWorldTearDown( output ) output.write( '} worldDescription_theWorld;\n\n' ) def writeWorldSetUp( output ): '''Write WorldDescription::setUp()''' output.write( ' void setUp() const {\n' ) for suite in suites: if isDynamic(suite): writeCreateSuite(output, suite) output.write( ' }\n' ) def writeCreateSuite( output, suite ): '''Write line in WorldDescription::setUp() to create a suite''' output.writelines( [ ' __TS_ASSERT_THROWS_NOTHING( %s, %s,\n' % (suite['cfile'], suite['create']), ' %s = %s::createSuite(),\n' % (suite['object'], suite['name']), ' "%s::createSuite()" );\n' % (suite['name']), ' __TS_ASSERT( %s, %s, %s != 0,\n' % (suite['cfile'], suite['create'], suite['object']), ' "%s::createSuite() != 0" );\n' % suite['name'], ] ) def writeWorldTearDown( output ): '''Write WorldDescription::tearDown()''' output.write( ' void tearDown() const {\n' ) for suite in suites: if isDynamic(suite): writeDestroySuite(output, suite) output.write( ' }\n' ) def writeDestroySuite( output, suite ): '''Write line in WorldDescription::tearDown() to destroy a suite''' output.writelines( [ ' if ( ', suite['object'], ' )\n', ' __TS_ASSERT_THROWS_NOTHING( %s, %s,\n' % (suite['cfile'], suite['destroy']), ' %s::destroySuite( %s ),\n' % (suite['name'], suite['object']), ' "%s::destroySuite()" );\n' % (suite['name']), ] ) def writeClassStatics(output): '''Write static members of CxxTest classes''' output.writelines( [ 'CxxTest::TestListener CxxTest::TestListener::_dummy;\n', 'CxxTest::TestTracker CxxTest::TestTracker::_dummy;\n', 'const CxxTest::WorldDescription CxxTest::WorldDescription::_dummy;\n', 'const CxxTest::SuiteDescription CxxTest::SuiteDescription::_dummy;\n', 'const CxxTest::TestDescription CxxTest::TestDescription::_dummy;\n', 'CxxTest::TestTracker *CxxTest::TestTracker::_tracker = &CxxTest::TestTracker::_dummy;\n', 'CxxTest::TestListener *CxxTest::TestListener::_listener = &CxxTest::TestListener::_dummy;\n', '\n', 'CxxTest::CountingTracker CxxTest::TestRunner::_counter;\n', 'const CxxTest::WorldDescription &CxxTest::TestRunner::_world = worldDescription_theWorld;\n\n', ] ) main() |