~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/trial/util.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2006-02-22 22:52:47 UTC
  • Revision ID: james.westby@ubuntu.com-20060222225247-0mjb8ij9473m5zse
Tags: 2.2.0-1ubuntu1
Synchronize with Debian unstable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- test-case-name: twisted.trial.test.test_trial -*-
 
1
# -*- test-case-name: twisted.trial.test.test_util -*-
2
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
3
# See LICENSE for details.
4
4
#
20
20
from __future__ import generators
21
21
 
22
22
import traceback, warnings, time, signal, gc, sys
23
 
from twisted.python import failure, util, log
24
 
from twisted.internet import defer, interfaces
25
 
from twisted.trial import itrial
26
 
import zope.interface as zi
 
23
from twisted.python import failure, util, log, threadpool
 
24
from twisted.internet import utils, defer, interfaces
27
25
 
28
26
# Methods in this list will be omitted from a failed test's traceback if
29
27
# they are the final frame.
37
35
DEFAULT_TIMEOUT_DURATION = 120.0
38
36
 
39
37
 
40
 
class SignalStateManager:
41
 
    """
42
 
    keeps state of signal handlers and provides methods for restoration
43
 
    """
44
 
 
45
 
    exclude = ['SIGKILL', 'SIGSTOP', 'SIGRTMIN', 'SIGRTMAX']
46
 
 
47
 
    def __init__(self):
48
 
        self._store = {}
49
 
 
50
 
    def save(self):
51
 
        for signum in [getattr(signal, n) for n in dir(signal)
52
 
                       if n.startswith('SIG') and n[3] != '_'
53
 
                       and n not in self.exclude]:
54
 
            self._store[signum] = signal.getsignal(signum)
55
 
 
56
 
    def restore(self):
57
 
        for signum, handler in self._store.iteritems():
58
 
            if handler is not None:
59
 
                signal.signal(signum, handler)
60
 
 
61
 
    def clear(self):
62
 
        self._store = {}
63
 
            
64
 
 
65
38
def deferredResult(d, timeout=None):
66
39
    """
67
40
    Waits for a Deferred to arrive, then returns or throws an exception,
91
64
        raise unittest.FailTest, "Deferred did not fail: %r" % (result,)
92
65
 
93
66
 
94
 
class MultiError(Exception):
95
 
    """smuggle a sequence of failures through a raise
96
 
    @ivar failures: a sequence of failure objects that prompted the raise
97
 
    @ivar args: additional arguments
