Skip to content

Commit dd0f762

Browse files
committed
WIP: Inherit signal blockers from QObject
1 parent 9c5a2d8 commit dd0f762

File tree

2 files changed

+34
-23
lines changed

2 files changed

+34
-23
lines changed

src/pytestqt/wait_signal_impl.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ class _AbstractSignalBlocker(qt_api.QtCore.QObject):
4747
"""
4848

4949
def __init__(self, timeout=5000, raising=True):
50+
super().__init__()
5051
self._loop = qt_api.QtCore.QEventLoop()
5152
self.timeout = timeout
5253
self.signal_triggered = False
5354
self.raising = raising
5455
self._signals = None # will be initialized by inheriting implementations
5556
self._timeout_message = ""
5657

57-
self._timer = qt_api.QtCore.QTimer(self._loop)
58+
self._timer = qt_api.QtCore.QTimer(self)
5859
self._timer.setSingleShot(True)
5960
if timeout is not None:
6061
self._timer.setInterval(timeout)
@@ -84,13 +85,15 @@ def wait(self):
8485
if not self.signal_triggered and self.raising:
8586
raise TimeoutError(self._timeout_message)
8687

88+
@qt_api.Slot()
8789
def _quit_loop_by_timeout(self):
8890
try:
8991
self._cleanup()
9092
finally:
9193
self._loop.quit()
9294

9395
def _cleanup(self):
96+
# assert self._timer.thread() == qt_api.QtCore.QThread.currentThread()
9497
# store timeout message before the data to construct it is lost
9598
self._timeout_message = self._get_timeout_error_message()
9699
self._timer.stop()
@@ -234,6 +237,7 @@ def connect(self, signal):
234237
actual_signal.connect(self._quit_loop_by_signal)
235238
self._signals.append(actual_signal)
236239

240+
@qt_api.Slot()
237241
def _quit_loop_by_signal(self, *args):
238242
"""
239243
quits the event loop and marks that we finished because of a signal.
@@ -251,6 +255,8 @@ def _quit_loop_by_signal(self, *args):
251255

252256
def _cleanup(self):
253257
super()._cleanup()
258+
# FIXME move to _AbstractSignalBlocker once we got MultiSignalBlocker correct
259+
assert self._timer.thread() == qt_api.QtCore.QThread.currentThread()
254260
for signal in self._signals:
255261
_silent_disconnect(signal, self._quit_loop_by_signal)
256262
self._signals = []
@@ -567,7 +573,7 @@ def _cleanup(self):
567573
del self._slots[:]
568574

569575

570-
class CallbackBlocker:
576+
class CallbackBlocker(qt_api.QtCore.QObject):
571577
"""
572578
.. versionadded:: 3.1
573579
@@ -595,14 +601,15 @@ class CallbackBlocker:
595601
"""
596602

597603
def __init__(self, timeout=5000, raising=True):
604+
super().__init__()
598605
self.timeout = timeout
599606
self.raising = raising
600607
self.args = None
601608
self.kwargs = None
602609
self.called = False
603610
self._loop = qt_api.QtCore.QEventLoop()
604611

605-
self._timer = qt_api.QtCore.QTimer(self._loop)
612+
self._timer = qt_api.QtCore.QTimer(self)
606613
self._timer.setSingleShot(True)
607614
if timeout is not None:
608615
self._timer.setInterval(timeout)
@@ -631,13 +638,15 @@ def assert_called_with(self, *args, **kwargs):
631638
assert self.args == list(args)
632639
assert self.kwargs == kwargs
633640

641+
@qt_api.Slot()
634642
def _quit_loop_by_timeout(self):
635643
try:
636644
self._cleanup()
637645
finally:
638646
self._loop.quit()
639647

640648
def _cleanup(self):
649+
assert self._timer.thread() == qt_api.QtCore.QThread.currentThread()
641650
self._timer.stop()
642651

643652
def __call__(self, *args, **kwargs):

tests/test_wait_signal.py

+22-20
Original file line numberDiff line numberDiff line change
@@ -1367,17 +1367,17 @@ def test_timeout_not_raising(self, qtbot):
13671367

13681368

13691369
@pytest.mark.parametrize(
1370-
"check_stderr, count",
1370+
"check_warnings, count",
13711371
[
1372-
# Checking stderr messages
1372+
# Checking for warnings
13731373
pytest.param(
1374-
True, # check stderr
1374+
True, # check warnings
13751375
200, # gets output reliably even with only few runs (often the first)
13761376
id="stderr",
13771377
),
13781378
# Triggering AttributeError
13791379
pytest.param(
1380-
False, # don't check stderr
1380+
False, # don't check warnings
13811381
# Hopefully enough to trigger the AttributeError race condition reliably.
13821382
# With 500 runs, only 1 of 5 Windows PySide6 CI jobs triggered it (but all
13831383
# Ubuntu/macOS jobs did). With 1500 runs, Windows jobs still only triggered
@@ -1392,7 +1392,11 @@ def test_timeout_not_raising(self, qtbot):
13921392
)
13931393
@pytest.mark.parametrize("multi_blocker", [True, False])
13941394
def test_signal_raised_from_thread(
1395-
pytester: pytest.Pytester, check_stderr: bool, multi_blocker: bool, count: int
1395+
monkeypatch: pytest.MonkeyPatch,
1396+
pytester: pytest.Pytester,
1397+
check_warnings: bool,
1398+
multi_blocker: bool,
1399+
count: int,
13961400
) -> None:
13971401
"""Wait for a signal with a thread.
13981402
@@ -1409,7 +1413,7 @@ class Worker(qt_api.QtCore.QObject):
14091413
14101414
14111415
@pytest.mark.parametrize("_", range({count}))
1412-
def test_thread(qtbot, capfd, _):
1416+
def test_thread(qtbot, _):
14131417
worker = Worker()
14141418
thread = qt_api.QtCore.QThread()
14151419
worker.moveToThread(thread)
@@ -1425,25 +1429,23 @@ def test_thread(qtbot, capfd, _):
14251429
finally:
14261430
thread.quit()
14271431
thread.wait()
1428-
1429-
if {check_stderr}: # check_stderr
1430-
out, err = capfd.readouterr()
1431-
assert not err
14321432
"""
14331433
)
1434-
1434+
if check_warnings:
1435+
monkeypatch.setenv("QT_FATAL_WARNINGS", "1")
14351436
res = pytester.runpytest_subprocess("-x", "-s")
1436-
outcomes = res.parseoutcomes()
14371437

1438-
if outcomes.get("failed", 0) and check_stderr and qt_api.pytest_qt_api == "pyside6":
1439-
# The test succeeds on PyQt (unsure why!), but we can't check
1440-
# qt_api.pytest_qt_api at import time, so we can't use
1441-
# pytest.mark.xfail conditionally.
1442-
pytest.xfail(
1443-
"Qt error: QObject::killTimer: "
1444-
"Timers cannot be stopped from another thread"
1445-
)
1438+
qtimer_message = "QObject::killTimer: Timers cannot be stopped from another thread"
1439+
if (
1440+
qtimer_message in res.stderr.str()
1441+
and multi_blocker
1442+
and check_warnings
1443+
and qt_api.pytest_qt_api == "pyside6"
1444+
):
1445+
# We haven't fixed MultiSignalBlocker yet...
1446+
pytest.xfail(f"Qt error: {qtimer_message}")
14461447

1448+
outcomes = res.parseoutcomes()
14471449
res.assert_outcomes(passed=outcomes["passed"]) # no failed/error
14481450

14491451

0 commit comments

Comments
 (0)