While looking at #31031, I noticed that sometimes, the test that was supposed to fail didn't. Some investigation showed that this was due to a test with a GUI toolkit running beforehand. What happens is, if there is no running GUI toolkit, then an exception in a callback will raise:
|
def _exception_printer(exc): |
|
if _get_running_interactive_framework() in ["headless", None]: |
|
raise exc |
|
else: |
|
traceback.print_exc() |
If a test with Qt runs, though, then it will count as running
forever:
|
def _get_running_interactive_framework(): |
|
""" |
|
Return the interactive framework whose event loop is currently running, if |
|
any, or "headless" if no event loop can be started, or None. |
|
|
|
Returns |
|
------- |
|
Optional[str] |
|
One of the following values: "qt", "gtk3", "gtk4", "wx", "tk", |
|
"macosx", "headless", ``None``. |
|
""" |
|
# Use ``sys.modules.get(name)`` rather than ``name in sys.modules`` as |
|
# entries can also have been explicitly set to None. |
|
QtWidgets = ( |
|
sys.modules.get("PyQt6.QtWidgets") |
|
or sys.modules.get("PySide6.QtWidgets") |
|
or sys.modules.get("PyQt5.QtWidgets") |
|
or sys.modules.get("PySide2.QtWidgets") |
|
) |
|
if QtWidgets and QtWidgets.QApplication.instance(): |
|
return "qt" |
|
Gtk = sys.modules.get("gi.repository.Gtk") |
|
if Gtk: |
|
if Gtk.MAJOR_VERSION == 4: |
|
from gi.repository import GLib |
|
if GLib.main_depth(): |
|
return "gtk4" |
|
if Gtk.MAJOR_VERSION == 3 and Gtk.main_level(): |
|
return "gtk3" |
|
wx = sys.modules.get("wx") |
|
if wx and wx.GetApp(): |
|
return "wx" |
|
tkinter = sys.modules.get("tkinter") |
|
if tkinter: |
|
codes = {tkinter.mainloop.__code__, tkinter.Misc.mainloop.__code__} |
|
for frame in sys._current_frames().values(): |
|
while frame: |
|
if frame.f_code in codes: |
|
return "tk" |
|
frame = frame.f_back |
|
# Preemptively break reference cycle between locals and the frame. |
|
del frame |
|
macosx = sys.modules.get("matplotlib.backends._macosx") |
|
if macosx and macosx.event_loop_is_running(): |
|
return "macosx" |
|
if not _c_internal_utils.display_is_valid(): |
|
return "headless" |
|
return None |
Unlike, e.g., GTK, all that's required is that an application instance is connected, so this is permanent.
If there are tests that check exceptions, this would be noticed rather quickly. However, in the linked case, the test is more of a negative check, trying to prove that no exception occurs. The problem here is that we may in the future silently break any such test, since an exception that should have occurred may not raise after Qt is loaded.
Probably the simplest fix is to move any test that uses a backend explicitly into a subprocess. But I did not count how many that would be and whether that might cause an undue burden on testing infrastructure.
While looking at #31031, I noticed that sometimes, the test that was supposed to fail didn't. Some investigation showed that this was due to a test with a GUI toolkit running beforehand. What happens is, if there is no running GUI toolkit, then an exception in a callback will raise:
matplotlib/lib/matplotlib/cbook.py
Lines 112 to 116 in 10ee372
If a test with Qt runs, though, then it will count as running forever:
matplotlib/lib/matplotlib/cbook.py
Lines 62 to 109 in 10ee372
Unlike, e.g., GTK, all that's required is that an application instance is connected, so this is permanent.
If there are tests that check exceptions, this would be noticed rather quickly. However, in the linked case, the test is more of a negative check, trying to prove that no exception occurs. The problem here is that we may in the future silently break any such test, since an exception that should have occurred may not raise after Qt is loaded.
Probably the simplest fix is to move any test that uses a backend explicitly into a subprocess. But I did not count how many that would be and whether that might cause an undue burden on testing infrastructure.