98
 
    """
99
 
    def __init__(self, failures, *args):
100
 
        if isinstance(failures, failure.Failure):
101
 
            self.failures = [failures]
102
 
        else:
103
 
            self.failures = list(failures)
104
 
        self.args = args
105
 
 
106
 
    def __str__(self):
107
 
        return '\n\n'.join([e.getTraceback() for e in self.failures])
108
 
 
109
 
 
110
 
class LoggedErrors(MultiError):
111
 
    """raised when there have been errors logged using log.err"""
112
 
    
113
 
class WaitError(MultiError):
114
 
    """raised when there have been errors during a call to wait"""
115
 
 
116
 
class JanitorError(MultiError):
117
 
    """raised when an error is encountered during a *Cleanup"""
 
67
class FailureError(Exception):
 
68
    """Wraps around a Failure so it can get re-raised as an Exception"""
 
69
 
 
70
    def __init__(self, failure):
 
71
        Exception.__init__(self)
 
72
        self.original = failure
 
73
 
118
74
 
119
75
class DirtyReactorError(Exception):
120
76
    """emitted when the reactor has been left in an unclean state"""
130
86
 
131
87
 
132
88
class _Janitor(object):
133
 
    logErrCheck = postCase = True
134
 
    cleanPending = cleanThreads = cleanReactor = postMethod = True
135
 
 
136
 
    def postMethodCleanup(self):
137
 
        if self.postMethod:
138
 
            return self._dispatch('logErrCheck', 'cleanPending')
 
89
    logErrCheck = True
 
90
    cleanPending = cleanThreads = cleanReactor = True
139
91
 
140
92
    def postCaseCleanup(self):
141
 
        if self.postCase:
142
 
            return self._dispatch('logErrCheck', 'cleanReactor',
143
 
                                  'cleanPending', 'cleanThreads')
 
93
        return self._dispatch('logErrCheck', 'cleanPending')
 
94
 
 
95
    def postClassCleanup(self):
 
96
        return self._dispatch('logErrCheck', 'cleanReactor',
 
97
                              'cleanPending', 'cleanThreads')
144
98
 
145
99
    def _dispatch(self, *attrs):
146
 
        errors = []
147
100
        for attr in attrs:
148
 
            if getattr(self, attr):
149
 
                try:
150
 
                    getattr(self, "do_%s" % attr)()
151
 
                except LoggedErrors, e:
152
 
                    errors.extend(e.failures)
153
 
                except PendingTimedCallsError:
154
 
                    errors.append(failure.Failure())
155
 
        if errors:
156
 
            raise JanitorError([e for e in errors if e is not None])
 
101
            getattr(self, "do_%s" % attr)()
157
102
 
158
103
    def do_logErrCheck(cls):
159
 
        if log._keptErrors:
160
 
            L = []
161
 
            for err in log._keptErrors:
162
 
                if isinstance(err, failure.Failure):
163
 
                    L.append(err)
164
 
                else:
165
 
                    L.append(repr(err))
 
104
        try:
 
105
            if len(log._keptErrors) > 0:
 
106
                raise FailureError(log._keptErrors[0])
 
107
        finally:
166
108
            log.flushErrors()
167
 
            raise LoggedErrors(L)
168
109
    do_logErrCheck = classmethod(do_logErrCheck)
169
110
 
170
111
    def do_cleanPending(cls):
171
112
        # don't import reactor when module is loaded
172
113
        from twisted.internet import reactor
173
 
        
 
114
 
174
115
        # flush short-range timers
175
116
        reactor.iterate(0)
176
117
        reactor.iterate(0)
177
 
        
 
118
 
178
119
        pending = reactor.getDelayedCalls()
179
120
        if pending:
180
121
            s = PENDING_TIMED_CALLS_MSG
181
 
 
182
122
            for p in pending:
183
123
                s += " %s\n" % (p,)
184
124
                if p.active():
185
125
                    p.cancel() # delete the rest
186
126
                else:
187
127
                    print "WEIRNESS! pending timed call not active+!"
188
 
 
189
 
            spinWhile(reactor.getDelayedCalls)
190
 
 
191
128
            raise PendingTimedCallsError(s)
192
129
    do_cleanPending = classmethod(do_cleanPending)
193
130
 
198
135
            if hasattr(reactor, 'threadpool') and reactor.threadpool:
199
136
                reactor.threadpool.stop()
200
137
                reactor.threadpool = None
 
138
                # *Put it back* and *start it up again*.  The
 
139
                # reactor's threadpool is *private*: we cannot just
 
140
                # rape it and walk away.
 
141
                reactor.threadpool = threadpool.ThreadPool(0, 10)
 
142
                reactor.threadpool.start()
 
143
 
 
144
 
201
145
    do_cleanThreads = classmethod(do_cleanThreads)
202
146
 
203
147
    def do_cleanReactor(cls):
211
155
                    sel.signalProcess('KILL')
212
156
                s.append(repr(sel))
213
157
        if s:
214
 
            # raise DirtyReactorError, s
215
 
            raise JanitorError(failure.Failure(DirtyReactorWarning(' '.join(s))))
 
158
            raise DirtyReactorError(' '.join(s))
216
159
    do_cleanReactor = classmethod(do_cleanReactor)
217
160
 
218
161
    def doGcCollect(cls):
219
162
         gc.collect()
220
163
 
 
164
def fireWhenDoneFunc(d, f):
 
165
    """Returns closure that when called calls f and then callbacks d.
 
