Skip to content

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

Open

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.

Activity

  1. anntzer commented on Jan 31, 2026

    @anntzer
    Contributor

    Actually I was looking recently (in another context) for whether qt had a way to test whether the main loop is currently running (something similar to Gtk.main_loop?), but I couldn't find anything...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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