From 73b2152954ee3ef3db59e38a5ed25221cc853ac1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 2 Dec 2023 04:30:59 -0500 Subject: [PATCH] TST: Cache available interactive backends There are currently 7 backends, and this function is called 8 times during test collection. This means that `XOpenDisplay` would be called 56 times (multiplied by process count if using xdist). Locally, I'm seeing a deadlock after too many calls to `XOpenDisplay` during collection. While this is not directly our bug, it seems prudent to cut this down to only the one check per collection process. I don't think we can cache this globally, as one might want to re-try a GUI figure after starting an X server (e.g., over SSH or similar), without restarting Python/Matplotlib. --- .../tests/test_backends_interactive.py | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 6048b2647b17..3a714319ab81 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -1,3 +1,4 @@ +import functools import importlib import importlib.util import inspect @@ -52,7 +53,10 @@ def wait_for(self, terminator): # PyPI-installable on CI. They are not available for all tested Python # versions so we don't fail on missing backends. -def _get_testable_interactive_backends(): +@functools.lru_cache +def _get_available_interactive_backends(): + _is_linux_and_display_invalid = (sys.platform == "linux" and + not _c_internal_utils.display_is_valid()) envs = [] for deps, env in [ *[([qt_api], @@ -70,8 +74,7 @@ def _get_testable_interactive_backends(): ]: reason = None missing = [dep for dep in deps if not importlib.util.find_spec(dep)] - if (sys.platform == "linux" and - not _c_internal_utils.display_is_valid()): + if _is_linux_and_display_invalid: reason = "$DISPLAY and $WAYLAND_DISPLAY are unset" elif missing: reason = "{} cannot be imported".format(", ".join(missing)) @@ -85,8 +88,7 @@ def _get_testable_interactive_backends(): reason = "no usable GTK bindings" marks = [] if reason: - marks.append(pytest.mark.skip( - reason=f"Skipping {env} because {reason}")) + marks.append(pytest.mark.skip(reason=f"Skipping {env} because {reason}")) elif env["MPLBACKEND"].startswith('wx') and sys.platform == 'darwin': # ignore on OSX because that's currently broken (github #16849) marks.append(pytest.mark.xfail(reason='github #16849')) @@ -97,15 +99,17 @@ def _get_testable_interactive_backends(): ): marks.append( # https://github.com/actions/setup-python/issues/649 pytest.mark.xfail(reason='Tk version mismatch on Azure macOS CI')) - envs.append( - pytest.param( - {**env, 'BACKEND_DEPS': ','.join(deps)}, - marks=marks, id=str(env) - ) - ) + envs.append(({**env, 'BACKEND_DEPS': ','.join(deps)}, marks)) return envs +def _get_testable_interactive_backends(): + # We re-create this because some of the callers below might modify the markers. + return [pytest.param({**env}, marks=[*marks], + id='-'.join(f'{k}={v}' for k, v in env.items())) + for env, marks in _get_available_interactive_backends()] + + def is_ci_environment(): # Common CI variables ci_environment_variables = [