1#------------------------------------------------------------------------- 2# CxxTest: A lightweight C++ unit testing library. 3# Copyright (c) 2008 Sandia Corporation. 4# This software is distributed under the LGPL License v2.1 5# For more information, see the COPYING file in the top CxxTest directory. 6# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, 7# the U.S. Government retains certain rights in this software. 8#------------------------------------------------------------------------- 9 10# vim: fileencoding=utf-8 11 12from __future__ import division 13# the above import important for forward-compatibility with python3, 14# which is already the default in archlinux! 15 16__all__ = ['main', 'create_manpage'] 17 18import __release__ 19import os 20import sys 21import re 22import glob 23from optparse import OptionParser 24import cxxtest_parser 25from string import Template 26 27try: 28 import cxxtest_fog 29 imported_fog=True 30except ImportError: 31 imported_fog=False 32 33from cxxtest_misc import abort 34 35options = [] 36suites = [] 37 38wrotePreamble = 0 39wroteWorld = 0 40lastIncluded = '' 41 42def main(args=sys.argv): 43 '''The main program''' 44 # 45 # Reset global state 46 # 47 global wrotePreamble 48 wrotePreamble=0 49 global wroteWorld 50 wroteWorld=0 51 global lastIncluded 52 lastIncluded = '' 53 54 global suites 55 global options 56 files = parseCommandline(args) 57 if imported_fog and options.fog: 58 [options,suites] = cxxtest_fog.scanInputFiles( files, options ) 59 else: 60 [options,suites] = cxxtest_parser.scanInputFiles( files, options ) 61 writeOutput() 62 63def create_parser(asciidoc=False): 64 parser = OptionParser("cxxtestgen [options] [<filename> ...]") 65 if asciidoc: 66 parser.description="The cxxtestgen command processes C++ header files to perform test discovery, and then it creates files for the CxxTest test runner." 67 else: 68 parser.description="The 'cxxtestgen' command processes C++ header files to perform test discovery, and then it creates files for the 'CxxTest' test runner." 69 70 parser.add_option("--version", 71 action="store_true", dest="version", default=False, 72 help="Write the CxxTest version.") 73 parser.add_option("-o", "--output", 74 dest="outputFileName", default=None, metavar="NAME", 75 help="Write output to file NAME.") 76 parser.add_option("-w","--world", dest="world", default="cxxtest", 77 help="The label of the tests, used to name the XML results.") 78 parser.add_option("", "--include", action="append", 79 dest="headers", default=[], metavar="HEADER", 80 help="Include file HEADER in the test runner before other headers.") 81 parser.add_option("", "--abort-on-fail", 82 action="store_true", dest="abortOnFail", default=False, 83 help="Abort tests on failed asserts (like xUnit).") 84 parser.add_option("", "--main", 85 action="store", dest="main", default="main", 86 help="Specify an alternative name for the main() function.") 87 parser.add_option("", "--headers", 88 action="store", dest="header_filename", default=None, 89 help="Specify a filename that contains a list of header files that are processed to generate a test runner.") 90 parser.add_option("", "--runner", 91 dest="runner", default="", metavar="CLASS", 92 help="Create a test runner that processes test events using the class CxxTest::CLASS.") 93 parser.add_option("", "--gui", 94 dest="gui", metavar="CLASS", 95 help="Create a GUI test runner that processes test events using the class CxxTest::CLASS. (deprecated)") 96 parser.add_option("", "--error-printer", 97 action="store_true", dest="error_printer", default=False, 98 help="Create a test runner using the ErrorPrinter class, and allow the use of the standard library.") 99 parser.add_option("", "--xunit-printer", 100 action="store_true", dest="xunit_printer", default=False, 101 help="Create a test runner using the XUnitPrinter class.") 102 parser.add_option("", "--xunit-file", dest="xunit_file", default="", 103 help="The file to which the XML summary is written for test runners using the XUnitPrinter class. The default XML filename is TEST-<world>.xml, where <world> is the value of the --world option. (default: cxxtest)") 104 parser.add_option("", "--have-std", 105 action="store_true", dest="haveStandardLibrary", default=False, 106 help="Use the standard library (even if not found in tests).") 107 parser.add_option("", "--no-std", 108 action="store_true", dest="noStandardLibrary", default=False, 109 help="Do not use standard library (even if found in tests).") 110 parser.add_option("", "--have-eh", 111 action="store_true", dest="haveExceptionHandling", default=False, 112 help="Use exception handling (even if not found in tests).") 113 parser.add_option("", "--no-eh", 114 action="store_true", dest="noExceptionHandling", default=False, 115 help="Do not use exception handling (even if found in tests).") 116 parser.add_option("", "--longlong", 117 dest="longlong", default=None, metavar="TYPE", 118 help="Use TYPE as for long long integers. (default: not supported)") 119 parser.add_option("", "--no-static-init", 120 action="store_true", dest="noStaticInit", default=False, 121 help="Do not rely on static initialization in the test runner.") 122 parser.add_option("", "--template", 123 dest="templateFileName", default=None, metavar="TEMPLATE", 124 help="Generate the test runner using file TEMPLATE to define a template.") 125 parser.add_option("", "--root", 126 action="store_true", dest="root", default=False, 127 help="Write the main() function and global data for a test runner.") 128 parser.add_option("", "--part", 129 action="store_true", dest="part", default=False, 130 help="Write the tester classes for a test runner.") 131 #parser.add_option("", "--factor", 132 #action="store_true", dest="factor", default=False, 133 #help="Declare the _CXXTEST_FACTOR macro. (deprecated)") 134 if imported_fog: 135 fog_help = "Use new FOG C++ parser" 136 else: 137 fog_help = "Use new FOG C++ parser (disabled)" 138 parser.add_option("-f", "--fog-parser", 139 action="store_true", 140 dest="fog", 141 default=False, 142 help=fog_help 143 ) 144 return parser 145 146def parseCommandline(args): 147 '''Analyze command line arguments''' 148 global imported_fog 149 global options 150 151 parser = create_parser() 152 (options, args) = parser.parse_args(args=args) 153 if not options.header_filename is None: 154 if not os.path.exists(options.header_filename): 155 abort( "ERROR: the file '%s' does not exist!" % options.header_filename ) 156 INPUT = open(options.header_filename) 157 headers = [line.strip() for line in INPUT] 158 args.extend( headers ) 159 INPUT.close() 160 161 if options.fog and not imported_fog: 162 abort( "Cannot use the FOG parser. Check that the 'ply' package is installed. The 'ordereddict' package is also required if running Python 2.6") 163 164 if options.version: 165 printVersion() 166 167 # the cxxtest builder relies on this behaviour! don't remove 168 if options.runner == 'none': 169 options.runner = None 170 171 if options.xunit_printer or options.runner == "XUnitPrinter": 172 options.xunit_printer=True 173 options.runner="XUnitPrinter" 174 if len(args) > 1: 175 if options.xunit_file == "": 176 if options.world == "": 177 options.world = "cxxtest" 178 options.xunit_file="TEST-"+options.world+".xml" 179 elif options.xunit_file == "": 180 if options.world == "": 181 options.world = "cxxtest" 182 options.xunit_file="TEST-"+options.world+".xml" 183 184 if options.error_printer: 185 options.runner= "ErrorPrinter" 186 options.haveStandardLibrary = True 187 188 if options.noStaticInit and (options.root or options.part): 189 abort( '--no-static-init cannot be used with --root/--part' ) 190 191 if options.gui and not options.runner: 192 options.runner = 'StdioPrinter' 193 194 files = setFiles(args[1:]) 195 if len(files) == 0 and not options.root: 196 sys.stderr.write(parser.error("No input files found")) 197 198 return files 199 200 201def printVersion(): 202 '''Print CxxTest version and exit''' 203 sys.stdout.write( "This is CxxTest version %s.\n" % __release__.__version__ ) 204 sys.exit(0) 205 206def setFiles(patterns ): 207 '''Set input files specified on command line''' 208 files = expandWildcards( patterns ) 209 return files 210 211def expandWildcards( patterns ): 212 '''Expand all wildcards in an array (glob)''' 213 fileNames = [] 214 for pathName in patterns: 215 patternFiles = glob.glob( pathName ) 216 for fileName in patternFiles: 217 fileNames.append( fixBackslashes( fileName ) ) 218 return fileNames 219 220def fixBackslashes( fileName ): 221 '''Convert backslashes to slashes in file name''' 222 return re.sub( r'\\', '/', fileName, 0 ) 223 224 225def writeOutput(): 226 '''Create output file''' 227 if options.templateFileName: 228 writeTemplateOutput() 229 else: 230 writeSimpleOutput() 231 232def writeSimpleOutput(): 233 '''Create output not based on template''' 234 output = startOutputFile() 235 writePreamble( output ) 236 if options.root or not options.part: 237 writeMain( output ) 238 239 if len(suites) > 0: 240 output.write("bool "+suites[0]['object']+"_init = false;\n") 241 242 writeWorld( output ) 243 output.close() 244 245include_re = re.compile( r"\s*\#\s*include\s+<cxxtest/" ) 246preamble_re = re.compile( r"^\s*<CxxTest\s+preamble>\s*$" ) 247world_re = re.compile( r"^\s*<CxxTest\s+world>\s*$" ) 248def writeTemplateOutput(): 249 '''Create output based on template file''' 250 template = open(options.templateFileName) 251 output = startOutputFile() 252 while 1: 253 line = template.readline() 254 if not line: 255 break; 256 if include_re.search( line ): 257 writePreamble( output ) 258 output.write( line ) 259 elif preamble_re.search( line ): 260 writePreamble( output ) 261 elif world_re.search( line ): 262 if len(suites) > 0: 263 output.write("bool "+suites[0]['object']+"_init = false;\n") 264 writeWorld( output ) 265 else: 266 output.write( line ) 267 template.close() 268 output.close() 269 270def startOutputFile(): 271 '''Create output file and write header''' 272 if options.outputFileName is not None: 273 output = open( options.outputFileName, 'w' ) 274 else: 275 output = sys.stdout 276 output.write( "/* Generated file, do not edit */\n\n" ) 277 return output 278 279def writePreamble( output ): 280 '''Write the CxxTest header (#includes and #defines)''' 281 global wrotePreamble 282 if wrotePreamble: return 283 output.write( "#ifndef CXXTEST_RUNNING\n" ) 284 output.write( "#define CXXTEST_RUNNING\n" ) 285 output.write( "#endif\n" ) 286 output.write( "\n" ) 287 if options.xunit_printer: 288 output.write( "#include <fstream>\n" ) 289 if options.haveStandardLibrary: 290 output.write( "#define _CXXTEST_HAVE_STD\n" ) 291 if options.haveExceptionHandling: 292 output.write( "#define _CXXTEST_HAVE_EH\n" ) 293 if options.abortOnFail: 294 output.write( "#define _CXXTEST_ABORT_TEST_ON_FAIL\n" ) 295 if options.longlong: 296 output.write( "#define _CXXTEST_LONGLONG %s\n" % options.longlong ) 297 #if options.factor: 298 #output.write( "#define _CXXTEST_FACTOR\n" ) 299 for header in options.headers: 300 output.write( "#include \"%s\"\n" % header ) 301 output.write( "#include <cxxtest/TestListener.h>\n" ) 302 output.write( "#include <cxxtest/TestTracker.h>\n" ) 303 output.write( "#include <cxxtest/TestRunner.h>\n" ) 304 output.write( "#include <cxxtest/RealDescriptions.h>\n" ) 305 output.write( "#include <cxxtest/TestMain.h>\n" ) 306 if options.runner: 307 output.write( "#include <cxxtest/%s.h>\n" % options.runner ) 308 if options.gui: 309 output.write( "#include <cxxtest/%s.h>\n" % options.gui ) 310 output.write( "\n" ) 311 wrotePreamble = 1 312 313def writeMain( output ): 314 '''Write the main() function for the test runner''' 315 if not (options.gui or options.runner): 316 return 317 output.write( 'int %s( int argc, char *argv[] ) {\n' % options.main ) 318 output.write( ' int status;\n' ) 319 if options.noStaticInit: 320 output.write( ' CxxTest::initialize();\n' ) 321 if options.gui: 322 tester_t = "CxxTest::GuiTuiRunner<CxxTest::%s, CxxTest::%s> " % (options.gui, options.runner) 323 else: 324 tester_t = "CxxTest::%s" % (options.runner) 325 if options.xunit_printer: 326 output.write( ' std::ofstream ofstr("%s");\n' % options.xunit_file ) 327 output.write( ' %s tmp(ofstr);\n' % tester_t ) 328 output.write( ' CxxTest::RealWorldDescription::_worldName = "%s";\n' % options.world ) 329 else: 330 output.write( ' %s tmp;\n' % tester_t ) 331 output.write( ' status = CxxTest::Main<%s>( tmp, argc, argv );\n' % tester_t ) 332 output.write( ' return status;\n') 333 output.write( '}\n' ) 334 335 336def writeWorld( output ): 337 '''Write the world definitions''' 338 global wroteWorld 339 if wroteWorld: return 340 writePreamble( output ) 341 writeSuites( output ) 342 if options.root or not options.part: 343 writeRoot( output ) 344 writeWorldDescr( output ) 345 if options.noStaticInit: 346 writeInitialize( output ) 347 wroteWorld = 1 348 349def writeSuites(output): 350 '''Write all TestDescriptions and SuiteDescriptions''' 351 for suite in suites: 352 writeInclude( output, suite['file'] ) 353 if isGenerated(suite): 354 generateSuite( output, suite ) 355 if isDynamic(suite): 356 writeSuitePointer( output, suite ) 357 else: 358 writeSuiteObject( output, suite ) 359 writeTestList( output, suite ) 360 writeSuiteDescription( output, suite ) 361 writeTestDescriptions( output, suite ) 362 363def isGenerated(suite): 364 '''Checks whether a suite class should be created''' 365 return suite['generated'] 366 367def isDynamic(suite): 368 '''Checks whether a suite is dynamic''' 369 return 'create' in suite 370 371def writeInclude(output, file): 372 '''Add #include "file" statement''' 373 global lastIncluded 374 if file == lastIncluded: return 375 output.writelines( [ '#include "', file, '"\n\n' ] ) 376 lastIncluded = file 377 378def generateSuite( output, suite ): 379 '''Write a suite declared with CXXTEST_SUITE()''' 380 output.write( 'class %s : public CxxTest::TestSuite {\n' % suite['name'] ) 381 output.write( 'public:\n' ) 382 for line in suite['lines']: 383 output.write(line) 384 output.write( '};\n\n' ) 385 386def writeSuitePointer( output, suite ): 387 '''Create static suite pointer object for dynamic suites''' 388 if options.noStaticInit: 389 output.write( 'static %s *%s;\n\n' % (suite['name'], suite['object']) ) 390 else: 391 output.write( 'static %s *%s = 0;\n\n' % (suite['name'], suite['object']) ) 392 393def writeSuiteObject( output, suite ): 394 '''Create static suite object for non-dynamic suites''' 395 output.writelines( [ "static ", suite['name'], " ", suite['object'], ";\n\n" ] ) 396 397def writeTestList( output, suite ): 398 '''Write the head of the test linked list for a suite''' 399 if options.noStaticInit: 400 output.write( 'static CxxTest::List %s;\n' % suite['tlist'] ) 401 else: 402 output.write( 'static CxxTest::List %s = { 0, 0 };\n' % suite['tlist'] ) 403 404def writeWorldDescr( output ): 405 '''Write the static name of the world name''' 406 if options.noStaticInit: 407 output.write( 'const char* CxxTest::RealWorldDescription::_worldName;\n' ) 408 else: 409 output.write( 'const char* CxxTest::RealWorldDescription::_worldName = "cxxtest";\n' ) 410 411def writeTestDescriptions( output, suite ): 412 '''Write all test descriptions for a suite''' 413 for test in suite['tests']: 414 writeTestDescription( output, suite, test ) 415 416def writeTestDescription( output, suite, test ): 417 '''Write test description object''' 418 output.write( 'static class %s : public CxxTest::RealTestDescription {\n' % test['class'] ) 419 output.write( 'public:\n' ) 420 if not options.noStaticInit: 421 output.write( ' %s() : CxxTest::RealTestDescription( %s, %s, %s, "%s" ) {}\n' % 422 (test['class'], suite['tlist'], suite['dobject'], test['line'], test['name']) ) 423 output.write( ' void runTest() { %s }\n' % runBody( suite, test ) ) 424 output.write( '} %s;\n\n' % test['object'] ) 425 426def runBody( suite, test ): 427 '''Body of TestDescription::run()''' 428 if isDynamic(suite): return dynamicRun( suite, test ) 429 else: return staticRun( suite, test ) 430 431def dynamicRun( suite, test ): 432 '''Body of TestDescription::run() for test in a dynamic suite''' 433 return 'if ( ' + suite['object'] + ' ) ' + suite['object'] + '->' + test['name'] + '();' 434 435def staticRun( suite, test ): 436 '''Body of TestDescription::run() for test in a non-dynamic suite''' 437 return suite['object'] + '.' + test['name'] + '();' 438 439def writeSuiteDescription( output, suite ): 440 '''Write SuiteDescription object''' 441 if isDynamic( suite ): 442 writeDynamicDescription( output, suite ) 443 else: 444 writeStaticDescription( output, suite ) 445 446def writeDynamicDescription( output, suite ): 447 '''Write SuiteDescription for a dynamic suite''' 448 output.write( 'CxxTest::DynamicSuiteDescription<%s> %s' % (suite['name'], suite['dobject']) ) 449 if not options.noStaticInit: 450 output.write( '( %s, %s, "%s", %s, %s, %s, %s )' % 451 (suite['cfile'], suite['line'], suite['name'], suite['tlist'], 452 suite['object'], suite['create'], suite['destroy']) ) 453 output.write( ';\n\n' ) 454 455def writeStaticDescription( output, suite ): 456 '''Write SuiteDescription for a static suite''' 457 output.write( 'CxxTest::StaticSuiteDescription %s' % suite['dobject'] ) 458 if not options.noStaticInit: 459 output.write( '( %s, %s, "%s", %s, %s )' % 460 (suite['cfile'], suite['line'], suite['name'], suite['object'], suite['tlist']) ) 461 output.write( ';\n\n' ) 462 463def writeRoot(output): 464 '''Write static members of CxxTest classes''' 465 output.write( '#include <cxxtest/Root.cpp>\n' ) 466 467def writeInitialize(output): 468 '''Write CxxTest::initialize(), which replaces static initialization''' 469 output.write( 'namespace CxxTest {\n' ) 470 output.write( ' void initialize()\n' ) 471 output.write( ' {\n' ) 472 for suite in suites: 473 output.write( ' %s.initialize();\n' % suite['tlist'] ) 474 if isDynamic(suite): 475 output.write( ' %s = 0;\n' % suite['object'] ) 476 output.write( ' %s.initialize( %s, %s, "%s", %s, %s, %s, %s );\n' % 477 (suite['dobject'], suite['cfile'], suite['line'], suite['name'], 478 suite['tlist'], suite['object'], suite['create'], suite['destroy']) ) 479 else: 480 output.write( ' %s.initialize( %s, %s, "%s", %s, %s );\n' % 481 (suite['dobject'], suite['cfile'], suite['line'], suite['name'], 482 suite['object'], suite['tlist']) ) 483 484 for test in suite['tests']: 485 output.write( ' %s.initialize( %s, %s, %s, "%s" );\n' % 486 (test['object'], suite['tlist'], suite['dobject'], test['line'], test['name']) ) 487 488 output.write( ' }\n' ) 489 output.write( '}\n' ) 490 491man_template=Template("""CXXTESTGEN(1) 492============= 493:doctype: manpage 494 495 496NAME 497---- 498cxxtestgen - performs test discovery to create a CxxTest test runner 499 500 501SYNOPSIS 502-------- 503${usage} 504 505 506DESCRIPTION 507----------- 508${description} 509 510 511OPTIONS 512------- 513${options} 514 515 516EXIT STATUS 517----------- 518*0*:: 519 Success 520 521*1*:: 522 Failure (syntax or usage error; configuration error; document 523 processing failure; unexpected error). 524 525 526BUGS 527---- 528See the CxxTest Home Page for the link to the CxxTest ticket repository. 529 530 531AUTHOR 532------ 533CxxTest was originally written by Erez Volk. Many people have 534contributed to it. 535 536 537RESOURCES 538--------- 539Home page: <http://cxxtest.com/> 540 541CxxTest User Guide: <http://cxxtest.com/cxxtest/doc/guide.html> 542 543 544 545COPYING 546------- 547Copyright (c) 2008 Sandia Corporation. This software is distributed 548under the Lesser GNU General Public License (LGPL) v2.1 549""") 550 551def create_manpage(): 552 """Write ASCIIDOC manpage file""" 553 parser = create_parser(asciidoc=True) 554 # 555 usage = parser.usage 556 description = parser.description 557 options="" 558 for opt in parser.option_list: 559 opts = opt._short_opts + opt._long_opts 560 optstr = '*' + ', '.join(opts) + '*' 561 if not opt.metavar is None: 562 optstr += "='%s'" % opt.metavar 563 optstr += '::\n' 564 options += optstr 565 # 566 options += opt.help 567 options += '\n\n' 568 # 569 OUTPUT = open('cxxtestgen.1.txt','w') 570 OUTPUT.write( man_template.substitute(usage=usage, description=description, options=options) ) 571 OUTPUT.close() 572 573 574