Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
------------------
Expand Down
22 changes: 22 additions & 0 deletions docs/signals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------------------------

Expand Down Expand Up @@ -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()")
9 changes: 7 additions & 2 deletions pytestqt/qtbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
7 changes: 5 additions & 2 deletions pytestqt/wait_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)

Expand Down
29 changes: 29 additions & 0 deletions tests/test_wait_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
)
Expand Down Expand Up @@ -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)