Skip to content
17 changes: 14 additions & 3 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
UNRELEASED
----------

* Added official support for Python 3.13.
* Dropped support for EOL Python 3.8.
* Dropped support for EOL PySide 2.
- Added official support for Python 3.13.
- Dropped support for EOL Python 3.8.
- Dropped support for EOL PySide 2.
- Fixed PySide6 exceptions / warnings about being unable to disconnect signals
with ``qtbot.waitSignal`` (`#552`_, `#558`_).
- Reduced the likelyhood of trouble when using ``qtbot.waitSignal(s)`` and
``qtbot.waitCallback`` where the signal/callback is emitted from a non-main
thread. In theory, more problems remain and this isn't a proper fix yet. In
practice, it seems impossible to provoke any problems in pytest-qt's testsuite.
(`#586`_)

.. _#552: https://github.com/pytest-dev/pytest-qt/issues/552
.. _#558: https://github.com/pytest-dev/pytest-qt/issues/558
.. _#586: https://github.com/pytest-dev/pytest-qt/issues/586

4.4.0 (2024-02-07)
------------------
Expand Down
2 changes: 1 addition & 1 deletion docs/wait_callback.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Anything following the ``with`` block will be run only after the callback has be

If the callback doesn't get called during the given timeout,
:class:`qtbot.TimeoutError <pytestqt.exceptions.TimeoutError>` is raised. If it is called more than once,
:class:`qtbot.CallbackCalledTwiceError <pytestqt.wait_signal.CallbackCalledTwiceError>` is raised.
:class:`qtbot.CallbackCalledTwiceError <pytestqt.exceptions.CallbackCalledTwiceError>` is raised.

raising parameter
-----------------
Expand Down
3 changes: 1 addition & 2 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
testpaths = tests
addopts = --strict-markers --strict-config
xfail_strict = true
markers =
filterwarnings: pytest's filterwarnings marker
filterwarnings = error
22 changes: 22 additions & 0 deletions src/pytestqt/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,25 @@ class ScreenshotError(Exception):

Access via ``qtbot.ScreenshotError``.
"""


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

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

Access via ``qtbot.SignalEmittedError``.
"""


class CallbackCalledTwiceError(Exception):
"""
.. versionadded:: 3.1

The exception thrown by :meth:`pytestqt.qtbot.QtBot.waitCallback` if a
callback was called twice.

Access via ``qtbot.CallbackCalledTwiceError``.
"""
22 changes: 10 additions & 12 deletions src/pytestqt/qtbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
import weakref
import warnings

from pytestqt.exceptions import TimeoutError, ScreenshotError
from pytestqt.qt_compat import qt_api
from pytestqt.wait_signal import (
SignalBlocker,
MultiSignalBlocker,
SignalEmittedSpy,
from pytestqt.exceptions import (
TimeoutError,
ScreenshotError,
SignalEmittedError,
CallbackBlocker,
CallbackCalledTwiceError,
)
from pytestqt.qt_compat import qt_api
from pytestqt import wait_signal


def _parse_ini_boolean(value):
Expand Down Expand Up @@ -350,7 +348,7 @@ def waitSignal(self, signal, *, timeout=5000, raising=None, check_params_cb=None
f"Passing None as signal isn't supported anymore, use qtbot.wait({timeout}) instead."
)
raising = self._should_raise(raising)
blocker = SignalBlocker(
blocker = wait_signal.SignalBlocker(
timeout=timeout, raising=raising, check_params_cb=check_params_cb
)
blocker.connect(signal)
Expand Down Expand Up @@ -437,7 +435,7 @@ def waitSignals(
len(check_params_cbs), len(signals)
)
)
blocker = MultiSignalBlocker(
blocker = wait_signal.MultiSignalBlocker(
timeout=timeout,
raising=raising,
order=order,
Expand All @@ -455,7 +453,7 @@ def wait(self, ms):
While waiting, events will be processed and your test will stay
responsive to user interface events or network communication.
"""
blocker = MultiSignalBlocker(timeout=ms, raising=False)
blocker = wait_signal.MultiSignalBlocker(timeout=ms, raising=False)
blocker.wait()

@contextlib.contextmanager
Expand All @@ -475,7 +473,7 @@ def assertNotEmitted(self, signal, *, wait=0):
.. note:: This method is also available as ``assert_not_emitted``
(pep-8 alias)
"""
spy = SignalEmittedSpy(signal)
spy = wait_signal.SignalEmittedSpy(signal)
with spy, self.waitSignal(signal, timeout=wait, raising=False):
yield
spy.assert_not_emitted()
Expand Down Expand Up @@ -589,7 +587,7 @@ def waitCallback(self, *, timeout=5000, raising=None):
.. note:: This method is also available as ``wait_callback`` (pep-8 alias)
"""
raising = self._should_raise(raising)
blocker = CallbackBlocker(timeout=timeout, raising=raising)
blocker = wait_signal.CallbackBlocker(timeout=timeout, raising=raising)
return blocker

@contextlib.contextmanager
Expand Down
20 changes: 20 additions & 0 deletions src/pytestqt/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import dataclasses
from typing import Any


def get_marker(item, name):
"""Get a marker from a pytest item.

Expand All @@ -9,3 +13,19 @@ def get_marker(item, name):
except AttributeError:
# pytest < 3.6
return item.get_marker(name)


@dataclasses.dataclass
class SignalAndArgs:
signal_name: str
args: list[Any]

def __str__(self) -> str:
args = repr(self.args) if self.args else ""

# remove signal parameter signature, e.g. turn "some_signal(str,int)" to "some_signal", because we're adding
# the actual parameters anyways
signal_name = self.signal_name
signal_name = signal_name.partition("(")[0]

return signal_name + args
Loading
Loading