From 3241de5749edf1eeafe00c6e526bdde33f9da9c8 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 16 Dec 2015 10:35:37 -0200 Subject: [PATCH 1/3] Use explicit try/finally to ensure loops quit when receiving a signal Fix #114 --- pytestqt/wait_signal.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/pytestqt/wait_signal.py b/pytestqt/wait_signal.py index 8d70f6e7..e77db2d8 100644 --- a/pytestqt/wait_signal.py +++ b/pytestqt/wait_signal.py @@ -48,8 +48,10 @@ def wait(self): self.timeout) def _quit_loop_by_timeout(self): - self._loop.quit() - self._cleanup() + try: + self._cleanup() + finally: + self._loop.quit() def _cleanup(self): if self._timer is not None: @@ -119,10 +121,12 @@ def _quit_loop_by_signal(self, *args): """ quits the event loop and marks that we finished because of a signal. """ - self.signal_triggered = True - self.args = list(args) - self._loop.quit() - self._cleanup() + try: + self.signal_triggered = True + self.args = list(args) + self._cleanup() + finally: + self._loop.quit() def _cleanup(self): super(SignalBlocker, self)._cleanup() @@ -171,8 +175,13 @@ def _signal_emitted(self, signal): """ self._signals[signal] = True if all(self._signals.values()): - self.signal_triggered = True - self._loop.quit() + try: + # of course setting signal_triggered can't raise, but + # leave this try/finally here as a reminder for future + # additions + self.signal_triggered = True + finally: + self._loop.quit() class SignalTimeoutError(Exception): From 86e41e3f5a09213d338b916e507a9d70bb0619a8 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 16 Dec 2015 11:15:04 -0200 Subject: [PATCH 2/3] Implement proper clean up for MultiSignalBlocker --- pytestqt/wait_signal.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pytestqt/wait_signal.py b/pytestqt/wait_signal.py index e77db2d8..ad93609b 100644 --- a/pytestqt/wait_signal.py +++ b/pytestqt/wait_signal.py @@ -136,6 +136,7 @@ def _cleanup(self): except (TypeError, RuntimeError): # pragma: no cover # already disconnected by Qt? pass + self._signals = [] class MultiSignalBlocker(_AbstractSignalBlocker): @@ -155,6 +156,7 @@ class MultiSignalBlocker(_AbstractSignalBlocker): def __init__(self, timeout=1000, raising=False): super(MultiSignalBlocker, self).__init__(timeout, raising=raising) self._signals = {} + self._slots = {} def _add_signal(self, signal): """ @@ -164,7 +166,9 @@ def _add_signal(self, signal): :param signal: QtCore.Signal """ self._signals[signal] = False - signal.connect(functools.partial(self._signal_emitted, signal)) + slot = functools.partial(self._signal_emitted, signal) + self._slots[signal] = slot + signal.connect(slot) def _signal_emitted(self, signal): """ @@ -176,13 +180,22 @@ def _signal_emitted(self, signal): self._signals[signal] = True if all(self._signals.values()): try: - # of course setting signal_triggered can't raise, but - # leave this try/finally here as a reminder for future - # additions self.signal_triggered = True + self._cleanup() finally: self._loop.quit() + def _cleanup(self): + super(MultiSignalBlocker, self)._cleanup() + for signal, slot in self._slots.items(): + try: + signal.disconnect(slot) + except (TypeError, RuntimeError): # pragma: no cover + # already disconnected by Qt? + pass + self._signals.clear() + self._slots.clear() + class SignalTimeoutError(Exception): """ From 81b317c5e2170b135076958a730ef62bdb448317 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 16 Dec 2015 11:50:04 -0200 Subject: [PATCH 3/3] Refactor signal disconnecting code --- pytestqt/wait_signal.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/pytestqt/wait_signal.py b/pytestqt/wait_signal.py index ad93609b..cae31eea 100644 --- a/pytestqt/wait_signal.py +++ b/pytestqt/wait_signal.py @@ -55,11 +55,7 @@ def _quit_loop_by_timeout(self): def _cleanup(self): if self._timer is not None: - try: - self._timer.timeout.disconnect(self._quit_loop_by_timeout) - except (TypeError, RuntimeError): - # already disconnected by Qt? - pass + _silent_disconnect(self._timer.timeout, self._quit_loop_by_timeout) self._timer.stop() self._timer = None @@ -131,11 +127,7 @@ def _quit_loop_by_signal(self, *args): def _cleanup(self): super(SignalBlocker, self)._cleanup() for signal in self._signals: - try: - signal.disconnect(self._quit_loop_by_signal) - except (TypeError, RuntimeError): # pragma: no cover - # already disconnected by Qt? - pass + _silent_disconnect(signal, self._quit_loop_by_signal) self._signals = [] @@ -188,11 +180,7 @@ def _signal_emitted(self, signal): def _cleanup(self): super(MultiSignalBlocker, self)._cleanup() for signal, slot in self._slots.items(): - try: - signal.disconnect(slot) - except (TypeError, RuntimeError): # pragma: no cover - # already disconnected by Qt? - pass + _silent_disconnect(signal, slot) self._signals.clear() self._slots.clear() @@ -206,3 +194,12 @@ class SignalTimeoutError(Exception): """ pass + +def _silent_disconnect(signal, slot): + """Disconnects a signal from a slot, ignoring errors. Sometimes + Qt might disconnect a signal automatically for unknown reasons. + """ + try: + signal.disconnect(slot) + except (TypeError, RuntimeError): # pragma: no cover + pass