PageRenderTime 58ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/python/python3/cxxtest/cxxtestgen.py

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