-
-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Leaking top level widgets #498
Comments
Indeed it is the user's responsibility to close the widgets, that's why there's the
Just closing (via We do mention to use
I also have seen crashes when one does not close/destroy things properly, which is unfortunate. Hope this helps. |
Hi Bruno,
Especially, my new test did only fail when adding e.g. this existing test from pytest-qt to the test run: def test_stop(qtbot, timer):
widget = qt_api.QtWidgets.QWidget()
qtbot.addWidget(widget)
with qtbot.waitExposed(widget):
widget.show()
timer.single_shot_callback(widget.close, 0)
qtbot.stop() Given that adding this test to the run makes the fault appear, what does this code to wrong then?
I tried (on our code base), they still stay alive when using qtbot.
Not the fault of pytest-qt, I consider PyQt and PySide quite fragile in that matter. You get the C++ behaviour "API misuse => crash" from Python that way, without of the tool support you got in C++. But I think that is out of scope here, I just would like better isolation between tests using pytest-qt. Greetings, Torsten |
Out of curiosity, I ran the tests with this patch: diff --git a/tests/conftest.py b/tests/conftest.py
index 6010c31..f3cec82 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,6 +7,13 @@ from pytestqt.qt_compat import qt_api
pytest_plugins = "pytester"
+def pytest_runtest_logfinish(nodeid, location):
+ app = qt_api.QtWidgets.QApplication.instance()
+ if app is not None:
+ n = len(app.topLevelWidgets())
+ print(f"{nodeid}: {n} widgets in the balance.")
+
+
@pytest.fixture
def stop_watch():
""" Turns out that pytest-qt's own tests keep up to 19 widgets lying around:
Probably, test_wait_twice actually deletes those because an actual event loop is run there. Seems like pytest-qt uses deleteLater to clean up widgets after running a test case: def _close_widgets(item):
"""
Close all widgets registered in the pytest item.
"""
widgets = getattr(item, "qt_widgets", None)
if widgets:
for w, before_close_func in item.qt_widgets:
w = w()
if w is not None:
if before_close_func is not None:
before_close_func(w)
w.close()
w.deleteLater()
del item.qt_widgets Note the docs on If the event loop is not running when this function is called (e.g. deleteLater() is called on an object before [QCoreApplication::exec](https://doc.qt.io/qt-5/qcoreapplication.html#exec)()), the object will be deleted once the event loop is started. So maybe, the simple fix would be to bring up a new QEventLoop in that code to actually purge those deleted widgets. Greetings, Torsten |
Hi Torsten, Unfortunately changing pytest-qt to close all widgets implicitly now (using Note that you do not need to patch pytest-qt to get that behavior, you can instead implement it using an autouse fixture in your @pytest.fixture
def close_all_widgets():
yield
app = QApplication.instance()
if app is not None:
for w in app.topLevelWidgets():
w.close() But as I said, I'm hesitant to add support for this directly in pytest-qt. @The-Compiler any thoughts? |
Sorry for the radio silence, this got lost with all the Europython and pytest training stuff! I feel like we should still aim to:
So yeah. This won't be a priority for myself for the forseeable future I'm afraid, but I still think we should investigate some more at some point. |
Hi *,
our pipeline broke lately, running in to segfaults. After a while of searching, it appears that some widgets live on until the the test session concludes. Somehow, during interpreter shutdown, this leads into updating dead QAbstractItemModel instances, which causes the crash.
It is still unclear to me how this unfolds, but the basics are reproducible in pytest-qt at 3e593f2. Look at this (new) test case (
tests/test_widget_leak.py
):I get a successful run exercising only this new test case:
But running some existing tests cases first, this fails because there are additional top level widgets. Also, the newly created widgets are kept now:
I failed to directly and explicitly delete/destroy those top level widgets (there is no usable
delete
/destroy
method exposed in Python). The following work around seems to get rid of extra top levels:I have no idea though if this has any bad side effects. Also, I am not sure what causes the survival of the widgets. Any insights appreciated.
Greetings, Torsten
The text was updated successfully, but these errors were encountered: