summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Lange <jml@mumak.net>2016-01-22 15:47:55 +0000
committerJonathan Lange <jml@mumak.net>2016-01-22 15:52:26 +0000
commitf824df064ec0c2a1a81bd91fb4214a07edeef010 (patch)
treea115ec96fec9c227b5844dc5e3f0edc0b10af2c7
parent27250d0ebfec54f5563238f1a1c90e27a81ba8e0 (diff)
downloadtesttools-f824df064ec0c2a1a81bd91fb4214a07edeef010.tar.gz
Handle case where Deferred fires after timeout
-rw-r--r--testtools/_spinner.py7
-rw-r--r--testtools/tests/test_spinner.py33
2 files changed, 39 insertions, 1 deletions
diff --git a/testtools/_spinner.py b/testtools/_spinner.py
index 425d478..fa1b18b 100644
--- a/testtools/_spinner.py
+++ b/testtools/_spinner.py
@@ -153,6 +153,7 @@ class Spinner(object):
self._saved_signals = []
self._junk = []
self._debug = debug
+ self._spinning = False
def _cancel_timeout(self):
if self._timeout_call:
@@ -186,7 +187,10 @@ class Spinner(object):
def _stop_reactor(self, ignored=None):
"""Stop the reactor!"""
- self._reactor.crash()
+ # XXX: Would like to emit a warning when called when *not* spinning.
+ if self._spinning:
+ self._reactor.crash()
+ self._spinning = False
def _timed_out(self, function, timeout):
e = TimeoutError(function, timeout)
@@ -287,6 +291,7 @@ class Spinner(object):
d.addBoth(self._stop_reactor)
try:
self._reactor.callWhenRunning(run_function)
+ self._spinning = True
self._reactor.run()
finally:
self._reactor.stop = real_stop
diff --git a/testtools/tests/test_spinner.py b/testtools/tests/test_spinner.py
index 6e5d007..ef26c68 100644
--- a/testtools/tests/test_spinner.py
+++ b/testtools/tests/test_spinner.py
@@ -297,6 +297,39 @@ class TestRunInReactor(NeedsTwistedTestCase):
def test_fast_sigint_raises_no_result_error_second_time(self):
self.test_fast_sigint_raises_no_result_error()
+ def test_fires_after_timeout(self):
+ # If we timeout, but the Deferred actually ends up firing after the
+ # time out (perhaps because Spinner's clean-up code is buggy, or
+ # perhaps because the code responsible for the callback is in a
+ # thread), then the next run of a spinner works as intended,
+ # completely isolated from the previous run.
+
+ # Ensure we've timed out, and that we have a handle on the Deferred
+ # that didn't fire.
+ reactor = self.make_reactor()
+ spinner1 = self.make_spinner(reactor)
+ timeout = self.make_timeout()
+ deferred1 = defer.Deferred()
+ self.expectThat(
+ lambda: spinner1.run(timeout, lambda: deferred1),
+ Raises(MatchesException(_spinner.TimeoutError)))
+
+ # Make a Deferred that will fire *after* deferred1 as long as the
+ # reactor keeps spinning. We don't care that it's a callback of
+ # deferred1 per se, only that it strictly fires afterwards.
+ marker = object()
+ deferred2 = defer.Deferred()
+ deferred1.addCallback(
+ lambda ignored: reactor.callLater(0, deferred2.callback, marker))
+
+ def fire_other():
+ """Fire Deferred from the last spin while waiting for this one."""
+ deferred1.callback(object())
+ return deferred2
+
+ spinner2 = self.make_spinner(reactor)
+ self.assertThat(spinner2.run(3 * timeout, fire_other), Is(marker))
+
def test_suite():
from unittest import TestLoader