166
    """
 
167
    def newf(*args, **kw):
 
168
        rtn = f(*args, **kw)
 
169
        d.callback('')
 
170
        return rtn
 
171
    return util.mergeFunctionMetadata(f, newf)
221
172
 
222
173
def spinUntil(f, timeout=DEFAULT_TIMEOUT_DURATION,
223
174
              msg="condition not met before timeout"):
224
175
    """spin the reactor while condition returned by f() == False or timeout
225
176
    seconds have elapsed i.e. spin until f() is True
226
177
    """
 
178
    warnings.warn("Do NOT use spinUntil.  Return a Deferred from your "
 
179
                  "test.", stacklevel=2, category=DeprecationWarning)
227
180
    assert callable(f)
228
181
    from twisted.internet import reactor
229
182
    now = time.time()
238
191
    """spin the reactor while condition returned by f() == True or until
239
192
    timeout seconds have elapsed i.e. spin until f() is False
240
193
    """
 
194
    warnings.warn("Do NOT use spinWhile.  Return a Deferred from your "
 
195
                  "test.", stacklevel=2, category=DeprecationWarning)
241
196
    assert callable(f)
242
197
    from twisted.internet import reactor
243
198
    now = time.time()
255
210
class WaitIsNotReentrantError(Exception):
256
211
    pass
257
212
 
258
 
def _wait(d, timeout=None, running=[]):
 
213
_wait_is_running = []
 
214
def _wait(d, timeout=None, running=_wait_is_running):
259
215
    from twisted.internet import reactor
260
 
 
261
216
    if running:
262
217
        raise WaitIsNotReentrantError, REENTRANT_WAIT_ERROR_MSG
 
218
 
 
219
    results = []
 
220
    def append(any):
 
221
        if results is not None:
 
222
            results.append(any)
 
223
    def crash(ign):
 
224
        if results is not None:
 
225
            reactor.crash()
 
226
    def stop():
 
227
        reactor.crash()
 
228
 
263
229
    running.append(None)
264
230
    try:
265
 
        assert isinstance(d, defer.Deferred), "first argument must be a deferred!"
266
 
 
267
 
        results = []
268
 
        def append(any):
269
 
             if results is not None:
270
 
                results.append(any)
271
231
        d.addBoth(append)
272
 
 
273
232
        if results:
274
233
            return results[0]
275
 
 
276
 
        def crash(ign):
277
 
            if results is not None:
278
 
                reactor.crash()
279
 
 
280
234
        d.addBoth(crash)
281
 
 
282
235
        if timeout is None:
283
236
            timeoutCall = None
284
237
        else:
285
238
            timeoutCall = reactor.callLater(timeout, reactor.crash)
286
 
 
287
 
        def stop():
288
 
            reactor.crash()
289
 
 
290
239
        reactor.stop = stop
291
240
        try:
292
241
            reactor.run()
293
242
        finally:
294
243
            del reactor.stop
295
 
 
296
244
        if timeoutCall is not None:
297
245
            if timeoutCall.active():
298
246
                timeoutCall.cancel()
299
247
            else:
300
 
                raise defer.TimeoutError()
 
248
                f = failure.Failure(defer.TimeoutError('_wait timed out'))
 
249
                return f
301
250
 
302
251
        if results:
303
252
            return results[0]
 
253
 
304
254
        # If the timeout didn't happen, and we didn't get a result or
305
255
        # a failure, then the user probably aborted the test, so let's
306
256
        # just raise KeyboardInterrupt.
315
265
        # this code set a "stop trial" flag, or otherwise notify trial
316
266
        # that it should really try to stop as soon as possible.
317
267
        raise KeyboardInterrupt()
318
 
 
319
268
    finally:
320
269
        results = None
321
270
        running.pop()
322
271
 
323
272
 
324
273
def wait(d, timeout=DEFAULT_TIMEOUT, useWaitError=False):
325
 
    """Waits (spins the reactor) for a Deferred to arrive, then returns or
326
 
    throws an exception, based on the result. The difference between this and
327
 
    deferredResult is that it actually throws the original exception, not the
