Skip to content

Commit 560f565

Browse files
committed
Disconnect SignalBlocker after exiting loop.
Otherwise, a second signal emission will still call the functions of the already garbage-collected SignalBlocker with PyQt5 < 5.3. The signals could already be disconnected, so the TypeError (older PyQt versions) or RuntimeError (newer PyQt versions) which is raised in that case is ignored. Fixes #69.
1 parent 14e69c8 commit 560f565

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

pytestqt/_tests/test_wait_signal.py

+27
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,30 @@ class TestException(Exception):
239239
with pytest.raises(TestException):
240240
with func(arg, timeout=10, raising=raising):
241241
raise TestException
242+
243+
244+
@pytest.mark.parametrize('multiple, do_timeout',
245+
[(True, False), (True, True), (False, False), (False, True)])
246+
def test_wait_twice(qtbot, single_shot, multiple, do_timeout):
247+
"""
248+
https://github.com/pytest-dev/pytest-qt/issues/69
249+
"""
250+
signaller = Signaller()
251+
252+
if multiple:
253+
func = qtbot.waitSignals
254+
arg = [signaller.signal]
255+
else:
256+
func = qtbot.waitSignal
257+
arg = signaller.signal
258+
259+
if do_timeout:
260+
with func(arg, timeout=100):
261+
single_shot(signaller.signal, 200)
262+
with func(arg, timeout=100):
263+
single_shot(signaller.signal, 200)
264+
else:
265+
with func(arg):
266+
signaller.signal.emit()
267+
with func(arg):
268+
signaller.signal.emit()

pytestqt/plugin.py

+28-1
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,10 @@ def __init__(self, timeout=1000, raising=False):
355355
self.timeout = timeout
356356
self.signal_triggered = False
357357
self.raising = raising
358+
self._timer = QtCore.QTimer()
359+
self._timer.setSingleShot(True)
360+
if timeout is not None:
361+
self._timer.setInterval(timeout)
358362

359363
def wait(self):
360364
"""
@@ -368,12 +372,25 @@ def wait(self):
368372
if self.timeout is None and not self._signals:
369373
raise ValueError("No signals or timeout specified.")
370374
if self.timeout is not None:
371-
QtCore.QTimer.singleShot(self.timeout, self._loop.quit)
375+
self._timer.timeout.connect(self._quit_loop_by_timeout)
376+
self._timer.start()
372377
self._loop.exec_()
373378
if not self.signal_triggered and self.raising:
374379
raise SignalTimeoutError("Didn't get signal after %sms." %
375380
self.timeout)
376381

382+
def _quit_loop_by_timeout(self):
383+
self._loop.quit()
384+
self._cleanup()
385+
386+
def _cleanup(self):
387+
if self.timeout is not None:
388+
try:
389+
self._timer.timeout.disconnect(self._quit_loop_by_timeout)
390+
except (TypeError, RuntimeError):
391+
# already disconnected by Qt?
392+
pass
393+
377394
def __enter__(self):
378395
return self
379396

@@ -426,6 +443,16 @@ def _quit_loop_by_signal(self):
426443
"""
427444
self.signal_triggered = True
428445
self._loop.quit()
446+
self._cleanup()
447+
448+
def _cleanup(self):
449+
super(SignalBlocker, self)._cleanup()
450+
for signal in self._signals:
451+
try:
452+
signal.disconnect(self._quit_loop_by_signal)
453+
except (TypeError, RuntimeError):
454+
# already disconnected by Qt?
455+
pass
429456

430457

431458
class MultiSignalBlocker(_AbstractSignalBlocker):

0 commit comments

Comments
 (0)