diff options
| author | Jonathan Lange <jml@mumak.net> | 2016-01-22 15:47:55 +0000 |
|---|---|---|
| committer | Jonathan Lange <jml@mumak.net> | 2016-01-22 15:52:26 +0000 |
| commit | f824df064ec0c2a1a81bd91fb4214a07edeef010 (patch) | |
| tree | a115ec96fec9c227b5844dc5e3f0edc0b10af2c7 | |
| parent | 27250d0ebfec54f5563238f1a1c90e27a81ba8e0 (diff) | |
| download | testtools-f824df064ec0c2a1a81bd91fb4214a07edeef010.tar.gz | |
Handle case where Deferred fires after timeout
| -rw-r--r-- | testtools/_spinner.py | 7 | ||||
| -rw-r--r-- | testtools/tests/test_spinner.py | 33 |
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 |
