|
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()
|