QApplication.processEvents() blocks qtbot on MacOS
If qtbot tries to stop waiting while QApplication.processEvents() is being called, the test will hang indefinitely.
What happens is that:
- qtbot creates a
QEventLoop - an event calling
QApplication.processEvents()is processed byQEventLoop - The qtbot timer event that calls
_quit_loop_by_timeoutis processed byQApplication.processEvents() - Somehow
self._loop.quit()doesn't quit the loop if it is called fromQApplication.processEvents()
Code to reproduce (save as test_qtbot.py):
import pytest
import time
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import QEventLoop, QTimer
class MyWidget(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._timer = QTimer(self)
self._timer.setSingleShot(True)
self._timer.setInterval(10)
self._timer.timeout.connect(self._wait_process_events)
self._timer.start()
def _wait_process_events(self):
time.sleep(1)
QApplication.processEvents()
def test_qtbot(qtbot):
"""
qtbot does the following:
1. start QEventLoop
2. process an event that calls QApplication.processEvents()
3. this processes an event that calls loop.quit()
which doesn't quit the wait loop
"""
widget = MyWidget()
qtbot.addWidget(widget)
widget.show()
qtbot.wait(500)
if __name__ == "__main__":
pytest.main()
Versions I am using:
PyQt5 5.12.3
pytest 5.3.1
pytest-qt 3.2.2
Sorry for the late answer! FWIW your code runs fine for me, with the same versions you're running:
============================= test session starts ==============================
platform linux -- Python 3.8.2, pytest-5.3.1, py-1.8.1, pluggy-0.13.1
PyQt5 5.12.3 -- Qt runtime 5.12.8 -- Qt compiled 5.12.8
rootdir: /home/florian/proj/pytest-qt, inifile: setup.cfg
plugins: qt-3.2.2
collected 1 item
test_qtbot.py . [100%]
============================== 1 passed in 1.13s ===============================
I tested it again and still have the problem. Maybe it is OSX specific?
% python3 test_qtbot.py
============================= test session starts ==============================
platform darwin -- Python 3.7.7, pytest-5.3.1, py-1.8.0, pluggy-0.13.1
PyQt5 5.12.3 -- Qt runtime 5.12.6 -- Qt compiled 5.12.6
rootdir: /Users/quentinpeter/Desktop
plugins: mock-1.12.1, lazy-fixture-0.6.2, flaky-3.6.1, ordering-0.6, qt-3.2.2
collected 1 item
test_qtbot.py
Yep, I can reproduce on macOS, even with pytest 5.4.2, pytest-qt 3.3.0 and PyQt5 5.14.2. Using -o faulthandler_timeout=10 confirms that it hangs in qtbot.wait:
Thread 0x00007fffa5eb8380 (most recent call first):
File "/Users/florian/tmp/.venv/lib/python3.7/site-packages/pytestqt/wait_signal.py", line 51 in wait
File "/Users/florian/tmp/.venv/lib/python3.7/site-packages/pytestqt/qtbot.py", line 448 in wait
File "/Users/florian/tmp/test_qtbot.py", line 32 in test_qtbot
[...]
Works fine on Windows as well FWIW.
Yeah works fine for me too. Not sure how to proceed here I'm afraid, it might be something in your system. 😕
But @The-Compiler replicated the bug on macOS?
on a similar note (apologies if I missed it in the docs), qtbot hangs if itemClicked signals are emitted directly:
My case was a QListWidgetItem, which I triggered manually (leading to hanging), and then fixed with the proper qtbot usage.
list_widget.itemClicked.emit(item)
FIx:
center = list_widget.visualItemRect(item).center()
qtbot.mouseClick(list_widget.viewport(), qc.Qt.LeftButton, pos=center)
(I'm running inside a x86 debian docker container with xvfb on for CI reasons)
Update: It was my mistake, a message_prompt blocked the entire process, and in headless mode it was not obvious.
@nicoddemus should the “question” label be removed as this is a reproduced bug rather than a question? (Reproduced on macOS by @The-Compiler)
Indeed, changed!
However we need a volunteer with access to MacOS to investigate this.
Calling QEventLoop.quit() inside QApplication.processEvents has no effect on macOS.
Code to reproduce:
import time
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QEventLoop, QTimer
# from PyQt6.QtWidgets import QApplication
# from PyQt6.QtCore import QEventLoop, QTimer
if __name__ == "__main__":
app = QApplication([])
loop = QEventLoop()
def _wait_process_events():
# While we wait here, _quitloop_by_timeout is triggered
time.sleep(1)
# The timeout event is triggered
QApplication.processEvents()
def _quitloop_by_timeout():
# Ask the loop to quit
loop.quit()
print("Requesting loop to quit")
# Call _wait_process_events in the event loop
timer_1 = QTimer(loop)
timer_1.setSingleShot(True)
timer_1.setInterval(10)
timer_1.timeout.connect(_wait_process_events)
timer_1.start()
# Call _quitloop_by_timeout in the event loop after _wait_process_events
timer_2 = QTimer(loop)
timer_2.setSingleShot(True)
timer_2.setInterval(500)
timer_2.timeout.connect(_quitloop_by_timeout)
timer_2.start()
# Enter eventloop
loop.exec()
print("loop exited")
Executing this code with Python 3.9.12 under macOS 12.3.1 with either
- PyQt5-5.15.7 PyQt5-Qt5-5.15.2 PyQt5-sip-12.11.0
- PyQt6-6.3.1 PyQt6-Qt6-6.3.1 PyQt6-sip-13.4.0
Results in "Requesting loop to quit" being printed and the program freezing.
From the discussion here I understand that this does not affect linux or window
I will send a bug report on the pyqt mailing list
Have you tried with PySide rather than PyQt? If it happens there as well, it's likely a Qt bug.