Skip to content

Add qtbot.assertNotEmitted. #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 6, 2016
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
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
1.11.0
------

- ``qtbot`` now has a new ``assertNotEmitted`` context manager which can be
used to ensure the given signal is not emitted.

1.10.0
------

Expand Down
15 changes: 15 additions & 0 deletions docs/signals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,18 @@ the new ``raising`` parameter::
# this will be reached after all workers emit their "finished"
# signal or a qtbot.SignalTimeoutError will be raised
assert_application_results(app)

**Making sure a given signal is not emitted**

.. versionadded:: 1.11

If you want to ensure a signal is **not** emitted in a given block of code, use
the :meth:`qtbot.assertNotEmitted <pytestqt.plugin.QtBot.assertNotEmitted>`
context manager:

.. code-block:: python

def test_no_error(qtbot):
...
with qtbot.assertNotEmitted(app.worker.error):
app.worker.start()
21 changes: 20 additions & 1 deletion pytestqt/qtbot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import functools
import contextlib
import weakref
from pytestqt.wait_signal import SignalBlocker, MultiSignalBlocker, SignalTimeoutError
from pytestqt.wait_signal import SignalBlocker, MultiSignalBlocker, SignalTimeoutError, SignalEmittedSpy
from pytestqt.qt_compat import QtTest, QApplication


Expand Down Expand Up @@ -68,6 +69,7 @@ class QtBot(object):

.. automethod:: waitSignal
.. automethod:: waitSignals
.. automethod:: assertNotEmitted

**Raw QTest API**

Expand Down Expand Up @@ -324,6 +326,23 @@ def wait(self, ms):
blocker = MultiSignalBlocker(timeout=ms)
blocker.wait()

@contextlib.contextmanager
def assertNotEmitted(self, signal):
"""
.. versionadded:: 1.11

Make sure the given ``signal`` doesn't get emitted.

This is intended to be used as a context manager.
"""
spy = SignalEmittedSpy(signal)
with spy:
yield
spy.assert_not_emitted()

assert_not_emitted = assertNotEmitted # pep-8 alias



# provide easy access to SignalTimeoutError to qtbot fixtures
QtBot.SignalTimeoutError = SignalTimeoutError
Expand Down
46 changes: 46 additions & 0 deletions pytestqt/wait_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,42 @@ def _cleanup(self):
self._slots.clear()


class SignalEmittedSpy(object):

"""
.. versionadded:: 1.11

An object which checks if a given signal has ever been emitted.

Intended to be used as a context manager.
"""

def __init__(self, signal):
self.signal = signal
self.emitted = False
self.args = None

def slot(self, *args):
self.emitted = True
self.args = args

def __enter__(self):
self.signal.connect(self.slot)

def __exit__(self, type, value, traceback):
self.signal.disconnect(self.slot)

def assert_not_emitted(self):
if self.emitted:
if self.args:
raise SignalEmittedError("Signal %r unexpectedly emitted with "
"arguments %r" %
(self.signal, list(self.args)))
else:
raise SignalEmittedError("Signal %r unexpectedly emitted" %
(self.signal,))


class SignalTimeoutError(Exception):
"""
.. versionadded:: 1.4
Expand All @@ -195,6 +231,16 @@ class SignalTimeoutError(Exception):
pass


class SignalEmittedError(Exception):
"""
.. versionadded:: 1.11

The exception thrown by :meth:`pytestqt.qtbot.QtBot.assertNotEmitted` if a
signal was emitted unexpectedly.
"""
pass


def _silent_disconnect(signal, slot):
"""Disconnects a signal from a slot, ignoring errors. Sometimes
Qt might disconnect a signal automatically for unknown reasons.
Expand Down
34 changes: 33 additions & 1 deletion tests/test_wait_signal.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import functools
import time
import sys
import fnmatch

import pytest

from pytestqt.qt_compat import QtCore, Signal, QT_API

from pytestqt.wait_signal import SignalEmittedError

def test_signal_blocker_exception(qtbot):
"""
Expand Down Expand Up @@ -312,3 +313,34 @@ def test_connected_signal(self, qtbot, signaller):
blocker.connect(signaller.signal_args_2)
signaller.signal_args_2.emit('foo', 2342)
assert blocker.args == ['foo', 2342]


class TestAssertNotEmitted:

"""Tests for qtbot.assertNotEmitted."""

def test_not_emitted(self, qtbot, signaller):
with qtbot.assertNotEmitted(signaller.signal):
pass

def test_emitted(self, qtbot, signaller):
with pytest.raises(SignalEmittedError) as excinfo:
with qtbot.assertNotEmitted(signaller.signal):
signaller.signal.emit()

fnmatch.fnmatchcase(str(excinfo.value),
"Signal * unexpectedly emitted.")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using fnmatch here because the repr of a signal is probably quite different between PyQt4/PyQt5/PySide.


def test_emitted_args(self, qtbot, signaller):
with pytest.raises(SignalEmittedError) as excinfo:
with qtbot.assertNotEmitted(signaller.signal_args):
signaller.signal_args.emit('foo', 123)

fnmatch.fnmatchcase(str(excinfo.value),
"Signal * unexpectedly emitted with arguments "
"['foo', 123]")

def test_disconnected(self, qtbot, signaller):
with qtbot.assertNotEmitted(signaller.signal):
pass
signaller.signal.emit()