328
 
    Failure, so synchronous exception handling is much more sane.  
329
 
 
330
 
    There are some caveats to follow when using this method:
331
 
    
332
 
      - There is an important restriction on the use of this method which may
333
 
        be difficult to predict at the time you're writing the test.The issue
334
 
        is that the reactor's L{twisted.internet.base.runUntilCurrent} is not
335
 
        reentrant, therefore wait is B{I{not reentrant!}} This means that you
336
 
        cannot call wait from within a callback of a deferred you are waiting
337
 
        on. Also, you may or may not be able to call wait on deferreds which
338
 
        you have not created, as the originating API may violate this rule
339
 
        without your knowledge. For an illustrative example, see 
340
 
        L{twisted.trial.test.test_trial.WaitReentrancyTest} 
341
 
 
342
 
      - If you are relying on the original traceback for some reason, do
343
 
        useWaitError=True. Due to the way that Deferreds and Failures work, the
344
 
        presence of the original traceback stack cannot be guaranteed without
345
 
        passing this flag (see below).  
346
 
 
347
 
    @param timeout: None indicates that we will wait indefinately, the default
348
 
        is to wait 4.0 seconds.  
349
 
    @type timeout: types.FloatType 
350
 
 
351
 
    @param useWaitError: The exception thrown is a
352
 
        L{twisted.trial.util.WaitError}, which saves the original failure object
353
 
        or objects in a list .failures, to aid in the retrieval of the original
354
 
        stack traces.  The tradeoff is between wait() raising the original
355
 
        exception *type* or being able to retrieve the original traceback
356
 
        reliably. (see issue 769) 
357
 
    @type useWaitError: boolean
 
274
    """Do NOT use this ever. 
358
275
    """
 
276
    warnings.warn("Do NOT use wait. It is a bad and buggy and deprecated since "
 
277
                  "Twisted 2.2.",
 
278
                  category=DeprecationWarning, stacklevel=2)
359
279
    if timeout is DEFAULT_TIMEOUT:
360
280
        timeout = DEFAULT_TIMEOUT_DURATION
361
281
    try:
364
284
        raise
365
285
    except:
366
286
        #  it would be nice if i didn't have to armor this call like
367
 
        # this (with a blank except:, but we *are* calling user code 
 
287
        # this (with a blank except:, but we *are* calling user code
368
288
        r = failure.Failure()
369
 
    
370
 
    if not useWaitError:
371
 
        if isinstance(r, failure.Failure):
 
289
 
 
290
    if isinstance(r, failure.Failure):
 
291
        if useWaitError:
 
292
            raise FailureError(r)
 
293
        else:
372
294
            r.raiseException()
373
 
    else:
374
 
        flist = []
375
 
        if isinstance(r, failure.Failure):
376
 
            flist.append(r)
377
 
        
378
 
        if flist:
379
 
            raise WaitError(flist)
380
 
 
381
295
    return r
382
296
 
383
297
def extract_tb(tb, limit=None):
384
 
    """Extract a list of frames from a traceback, without unittest internals.
385
 
 
386
 
    Functionally identical to L{traceback.extract_tb}, but cropped to just
387
 
    the test case itself, excluding frames that are part of the Trial
388
 
    testing framework.
389
 
    """
 
298
    """DEPRECATED in Twisted 2.2"""
 
299
    warnings.warn("Deprecated in Twisted 2.2", category=DeprecationWarning)
390
300
    from twisted.trial import unittest, runner
391
301
    l = traceback.extract_tb(tb, limit)
392
302
    util_file = __file__.replace('.pyc','.py')
405
315
    return l
406
316
 
407
317
def format_exception(eType, eValue, tb, limit=None):
408
 
    """A formatted traceback and exception, without exposing the framework.
409
 
 
410
 
    I am identical in function to L{traceback.format_exception},
411
 
    but I screen out frames from the traceback that are part of
412
 
    the testing framework itself, leaving only the code being tested.
413
 
    """
 
318
    """DEPRECATED in Twisted 2.2"""
 
