root/trunk/twisted/trial/util.py @ 23101

Revision 23101, 11.7 KB (checked in by radix, 2 years ago)

Merge twisted-8.0.x-3085-4

Author: radix
Reviewer: therve
Fixes #3085

This includes lots of release shrapnel from the release of Twisted 8.0.0
and 8.0.1. release notes were written, version numbers were updated,
and deprecation warnings were updated.

Line 
1# -*- test-case-name: twisted.trial.test.test_util -*-
2# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
3# See LICENSE for details.
4#
5
6"""
7A collection of utility functions and classes, used internally by Trial.
8
9This code is for Trial's internal use.  Do NOT use this code if you are writing
10tests.  It is subject to change at the Trial maintainer's whim.  There is
11nothing here in this module for you to use unless you are maintaining Trial.
12
13Any non-Trial Twisted code that uses this module will be shot.
14
15Maintainer: U{Jonathan Lange<mailto:jml@twistedmatrix.com>}
16"""
17
18import traceback, sys
19
20from twisted.internet import defer, utils, interfaces
21from twisted.python.failure import Failure
22
23
24DEFAULT_TIMEOUT = object()
25DEFAULT_TIMEOUT_DURATION = 120.0
26
27
28class FailureError(Exception):
29    """
30    DEPRECATED in Twisted 8.0. This exception is never raised by Trial.
31
32    Wraps around a Failure so it can get re-raised as an Exception.
33    """
34
35    def __init__(self, failure):
36        Exception.__init__(self)
37        self.original = failure
38
39
40
41class DirtyReactorWarning(Warning):
42    """
43    DEPRECATED in Twisted 8.0.
44
45    This warning is not used by Trial any more.
46    """
47
48
49
50class DirtyReactorError(Exception):
51    """
52    DEPRECATED in Twisted 8.0. This is not used by Trial any more.
53    """
54
55    def __init__(self, msg):
56        Exception.__init__(self, self._getMessage(msg))
57
58    def _getMessage(self, msg):
59        return ("reactor left in unclean state, the following Selectables "
60                "were left over: %s" % (msg,))
61
62
63
64
65class PendingTimedCallsError(DirtyReactorError):
66    """
67    DEPRECATED in Twisted 8.0. This is not used by Trial any more.
68    """
69
70    def _getMessage(self, msg):
71        return ("pendingTimedCalls still pending (consider setting "
72                "twisted.internet.base.DelayedCall.debug = True): %s" % (msg,))
73
74
75
76class DirtyReactorAggregateError(Exception):
77    """
78    Passed to L{twisted.trial.itrial.IReporter.addError} when the reactor is
79    left in an unclean state after a test.
80
81    @ivar delayedCalls: The L{DelayedCall} objects which weren't cleaned up.
82    @ivar selectables: The selectables which weren't cleaned up.
83    """
84
85    def __init__(self, delayedCalls, selectables=None):
86        self.delayedCalls = delayedCalls
87        self.selectables = selectables
88
89    def __str__(self):
90        """
91        Return a multi-line message describing all of the unclean state.
92        """
93        msg = "Reactor was unclean."
94        if self.delayedCalls:
95            msg += ("\nDelayedCalls: (set "
96                    "twisted.internet.base.DelayedCall.debug = True to "
97                    "debug)\n")
98            msg += "\n".join(map(str, self.delayedCalls))
99        if self.selectables:
100            msg += "\nSelectables:\n"
101            msg += "\n".join(map(str, self.selectables))
102        return msg
103
104
105
106class _Janitor(object):
107    """
108    The guy that cleans up after you.
109
110    @ivar test: The L{TestCase} to report errors about.
111    @ivar result: The L{IReporter} to report errors to.
112    @ivar reactor: The reactor to use. If None, the global reactor
113        will be used.
114    """
115    def __init__(self, test, result, reactor=None):
116        """
117        @param test: See L{_Janitor.test}.
118        @param result: See L{_Janitor.result}.
119        @param reactor: See L{_Janitor.reactor}.
120        """
121        self.test = test
122        self.result = result
123        self.reactor = reactor
124
125
126    def postCaseCleanup(self):
127        """
128        Called by L{unittest.TestCase} after a test to catch any logged errors
129        or pending L{DelayedCall}s.
130        """
131        calls = self._cleanPending()
132        if calls:
133            aggregate = DirtyReactorAggregateError(calls)
134            self.result.addError(self.test, Failure(aggregate))
135            return False
136        return True
137
138
139    def postClassCleanup(self):
140        """
141        Called by L{unittest.TestCase} after the last test in a C{TestCase}
142        subclass. Ensures the reactor is clean by murdering the threadpool,
143        catching any pending L{DelayedCall}s, open sockets etc.
144        """
145        selectables = self._cleanReactor()
146        calls = self._cleanPending()
147        if selectables or calls:
148            aggregate = DirtyReactorAggregateError(calls, selectables)
149            self.result.addError(self.test, Failure(aggregate))
150        self._cleanThreads()
151
152
153    def _getReactor(self):
154        """
155        Get either the passed-in reactor or the global reactor.
156        """
157        if self.reactor is not None:
158            reactor = self.reactor
159        else:
160            from twisted.internet import reactor
161        return reactor
162
163
164    def _cleanPending(self):
165        """
166        Cancel all pending calls and return their string representations.
167        """
168        reactor = self._getReactor()
169
170        # flush short-range timers
171        reactor.iterate(0)
172        reactor.iterate(0)
173
174        delayedCallStrings = []
175        for p in reactor.getDelayedCalls():
176            if p.active():
177                delayedString = str(p)
178                p.cancel()
179            else:
180                print "WEIRDNESS! pending timed call not active!"
181            delayedCallStrings.append(delayedString)
182        return delayedCallStrings
183    _cleanPending = utils.suppressWarnings(
184        _cleanPending, (('ignore',), {'category': DeprecationWarning,
185                                      'message':
186                                      r'reactor\.iterate cannot be used.*'}))
187
188    def _cleanThreads(self):
189        reactor = self._getReactor()
190        if interfaces.IReactorThreads.providedBy(reactor):
191            reactor.suggestThreadPoolSize(0)
192            if getattr(reactor, 'threadpool', None) is not None:
193                try:
194                    reactor.removeSystemEventTrigger(
195                        reactor.threadpoolShutdownID)
196                except KeyError:
197                    pass
198                # Remove the threadpool, and let the reactor put it back again
199                # later like a good boy
200                reactor._stopThreadPool()
201
202    def _cleanReactor(self):
203        """
204        Remove all selectables from the reactor, kill any of them that were
205        processes, and return their string representation.
206        """
207        reactor = self._getReactor()
208        selectableStrings = []
209        for sel in reactor.removeAll():
210            if interfaces.IProcessTransport.providedBy(sel):
211                sel.signalProcess('KILL')
212            selectableStrings.append(repr(sel))
213        return selectableStrings
214
215
216def suppress(action='ignore', **kwarg):
217    """
218    Sets up the .suppress tuple properly, pass options to this method as you
219    would the stdlib warnings.filterwarnings()
220
221    So, to use this with a .suppress magic attribute you would do the
222    following:
223
224      >>> from twisted.trial import unittest, util
225      >>> import warnings
226      >>>
227      >>> class TestFoo(unittest.TestCase):
228      ...     def testFooBar(self):
229      ...         warnings.warn("i am deprecated", DeprecationWarning)
230      ...     testFooBar.suppress = [util.suppress(message='i am deprecated')]
231      ...
232      >>>
233
234    Note that as with the todo and timeout attributes: the module level
235    attribute acts as a default for the class attribute which acts as a default
236    for the method attribute. The suppress attribute can be overridden at any
237    level by specifying C{.suppress = []}
238    """
239    return ((action,), kwarg)
240
241
242def profiled(f, outputFile):
243    def _(*args, **kwargs):
244        if sys.version_info[0:2] != (2, 4):
245            import profile
246            prof = profile.Profile()
247            try:
248                result = prof.runcall(f, *args, **kwargs)
249                prof.dump_stats(outputFile)
250            except SystemExit:
251                pass
252            prof.print_stats()
253            return result
254        else: # use hotshot, profile is broken in 2.4
255            import hotshot.stats
256            prof = hotshot.Profile(outputFile)
257            try:
258                return prof.runcall(f, *args, **kwargs)
259            finally:
260                stats = hotshot.stats.load(outputFile)
261                stats.strip_dirs()
262                stats.sort_stats('cum')   # 'time'
263                stats.print_stats(100)
264    return _
265
266
267def getPythonContainers(meth):
268    """Walk up the Python tree from method 'meth', finding its class, its module
269    and all containing packages."""
270    containers = []
271    containers.append(meth.im_class)
272    moduleName = meth.im_class.__module__
273    while moduleName is not None:
274        module = sys.modules.get(moduleName, None)
275        if module is None:
276            module = __import__(moduleName)
277        containers.append(module)
278        moduleName = getattr(module, '__module__', None)
279    return containers
280
281
282_DEFAULT = object()
283def acquireAttribute(objects, attr, default=_DEFAULT):
284    """Go through the list 'objects' sequentially until we find one which has
285    attribute 'attr', then return the value of that attribute.  If not found,
286    return 'default' if set, otherwise, raise AttributeError. """
287    for obj in objects:
288        if hasattr(obj, attr):
289            return getattr(obj, attr)
290    if default is not _DEFAULT:
291        return default
292    raise AttributeError('attribute %r not found in %r' % (attr, objects))
293
294
295def findObject(name):
296    """Get a fully-named package, module, module-global object or attribute.
297    Forked from twisted.python.reflect.namedAny.
298
299    Returns a tuple of (bool, obj).  If bool is True, the named object exists
300    and is returned as obj.  If bool is False, the named object does not exist
301    and the value of obj is unspecified.
302    """
303    names = name.split('.')
304    topLevelPackage = None
305    moduleNames = names[:]
306    while not topLevelPackage:
307        trialname = '.'.join(moduleNames)
308        if len(trialname) == 0:
309            return (False, None)
310        try:
311            topLevelPackage = __import__(trialname)
312        except ImportError:
313            # if the ImportError happened in the module being imported,
314            # this is a failure that should be handed to our caller.
315            # count stack frames to tell the difference.
316            exc_info = sys.exc_info()
317            if len(traceback.extract_tb(exc_info[2])) > 1:
318                try:
319                    # Clean up garbage left in sys.modules.
320                    del sys.modules[trialname]
321                except KeyError:
322                    # Python 2.4 has fixed this.  Yay!
323                    pass
324                raise exc_info[0], exc_info[1], exc_info[2]
325            moduleNames.pop()
326    obj = topLevelPackage
327    for n in names[1:]:
328        try:
329            obj = getattr(obj, n)
330        except AttributeError:
331            return (False, obj)
332    return (True, obj)
333
334
335
336def _runSequentially(callables, stopOnFirstError=False):
337    """
338    Run the given callables one after the other. If a callable returns a
339    Deferred, wait until it has finished before running the next callable.
340
341    @param callables: An iterable of callables that take no parameters.
342
343    @param stopOnFirstError: If True, then stop running callables as soon as
344        one raises an exception or fires an errback. False by default.
345
346    @return: A L{Deferred} that fires a list of C{(flag, value)} tuples. Each
347        tuple will be either C{(SUCCESS, <return value>)} or C{(FAILURE,
348        <Failure>)}.
349    """
350    results = []
351    for f in callables:
352        d = defer.maybeDeferred(f)
353        thing = defer.waitForDeferred(d)
354        yield thing
355        try:
356            results.append((defer.SUCCESS, thing.getResult()))
357        except:
358            results.append((defer.FAILURE, Failure()))
359            if stopOnFirstError:
360                break
361    yield results
362_runSequentially = defer.deferredGenerator(_runSequentially)
363
364
365
366__all__ = ['FailureError', 'DirtyReactorWarning', 'DirtyReactorError',
367           'PendingTimedCallsError', 'runSequentially']
Note: See TracBrowser for help on using the browser.