From 9d18af446c4259f8e80509de5c1df43ad9dd4a6e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 17 Dec 2015 18:58:04 +0100 Subject: [PATCH 1/2] Add qtbot.assertNotEmitted. Closes #92. --- CHANGELOG.rst | 6 +++++ docs/signals.rst | 15 +++++++++++++ pytestqt/qtbot.py | 21 +++++++++++++++++- pytestqt/wait_signal.py | 46 +++++++++++++++++++++++++++++++++++++++ tests/test_wait_signal.py | 34 ++++++++++++++++++++++++++++- 5 files changed, 120 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52be5225..5551417f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 ------ diff --git a/docs/signals.rst b/docs/signals.rst index 21b22271..31d7de6e 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -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 ` +context manager: + +.. code-block:: python + + def test_no_error(qtbot): + ... + with qtbot.assertNotEmitted(app.worker.error): + app.worker.start() diff --git a/pytestqt/qtbot.py b/pytestqt/qtbot.py index 188a1c3d..cad358c0 100644 --- a/pytestqt/qtbot.py +++ b/pytestqt/qtbot.py @@ -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 @@ -68,6 +69,7 @@ class QtBot(object): .. automethod:: waitSignal .. automethod:: waitSignals + .. automethod:: assertNotEmitted **Raw QTest API** @@ -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 diff --git a/pytestqt/wait_signal.py b/pytestqt/wait_signal.py index cae31eea..55d59ebf 100644 --- a/pytestqt/wait_signal.py +++ b/pytestqt/wait_signal.py @@ -185,6 +185,42 @@ def _cleanup(self): self._slots.clear() +class SignalEmittedSpy(object): + + """ + .. versionadded:: 1.4 + + 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 @@ -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. diff --git a/tests/test_wait_signal.py b/tests/test_wait_signal.py index c867e6fe..33c97f57 100644 --- a/tests/test_wait_signal.py +++ b/tests/test_wait_signal.py @@ -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): """ @@ -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.") + + 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() From 1258e287b4c971f8afd40dbfc467ff8a86ead0ec Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 17 Dec 2015 21:39:21 +0100 Subject: [PATCH 2/2] Fix version number. --- pytestqt/wait_signal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytestqt/wait_signal.py b/pytestqt/wait_signal.py index 55d59ebf..42873cd6 100644 --- a/pytestqt/wait_signal.py +++ b/pytestqt/wait_signal.py @@ -188,7 +188,7 @@ def _cleanup(self): class SignalEmittedSpy(object): """ - .. versionadded:: 1.4 + .. versionadded:: 1.11 An object which checks if a given signal has ever been emitted.