Skip to content

TST: Any loaded backend may break CallbackRegistry exception tests #31049

@QuLogic

Description

@QuLogic

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions