PageRenderTime 58ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/resources/lib/twisted/twisted/scripts/trial.py

https://github.com/analogue/mythbox
Python | 370 lines | 353 code | 8 blank | 9 comment | 6 complexity | 0d94c7ec7632da49ef104b8c2b2210fa MD5 | raw file
  1. # -*- test-case-name: twisted.trial.test.test_script -*-
  2. # Copyright (c) 2001-2007 Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. import sys, os, random, gc, time, warnings
  5. from twisted.internet import defer
  6. from twisted.application import app
  7. from twisted.python import usage, reflect, failure
  8. from twisted import plugin
  9. from twisted.python.util import spewer
  10. from twisted.python.compat import set
  11. from twisted.trial import runner, itrial, reporter
  12. # Yea, this is stupid. Leave it for for command-line compatibility for a
  13. # while, though.
  14. TBFORMAT_MAP = {
  15. 'plain': 'default',
  16. 'default': 'default',
  17. 'emacs': 'brief',
  18. 'brief': 'brief',
  19. 'cgitb': 'verbose',
  20. 'verbose': 'verbose'
  21. }
  22. def _parseLocalVariables(line):
  23. """Accepts a single line in Emacs local variable declaration format and
  24. returns a dict of all the variables {name: value}.
  25. Raises ValueError if 'line' is in the wrong format.
  26. See http://www.gnu.org/software/emacs/manual/html_node/File-Variables.html
  27. """
  28. paren = '-*-'
  29. start = line.find(paren) + len(paren)
  30. end = line.rfind(paren)
  31. if start == -1 or end == -1:
  32. raise ValueError("%r not a valid local variable declaration" % (line,))
  33. items = line[start:end].split(';')
  34. localVars = {}
  35. for item in items:
  36. if len(item.strip()) == 0:
  37. continue
  38. split = item.split(':')
  39. if len(split) != 2:
  40. raise ValueError("%r contains invalid declaration %r"
  41. % (line, item))
  42. localVars[split[0].strip()] = split[1].strip()
  43. return localVars
  44. def loadLocalVariables(filename):
  45. """Accepts a filename and attempts to load the Emacs variable declarations
  46. from that file, simulating what Emacs does.
  47. See http://www.gnu.org/software/emacs/manual/html_node/File-Variables.html
  48. """
  49. f = file(filename, "r")
  50. lines = [f.readline(), f.readline()]
  51. f.close()
  52. for line in lines:
  53. try:
  54. return _parseLocalVariables(line)
  55. except ValueError:
  56. pass
  57. return {}
  58. def getTestModules(filename):
  59. testCaseVar = loadLocalVariables(filename).get('test-case-name', None)
  60. if testCaseVar is None:
  61. return []
  62. return testCaseVar.split(',')
  63. def isTestFile(filename):
  64. """Returns true if 'filename' looks like a file containing unit tests.
  65. False otherwise. Doesn't care whether filename exists.
  66. """
  67. basename = os.path.basename(filename)
  68. return (basename.startswith('test_')
  69. and os.path.splitext(basename)[1] == ('.py'))
  70. def _zshReporterAction():
  71. return "(%s)" % (" ".join([p.longOpt for p in plugin.getPlugins(itrial.IReporter)]),)
  72. class Options(usage.Options, app.ReactorSelectionMixin):
  73. synopsis = """%s [options] [[file|package|module|TestCase|testmethod]...]
  74. """ % (os.path.basename(sys.argv[0]),)
  75. longdesc = ("trial loads and executes a suite of unit tests, obtained "
  76. "from modules, packages and files listed on the command line.")
  77. optFlags = [["help", "h"],
  78. ["rterrors", "e", "realtime errors, print out tracebacks as "
  79. "soon as they occur"],
  80. ["debug", "b", "Run tests in the Python debugger. Will load "
  81. "'.pdbrc' from current directory if it exists."],
  82. ["debug-stacktraces", "B", "Report Deferred creation and "
  83. "callback stack traces"],
  84. ["nopm", None, "don't automatically jump into debugger for "
  85. "postmorteming of exceptions"],
  86. ["dry-run", 'n', "do everything but run the tests"],
  87. ["force-gc", None, "Have Trial run gc.collect() before and "
  88. "after each test case."],
  89. ["profile", None, "Run tests under the Python profiler"],
  90. ["unclean-warnings", None,
  91. "Turn dirty reactor errors into warnings"],
  92. ["until-failure", "u", "Repeat test until it fails"],
  93. ["no-recurse", "N", "Don't recurse into packages"],
  94. ['help-reporters', None,
  95. "Help on available output plugins (reporters)"]
  96. ]
  97. optParameters = [
  98. ["logfile", "l", "test.log", "log file name"],
  99. ["random", "z", None,
  100. "Run tests in random order using the specified seed"],
  101. ['temp-directory', None, '_trial_temp',
  102. 'Path to use as working directory for tests.'],
  103. ['reporter', None, 'verbose',
  104. 'The reporter to use for this test run. See --help-reporters for '
  105. 'more info.']]
  106. zsh_actions = {"tbformat":"(plain emacs cgitb)",
  107. "reporter":_zshReporterAction}
  108. zsh_actionDescr = {"logfile":"log file name",
  109. "random":"random seed"}
  110. zsh_extras = ["*:file|module|package|TestCase|testMethod:_files -g '*.py'"]
  111. fallbackReporter = reporter.TreeReporter
  112. extra = None
  113. tracer = None
  114. def __init__(self):
  115. self['tests'] = set()
  116. usage.Options.__init__(self)
  117. def opt_coverage(self):
  118. """
  119. Generate coverage information in the _trial_temp/coverage. Requires
  120. Python 2.3.3.
  121. """
  122. coverdir = 'coverage'
  123. print "Setting coverage directory to %s." % (coverdir,)
  124. import trace
  125. # begin monkey patch ---------------------------
  126. # Before Python 2.4, this function asserted that 'filename' had
  127. # to end with '.py' This is wrong for at least two reasons:
  128. # 1. We might be wanting to find executable line nos in a script
  129. # 2. The implementation should use os.splitext
  130. # This monkey patch is the same function as in the stdlib (v2.3)
  131. # but with the assertion removed.
  132. def find_executable_linenos(filename):
  133. """Return dict where keys are line numbers in the line number
  134. table.
  135. """
  136. #assert filename.endswith('.py') # YOU BASTARDS
  137. try:
  138. prog = open(filename).read()
  139. prog = '\n'.join(prog.splitlines()) + '\n'
  140. except IOError, err:
  141. sys.stderr.write("Not printing coverage data for %r: %s\n"
  142. % (filename, err))
  143. sys.stderr.flush()
  144. return {}
  145. code = compile(prog, filename, "exec")
  146. strs = trace.find_strings(filename)
  147. return trace.find_lines(code, strs)
  148. trace.find_executable_linenos = find_executable_linenos
  149. # end monkey patch ------------------------------
  150. self.coverdir = os.path.abspath(os.path.join(self['temp-directory'], coverdir))
  151. self.tracer = trace.Trace(count=1, trace=0)
  152. sys.settrace(self.tracer.globaltrace)
  153. def opt_testmodule(self, filename):
  154. "Filename to grep for test cases (-*- test-case-name)"
  155. # If the filename passed to this parameter looks like a test module
  156. # we just add that to the test suite.
  157. #
  158. # If not, we inspect it for an Emacs buffer local variable called
  159. # 'test-case-name'. If that variable is declared, we try to add its
  160. # value to the test suite as a module.
  161. #
  162. # This parameter allows automated processes (like Buildbot) to pass
  163. # a list of files to Trial with the general expectation of "these files,
  164. # whatever they are, will get tested"
  165. if not os.path.isfile(filename):
  166. sys.stderr.write("File %r doesn't exist\n" % (filename,))
  167. return
  168. filename = os.path.abspath(filename)
  169. if isTestFile(filename):
  170. self['tests'].add(filename)
  171. else:
  172. self['tests'].update(getTestModules(filename))
  173. def opt_spew(self):
  174. """Print an insanely verbose log of everything that happens. Useful
  175. when debugging freezes or locks in complex code."""
  176. sys.settrace(spewer)
  177. def opt_help_reporters(self):
  178. synopsis = ("Trial's output can be customized using plugins called "
  179. "Reporters. You can\nselect any of the following "
  180. "reporters using --reporter=<foo>\n")
  181. print synopsis
  182. for p in plugin.getPlugins(itrial.IReporter):
  183. print ' ', p.longOpt, '\t', p.description
  184. print
  185. sys.exit(0)
  186. def opt_disablegc(self):
  187. """Disable the garbage collector"""
  188. gc.disable()
  189. def opt_tbformat(self, opt):
  190. """Specify the format to display tracebacks with. Valid formats are
  191. 'plain', 'emacs', and 'cgitb' which uses the nicely verbose stdlib
  192. cgitb.text function"""
  193. try:
  194. self['tbformat'] = TBFORMAT_MAP[opt]
  195. except KeyError:
  196. raise usage.UsageError(
  197. "tbformat must be 'plain', 'emacs', or 'cgitb'.")
  198. def opt_extra(self, arg):
  199. """
  200. Add an extra argument. (This is a hack necessary for interfacing with
  201. emacs's `gud'.)
  202. """
  203. if self.extra is None:
  204. self.extra = []
  205. self.extra.append(arg)
  206. opt_x = opt_extra
  207. def opt_recursionlimit(self, arg):
  208. """see sys.setrecursionlimit()"""
  209. try:
  210. sys.setrecursionlimit(int(arg))
  211. except (TypeError, ValueError):
  212. raise usage.UsageError(
  213. "argument to recursionlimit must be an integer")
  214. def opt_random(self, option):
  215. try:
  216. self['random'] = long(option)
  217. except ValueError:
  218. raise usage.UsageError(
  219. "Argument to --random must be a positive integer")
  220. else:
  221. if self['random'] < 0:
  222. raise usage.UsageError(
  223. "Argument to --random must be a positive integer")
  224. elif self['random'] == 0:
  225. self['random'] = long(time.time() * 100)
  226. def opt_without_module(self, option):
  227. """
  228. Fake the lack of the specified modules, separated with commas.
  229. """
  230. for module in option.split(","):
  231. if module in sys.modules:
  232. warnings.warn("Module '%s' already imported, "
  233. "disabling anyway." % (module,),
  234. category=RuntimeWarning)
  235. sys.modules[module] = None
  236. def parseArgs(self, *args):
  237. self['tests'].update(args)
  238. if self.extra is not None:
  239. self['tests'].update(self.extra)
  240. def _loadReporterByName(self, name):
  241. for p in plugin.getPlugins(itrial.IReporter):
  242. qual = "%s.%s" % (p.module, p.klass)
  243. if p.longOpt == name:
  244. return reflect.namedAny(qual)
  245. raise usage.UsageError("Only pass names of Reporter plugins to "
  246. "--reporter. See --help-reporters for "
  247. "more info.")
  248. def postOptions(self):
  249. # Only load reporters now, as opposed to any earlier, to avoid letting
  250. # application-defined plugins muck up reactor selecting by importing
  251. # t.i.reactor and causing the default to be installed.
  252. self['reporter'] = self._loadReporterByName(self['reporter'])
  253. if 'tbformat' not in self:
  254. self['tbformat'] = 'default'
  255. if self['nopm']:
  256. if not self['debug']:
  257. raise usage.UsageError("you must specify --debug when using "
  258. "--nopm ")
  259. failure.DO_POST_MORTEM = False
  260. def _initialDebugSetup(config):
  261. # do this part of debug setup first for easy debugging of import failures
  262. if config['debug']:
  263. failure.startDebugMode()
  264. if config['debug'] or config['debug-stacktraces']:
  265. defer.setDebugging(True)
  266. def _getSuite(config):
  267. loader = _getLoader(config)
  268. recurse = not config['no-recurse']
  269. return loader.loadByNames(config['tests'], recurse)
  270. def _getLoader(config):
  271. loader = runner.TestLoader()
  272. if config['random']:
  273. randomer = random.Random()
  274. randomer.seed(config['random'])
  275. loader.sorter = lambda x : randomer.random()
  276. print 'Running tests shuffled with seed %d\n' % config['random']
  277. if not config['until-failure']:
  278. loader.suiteFactory = runner.DestructiveTestSuite
  279. return loader
  280. def _makeRunner(config):
  281. mode = None
  282. if config['debug']:
  283. mode = runner.TrialRunner.DEBUG
  284. if config['dry-run']:
  285. mode = runner.TrialRunner.DRY_RUN
  286. return runner.TrialRunner(config['reporter'],
  287. mode=mode,
  288. profile=config['profile'],
  289. logfile=config['logfile'],
  290. tracebackFormat=config['tbformat'],
  291. realTimeErrors=config['rterrors'],
  292. uncleanWarnings=config['unclean-warnings'],
  293. workingDirectory=config['temp-directory'],
  294. forceGarbageCollection=config['force-gc'])
  295. def run():
  296. if len(sys.argv) == 1:
  297. sys.argv.append("--help")
  298. config = Options()
  299. try:
  300. config.parseOptions()
  301. except usage.error, ue:
  302. raise SystemExit, "%s: %s" % (sys.argv[0], ue)
  303. _initialDebugSetup(config)
  304. trialRunner = _makeRunner(config)
  305. suite = _getSuite(config)
  306. if config['until-failure']:
  307. test_result = trialRunner.runUntilFailure(suite)
  308. else:
  309. test_result = trialRunner.run(suite)
  310. if config.tracer:
  311. sys.settrace(None)
  312. results = config.tracer.results()
  313. results.write_results(show_missing=1, summary=False,
  314. coverdir=config.coverdir)
  315. sys.exit(not test_result.wasSuccessful())