From 751b356e87189c7c4496a3eed76bde038a99d341 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 14 Sep 2018 20:43:07 +0200 Subject: [PATCH 1/3] Add a wait-argument for assertNotEmitted --- CHANGELOG.rst | 3 +++ docs/signals.rst | 11 +++++++++++ pytestqt/qtbot.py | 9 ++++++++- tests/test_wait_signal.py | 5 +++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 363c3f9f..802797ee 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,9 @@ - Improve debugging output when no Qt wrapper was found. - Register the ``no_qt_log`` marker with pytest so ``--strict`` can be used. +- ``qtbot.assertNotEmitted`` now has a new ``wait`` parameter which can be used + to make sure asynchronous signals aren't emitted by waiting after the code in + the ``with`` block finished. 3.0.2 (2018-08-31) ------------------ diff --git a/docs/signals.rst b/docs/signals.rst index 5683be21..cb354c01 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -239,3 +239,14 @@ context manager: ... with qtbot.assertNotEmitted(app.worker.error): app.worker.start() + +By default, this only catches signals emitted directly inside the block. +You can pass ``wait=...`` to wait for a given duration (in milliseconds) for +asynchronous signals to (not) arrive: + +.. code-block:: python + + def test_no_error(qtbot): + ... + with qtbot.assertNotEmitted(page.loadFinished, wait=100): + page.runJavaScript("document.getElementById('not-a-link').click()") diff --git a/pytestqt/qtbot.py b/pytestqt/qtbot.py index 6cde4f40..e7b01991 100644 --- a/pytestqt/qtbot.py +++ b/pytestqt/qtbot.py @@ -435,12 +435,17 @@ def wait(self, ms): blocker.wait() @contextlib.contextmanager - def assertNotEmitted(self, signal): + def assertNotEmitted(self, signal, wait=0): """ .. versionadded:: 1.11 Make sure the given ``signal`` doesn't get emitted. + :param int wait: + How many milliseconds to wait to make sure the signal isn't emitted + asynchronously. By default, this method returns immediately and only + catches signals emitted inside the ``with``-block. + This is intended to be used as a context manager. .. note:: This method is also available as ``assert_not_emitted`` @@ -449,6 +454,8 @@ def assertNotEmitted(self, signal): spy = SignalEmittedSpy(signal) with spy: yield + if wait: + self.wait(wait) spy.assert_not_emitted() assert_not_emitted = assertNotEmitted # pep-8 alias diff --git a/tests/test_wait_signal.py b/tests/test_wait_signal.py index bcd55c5b..8637ebee 100644 --- a/tests/test_wait_signal.py +++ b/tests/test_wait_signal.py @@ -1314,3 +1314,8 @@ def test_disconnected(self, qtbot, signaller): with qtbot.assertNotEmitted(signaller.signal): pass signaller.signal.emit() + + def test_emitted_late(self, qtbot, signaller, timer): + with pytest.raises(SignalEmittedError): + with qtbot.assertNotEmitted(signaller.signal, wait=100): + timer.single_shot(signaller.signal, 10) From a5aba3be47022dd70648fa010a31c02a6f4ee17a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 14 Sep 2018 22:29:16 +0200 Subject: [PATCH 2/3] Don't continue waiting after signal is emitted --- pytestqt/qtbot.py | 4 +--- tests/test_wait_signal.py | 9 +++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pytestqt/qtbot.py b/pytestqt/qtbot.py index e7b01991..3d0eeb62 100644 --- a/pytestqt/qtbot.py +++ b/pytestqt/qtbot.py @@ -452,10 +452,8 @@ def assertNotEmitted(self, signal, wait=0): (pep-8 alias) """ spy = SignalEmittedSpy(signal) - with spy: + with spy, self.waitSignal(signal, timeout=wait, raising=False): yield - if wait: - self.wait(wait) spy.assert_not_emitted() assert_not_emitted = assertNotEmitted # pep-8 alias diff --git a/tests/test_wait_signal.py b/tests/test_wait_signal.py index 8637ebee..6dfc9470 100644 --- a/tests/test_wait_signal.py +++ b/tests/test_wait_signal.py @@ -1319,3 +1319,12 @@ def test_emitted_late(self, qtbot, signaller, timer): with pytest.raises(SignalEmittedError): with qtbot.assertNotEmitted(signaller.signal, wait=100): timer.single_shot(signaller.signal, 10) + + def test_continues_when_emitted(self, qtbot, signaller, stop_watch): + stop_watch.start() + + with pytest.raises(SignalEmittedError): + with qtbot.assertNotEmitted(signaller.signal, wait=5000): + signaller.signal.emit() + + stop_watch.check(4000) From a2af859202d6d188366030f9e50092a11e21d916 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 17 Sep 2018 08:29:40 +0200 Subject: [PATCH 3/3] Don't spin up a loop with timeout=0 --- CHANGELOG.rst | 2 ++ docs/signals.rst | 11 +++++++++++ pytestqt/wait_signal.py | 7 +++++-- tests/test_wait_signal.py | 15 +++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 802797ee..c0b88079 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ - ``qtbot.assertNotEmitted`` now has a new ``wait`` parameter which can be used to make sure asynchronous signals aren't emitted by waiting after the code in the ``with`` block finished. +- ``qtbot.waitSignal`` with timeout ``0`` now expects the signal to arrive + directly in the code enclosed by it. 3.0.2 (2018-08-31) ------------------ diff --git a/docs/signals.rst b/docs/signals.rst index cb354c01..13b37347 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -98,6 +98,17 @@ parameters match, ``False`` otherwise. app.worker.start() +timeout parameter +---------------- + +The ``timeout`` parameter specifies how long ``waitSignal`` should wait for a +signal to arrive. If the timeout is ``None``, there won't be any timeout, i.e. +it'll wait indefinitely. + +If the timeout is set to ``0``, it's expected that the signal arrives directly +in the code inside the ``with qtbot.waitSignal(...):`` block. + + Getting arguments of the emitted signal --------------------------------------- diff --git a/pytestqt/wait_signal.py b/pytestqt/wait_signal.py index 901e6f50..2b016d65 100644 --- a/pytestqt/wait_signal.py +++ b/pytestqt/wait_signal.py @@ -24,7 +24,7 @@ def __init__(self, timeout=1000, raising=True): self.raising = raising self._signals = None # will be initialized by inheriting implementations self._timeout_message = "" - if timeout is None: + if timeout is None or timeout == 0: self._timer = None else: self._timer = qt_api.QtCore.QTimer(self._loop) @@ -46,7 +46,10 @@ def wait(self): if self._timer is not None: self._timer.timeout.connect(self._quit_loop_by_timeout) self._timer.start() - self._loop.exec_() + + if self.timeout != 0: + self._loop.exec_() + if not self.signal_triggered and self.raising: raise TimeoutError(self._timeout_message) diff --git a/tests/test_wait_signal.py b/tests/test_wait_signal.py index 6dfc9470..9270b575 100644 --- a/tests/test_wait_signal.py +++ b/tests/test_wait_signal.py @@ -111,6 +111,21 @@ def test_signal_triggered( stop_watch.check(timeout, delay) +@pytest.mark.parametrize("delayed", [True, False]) +def test_zero_timeout(qtbot, timer, delayed, signaller): + """ + With a zero timeout, we don't run a main loop, so only immediate signals are + processed. + """ + with qtbot.waitSignal(signaller.signal, raising=False, timeout=0) as blocker: + if delayed: + timer.single_shot(signaller.signal, 0) + else: + signaller.signal.emit() + + assert blocker.signal_triggered != delayed + + @pytest.mark.parametrize( "configval, raises", [("false", False), ("true", True), (None, True)] )