diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 363c3f9f..c0b88079 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,11 @@ - 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. +- ``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 5683be21..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 --------------------------------------- @@ -239,3 +250,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..3d0eeb62 100644 --- a/pytestqt/qtbot.py +++ b/pytestqt/qtbot.py @@ -435,19 +435,24 @@ 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`` (pep-8 alias) """ spy = SignalEmittedSpy(signal) - with spy: + with spy, self.waitSignal(signal, timeout=wait, raising=False): yield spy.assert_not_emitted() 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 bcd55c5b..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)] ) @@ -1314,3 +1329,17 @@ 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) + + 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)