pyface
pyface copied to clipboard
Bug report: Tests halt on Rocky Linux 8 using PySide6-6.7.1
Environment
OS: Linux Python version: 3.11 Toolkit: Qt Qt API: PySide6 6.7.1
Description
Tests halt on (WSL2) Rocky Linux 8 using PySide6-6.7.1. The test run will resume if the pop up windows are closed but similar errors appear later and the run does not finish.
Steps to Reproduce
edm env create test --version 3.11
edm install -e test PySide6 numpy pygments packaging pillow importlib_resources build wheel
edm run -e test -- pip install traits pyface
edm run -e test -- python -m unittest discover pyface
output
test_set_title_change_active_task (pyface.tasks.tests.test_task_window.TestTaskWindow.test_set_title_change_active_task) ... ok
test_set_title_change_active_task_name (pyface.tasks.tests.test_task_window.TestTaskWindow.test_set_title_change_active_task_name) ... ok
test_set_title_change_title (pyface.tasks.tests.test_task_window.TestTaskWindow.test_set_title_change_title) ... ok
test_set_title_no_active_task (pyface.tasks.tests.test_task_window.TestTaskWindow.test_set_title_no_active_task) ... ok
test_set_title_no_tasks (pyface.tasks.tests.test_task_window.TestTaskWindow.test_set_title_no_tasks) ... ok
test_title_activate_task (pyface.tasks.tests.test_task_window.TestTaskWindow.test_title_activate_task) ... ok
test_title_change_active_task (pyface.tasks.tests.test_task_window.TestTaskWindow.test_title_change_active_task) ... ok
test_title_change_active_task_name (pyface.tasks.tests.test_task_window.TestTaskWindow.test_title_change_active_task_name) ... ok
test_title_change_deactivate_task (pyface.tasks.tests.test_task_window.TestTaskWindow.test_title_change_deactivate_task) ... ok
test_title_default (pyface.tasks.tests.test_task_window.TestTaskWindow.test_title_default) ... ok
test_title_no_active_task (pyface.tasks.tests.test_task_window.TestTaskWindow.test_title_no_active_task) ... ok
test_defaults (pyface.tasks.tests.test_tasks_application.TestApplication.test_defaults) ... ok
test_lifecycle (pyface.tasks.tests.test_tasks_application.TestApplication.test_lifecycle) ... Traceback (most recent call last):
File "/home/itziakos/.edm/envs/build-env/lib/python3.11/site-packages/pyface/ui/qt/gui.py", line 190, in _dispatch
self._callable(*self._args, **self._kw)
File "/home/itziakos/.edm/envs/build-env/lib/python3.11/site-packages/pyface/application.py", line 220, in exit
self._exit()
File "/home/itziakos/.edm/envs/build-env/lib/python3.11/site-packages/pyface/gui_application.py", line 236, in _exit
self.gui.stop_event_loop()
^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: '_Undefined' object has no attribute 'stop_event_loop'
On WSL2 the tests halt but on linux VM they segfault.
[2024-06-12T15:33:10.219Z] b'test_lifecycle (pyface.tasks.tests.test_tasks_application.TestApplication.test_lifecycle) ... Traceback (most recent call last):'
[2024-06-12T15:33:10.220Z] b' File "/home/builder/.edm/envs/build-env/lib/python3.11/site-packages/pyface/ui/qt/gui.py", line 190, in _dispatch'
[2024-06-12T15:33:10.220Z] b' self._callable(*self._args, **self._kw)'
[2024-06-12T15:33:10.220Z] b' File "/home/builder/.edm/envs/build-env/lib/python3.11/site-packages/pyface/application.py", line 220, in exit'
[2024-06-12T15:33:10.220Z] b' self._exit()'
[2024-06-12T15:33:10.220Z] b' File "/home/builder/.edm/envs/build-env/lib/python3.11/site-packages/pyface/gui_application.py", line 236, in _exit'
[2024-06-12T15:33:10.220Z] b' self.gui.stop_event_loop()'
[2024-06-12T15:33:10.220Z] b' ^^^^^^^^^^^^^^^^^^^^^^^^'
[2024-06-12T15:33:10.220Z] b"AttributeError: '_Undefined' object has no attribute 'stop_event_loop'"
[2024-06-12T15:33:10.220Z] b'FAIL'
[2024-06-12T15:33:10.220Z] b''
[2024-06-12T15:33:10.220Z] b'======================================================================'
[2024-06-12T15:33:10.220Z] b'FAIL: test2_test_pyside6 (__main__.Tests.test2_test_pyside6)'
[2024-06-12T15:33:10.220Z] b'----------------------------------------------------------------------'
[2024-06-12T15:33:10.220Z] b'Traceback (most recent call last):'
[2024-06-12T15:33:10.220Z] b' File "/home/builder/workspace/Buildsystem/test/new/cp311/pyface-8.0.0/tests/run.py", line 27, in test2_test_pyside6'
[2024-06-12T15:33:10.220Z] b' self.runpy(args, env=env)'
[2024-06-12T15:33:10.220Z] b' File "/home/builder/workspace/Buildsystem/test/new/cp311/entest_utils.py", line 469, in runpy'
[2024-06-12T15:33:10.220Z] b' self.run_test_function('
[2024-06-12T15:33:10.220Z] b' File "/home/builder/workspace/Buildsystem/test/new/cp311/entest_utils.py", line 546, in run_test_function'
[2024-06-12T15:33:10.220Z] b' self.fail("unexpected test exit code {!r}".format(ret))'
[2024-06-12T15:33:10.220Z] b'AssertionError: unexpected test exit code -15'
[2024-06-12T15:33:10.220Z] b''
[2024-06-12T15:33:10.220Z] b'----------------------------------------------------------------------'
[2024-06-12T15:33:10.220Z] b'Ran 18 tests in 1180.661s'
[2024-06-12T15:33:10.220Z] b''
[2024-06-12T15:33:10.220Z] b'FAILED (failures=1, skipped=11)'
[2024-06-12T15:33:10.220Z] b'================================================='
[2024-06-12T15:33:10.220Z] b"ENTEST STOP -- TOTAL FAILURES: 1 ('pyface-8.0.0')"
I think there's a race condition in these tests: they do a self.invoke_after(1000, app.exit), and as a result app.exit is getting invoked during application startup, before the gui trait of the application has been set.
I'm able to reproduce locally; running just this one test by itself using python -m unittest pyface.tasks.tests.test_tasks_application.TestApplication.test_lifecycle, the test sometimes passed and sometimes fails. If I change the 1000 to 10000, it always passes, while if I change the 1000 to 100 it almost always fails.
I think there's a race condition in these tests: they do a
self.invoke_after(1000, app.exit), and as a resultapp.exitis getting invoked during application startup, before theguitrait of the application has been set.
In more detail: the GuiApplication.start method is responsible for setting the gui attribute. If start is successful, the Application.run method then fires the "started" event. So it should be enough to make sure that we don't invoke app.exit until after thestarted event has fired.