Code

Merge from trunk.
[inkscape.git] / cxxtest / cxxtestgen.py
1 #!/usr/bin/python
2 '''Usage: %s [OPTIONS] <input file(s)>
3 Generate test source file for CxxTest.
5   -v, --version          Write CxxTest version
6   -o, --output=NAME      Write output to file NAME
7   --runner=CLASS         Create a main() function that runs CxxTest::CLASS
8   --gui=CLASS            Like --runner, with GUI component
9   --error-printer        Same as --runner=ErrorPrinter
10   --abort-on-fail        Abort tests on failed asserts (like xUnit)
11   --have-std             Use standard library (even if not found in tests)
12   --no-std               Don\'t use standard library (even if found in tests)
13   --have-eh              Use exception handling (even if not found in tests)
14   --no-eh                Don\'t use exception handling (even if found in tests)
15   --longlong=[TYPE]      Use TYPE (default: long long) as long long
16   --template=TEMPLATE    Use TEMPLATE file to generate the test runner
17   --include=HEADER       Include HEADER in test runner before other headers
18   --root                 Write CxxTest globals
19   --part                 Don\'t write CxxTest globals
20   --no-static-init       Don\'t rely on static initialization
21 '''
23 import re
24 import sys
25 import getopt
26 import glob
27 import string
29 # Global variables
30 suites = []
31 suite = None
32 inBlock = 0
34 outputFileName = None
35 runner = None
36 gui = None
37 root = None
38 part = None
39 noStaticInit = None
40 templateFileName = None
41 headers = []
43 haveExceptionHandling = 0
44 noExceptionHandling = 0
45 haveStandardLibrary = 0
46 noStandardLibrary = 0
47 abortOnFail = 0
48 factor = 0
49 longlong = 0
51 def main():
52     '''The main program'''
53     files = parseCommandline()
54     scanInputFiles( files )
55     writeOutput()
57 def usage( problem = None ):
58     '''Print usage info and exit'''
59     if problem is None:
60         print usageString()
61         sys.exit(0)
62     else:
63         sys.stderr.write( usageString() )
64         abort( problem )
66 def usageString():
67     '''Construct program usage string'''
68     return __doc__ % sys.argv[0]
70 def abort( problem ):
71     '''Print error message and exit'''
72     sys.stderr.write( '\n' )
73     sys.stderr.write( problem )
74     sys.stderr.write( '\n\n' )
75     sys.exit(2)
77 def parseCommandline():
78     '''Analyze command line arguments'''
79     try:
80         options, patterns = getopt.getopt( sys.argv[1:], 'o:r:',
81                                            ['version', 'output=', 'runner=', 'gui=',
82                                             'error-printer', 'abort-on-fail', 'have-std', 'no-std',
83                                             'have-eh', 'no-eh', 'template=', 'include=',
84                                             'root', 'part', 'no-static-init', 'factor', 'longlong='] )
85     except getopt.error, problem:
86         usage( problem )
87     setOptions( options )
88     return setFiles( patterns )
90 def setOptions( options ):
91     '''Set options specified on command line'''
92     global outputFileName, templateFileName, runner, gui, haveStandardLibrary, factor, longlong
93     global haveExceptionHandling, noExceptionHandling, abortOnFail, headers, root, part, noStaticInit
94     for o, a in options:
95         if o in ('-v', '--version'):
96             printVersion()
97         elif o in ('-o', '--output'):
98             outputFileName = a
99         elif o == '--template':
100             templateFileName = a
101         elif o == '--runner':
102             runner = a
103         elif o == '--gui':
104             gui = a
105         elif o == '--include':
106             if not re.match( r'^["<].*[>"]$', a ):
107                 a = ('"%s"' % a)
108             headers.append( a )
109         elif o == '--error-printer':
110             runner = 'ErrorPrinter'
111             haveStandardLibrary = 1
112         elif o == '--abort-on-fail':
113             abortOnFail = 1
114         elif o == '--have-std':
115             haveStandardLibrary = 1
116         elif o == '--no-std':
117             noStandardLibrary = 1
118         elif o == '--have-eh':
119             haveExceptionHandling = 1
120         elif o == '--no-eh':
121             noExceptionHandling = 1
122         elif o == '--root':
123             root = 1
124         elif o == '--part':
125             part = 1
126         elif o == '--no-static-init':
127             noStaticInit = 1
128         elif o == '--factor':
129             factor = 1
130         elif o == '--longlong':
131             if a:
132                 longlong = a
133             else:
134                 longlong = 'long long'
136     if noStaticInit and (root or part):
137         abort( '--no-static-init cannot be used with --root/--part' )
139     if gui and not runner:
140         runner = 'StdioPrinter'
142 def printVersion():
143     '''Print CxxTest version and exit'''
144     sys.stdout.write( "This is CxxTest version 3.10.1.\n" )
145     sys.exit(0)
147 def setFiles( patterns ):
148     '''Set input files specified on command line'''
149     files = expandWildcards( patterns )
150     if len(files) is 0 and not root:
151         usage( "No input files found" )
152     return files
154 def expandWildcards( patterns ):
155     '''Expand all wildcards in an array (glob)'''
156     fileNames = []
157     for pathName in patterns:
158         patternFiles = glob.glob( pathName )
159         for fileName in patternFiles:
160             fileNames.append( fixBackslashes( fileName ) )
161     return fileNames
163 def fixBackslashes( fileName ):
164     '''Convert backslashes to slashes in file name'''
165     return re.sub( r'\\', '/', fileName, 0 )
167 def scanInputFiles(files):
168     '''Scan all input files for test suites'''
169     for file in files:
170         scanInputFile(file)
171     global suites
172     if len(suites) is 0 and not root:
173         abort( 'No tests defined' )
175 def scanInputFile(fileName):
176     '''Scan single input file for test suites'''
177     file = open(fileName)
178     lineNo = 0
179     while 1:
180         line = file.readline()
181         if not line:
182             break
183         lineNo = lineNo + 1
185         scanInputLine( fileName, lineNo, line )
186     closeSuite()
187     file.close()
189 def scanInputLine( fileName, lineNo, line ):
190     '''Scan single input line for interesting stuff'''
191     scanLineForExceptionHandling( line )
192     scanLineForStandardLibrary( line )
194     scanLineForSuiteStart( fileName, lineNo, line )
196     global suite
197     if suite:
198         scanLineInsideSuite( suite, lineNo, line )
200 def scanLineInsideSuite( suite, lineNo, line ):
201     '''Analyze line which is part of a suite'''
202     global inBlock
203     if lineBelongsToSuite( suite, lineNo, line ):
204         scanLineForTest( suite, lineNo, line )
205         scanLineForCreate( suite, lineNo, line )
206         scanLineForDestroy( suite, lineNo, line )
208 def lineBelongsToSuite( suite, lineNo, line ):
209     '''Returns whether current line is part of the current suite.
210     This can be false when we are in a generated suite outside of CXXTEST_CODE() blocks
211     If the suite is generated, adds the line to the list of lines'''
212     if not suite['generated']:
213         return 1
215     global inBlock
216     if not inBlock:
217         inBlock = lineStartsBlock( line )
218     if inBlock:
219         inBlock = addLineToBlock( suite, lineNo, line )
220     return inBlock
223 std_re = re.compile( r"\b(std\s*::|CXXTEST_STD|using\s+namespace\s+std\b|^\s*\#\s*include\s+<[a-z0-9]+>)" )
224 def scanLineForStandardLibrary( line ):
225     '''Check if current line uses standard library'''
226     global haveStandardLibrary, noStandardLibrary
227     if not haveStandardLibrary and std_re.search(line):
228         if not noStandardLibrary:
229             haveStandardLibrary = 1
231 exception_re = re.compile( r"\b(throw|try|catch|TSM?_ASSERT_THROWS[A-Z_]*)\b" )
232 def scanLineForExceptionHandling( line ):
233     '''Check if current line uses exception handling'''
234     global haveExceptionHandling, noExceptionHandling
235     if not haveExceptionHandling and exception_re.search(line):
236         if not noExceptionHandling:
237             haveExceptionHandling = 1
239 suite_re = re.compile( r'\bclass\s+(\w+)\s*:\s*public\s+((::)?\s*CxxTest\s*::\s*)?TestSuite\b' )
240 generatedSuite_re = re.compile( r'\bCXXTEST_SUITE\s*\(\s*(\w*)\s*\)' )
241 def scanLineForSuiteStart( fileName, lineNo, line ):
242     '''Check if current line starts a new test suite'''
243     m = suite_re.search( line )
244     if m:
245         startSuite( m.group(1), fileName, lineNo, 0 )
246     m = generatedSuite_re.search( line )
247     if m:
248         sys.stdout.write( "%s:%s: Warning: Inline test suites are deprecated.\n" % (fileName, lineNo) )
249         startSuite( m.group(1), fileName, lineNo, 1 )
251 def startSuite( name, file, line, generated ):
252     '''Start scanning a new suite'''
253     global suite
254     closeSuite()
255     suite = { 'name'         : name,
256               'file'         : file,
257               'cfile'        : cstr(file),
258               'line'         : line,
259               'generated'    : generated,
260               'object'       : 'suite_%s' % name,
261               'dobject'      : 'suiteDescription_%s' % name,
262               'tlist'        : 'Tests_%s' % name,
263               'tests'        : [],
264               'lines'        : [] }
266 def lineStartsBlock( line ):
267     '''Check if current line starts a new CXXTEST_CODE() block'''
268     return re.search( r'\bCXXTEST_CODE\s*\(', line ) is not None
270 test_re = re.compile( r'^([^/]|/[^/])*\bvoid\s+([Tt]est\w+)\s*\(\s*(void)?\s*\)' )
271 def scanLineForTest( suite, lineNo, line ):
272     '''Check if current line starts a test'''
273     m = test_re.search( line )
274     if m:
275         addTest( suite, m.group(2), lineNo )
277 def addTest( suite, name, line ):
278     '''Add a test function to the current suite'''
279     test = { 'name'   : name,
280              'suite'  : suite,
281              'class'  : 'TestDescription_%s_%s' % (suite['name'], name),
282              'object' : 'testDescription_%s_%s' % (suite['name'], name),
283              'line'   : line,
284              }
285     suite['tests'].append( test )
287 def addLineToBlock( suite, lineNo, line ):
288     '''Append the line to the current CXXTEST_CODE() block'''
289     line = fixBlockLine( suite, lineNo, line )
290     line = re.sub( r'^.*\{\{', '', line )
291     
292     e = re.search( r'\}\}', line )
293     if e:
294         line = line[:e.start()]
295     suite['lines'].append( line )
296     return e is None
298 def fixBlockLine( suite, lineNo, line):
299     '''Change all [E]TS_ macros used in a line to _[E]TS_ macros with the correct file/line'''
300     return re.sub( r'\b(E?TSM?_(ASSERT[A-Z_]*|FAIL))\s*\(',
301                    r'_\1(%s,%s,' % (suite['cfile'], lineNo),
302                    line, 0 )
304 create_re = re.compile( r'\bstatic\s+\w+\s*\*\s*createSuite\s*\(\s*(void)?\s*\)' )
305 def scanLineForCreate( suite, lineNo, line ):
306     '''Check if current line defines a createSuite() function'''
307     if create_re.search( line ):
308         addSuiteCreateDestroy( suite, 'create', lineNo )
310 destroy_re = re.compile( r'\bstatic\s+void\s+destroySuite\s*\(\s*\w+\s*\*\s*\w*\s*\)' )
311 def scanLineForDestroy( suite, lineNo, line ):
312     '''Check if current line defines a destroySuite() function'''
313     if destroy_re.search( line ):
314         addSuiteCreateDestroy( suite, 'destroy', lineNo )
316 def cstr( str ):
317     '''Convert a string to its C representation'''
318     return '"' + string.replace( str, '\\', '\\\\' ) + '"'
321 def addSuiteCreateDestroy( suite, which, line ):
322     '''Add createSuite()/destroySuite() to current suite'''
323     if suite.has_key(which):
324         abort( '%s:%s: %sSuite() already declared' % ( suite['file'], str(line), which ) )
325     suite[which] = line
327 def closeSuite():
328     '''Close current suite and add it to the list if valid'''
329     global suite
330     if suite is not None:
331         if len(suite['tests']) is not 0:
332             verifySuite(suite)
333             rememberSuite(suite)
334         suite = None
336 def verifySuite(suite):
337     '''Verify current suite is legal'''
338     if suite.has_key('create') and not suite.has_key('destroy'):
339         abort( '%s:%s: Suite %s has createSuite() but no destroySuite()' %
340                (suite['file'], suite['create'], suite['name']) )
341     if suite.has_key('destroy') and not suite.has_key('create'):
342         abort( '%s:%s: Suite %s has destroySuite() but no createSuite()' %
343                (suite['file'], suite['destroy'], suite['name']) )
345 def rememberSuite(suite):
346     '''Add current suite to list'''
347     global suites
348     suites.append( suite )
350 def writeOutput():
351     '''Create output file'''
352     if templateFileName:
353         writeTemplateOutput()
354     else:
355         writeSimpleOutput()
357 def writeSimpleOutput():
358     '''Create output not based on template'''
359     output = startOutputFile()
360     writePreamble( output )
361     writeMain( output )
362     writeWorld( output )
363     output.close()
365 include_re = re.compile( r"\s*\#\s*include\s+<cxxtest/" )
366 preamble_re = re.compile( r"^\s*<CxxTest\s+preamble>\s*$" )
367 world_re = re.compile( r"^\s*<CxxTest\s+world>\s*$" )
368 def writeTemplateOutput():
369     '''Create output based on template file'''
370     template = open(templateFileName)
371     output = startOutputFile()
372     while 1:
373         line = template.readline()
374         if not line:
375             break;
376         if include_re.search( line ):
377             writePreamble( output )
378             output.write( line )
379         elif preamble_re.search( line ):
380             writePreamble( output )
381         elif world_re.search( line ):
382             writeWorld( output )
383         else:
384             output.write( line )
385     template.close()
386     output.close()
388 def startOutputFile():
389     '''Create output file and write header'''
390     if outputFileName is not None:
391         output = open( outputFileName, 'w' )
392     else:
393         output = sys.stdout
394     output.write( "/* Generated file, do not edit */\n\n" )
395     return output
397 wrotePreamble = 0
398 def writePreamble( output ):
399     '''Write the CxxTest header (#includes and #defines)'''
400     global wrotePreamble, headers, longlong
401     if wrotePreamble: return
402     output.write( "#ifndef CXXTEST_RUNNING\n" )
403     output.write( "#define CXXTEST_RUNNING\n" )
404     output.write( "#endif\n" )
405     output.write( "\n" )
406     if haveStandardLibrary:
407         output.write( "#define _CXXTEST_HAVE_STD\n" )
408     if haveExceptionHandling:
409         output.write( "#define _CXXTEST_HAVE_EH\n" )
410     if abortOnFail:
411         output.write( "#define _CXXTEST_ABORT_TEST_ON_FAIL\n" )
412     if longlong:
413         output.write( "#define _CXXTEST_LONGLONG %s\n" % longlong )
414     if factor:
415         output.write( "#define _CXXTEST_FACTOR\n" )
416     for header in headers:
417         output.write( "#include %s\n" % header )
418     output.write( "#include <cxxtest/TestListener.h>\n" )
419     output.write( "#include <cxxtest/TestTracker.h>\n" )
420     output.write( "#include <cxxtest/TestRunner.h>\n" )
421     output.write( "#include <cxxtest/RealDescriptions.h>\n" )
422     if runner:
423         output.write( "#include <cxxtest/%s.h>\n" % runner )
424     if gui:
425         output.write( "#include <cxxtest/%s.h>\n" % gui )
426     output.write( "\n" )
427     wrotePreamble = 1
429 def writeMain( output ):
430     '''Write the main() function for the test runner'''
431     if gui:
432         output.write( 'int main( int argc, char *argv[] ) {\n' )
433         if noStaticInit:
434             output.write( ' CxxTest::initialize();\n' )
435         output.write( ' return CxxTest::GuiTuiRunner<CxxTest::%s, CxxTest::%s>( argc, argv ).run();\n' % (gui, runner) )
436         output.write( '}\n' )
437     elif runner:
438         output.write( 'int main() {\n' )
439         if noStaticInit:
440             output.write( ' CxxTest::initialize();\n' )
441         output.write( ' return CxxTest::%s().run();\n' % runner )
442         output.write( '}\n' )
444 wroteWorld = 0
445 def writeWorld( output ):
446     '''Write the world definitions'''
447     global wroteWorld, part
448     if wroteWorld: return
449     writePreamble( output )
450     writeSuites( output )
451     if root or not part:
452         writeRoot( output )
453     if noStaticInit:
454         writeInitialize( output )
455     wroteWorld = 1
457 def writeSuites(output):
458     '''Write all TestDescriptions and SuiteDescriptions'''
459     for suite in suites:
460         writeInclude( output, suite['file'] )
461         if isGenerated(suite):
462             generateSuite( output, suite )
463         if isDynamic(suite):
464             writeSuitePointer( output, suite )
465         else:
466             writeSuiteObject( output, suite )
467         writeTestList( output, suite )
468         writeSuiteDescription( output, suite )
469         writeTestDescriptions( output, suite )
471 def isGenerated(suite):
472     '''Checks whether a suite class should be created'''
473     return suite['generated']
475 def isDynamic(suite):
476     '''Checks whether a suite is dynamic'''
477     return suite.has_key('create')
479 lastIncluded = ''
480 def writeInclude(output, file):
481     '''Add #include "file" statement'''
482     global lastIncluded
483     if file == lastIncluded: return
484     output.writelines( [ '#include "', file, '"\n\n' ] )
485     lastIncluded = file
487 def generateSuite( output, suite ):
488     '''Write a suite declared with CXXTEST_SUITE()'''
489     output.write( 'class %s : public CxxTest::TestSuite {\n' % suite['name'] )
490     output.write( 'public:\n' )
491     for line in suite['lines']:
492         output.write(line)
493     output.write( '};\n\n' )
495 def writeSuitePointer( output, suite ):
496     '''Create static suite pointer object for dynamic suites'''
497     if noStaticInit:
498         output.write( 'static %s *%s;\n\n' % (suite['name'], suite['object']) )
499     else:
500         output.write( 'static %s *%s = 0;\n\n' % (suite['name'], suite['object']) )
502 def writeSuiteObject( output, suite ):
503     '''Create static suite object for non-dynamic suites'''
504     output.writelines( [ "static ", suite['name'], " ", suite['object'], ";\n\n" ] )
506 def writeTestList( output, suite ):
507     '''Write the head of the test linked list for a suite'''
508     if noStaticInit:
509         output.write( 'static CxxTest::List %s;\n' % suite['tlist'] )
510     else:
511         output.write( 'static CxxTest::List %s = { 0, 0 };\n' % suite['tlist'] )
513 def writeTestDescriptions( output, suite ):
514     '''Write all test descriptions for a suite'''
515     for test in suite['tests']:
516         writeTestDescription( output, suite, test )
518 def writeTestDescription( output, suite, test ):
519     '''Write test description object'''
520     output.write( 'static class %s : public CxxTest::RealTestDescription {\n' % test['class'] )
521     output.write( 'public:\n' )
522     if not noStaticInit:
523         output.write( ' %s() : CxxTest::RealTestDescription( %s, %s, %s, "%s" ) {}\n' %
524                       (test['class'], suite['tlist'], suite['dobject'], test['line'], test['name']) )
525     output.write( ' void runTest() { %s }\n' % runBody( suite, test ) )
526     output.write( '} %s;\n\n' % test['object'] )
528 def runBody( suite, test ):
529     '''Body of TestDescription::run()'''
530     if isDynamic(suite): return dynamicRun( suite, test )
531     else: return staticRun( suite, test )
533 def dynamicRun( suite, test ):
534     '''Body of TestDescription::run() for test in a dynamic suite'''
535     return 'if ( ' + suite['object'] + ' ) ' + suite['object'] + '->' + test['name'] + '();'
536     
537 def staticRun( suite, test ):
538     '''Body of TestDescription::run() for test in a non-dynamic suite'''
539     return suite['object'] + '.' + test['name'] + '();'
540     
541 def writeSuiteDescription( output, suite ):
542     '''Write SuiteDescription object'''
543     if isDynamic( suite ):
544         writeDynamicDescription( output, suite )
545     else:
546         writeStaticDescription( output, suite )
548 def writeDynamicDescription( output, suite ):
549     '''Write SuiteDescription for a dynamic suite'''
550     output.write( 'CxxTest::DynamicSuiteDescription<%s> %s' % (suite['name'], suite['dobject']) )
551     if not noStaticInit:
552         output.write( '( %s, %s, "%s", %s, %s, %s, %s )' %
553                       (suite['cfile'], suite['line'], suite['name'], suite['tlist'],
554                        suite['object'], suite['create'], suite['destroy']) )
555     output.write( ';\n\n' )
557 def writeStaticDescription( output, suite ):
558     '''Write SuiteDescription for a static suite'''
559     output.write( 'CxxTest::StaticSuiteDescription %s' % suite['dobject'] )
560     if not noStaticInit:
561         output.write( '( %s, %s, "%s", %s, %s )' %
562                       (suite['cfile'], suite['line'], suite['name'], suite['object'], suite['tlist']) )
563     output.write( ';\n\n' )
565 def writeRoot(output):
566     '''Write static members of CxxTest classes'''
567     output.write( '#include <cxxtest/Root.cpp>\n' )
569 def writeInitialize(output):
570     '''Write CxxTest::initialize(), which replaces static initialization'''
571     output.write( 'namespace CxxTest {\n' )
572     output.write( ' void initialize()\n' )
573     output.write( ' {\n' )
574     for suite in suites:
575         output.write( '  %s.initialize();\n' % suite['tlist'] )
576         if isDynamic(suite):
577             output.write( '  %s = 0;\n' % suite['object'] )
578             output.write( '  %s.initialize( %s, %s, "%s", %s, %s, %s, %s );\n' %
579                           (suite['dobject'], suite['cfile'], suite['line'], suite['name'],
580                            suite['tlist'], suite['object'], suite['create'], suite['destroy']) )
581         else:
582             output.write( '  %s.initialize( %s, %s, "%s", %s, %s );\n' %
583                           (suite['dobject'], suite['cfile'], suite['line'], suite['name'],
584                            suite['object'], suite['tlist']) )
586         for test in suite['tests']:
587             output.write( '  %s.initialize( %s, %s, %s, "%s" );\n' %
588                           (test['object'], suite['tlist'], suite['dobject'], test['line'], test['name']) )
590     output.write( ' }\n' )
591     output.write( '}\n' )
593 main()