| OLD | NEW |
| (Empty) |
| 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 """ | |
| 7 A collection of utility functions and classes, used internally by Trial. | |
| 8 | |
| 9 This code is for Trial's internal use. Do NOT use this code if you are writing | |
| 10 tests. It is subject to change at the Trial maintainer's whim. There is | |
| 11 nothing here in this module for you to use unless you are maintaining Trial. | |
| 12 | |
| 13 Any non-Trial Twisted code that uses this module will be shot. | |
| 14 | |
| 15 Maintainer: U{Jonathan Lange<mailto:jml@twistedmatrix.com>} | |
| 16 """ | |
| 17 | |
| 18 import traceback, sys | |
| 19 | |
| 20 from twisted.internet import defer, utils, interfaces | |
| 21 from twisted.python.failure import Failure | |
| 22 | |
| 23 | |
| 24 DEFAULT_TIMEOUT = object() | |
| 25 DEFAULT_TIMEOUT_DURATION = 120.0 | |
| 26 | |
| 27 | |
| 28 class 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 | |
| 41 class DirtyReactorWarning(Warning): | |
| 42 """ | |
| 43 DEPRECATED in Twisted 8.0. | |
| 44 | |
| 45 This warning is not used by Trial any more. | |
| 46 """ | |
| 47 | |
| 48 | |
| 49 | |
| 50 class 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 | |
| 65 class 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 | |
| 76 class 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 | |
| 106 class _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 | |
| 216 def 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 | |
| 242 def 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 | |
| 267 def 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() | |
| 283 def 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 | |
| 295 def 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 | |
| 336 def _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'] | |
| OLD | NEW |