~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/scripts/trial.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

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