Closed bersbersbers closed 1 month ago
That seems somewhat expected, see Exceptions in virtual methods — pytest-qt documentation.
Though I'm confused by:
PySide6 6.5.2+
automatically captures exceptions that happen during the Qt event loop and re-raises them when control is moved back to Python, so the functionality described here does not work withPySide6
(nor is necessary).
To me, this sounds more like a change (or bug?) in PySide 6.8 made that not happen anymore?
I will investigate further. So long, I have found that
always returns False
because disabled
is either "0"
or "1"
(as strings) or Mark(...)
, depending on whether I use pytest.mark.qt_no_exception_capture
or qt_no_exception_capture = 1
, all of which evaluate to True
. That may explain some of the confusion regarding pytest-qt
behavior.
The PySide 6.8.0 changelog does not give much information on such differences: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/doc/changelogs/changes-6.8.0
Also the list of commits between v6.8.0 (https://code.qt.io/cgit/pyside/pyside-setup.git/log/?h=v6.8.0) and v6.7.3 (https://code.qt.io/cgit/pyside/pyside-setup.git/log/?h=v6.7.3), less than 50, does not reveal anything special for exceptions.
Did not find anything in https://doc.qt.io/qt-6/whatsnew68.html, either.
In Fedora (rawhide) with PySide6 6.8.0 and Python 3.13.0 I'm seeing the following failures when running tests:
=================================== FAILURES ===================================
________________ test_catch_exceptions_in_virtual_methods[True] ________________
testdir = <Testdir local('/tmp/pytest-of-mockbuild/pytest-1/test_catch_exceptions_in_virtual_methods1')>
raise_error = True
@pytest.mark.parametrize("raise_error", [False, True])
def test_catch_exceptions_in_virtual_methods(testdir, raise_error):
"""
Catch exceptions that happen inside Qt's event loop and make the
tests fail if any.
:type testdir: _pytest.pytester.TmpTestdir
"""
testdir.makepyfile(
"""
from pytestqt.qt_compat import qt_api
class Receiver(qt_api.QtCore.QObject):
def event(self, ev):
if {raise_error}:
try:
raise RuntimeError('original error')
except RuntimeError:
raise ValueError('mistakes were made')
return qt_api.QtCore.QObject.event(self, ev)
def test_exceptions(qtbot):
v = Receiver()
app = qt_api.QtWidgets.QApplication.instance()
app.sendEvent(v, qt_api.QtCore.QEvent(qt_api.QtCore.QEvent.Type.User))
app.sendEvent(v, qt_api.QtCore.QEvent(qt_api.QtCore.QEvent.Type.User))
app.processEvents()
""".format(
raise_error=raise_error
)
)
result = testdir.runpytest()
if raise_error:
if qt_api.pytest_qt_api == "pyside6":
# PySide6 automatically captures exceptions during the event loop,
# and re-raises them when control gets back to Python.
# This results in the exception not being captured by
# us, and a more natural traceback which includes the app.sendEvent line.
expected_lines = [
"*RuntimeError: original error",
"*app.sendEvent*",
"*ValueError: mistakes were made*",
"*1 failed*",
]
else:
expected_lines = [
"*Exceptions caught in Qt event loop:*",
"RuntimeError: original error",
"*ValueError: mistakes were made*",
"*1 failed*",
]
> result.stdout.fnmatch_lines(expected_lines)
E Failed: nomatch: '*RuntimeError: original error'
E and: '============================= test session starts =============================='
E and: 'platform linux -- Python 3.13.0, pytest-8.3.3, pluggy-1.5.0'
E and: 'PyQtAPI 1.0 -- Qt runtime 2.5 -- Qt compiled 3.5'
E and: 'rootdir: /tmp/pytest-of-mockbuild/pytest-1/test_catch_exceptions_in_virtual_methods1'
E and: 'plugins: qt-4.4.0'
E and: 'collected 1 item'
E and: ''
E and: 'test_catch_exceptions_in_virtual_methods.py F [100%]'
E and: ''
E and: '=================================== FAILURES ==================================='
E and: '_______________________________ test_exceptions ________________________________'
E and: ''
E and: 'self = <test_catch_exceptions_in_virtual_methods.Receiver(0x564b3bc33870) at 0x7f604702f500>'
E and: 'ev = <PySide6.QtCore.QEvent(QEvent::User)>'
E and: ''
E and: ' def event(self, ev):'
E and: ' if True:'
E and: ' try:'
E and: "> raise RuntimeError('original error')"
E fnmatch: '*RuntimeError: original error'
E with: 'E RuntimeError: original error'
E nomatch: '*app.sendEvent*'
E and: ''
E and: 'test_catch_exceptions_in_virtual_methods.py:8: RuntimeError'
E and: ''
E and: 'During handling of the above exception, another exception occurred:'
E and: ''
E and: 'qtbot = <pytestqt.qtbot.QtBot object at 0x7f60472343c0>'
E and: ''
E and: ' def test_exceptions(qtbot):'
E and: ' v = Receiver()'
E and: ' app = qt_api.QtWidgets.QApplication.instance()'
E fnmatch: '*app.sendEvent*'
E with: '> app.sendEvent(v, qt_api.QtCore.QEvent(qt_api.QtCore.QEvent.Type.User))'
E nomatch: '*ValueError: mistakes were made*'
E and: ''
E and: 'test_catch_exceptions_in_virtual_methods.py:18: '
E and: '_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ '
E and: ''
E and: 'self = <test_catch_exceptions_in_virtual_methods.Receiver(0x564b3bc33870) at 0x7f604702f500>'
E and: 'ev = <PySide6.QtCore.QEvent(QEvent::User)>'
E and: ''
E and: ' def event(self, ev):'
E and: ' if True:'
E and: ' try:'
E and: " raise RuntimeError('original error')"
E and: ' except RuntimeError:'
E and: "> raise ValueError('mistakes were made')"
E and: 'E ValueError: Error calling Python override of QObject::event(): mistakes were made'
E and: ''
E and: 'test_catch_exceptions_in_virtual_methods.py:10: ValueError'
E and: '=========================== short test summary info ============================'
E and: 'FAILED test_catch_exceptions_in_virtual_methods.py::test_exceptions - ValueEr...'
E and: '============================== 1 failed in 0.02s ==============================='
E remains unmatched: '*ValueError: mistakes were made*'
/builddir/build/BUILD/python-pytest-qt-4.4.0-build/pytest-qt-4.4.0/tests/test_exceptions.py:72: Failed
----------------------------- Captured stdout call -----------------------------
============================= test session starts ==============================
platform linux -- Python 3.13.0, pytest-8.3.3, pluggy-1.5.0
PyQtAPI 1.0 -- Qt runtime 2.5 -- Qt compiled 3.5
rootdir: /tmp/pytest-of-mockbuild/pytest-1/test_catch_exceptions_in_virtual_methods1
plugins: qt-4.4.0
collected 1 item
test_catch_exceptions_in_virtual_methods.py F [100%]
=================================== FAILURES ===================================
_______________________________ test_exceptions ________________________________
self = <test_catch_exceptions_in_virtual_methods.Receiver(0x564b3bc33870) at 0x7f604702f500>
ev = <PySide6.QtCore.QEvent(QEvent::User)>
def event(self, ev):
if True:
try:
> raise RuntimeError('original error')
E RuntimeError: original error
test_catch_exceptions_in_virtual_methods.py:8: RuntimeError
During handling of the above exception, another exception occurred:
qtbot = <pytestqt.qtbot.QtBot object at 0x7f60472343c0>
def test_exceptions(qtbot):
v = Receiver()
app = qt_api.QtWidgets.QApplication.instance()
> app.sendEvent(v, qt_api.QtCore.QEvent(qt_api.QtCore.QEvent.Type.User))
test_catch_exceptions_in_virtual_methods.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <test_catch_exceptions_in_virtual_methods.Receiver(0x564b3bc33870) at 0x7f604702f500>
ev = <PySide6.QtCore.QEvent(QEvent::User)>
def event(self, ev):
if True:
try:
raise RuntimeError('original error')
except RuntimeError:
> raise ValueError('mistakes were made')
E ValueError: Error calling Python override of QObject::event(): mistakes were made
test_catch_exceptions_in_virtual_methods.py:10: ValueError
This seems to be related. Maybe it helps in tracking it down.
This is great. It doesn't help me directly, but it means the problem is not restricted to QShortcut
(I had been unable before to come up with an example that does not involve QShortcut
) and maybe helps pytest-qt guys explain what's going on.
Since @penguinpee is also using Python 3.13, I quickly verified that I am getting the same test failures on Python 3.12, and I do:
Python 3.12/3.13 and PySide6 v6.8.0.1:
====================================================================================== short test summary info ======================================================================================
FAILED tests/test_exceptions.py::test_catch_exceptions_in_virtual_methods[True] - Failed: nomatch: '*RuntimeError: original error'
FAILED tests/test_exceptions.py::test_no_capture_preserves_custom_excepthook - Failed: nomatch: '*2 passed*'
On both Python versions, PySide6 v6.7.3 and v6.6.0 fails (only) test_no_capture_preserves_custom_excepthook
, which means that
test_catch_exceptions_in_virtual_methods
fails exclusively using the newest PySide6, but also thattest_no_capture_preserves_custom_excepthook
(which looks related to my original problem) has problems for a longer time.In daily use, I find that v6.8.0(.1) behaves quite differently from 6.7.x in terms of exception handling. This is not a pytest-qt-only thing, I'd conclude by now. If anyone has a reference to a more general issue description, I'd be glad to know.
I must confess I hadn't looked into the issue at all. I was caught a bit by surprise when PySide 6.8.0 landed and "broke" pytest-qt.
It seems the test fails solely because of changed (more verbose?) output passed down.
Changing:
to
"*ValueError: *mistakes were made*",
makes the test pass.
That makes it a different issue, I believe, then the one reported. I'm happy to split this off into a separate issue. The final fix might need to be a bit more sophisticated then simply extending the glob.
That makes it a different issue, I believe, then the one reported. I'm happy to split this off into a separate issue.
I believe you are right. (I was, and still are, confused by the test failure message Failed: nomatch: '*RuntimeError: original error'
, but I tried your change and it makes the test pass.)
I'm all for fixing this in a separate issue.
I think the original issue is independent of pytest-qt. I opened https://bugreports.qt.io/browse/PYSIDE-2900, and close this as a duplicate.
With
PySide6==6.8.0
, the following code does not callexcepthook()
when run viapytest
, while it does with version6.7.3
or when run viapython
:This is on Windows 11 with Python 3.13.0.