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