Merge lp:~jml/testtools/better-twisted-debugging into lp:~testtools-committers/testtools/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
Reviewer Review Type Date Requested Status
testtools developers Pending
Review via email: mp+42278@code.launchpad.net

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 :

Strictly speaking, this:

+ finally:
+ self._reactor.stop = stop
+ self._restore_signals()

needs two finally clauses.

Otherwise its grand.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'testtools/_spinner.py'
--- testtools/_spinner.py 2010-11-21 16:43:26 +0000
+++ testtools/_spinner.py 2010-11-30 17:15:41 +0000
@@ -20,7 +20,10 @@
2020
21import signal21import signal
2222
23from testtools.monkey import MonkeyPatcher
24
23from twisted.internet import defer25from twisted.internet import defer
26from twisted.internet.base import DelayedCall
24from twisted.internet.interfaces import IReactorThreads27from twisted.internet.interfaces import IReactorThreads
25from twisted.python.failure import Failure28from twisted.python.failure import Failure
26from twisted.python.util import mergeFunctionMetadata29from twisted.python.util import mergeFunctionMetadata
@@ -165,13 +168,20 @@
165 # the ideal, and it actually works for many cases.168 # the ideal, and it actually works for many cases.
166 _OBLIGATORY_REACTOR_ITERATIONS = 0169 _OBLIGATORY_REACTOR_ITERATIONS = 0
167170
168 def __init__(self, reactor):171 def __init__(self, reactor, debug=False):
172 """Construct a Spinner.
173
174 :param reactor: A Twisted reactor.
175 :param debug: Whether or not to enable Twisted's debugging. Defaults
176 to False.
177 """
169 self._reactor = reactor178 self._reactor = reactor
170 self._timeout_call = None179 self._timeout_call = None
171 self._success = self._UNSET180 self._success = self._UNSET
172 self._failure = self._UNSET181 self._failure = self._UNSET
173 self._saved_signals = []182 self._saved_signals = []
174 self._junk = []183 self._junk = []
184 self._debug = debug
175185
176 def _cancel_timeout(self):186 def _cancel_timeout(self):
177 if self._timeout_call:187 if self._timeout_call:
@@ -270,30 +280,38 @@
270 :return: Whatever is at the end of the function's callback chain. If280 :return: Whatever is at the end of the function's callback chain. If
271 it's an error, then raise that.281 it's an error, then raise that.
272 """282 """
273 junk = self.get_junk()283 debug = MonkeyPatcher()
274 if junk:284 if self._debug:
275 raise StaleJunkError(junk)285 debug.add_patch(defer.Deferred, 'debug', True)
276 self._save_signals()286 debug.add_patch(DelayedCall, 'debug', True)
277 self._timeout_call = self._reactor.callLater(287 debug.patch()
278 timeout, self._timed_out, function, timeout)288 try:
279 # Calling 'stop' on the reactor will make it impossible to re-start289 junk = self.get_junk()
280 # the reactor. Since the default signal handlers for TERM, BREAK and290 if junk:
281 # INT all call reactor.stop(), we'll patch it over with crash.291 raise StaleJunkError(junk)
282 # XXX: It might be a better idea to either install custom signal292 self._save_signals()
283 # handlers or to override the methods that are Twisted's signal293 self._timeout_call = self._reactor.callLater(
284 # handlers.294 timeout, self._timed_out, function, timeout)
285 stop, self._reactor.stop = self._reactor.stop, self._reactor.crash295 # Calling 'stop' on the reactor will make it impossible to
286 def run_function():296 # re-start the reactor. Since the default signal handlers for
287 d = defer.maybeDeferred(function, *args, **kwargs)297 # TERM, BREAK and INT all call reactor.stop(), we'll patch it over
288 d.addCallbacks(self._got_success, self._got_failure)298 # with crash. XXX: It might be a better idea to either install
289 d.addBoth(self._stop_reactor)299 # custom signal handlers or to override the methods that are
290 try:300 # Twisted's signal handlers.
291 self._reactor.callWhenRunning(run_function)301 stop, self._reactor.stop = self._reactor.stop, self._reactor.crash
292 self._reactor.run()302 def run_function():
293 finally:303 d = defer.maybeDeferred(function, *args, **kwargs)
294 self._reactor.stop = stop304 d.addCallbacks(self._got_success, self._got_failure)
295 self._restore_signals()305 d.addBoth(self._stop_reactor)
296 try:306 try:
297 return self._get_result()307 self._reactor.callWhenRunning(run_function)
298 finally:308 self._reactor.run()
299 self._clean()309 finally:
310 self._reactor.stop = stop
311 self._restore_signals()
312 try:
313 return self._get_result()
314 finally:
315 self._clean()
316 finally:
317 debug.restore()
300318
=== modified file 'testtools/deferredruntest.py'
--- testtools/deferredruntest.py 2010-11-25 11:32:24 +0000
+++ testtools/deferredruntest.py 2010-11-30 17:15:41 +0000
@@ -91,7 +91,8 @@
91 This is highly experimental code. Use at your own risk.91 This is highly experimental code. Use at your own risk.
92 """92 """
9393
94 def __init__(self, case, handlers=None, reactor=None, timeout=0.005):94 def __init__(self, case, handlers=None, reactor=None, timeout=0.005,
95 debug=False):
95 """Construct an `AsynchronousDeferredRunTest`.96 """Construct an `AsynchronousDeferredRunTest`.
9697
97 :param case: The `testtools.TestCase` to run.98 :param case: The `testtools.TestCase` to run.
@@ -102,22 +103,26 @@
102 default reactor.103 default reactor.
103 :param timeout: The maximum time allowed for running a test. The104 :param timeout: The maximum time allowed for running a test. The
104 default is 0.005s.105 default is 0.005s.
106 :param debug: Whether or not to enable Twisted's debugging. Use this
107 to get information about unhandled Deferreds and left-over
108 DelayedCalls. Defaults to False.
105 """109 """
106 super(AsynchronousDeferredRunTest, self).__init__(case, handlers)110 super(AsynchronousDeferredRunTest, self).__init__(case, handlers)
107 if reactor is None:111 if reactor is None:
108 from twisted.internet import reactor112 from twisted.internet import reactor
109 self._reactor = reactor113 self._reactor = reactor
110 self._timeout = timeout114 self._timeout = timeout
115 self._debug = debug
111116
112 @classmethod117 @classmethod
113 def make_factory(cls, reactor=None, timeout=0.005):118 def make_factory(cls, reactor=None, timeout=0.005, debug=False):
114 """Make a factory that conforms to the RunTest factory interface."""119 """Make a factory that conforms to the RunTest factory interface."""
115 # This is horrible, but it means that the return value of the method120 # This is horrible, but it means that the return value of the method
116 # will be able to be assigned to a class variable *and* also be121 # will be able to be assigned to a class variable *and* also be
117 # invoked directly.122 # invoked directly.
118 class AsynchronousDeferredRunTestFactory:123 class AsynchronousDeferredRunTestFactory:
119 def __call__(self, case, handlers=None):124 def __call__(self, case, handlers=None):
120 return cls(case, handlers, reactor, timeout)125 return cls(case, handlers, reactor, timeout, debug)
121 return AsynchronousDeferredRunTestFactory()126 return AsynchronousDeferredRunTestFactory()
122127
123 @defer.deferredGenerator128 @defer.deferredGenerator
@@ -143,7 +148,7 @@
143148
144 def _make_spinner(self):149 def _make_spinner(self):
145 """Make the `Spinner` to be used to run the tests."""150 """Make the `Spinner` to be used to run the tests."""
146 return Spinner(self._reactor)151 return Spinner(self._reactor, debug=self._debug)
147152
148 def _run_deferred(self):153 def _run_deferred(self):
149 """Run the test, assuming everything in it is Deferred-returning.154 """Run the test, assuming everything in it is Deferred-returning.
150155
=== modified file 'testtools/tests/test_deferredruntest.py'
--- testtools/tests/test_deferredruntest.py 2010-11-27 11:22:15 +0000
+++ testtools/tests/test_deferredruntest.py 2010-11-30 17:15:41 +0000
@@ -34,6 +34,7 @@
34defer = try_import('twisted.internet.defer')34defer = try_import('twisted.internet.defer')
35failure = try_import('twisted.python.failure')35failure = try_import('twisted.python.failure')
36log = try_import('twisted.python.log')36log = try_import('twisted.python.log')
37DelayedCall = try_import('twisted.internet.base.DelayedCall')
3738
3839
39class X(object):40class X(object):
@@ -491,6 +492,17 @@
491 self.assertIs(self, runner.case)492 self.assertIs(self, runner.case)
492 self.assertEqual([handler], runner.handlers)493 self.assertEqual([handler], runner.handlers)
493494
495 def test_convenient_construction_default_debugging(self):
496 # As a convenience method, AsynchronousDeferredRunTest has a
497 # classmethod that returns an AsynchronousDeferredRunTest
498 # factory. This factory has the same API as the RunTest constructor.
499 handler = object()
500 factory = AsynchronousDeferredRunTest.make_factory(debug=True)
501 runner = factory(self, [handler])
502 self.assertIs(self, runner.case)
503 self.assertEqual([handler], runner.handlers)
504 self.assertEqual(True, runner._debug)
505
494 def test_deferred_error(self):506 def test_deferred_error(self):
495 class SomeTest(TestCase):507 class SomeTest(TestCase):
496 def test_something(self):508 def test_something(self):
@@ -606,6 +618,35 @@
606 error = result._events[1][2]618 error = result._events[1][2]
607 self.assertThat(error, KeysEqual('traceback', 'twisted-log'))619 self.assertThat(error, KeysEqual('traceback', 'twisted-log'))
608620
621 def test_debugging_unchanged_during_test_by_default(self):
622 debugging = [(defer.Deferred.debug, DelayedCall.debug)]
623 class SomeCase(TestCase):
624 def test_debugging_enabled(self):
625 debugging.append((defer.Deferred.debug, DelayedCall.debug))
626 test = SomeCase('test_debugging_enabled')
627 runner = AsynchronousDeferredRunTest(
628 test, handlers=test.exception_handlers,
629 reactor=self.make_reactor(), timeout=self.make_timeout())
630 runner.run(self.make_result())
631 self.assertEqual(debugging[0], debugging[1])
632
633 def test_debugging_enabled_during_test_with_debug_flag(self):
634 self.patch(defer.Deferred, 'debug', False)
635 self.patch(DelayedCall, 'debug', False)
636 debugging = []
637 class SomeCase(TestCase):
638 def test_debugging_enabled(self):
639 debugging.append((defer.Deferred.debug, DelayedCall.debug))
640 test = SomeCase('test_debugging_enabled')
641 runner = AsynchronousDeferredRunTest(
642 test, handlers=test.exception_handlers,
643 reactor=self.make_reactor(), timeout=self.make_timeout(),
644 debug=True)
645 runner.run(self.make_result())
646 self.assertEqual([(True, True)], debugging)
647 self.assertEqual(False, defer.Deferred.debug)
648 self.assertEqual(False, defer.Deferred.debug)
649
609650
610class TestAssertFailsWith(NeedsTwistedTestCase):651class TestAssertFailsWith(NeedsTwistedTestCase):
611 """Tests for `assert_fails_with`."""652 """Tests for `assert_fails_with`."""

Subscribers

People subscribed via source and target branches