Merge lp:~jml/testtools/better-twisted-debugging into lp:~testtools-committers/testtools/trunk
- better-twisted-debugging
- Merge into trunk
Proposed by
Jonathan Lange
Status: | Merged |
---|---|
Merged at revision: | 149 |
Proposed branch: | lp:~jml/testtools/better-twisted-debugging |
Merge into: | lp:~testtools-committers/testtools/trunk |
Diff against target: |
220 lines (+96/-32) 3 files modified
testtools/_spinner.py (+46/-28) testtools/deferredruntest.py (+9/-4) testtools/tests/test_deferredruntest.py (+41/-0) |
To merge this branch: | bzr merge lp:~jml/testtools/better-twisted-debugging |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
testtools developers | Pending | ||
Review via email: mp+42278@code.launchpad.net |
Commit message
Description of the change
Better debugging for Twisted tests.
To post a comment you must log in.
- 150. By Jonathan Lange
-
Make debug available from the factory.
Revision history for this message
Robert Collins (lifeless) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'testtools/_spinner.py' | |||
2 | --- testtools/_spinner.py 2010-11-21 16:43:26 +0000 | |||
3 | +++ testtools/_spinner.py 2010-11-30 17:15:41 +0000 | |||
4 | @@ -20,7 +20,10 @@ | |||
5 | 20 | 20 | ||
6 | 21 | import signal | 21 | import signal |
7 | 22 | 22 | ||
8 | 23 | from testtools.monkey import MonkeyPatcher | ||
9 | 24 | |||
10 | 23 | from twisted.internet import defer | 25 | from twisted.internet import defer |
11 | 26 | from twisted.internet.base import DelayedCall | ||
12 | 24 | from twisted.internet.interfaces import IReactorThreads | 27 | from twisted.internet.interfaces import IReactorThreads |
13 | 25 | from twisted.python.failure import Failure | 28 | from twisted.python.failure import Failure |
14 | 26 | from twisted.python.util import mergeFunctionMetadata | 29 | from twisted.python.util import mergeFunctionMetadata |
15 | @@ -165,13 +168,20 @@ | |||
16 | 165 | # the ideal, and it actually works for many cases. | 168 | # the ideal, and it actually works for many cases. |
17 | 166 | _OBLIGATORY_REACTOR_ITERATIONS = 0 | 169 | _OBLIGATORY_REACTOR_ITERATIONS = 0 |
18 | 167 | 170 | ||
20 | 168 | def __init__(self, reactor): | 171 | def __init__(self, reactor, debug=False): |
21 | 172 | """Construct a Spinner. | ||
22 | 173 | |||
23 | 174 | :param reactor: A Twisted reactor. | ||
24 | 175 | :param debug: Whether or not to enable Twisted's debugging. Defaults | ||
25 | 176 | to False. | ||
26 | 177 | """ | ||
27 | 169 | self._reactor = reactor | 178 | self._reactor = reactor |
28 | 170 | self._timeout_call = None | 179 | self._timeout_call = None |
29 | 171 | self._success = self._UNSET | 180 | self._success = self._UNSET |
30 | 172 | self._failure = self._UNSET | 181 | self._failure = self._UNSET |
31 | 173 | self._saved_signals = [] | 182 | self._saved_signals = [] |
32 | 174 | self._junk = [] | 183 | self._junk = [] |
33 | 184 | self._debug = debug | ||
34 | 175 | 185 | ||
35 | 176 | def _cancel_timeout(self): | 186 | def _cancel_timeout(self): |
36 | 177 | if self._timeout_call: | 187 | if self._timeout_call: |
37 | @@ -270,30 +280,38 @@ | |||
38 | 270 | :return: Whatever is at the end of the function's callback chain. If | 280 | :return: Whatever is at the end of the function's callback chain. If |
39 | 271 | it's an error, then raise that. | 281 | it's an error, then raise that. |
40 | 272 | """ | 282 | """ |
68 | 273 | junk = self.get_junk() | 283 | debug = MonkeyPatcher() |
69 | 274 | if junk: | 284 | if self._debug: |
70 | 275 | raise StaleJunkError(junk) | 285 | debug.add_patch(defer.Deferred, 'debug', True) |
71 | 276 | self._save_signals() | 286 | debug.add_patch(DelayedCall, 'debug', True) |
72 | 277 | self._timeout_call = self._reactor.callLater( | 287 | debug.patch() |
73 | 278 | timeout, self._timed_out, function, timeout) | 288 | try: |
74 | 279 | # Calling 'stop' on the reactor will make it impossible to re-start | 289 | junk = self.get_junk() |
75 | 280 | # the reactor. Since the default signal handlers for TERM, BREAK and | 290 | if junk: |
76 | 281 | # INT all call reactor.stop(), we'll patch it over with crash. | 291 | raise StaleJunkError(junk) |
77 | 282 | # XXX: It might be a better idea to either install custom signal | 292 | self._save_signals() |
78 | 283 | # handlers or to override the methods that are Twisted's signal | 293 | self._timeout_call = self._reactor.callLater( |
79 | 284 | # handlers. | 294 | timeout, self._timed_out, function, timeout) |
80 | 285 | stop, self._reactor.stop = self._reactor.stop, self._reactor.crash | 295 | # Calling 'stop' on the reactor will make it impossible to |
81 | 286 | def run_function(): | 296 | # re-start the reactor. Since the default signal handlers for |
82 | 287 | d = defer.maybeDeferred(function, *args, **kwargs) | 297 | # TERM, BREAK and INT all call reactor.stop(), we'll patch it over |
83 | 288 | d.addCallbacks(self._got_success, self._got_failure) | 298 | # with crash. XXX: It might be a better idea to either install |
84 | 289 | d.addBoth(self._stop_reactor) | 299 | # custom signal handlers or to override the methods that are |
85 | 290 | try: | 300 | # Twisted's signal handlers. |
86 | 291 | self._reactor.callWhenRunning(run_function) | 301 | stop, self._reactor.stop = self._reactor.stop, self._reactor.crash |
87 | 292 | self._reactor.run() | 302 | def run_function(): |
88 | 293 | finally: | 303 | d = defer.maybeDeferred(function, *args, **kwargs) |
89 | 294 | self._reactor.stop = stop | 304 | d.addCallbacks(self._got_success, self._got_failure) |
90 | 295 | self._restore_signals() | 305 | d.addBoth(self._stop_reactor) |
91 | 296 | try: | 306 | try: |
92 | 297 | return self._get_result() | 307 | self._reactor.callWhenRunning(run_function) |
93 | 298 | finally: | 308 | self._reactor.run() |
94 | 299 | self._clean() | 309 | finally: |
95 | 310 | self._reactor.stop = stop | ||
96 | 311 | self._restore_signals() | ||
97 | 312 | try: | ||
98 | 313 | return self._get_result() | ||
99 | 314 | finally: | ||
100 | 315 | self._clean() | ||
101 | 316 | finally: | ||
102 | 317 | debug.restore() | ||
103 | 300 | 318 | ||
104 | === modified file 'testtools/deferredruntest.py' | |||
105 | --- testtools/deferredruntest.py 2010-11-25 11:32:24 +0000 | |||
106 | +++ testtools/deferredruntest.py 2010-11-30 17:15:41 +0000 | |||
107 | @@ -91,7 +91,8 @@ | |||
108 | 91 | This is highly experimental code. Use at your own risk. | 91 | This is highly experimental code. Use at your own risk. |
109 | 92 | """ | 92 | """ |
110 | 93 | 93 | ||
112 | 94 | def __init__(self, case, handlers=None, reactor=None, timeout=0.005): | 94 | def __init__(self, case, handlers=None, reactor=None, timeout=0.005, |
113 | 95 | debug=False): | ||
114 | 95 | """Construct an `AsynchronousDeferredRunTest`. | 96 | """Construct an `AsynchronousDeferredRunTest`. |
115 | 96 | 97 | ||
116 | 97 | :param case: The `testtools.TestCase` to run. | 98 | :param case: The `testtools.TestCase` to run. |
117 | @@ -102,22 +103,26 @@ | |||
118 | 102 | default reactor. | 103 | default reactor. |
119 | 103 | :param timeout: The maximum time allowed for running a test. The | 104 | :param timeout: The maximum time allowed for running a test. The |
120 | 104 | default is 0.005s. | 105 | default is 0.005s. |
121 | 106 | :param debug: Whether or not to enable Twisted's debugging. Use this | ||
122 | 107 | to get information about unhandled Deferreds and left-over | ||
123 | 108 | DelayedCalls. Defaults to False. | ||
124 | 105 | """ | 109 | """ |
125 | 106 | super(AsynchronousDeferredRunTest, self).__init__(case, handlers) | 110 | super(AsynchronousDeferredRunTest, self).__init__(case, handlers) |
126 | 107 | if reactor is None: | 111 | if reactor is None: |
127 | 108 | from twisted.internet import reactor | 112 | from twisted.internet import reactor |
128 | 109 | self._reactor = reactor | 113 | self._reactor = reactor |
129 | 110 | self._timeout = timeout | 114 | self._timeout = timeout |
130 | 115 | self._debug = debug | ||
131 | 111 | 116 | ||
132 | 112 | @classmethod | 117 | @classmethod |
134 | 113 | def make_factory(cls, reactor=None, timeout=0.005): | 118 | def make_factory(cls, reactor=None, timeout=0.005, debug=False): |
135 | 114 | """Make a factory that conforms to the RunTest factory interface.""" | 119 | """Make a factory that conforms to the RunTest factory interface.""" |
136 | 115 | # This is horrible, but it means that the return value of the method | 120 | # This is horrible, but it means that the return value of the method |
137 | 116 | # will be able to be assigned to a class variable *and* also be | 121 | # will be able to be assigned to a class variable *and* also be |
138 | 117 | # invoked directly. | 122 | # invoked directly. |
139 | 118 | class AsynchronousDeferredRunTestFactory: | 123 | class AsynchronousDeferredRunTestFactory: |
140 | 119 | def __call__(self, case, handlers=None): | 124 | def __call__(self, case, handlers=None): |
142 | 120 | return cls(case, handlers, reactor, timeout) | 125 | return cls(case, handlers, reactor, timeout, debug) |
143 | 121 | return AsynchronousDeferredRunTestFactory() | 126 | return AsynchronousDeferredRunTestFactory() |
144 | 122 | 127 | ||
145 | 123 | @defer.deferredGenerator | 128 | @defer.deferredGenerator |
146 | @@ -143,7 +148,7 @@ | |||
147 | 143 | 148 | ||
148 | 144 | def _make_spinner(self): | 149 | def _make_spinner(self): |
149 | 145 | """Make the `Spinner` to be used to run the tests.""" | 150 | """Make the `Spinner` to be used to run the tests.""" |
151 | 146 | return Spinner(self._reactor) | 151 | return Spinner(self._reactor, debug=self._debug) |
152 | 147 | 152 | ||
153 | 148 | def _run_deferred(self): | 153 | def _run_deferred(self): |
154 | 149 | """Run the test, assuming everything in it is Deferred-returning. | 154 | """Run the test, assuming everything in it is Deferred-returning. |
155 | 150 | 155 | ||
156 | === modified file 'testtools/tests/test_deferredruntest.py' | |||
157 | --- testtools/tests/test_deferredruntest.py 2010-11-27 11:22:15 +0000 | |||
158 | +++ testtools/tests/test_deferredruntest.py 2010-11-30 17:15:41 +0000 | |||
159 | @@ -34,6 +34,7 @@ | |||
160 | 34 | defer = try_import('twisted.internet.defer') | 34 | defer = try_import('twisted.internet.defer') |
161 | 35 | failure = try_import('twisted.python.failure') | 35 | failure = try_import('twisted.python.failure') |
162 | 36 | log = try_import('twisted.python.log') | 36 | log = try_import('twisted.python.log') |
163 | 37 | DelayedCall = try_import('twisted.internet.base.DelayedCall') | ||
164 | 37 | 38 | ||
165 | 38 | 39 | ||
166 | 39 | class X(object): | 40 | class X(object): |
167 | @@ -491,6 +492,17 @@ | |||
168 | 491 | self.assertIs(self, runner.case) | 492 | self.assertIs(self, runner.case) |
169 | 492 | self.assertEqual([handler], runner.handlers) | 493 | self.assertEqual([handler], runner.handlers) |
170 | 493 | 494 | ||
171 | 495 | def test_convenient_construction_default_debugging(self): | ||
172 | 496 | # As a convenience method, AsynchronousDeferredRunTest has a | ||
173 | 497 | # classmethod that returns an AsynchronousDeferredRunTest | ||
174 | 498 | # factory. This factory has the same API as the RunTest constructor. | ||
175 | 499 | handler = object() | ||
176 | 500 | factory = AsynchronousDeferredRunTest.make_factory(debug=True) | ||
177 | 501 | runner = factory(self, [handler]) | ||
178 | 502 | self.assertIs(self, runner.case) | ||
179 | 503 | self.assertEqual([handler], runner.handlers) | ||
180 | 504 | self.assertEqual(True, runner._debug) | ||
181 | 505 | |||
182 | 494 | def test_deferred_error(self): | 506 | def test_deferred_error(self): |
183 | 495 | class SomeTest(TestCase): | 507 | class SomeTest(TestCase): |
184 | 496 | def test_something(self): | 508 | def test_something(self): |
185 | @@ -606,6 +618,35 @@ | |||
186 | 606 | error = result._events[1][2] | 618 | error = result._events[1][2] |
187 | 607 | self.assertThat(error, KeysEqual('traceback', 'twisted-log')) | 619 | self.assertThat(error, KeysEqual('traceback', 'twisted-log')) |
188 | 608 | 620 | ||
189 | 621 | def test_debugging_unchanged_during_test_by_default(self): | ||
190 | 622 | debugging = [(defer.Deferred.debug, DelayedCall.debug)] | ||
191 | 623 | class SomeCase(TestCase): | ||
192 | 624 | def test_debugging_enabled(self): | ||
193 | 625 | debugging.append((defer.Deferred.debug, DelayedCall.debug)) | ||
194 | 626 | test = SomeCase('test_debugging_enabled') | ||
195 | 627 | runner = AsynchronousDeferredRunTest( | ||
196 | 628 | test, handlers=test.exception_handlers, | ||
197 | 629 | reactor=self.make_reactor(), timeout=self.make_timeout()) | ||
198 | 630 | runner.run(self.make_result()) | ||
199 | 631 | self.assertEqual(debugging[0], debugging[1]) | ||
200 | 632 | |||
201 | 633 | def test_debugging_enabled_during_test_with_debug_flag(self): | ||
202 | 634 | self.patch(defer.Deferred, 'debug', False) | ||
203 | 635 | self.patch(DelayedCall, 'debug', False) | ||
204 | 636 | debugging = [] | ||
205 | 637 | class SomeCase(TestCase): | ||
206 | 638 | def test_debugging_enabled(self): | ||
207 | 639 | debugging.append((defer.Deferred.debug, DelayedCall.debug)) | ||
208 | 640 | test = SomeCase('test_debugging_enabled') | ||
209 | 641 | runner = AsynchronousDeferredRunTest( | ||
210 | 642 | test, handlers=test.exception_handlers, | ||
211 | 643 | reactor=self.make_reactor(), timeout=self.make_timeout(), | ||
212 | 644 | debug=True) | ||
213 | 645 | runner.run(self.make_result()) | ||
214 | 646 | self.assertEqual([(True, True)], debugging) | ||
215 | 647 | self.assertEqual(False, defer.Deferred.debug) | ||
216 | 648 | self.assertEqual(False, defer.Deferred.debug) | ||
217 | 649 | |||
218 | 609 | 650 | ||
219 | 610 | class TestAssertFailsWith(NeedsTwistedTestCase): | 651 | class TestAssertFailsWith(NeedsTwistedTestCase): |
220 | 611 | """Tests for `assert_fails_with`.""" | 652 | """Tests for `assert_fails_with`.""" |
Strictly speaking, this:
+ finally: signals( )
+ self._reactor.stop = stop
+ self._restore_
needs two finally clauses.
Otherwise its grand.