← Back to team overview

testtools-dev team mailing list archive

[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