| 1 | |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 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 | |
|---|
| 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 | |
|---|
| 199 | |
|---|
| 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: |
|---|
| 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') |
|---|
| 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 | |
|---|
| 314 | |
|---|
| 315 | |
|---|
| 316 | exc_info = sys.exc_info() |
|---|
| 317 | if len(traceback.extract_tb(exc_info[2])) > 1: |
|---|
| 318 | try: |
|---|
| 319 | |
|---|
| 320 | del sys.modules[trialname] |
|---|
| 321 | except KeyError: |
|---|
| 322 | |
|---|
| 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'] |
|---|