testtools-dev team mailing list archive
-
testtools-dev team
-
Mailing list archive
-
Message #00345
[Merge] lp:~jml/testtools/better-twisted-debugging into lp:testtools
Jonathan Lange has proposed merging lp:~jml/testtools/better-twisted-debugging into lp:testtools.
Requested reviews:
testtools developers (testtools-dev)
Better debugging for Twisted tests.
--
https://code.launchpad.net/~jml/testtools/better-twisted-debugging/+merge/42278
Your team testtools developers is requested to review the proposed merge of lp:~jml/testtools/better-twisted-debugging into lp:testtools.
=== modified file 'testtools/_spinner.py'
--- testtools/_spinner.py 2010-11-21 16:43:26 +0000
+++ testtools/_spinner.py 2010-11-30 17:11:44 +0000
@@ -20,7 +20,10 @@
import signal
+from testtools.monkey import MonkeyPatcher
+
from twisted.internet import defer
+from twisted.internet.base import DelayedCall
from twisted.internet.interfaces import IReactorThreads
from twisted.python.failure import Failure
from twisted.python.util import mergeFunctionMetadata
@@ -165,13 +168,20 @@
# the ideal, and it actually works for many cases.
_OBLIGATORY_REACTOR_ITERATIONS = 0
- def __init__(self, reactor):
+ def __init__(self, reactor, debug=False):
+ """Construct a Spinner.
+
+ :param reactor: A Twisted reactor.
+ :param debug: Whether or not to enable Twisted's debugging. Defaults
+ to False.
+ """
self._reactor = reactor
self._timeout_call = None
self._success = self._UNSET
self._failure = self._UNSET
self._saved_signals = []
self._junk = []
+ self._debug = debug
def _cancel_timeout(self):
if self._timeout_call:
@@ -270,30 +280,38 @@
:return: Whatever is at the end of the function's callback chain. If
it's an error, then raise that.
"""
- junk = self.get_junk()
- if junk:
- raise StaleJunkError(junk)
- self._save_signals()
- self._timeout_call = self._reactor.callLater(
- timeout, self._timed_out, function, timeout)
- # Calling 'stop' on the reactor will make it impossible to re-start
- # the reactor. Since the default signal handlers for TERM, BREAK and
- # INT all call reactor.stop(), we'll patch it over with crash.
- # XXX: It might be a better idea to either install custom signal
- # handlers or to override the methods that are Twisted's signal
- # handlers.
- stop, self._reactor.stop = self._reactor.stop, self._reactor.crash
- def run_function():
- d = defer.maybeDeferred(function, *args, **kwargs)
- d.addCallbacks(self._got_success, self._got_failure)
- d.addBoth(self._stop_reactor)
- try:
- self._reactor.callWhenRunning(run_function)
- self._reactor.run()
- finally:
- self._reactor.stop = stop
- self._restore_signals()
- try:
- return self._get_result()
- finally:
- self._clean()
+ debug = MonkeyPatcher()
+ if self._debug:
+ debug.add_patch(defer.Deferred, 'debug', True)
+ debug.add_patch(DelayedCall, 'debug', True)
+ debug.patch()
+ try:
+ junk = self.get_junk()
+ if junk:
+ raise StaleJunkError(junk)
+ self._save_signals()
+ self._timeout_call = self._reactor.callLater(
+ timeout, self._timed_out, function, timeout)
+ # Calling 'stop' on the reactor will make it impossible to
+ # re-start the reactor. Since the default signal handlers for
+ # TERM, BREAK and INT all call reactor.stop(), we'll patch it over
+ # with crash. XXX: It might be a better idea to either install
+ # custom signal handlers or to override the methods that are
+ # Twisted's signal handlers.
+ stop, self._reactor.stop = self._reactor.stop, self._reactor.crash
+ def run_function():
+ d = defer.maybeDeferred(function, *args, **kwargs)
+ d.addCallbacks(self._got_success, self._got_failure)
+ d.addBoth(self._stop_reactor)
+ try:
+ self._reactor.callWhenRunning(run_function)
+ self._reactor.run()
+ finally:
+ self._reactor.stop = stop
+ self._restore_signals()
+ try:
+ return self._get_result()
+ finally:
+ self._clean()
+ finally:
+ debug.restore()
=== modified file 'testtools/deferredruntest.py'
--- testtools/deferredruntest.py 2010-11-25 11:32:24 +0000
+++ testtools/deferredruntest.py 2010-11-30 17:11:44 +0000
@@ -91,7 +91,8 @@
This is highly experimental code. Use at your own risk.
"""
- def __init__(self, case, handlers=None, reactor=None, timeout=0.005):
+ def __init__(self, case, handlers=None, reactor=None, timeout=0.005,
+ debug=False):
"""Construct an `AsynchronousDeferredRunTest`.
:param case: The `testtools.TestCase` to run.
@@ -102,12 +103,16 @@
default reactor.
:param timeout: The maximum time allowed for running a test. The
default is 0.005s.
+ :param debug: Whether or not to enable Twisted's debugging. Use this
+ to get information about unhandled Deferreds and left-over
+ DelayedCalls. Defaults to False.
"""
super(AsynchronousDeferredRunTest, self).__init__(case, handlers)
if reactor is None:
from twisted.internet import reactor
self._reactor = reactor
self._timeout = timeout
+ self._debug = debug
@classmethod
def make_factory(cls, reactor=None, timeout=0.005):
@@ -143,7 +148,7 @@
def _make_spinner(self):
"""Make the `Spinner` to be used to run the tests."""
- return Spinner(self._reactor)
+ return Spinner(self._reactor, debug=self._debug)
def _run_deferred(self):
"""Run the test, assuming everything in it is Deferred-returning.
=== 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:11:44 +0000
@@ -34,6 +34,7 @@
defer = try_import('twisted.internet.defer')
failure = try_import('twisted.python.failure')
log = try_import('twisted.python.log')
+DelayedCall = try_import('twisted.internet.base.DelayedCall')
class X(object):
@@ -606,6 +607,35 @@
error = result._events[1][2]
self.assertThat(error, KeysEqual('traceback', 'twisted-log'))
+ def test_debugging_unchanged_during_test_by_default(self):
+ debugging = [(defer.Deferred.debug, DelayedCall.debug)]
+ class SomeCase(TestCase):
+ def test_debugging_enabled(self):
+ debugging.append((defer.Deferred.debug, DelayedCall.debug))
+ test = SomeCase('test_debugging_enabled')
+ runner = AsynchronousDeferredRunTest(
+ test, handlers=test.exception_handlers,
+ reactor=self.make_reactor(), timeout=self.make_timeout())
+ runner.run(self.make_result())
+ self.assertEqual(debugging[0], debugging[1])
+
+ def test_debugging_enabled_during_test_with_debug_flag(self):
+ self.patch(defer.Deferred, 'debug', False)
+ self.patch(DelayedCall, 'debug', False)
+ debugging = []
+ class SomeCase(TestCase):
+ def test_debugging_enabled(self):
+ debugging.append((defer.Deferred.debug, DelayedCall.debug))
+ test = SomeCase('test_debugging_enabled')
+ runner = AsynchronousDeferredRunTest(
+ test, handlers=test.exception_handlers,
+ reactor=self.make_reactor(), timeout=self.make_timeout(),
+ debug=True)
+ runner.run(self.make_result())
+ self.assertEqual([(True, True)], debugging)
+ self.assertEqual(False, defer.Deferred.debug)
+ self.assertEqual(False, defer.Deferred.debug)
+
class TestAssertFailsWith(NeedsTwistedTestCase):
"""Tests for `assert_fails_with`."""
Follow ups