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 |