319
    warnings.warn("Deprecated in Twisted 2.2", category=DeprecationWarning)
414
320
    from twisted.trial import unittest
415
321
    result = [x.strip()+'\n' for x in
416
322
              failure.Failure(eValue,eType,tb).getBriefTraceback().split('\n')]
429
335
    return l
430
336
 
431
337
def suppressWarnings(f, *warningz):
432
 
    """this method is not supported for the moment and is 
433
 
    awaiting a fix (see U{issue627 
434
 
    <http://www.twistedmatrix.com/users/roundup.twistd/twisted/issue627>})
435
 
    """
 
338
    warnings.warn("Don't use this.  Use the .suppress attribute instead",
 
339
                  category=DeprecationWarning, stacklevel=2)
436
340
    def enclosingScope(warnings, warningz):
437
341
        exec """def %s(*args, **kwargs):
438
342
    for warning in warningz:
450
354
def suppress(action='ignore', **kwarg):
451
355
    """sets up the .suppress tuple properly, pass options to this method
452
356
    as you would the stdlib warnings.filterwarnings()
453
 
    
454
 
    so to use this with a .suppress magic attribute you would do the 
 
357
 
 
358
    so to use this with a .suppress magic attribute you would do the
455
359
    following:
456
360
 
457
361
      >>> from twisted.trial import unittest, util
466
370
 
467
371
    note that as with the todo and timeout attributes: the module level
468
372
    attribute acts as a default for the class attribute which acts as a default
469
 
    for the method attribute. The suppress attribute can be overridden at any 
 
373
    for the method attribute. The suppress attribute can be overridden at any
470
374
    level by specifying .suppress = []
471
375
    """
472
376
    return ((action,), kwarg)
473
377
 
474
378
 
475
 
class UserMethodError(Exception):
476
 
    """indicates that the user method had an error, but raised after
477
 
    call is complete
478
 
    """
479
 
 
480
 
class UserMethodWrapper(object):
481
 
    def __init__(self, original, raiseOnErr=True, timeout=None, suppress=None):
482
 
        self.original = original
483
 
        self.timeout = timeout
484
 
        self.raiseOnErr = raiseOnErr
485
 
        self.errors = []
486
 
        self.suppress = suppress
487
 
        self.name = original.__name__
488
 
 
489
 
    def __call__(self, *a, **kw):
490
 
        timeout = getattr(self, 'timeout', None)
491
 
        if timeout is None:
492
 
            timeout = getattr(self.original, 'timeout', DEFAULT_TIMEOUT)
493
 
        self.startTime = time.time()
494
 
        def run():
495
 
            return wait(defer.maybeDeferred(self.original, *a, **kw),
496
 
                        timeout, useWaitError=True)
497
 
        try:
498
 
            self._runWithWarningFilters(run)
499
 
        except MultiError, e:
500
 
            for f in e.failures:
501
 
                self.errors.append(f)
502
 
        except KeyboardInterrupt:
503
 
            f = failure.Failure()
504
 
            self.errors.append(f)
505
 
        self.endTime = time.time()
506
 
        for e in self.errors:
507
 
            self.errorHook(e)
508
 
        if self.raiseOnErr and self.errors:
509
 
            raise UserMethodError
510
 
 
511
 
    def _runWithWarningFilters(self, f, *a, **kw):
512
 
        """calls warnings.filterwarnings(*item[0], **item[1]) 
513
 
        for each item in alist, then runs func f(*a, **kw) and 
514
 
        resets warnings.filters to original state at end
