20
20
from __future__ import generators
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
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
40
class SignalStateManager:
42
keeps state of signal handlers and provides methods for restoration
45
exclude = ['SIGKILL', 'SIGSTOP', 'SIGRTMIN', 'SIGRTMAX']
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)
57
for signum, handler in self._store.iteritems():
58
if handler is not None:
59
signal.signal(signum, handler)
65
38
def deferredResult(d, timeout=None):
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,)
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
99
def __init__(self, failures, *args):
100
if isinstance(failures, failure.Failure):
101
self.failures = [failures]
103
self.failures = list(failures)
107
return '\n\n'.join([e.getTraceback() for e in self.failures])
110
class LoggedErrors(MultiError):
111
"""raised when there have been errors logged using log.err"""
113
class WaitError(MultiError):
114
"""raised when there have been errors during a call to wait"""
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"""
70
def __init__(self, failure):
71
Exception.__init__(self)
72
self.original = failure
119
75
class DirtyReactorError(Exception):
120
76
"""emitted when the reactor has been left in an unclean state"""
132
88
class _Janitor(object):
133
logErrCheck = postCase = True
134
cleanPending = cleanThreads = cleanReactor = postMethod = True
136
def postMethodCleanup(self):
138
return self._dispatch('logErrCheck', 'cleanPending')
90
cleanPending = cleanThreads = cleanReactor = True
140
92
def postCaseCleanup(self):
142
return self._dispatch('logErrCheck', 'cleanReactor',
143
'cleanPending', 'cleanThreads')
93
return self._dispatch('logErrCheck', 'cleanPending')
95
def postClassCleanup(self):
96
return self._dispatch('logErrCheck', 'cleanReactor',
97
'cleanPending', 'cleanThreads')
145
99
def _dispatch(self, *attrs):
147
100
for attr in attrs:
148
if getattr(self, attr):
150
getattr(self, "do_%s" % attr)()
151
except LoggedErrors, e:
152
errors.extend(e.failures)
153
except PendingTimedCallsError:
154
errors.append(failure.Failure())
156
raise JanitorError([e for e in errors if e is not None])
101
getattr(self, "do_%s" % attr)()
158
103
def do_logErrCheck(cls):
161
for err in log._keptErrors:
162
if isinstance(err, failure.Failure):
105
if len(log._keptErrors) > 0:
106
raise FailureError(log._keptErrors[0])
166
108
log.flushErrors()
167
raise LoggedErrors(L)
168
109
do_logErrCheck = classmethod(do_logErrCheck)
170
111
def do_cleanPending(cls):
171
112
# don't import reactor when module is loaded
172
113
from twisted.internet import reactor
174
115
# flush short-range timers
175
116
reactor.iterate(0)
176
117
reactor.iterate(0)
178
119
pending = reactor.getDelayedCalls()
180
121
s = PENDING_TIMED_CALLS_MSG
182
122
for p in pending:
183
123
s += " %s\n" % (p,)
185
125
p.cancel() # delete the rest
187
127
print "WEIRNESS! pending timed call not active+!"
189
spinWhile(reactor.getDelayedCalls)
191
128
raise PendingTimedCallsError(s)
192
129
do_cleanPending = classmethod(do_cleanPending)
211
155
sel.signalProcess('KILL')
212
156
s.append(repr(sel))
214
# raise DirtyReactorError, s
215
raise JanitorError(failure.Failure(DirtyReactorWarning(' '.join(s))))
158
raise DirtyReactorError(' '.join(s))
216
159
do_cleanReactor = classmethod(do_cleanReactor)
218
161
def doGcCollect(cls):
164
def fireWhenDoneFunc(d, f):
165
"""Returns closure that when called calls f and then callbacks d.
167
def newf(*args, **kw):
171
return util.mergeFunctionMetadata(f, newf)
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
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()
255
210
class WaitIsNotReentrantError(Exception):
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
262
217
raise WaitIsNotReentrantError, REENTRANT_WAIT_ERROR_MSG
221
if results is not None:
224
if results is not None:
263
229
running.append(None)
265
assert isinstance(d, defer.Deferred), "first argument must be a deferred!"
269
if results is not None:
271
231
d.addBoth(append)
274
233
return results[0]
277
if results is not None:
282
235
if timeout is None:
283
236
timeoutCall = None
285
238
timeoutCall = reactor.callLater(timeout, reactor.crash)
290
239
reactor.stop = stop
296
244
if timeoutCall is not None:
297
245
if timeoutCall.active():
298
246
timeoutCall.cancel()
300
raise defer.TimeoutError()
248
f = failure.Failure(defer.TimeoutError('_wait timed out'))
303
252
return results[0]
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()
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.
330
There are some caveats to follow when using this method:
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}
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).
347
@param timeout: None indicates that we will wait indefinately, the default
348
is to wait 4.0 seconds.
349
@type timeout: types.FloatType
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.
276
warnings.warn("Do NOT use wait. It is a bad and buggy and deprecated since "
278
category=DeprecationWarning, stacklevel=2)
359
279
if timeout is DEFAULT_TIMEOUT:
360
280
timeout = DEFAULT_TIMEOUT_DURATION
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()
371
if isinstance(r, failure.Failure):
290
if isinstance(r, failure.Failure):
292
raise FailureError(r)
372
294
r.raiseException()
375
if isinstance(r, failure.Failure):
379
raise WaitError(flist)
383
297
def extract_tb(tb, limit=None):
384
"""Extract a list of frames from a traceback, without unittest internals.
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
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')
407
317
def format_exception(eType, eValue, tb, limit=None):
408
"""A formatted traceback and exception, without exposing the framework.
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.
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')]
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>})
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:
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 = []
472
376
return ((action,), kwarg)
475
class UserMethodError(Exception):
476
"""indicates that the user method had an error, but raised after
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
486
self.suppress = suppress
487
self.name = original.__name__
489
def __call__(self, *a, **kw):
490
timeout = getattr(self, 'timeout', None)
492
timeout = getattr(self.original, 'timeout', DEFAULT_TIMEOUT)
493
self.startTime = time.time()
495
return wait(defer.maybeDeferred(self.original, *a, **kw),
496
timeout, useWaitError=True)
498
self._runWithWarningFilters(run)
499
except MultiError, e:
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:
508
if self.raiseOnErr and self.errors:
509
raise UserMethodError
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
516
filters = warnings.filters[:]
518
if self.suppress is not None:
519
for args, kwargs in self.suppress:
520
warnings.filterwarnings(*args, **kwargs)
523
warnings.filters = filters[:]
525
def errorHook(self, fail):
379
def timedRun(timeout, f, *a, **kw):
380
return wait(defer.maybeDeferred(f, *a, **kw), timeout, useWaitError=True)
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)
390
def profiled(f, outputFile):
391
def _(*args, **kwargs):
392
if sys.version_info[0:2] != (2, 4):
394
prof = profile.Profile()
396
result = prof.runcall(f, *args, **kwargs)
397
prof.dump_stats(outputFile)
402
else: # use hotshot, profile is broken in 2.4
404
prof = hotshot.Profile(outputFile)
406
return prof.runcall(f, *args, **kwargs)
408
stats = hotshot.stats.load(outputFile)
410
stats.sort_stats('cum') # 'time'
411
stats.print_stats(100)
529
415
def getPythonContainers(meth):
554
440
raise AttributeError('attribute %r not found in %r' % (attr, objects))
557
__all__ = ["MultiError", "LoggedErrors", 'WaitError', 'JanitorError', 'DirtyReactorWarning',
558
'DirtyReactorError', 'PendingTimedCallsError', 'WaitIsNotReentrantError',
559
'deferredResult', 'deferredError', 'wait', 'extract_tb', 'format_exception',
443
def findObject(name):
444
"""Get a fully-named package, module, module-global object or attribute.
445
Forked from twisted.python.reflect.namedAny.
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.
451
names = name.split('.')
452
topLevelPackage = None
453
moduleNames = names[:]
454
while not topLevelPackage:
455
trialname = '.'.join(moduleNames)
456
if len(trialname) == 0:
459
topLevelPackage = __import__(trialname)
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:
467
# Clean up garbage left in sys.modules.
468
del sys.modules[trialname]
470
# Python 2.4 has fixed this. Yay!
472
raise exc_info[0], exc_info[1], exc_info[2]
474
obj = topLevelPackage
477
obj = getattr(obj, n)
478
except AttributeError:
483
__all__ = ['FailureError', 'DirtyReactorWarning', 'DirtyReactorError',
484
'PendingTimedCallsError', 'WaitIsNotReentrantError',
485
'deferredResult', 'deferredError', 'wait', 'extract_tb',
486
'format_exception', 'suppressWarnings']