515
 
        """
516
 
        filters = warnings.filters[:]
517
 
        try:
518
 
            if self.suppress is not None:
519
 
                for args, kwargs in self.suppress:
520
 
                    warnings.filterwarnings(*args, **kwargs)
521
 
            return f(*a, **kw)
522
 
        finally:
523
 
            warnings.filters = filters[:]
524
 
 
525
 
    def errorHook(self, fail):
526
 
        pass
 
379
def timedRun(timeout, f, *a, **kw):
 
380
    return wait(defer.maybeDeferred(f, *a, **kw), timeout, useWaitError=True)
 
381
 
 
382
 
 
383
def testFunction(f):
 
384
    containers = [f] + getPythonContainers(f)
 
385
    suppress = acquireAttribute(containers, 'suppress', [])
 
386
    timeout = acquireAttribute(containers, 'timeout', DEFAULT_TIMEOUT)
 
387
    return utils.runWithWarningsSuppressed(suppress, timedRun, timeout, f)
 
388
 
 
389
 
 
390
def profiled(f, outputFile):
 
391
    def _(*args, **kwargs):
 
392
        if sys.version_info[0:2] != (2, 4):
 
393
            import profile
 
394
            prof = profile.Profile()
 
395
            try:
 
396
                result = prof.runcall(f, *args, **kwargs)
 
397
                prof.dump_stats(outputFile)
 
398
            except SystemExit:
 
399
                pass
 
400
            prof.print_stats()
 
401
            return result
 
402
        else: # use hotshot, profile is broken in 2.4
 
403
            import hotshot.stats
 
404
            prof = hotshot.Profile(outputFile)
 
405
            try:
 
406
                return prof.runcall(f, *args, **kwargs)
 
407
            finally:
 
408
                stats = hotshot.stats.load(outputFile)
 
409
                stats.strip_dirs()
 
410
                stats.sort_stats('cum')   # 'time'
 
411
                stats.print_stats(100)
 
412
    return _
527
413
 
528
414
 
529
415
def getPythonContainers(meth):
554
440
    raise AttributeError('attribute %r not found in %r' % (attr, objects))
555
441
 
556
442
 
557
 
__all__ = ["MultiError", "LoggedErrors", 'WaitError', 'JanitorError', 'DirtyReactorWarning',
558
 
           'DirtyReactorError', 'PendingTimedCallsError', 'WaitIsNotReentrantError',
559
 
           'deferredResult', 'deferredError', 'wait', 'extract_tb', 'format_exception',
560
 
           'suppressWarnings']
 
443
def findObject(name):
 
444
    """Get a fully-named package, module, module-global object or attribute.
 
445
    Forked from twisted.python.reflect.namedAny.
 
446
 
 
447
    Returns a tuple of (bool, obj).  If bool is True, the named object exists
 
448
    and is returned as obj.  If bool is False, the named object does not exist
 
449
    and the value of obj is unspecified.
 
450
    """
 
451
    names = name.split('.')
 
452
    topLevelPackage = None
 
453
    moduleNames = names[:]
 
454
    while not topLevelPackage:
 
455
        trialname = '.'.join(moduleNames)
 
456
        if len(trialname) == 0:
 
457
            return (False, None)
 
458
        try:
 
459
            topLevelPackage = __import__(trialname)
 
460
        except ImportError:
 
461
            # if the ImportError happened in the module being imported,
 
462
            # this is a failure that should be handed to our caller.
 
463
            # count stack frames to tell the difference.
 
464
            exc_info = sys.exc_info()
 
465
            if len(traceback.extract_tb(exc_info[2])) > 1:
 
466
                try:
 
467
                    # Clean up garbage left in sys.modules.
 
468
                    del sys.modules[trialname]
 
469
                except KeyError:
 
470
                    # Python 2.4 has fixed this.  Yay!
 
471
                    pass
 
472
                raise exc_info[0], exc_info[1], exc_info[2]
 
473
            moduleNames.pop()
 
474
    obj = topLevelPackage
 
475
    for n in names[1:]:
 
476
        try:
 
477
            obj = getattr(obj, n)
 
478
        except AttributeError:
 
479
            return (False, obj)
 
480
    return (True, obj)
 
481
 
 
482
 
 
483
__all__ = ['FailureError', 'DirtyReactorWarning', 'DirtyReactorError',
 
484
           'PendingTimedCallsError', 'WaitIsNotReentrantError',
 
485
           'deferredResult', 'deferredError', 'wait', 'extract_tb',
 
486
           'format_exception', 'suppressWarnings']