diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f79beae1bd5..f575e9c536c7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,34 +47,6 @@ jobs: fail-fast: false matrix: include: - - name-suffix: "(Minimum Versions)" - os: ubuntu-22.04 - python-version: '3.11' - extra-requirements: '-c requirements/testing/minver.txt' - delete-font-cache: true - # https://github.com/matplotlib/matplotlib/issues/29844 - pygobject-ver: '<3.52.0' - - os: ubuntu-22.04 - python-version: '3.11' - CFLAGS: "-fno-lto" # Ensure that disabling LTO works. - extra-requirements: '-r requirements/testing/extra.txt' - # https://github.com/matplotlib/matplotlib/issues/29844 - pygobject-ver: '<3.52.0' - - name-suffix: "(Extra TeX packages)" - os: ubuntu-22.04 - python-version: '3.13' - extra-packages: 'texlive-fonts-extra texlive-lang-cyrillic' - # https://github.com/matplotlib/matplotlib/issues/29844 - pygobject-ver: '<3.52.0' - - name-suffix: "Free-threaded" - os: ubuntu-22.04 - python-version: '3.13t' - # https://github.com/matplotlib/matplotlib/issues/29844 - pygobject-ver: '<3.52.0' - - os: ubuntu-24.04 - python-version: '3.12' - - os: ubuntu-24.04 - python-version: '3.14' - os: ubuntu-24.04-arm python-version: '3.12' - os: macos-14 # This runner is on M1 (arm64) chips. @@ -336,7 +308,7 @@ jobs: if [[ "${{ matrix.python-version }}" == '3.13t' ]]; then export PYTHON_GIL=0 fi - pytest -rfEsXR -n auto \ + pytest -vrfEsXR -n 2 -m starts_subprocess\ --maxfail=50 --timeout=300 --durations=25 \ --cov-report=xml --cov=lib --log-level=DEBUG --color=yes diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d68a9d36f0d3..5eb69258b671 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -120,7 +120,7 @@ stages: echo "##vso[task.setvariable variable=VS_COVERAGE_TOOL]$TOOL" - PYTHONFAULTHANDLER=1 pytest -rfEsXR -n 2 \ + PYTHONFAULTHANDLER=1 pytest -vrfEsXR -n 2 \ --maxfail=50 --timeout=300 --durations=25 \ --junitxml=junit/test-results.xml --cov-report=xml --cov=lib diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index f07157a63524..1c53214cd25c 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -23,10 +23,9 @@ import os import re import struct -import subprocess import sys from collections import namedtuple -from functools import cache, cached_property, lru_cache, partial, wraps +from functools import cached_property, lru_cache, partial, wraps from pathlib import Path import fontTools.agl @@ -1244,31 +1243,6 @@ def _parse_enc(path): raise ValueError(f"Failed to parse {path} as Postscript encoding") -class _LuatexKpsewhich: - @cache # A singleton. - def __new__(cls): - self = object.__new__(cls) - self._proc = self._new_proc() - return self - - def _new_proc(self): - return subprocess.Popen( - ["luatex", "--luaonly", str(cbook._get_data_path("kpsewhich.lua"))], - # mktexpk logs to stderr; suppress that. - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, - # Store generated pk fonts in our own cache. - env={"MT_VARTEXFONTS": str(Path(mpl.get_cachedir(), "vartexfonts")), - **os.environ}) - - def search(self, filename): - if self._proc.poll() is not None: # Dead, restart it. - self._proc = self._new_proc() - self._proc.stdin.write(os.fsencode(filename) + b"\n") - self._proc.stdin.flush() - out = self._proc.stdout.readline().rstrip() - return None if out == b"nil" else os.fsdecode(out) - - @lru_cache def find_tex_file(filename): """ @@ -1295,10 +1269,7 @@ def find_tex_file(filename): if isinstance(filename, bytes): filename = filename.decode('utf-8', errors='replace') - try: - lk = _LuatexKpsewhich() - except FileNotFoundError: - lk = None # Fallback to directly calling kpsewhich, as below. + lk = None # Fallback to directly calling kpsewhich, as below. if lk: path = lk.search(filename) diff --git a/lib/matplotlib/testing/_markers.py b/lib/matplotlib/testing/_markers.py index c7ef8687a8b3..c8795ef8ee90 100644 --- a/lib/matplotlib/testing/_markers.py +++ b/lib/matplotlib/testing/_markers.py @@ -47,3 +47,4 @@ def _checkdep_usetex() -> bool: needs_usetex = pytest.mark.skipif( not _checkdep_usetex(), reason="This test needs a TeX installation") +starts_subprocess = pytest.mark.starts_subprocess diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index 6f87d9826cc3..1bf57894151f 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -1,4 +1,5 @@ import os +import gc import sys import pytest @@ -18,6 +19,7 @@ def pytest_configure(config): ("markers", "backend: Set alternate Matplotlib backend temporarily."), ("markers", "baseline_images: Compare output against references."), ("markers", "pytz: Tests that require pytz to be installed."), + ("markers", "starts_subprocess: Tests that start subprocesses."), ("filterwarnings", "error"), ("filterwarnings", "ignore:.*The py23 module has been deprecated:DeprecationWarning"), @@ -81,6 +83,7 @@ def mpl_test_settings(request): if backend is not None: plt.close("all") matplotlib.use(prev_backend) + gc.collect() @pytest.fixture diff --git a/lib/matplotlib/tests/test_backend_inline.py b/lib/matplotlib/tests/test_backend_inline.py index 997e1e7186b1..70e68ac2fb43 100644 --- a/lib/matplotlib/tests/test_backend_inline.py +++ b/lib/matplotlib/tests/test_backend_inline.py @@ -5,11 +5,13 @@ import pytest from matplotlib.testing import subprocess_run_for_testing +from matplotlib.testing._markers import starts_subprocess nbformat = pytest.importorskip('nbformat') pytest.importorskip('nbconvert') pytest.importorskip('ipykernel') pytest.importorskip('matplotlib_inline') +pytestmark = starts_subprocess def test_ipynb(): diff --git a/lib/matplotlib/tests/test_backend_nbagg.py b/lib/matplotlib/tests/test_backend_nbagg.py index ccf74df20aab..eaf711502c44 100644 --- a/lib/matplotlib/tests/test_backend_nbagg.py +++ b/lib/matplotlib/tests/test_backend_nbagg.py @@ -3,12 +3,14 @@ from tempfile import TemporaryDirectory import pytest +from matplotlib.testing._markers import starts_subprocess from matplotlib.testing import subprocess_run_for_testing nbformat = pytest.importorskip('nbformat') pytest.importorskip('nbconvert') pytest.importorskip('ipykernel') +pytestmark = starts_subprocess # From https://blog.thedataincubator.com/2016/06/testing-jupyter-notebooks/ diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index 9859a286e5fd..dd811ae3f8f5 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -18,8 +18,6 @@ import matplotlib.pyplot as plt -# This tests tends to hit a TeX cache lock on AppVeyor. -@pytest.mark.flaky(reruns=3) @pytest.mark.parametrize('papersize', ['letter', 'figure']) @pytest.mark.parametrize('orientation', ['portrait', 'landscape']) @pytest.mark.parametrize('format, use_log, rcParams', [ diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index 1f96ad1308cb..54d90cd09644 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -129,7 +129,6 @@ def legitimate_quit(): @pytest.mark.skipif(platform.python_implementation() != 'CPython', reason='PyPy does not support Tkinter threading: ' 'https://foss.heptapod.net/pypy/pypy/-/issues/1929') -@pytest.mark.flaky(reruns=3) @_isolated_tk_test(success_count=1) def test_figuremanager_cleans_own_mainloop(): import tkinter @@ -156,7 +155,6 @@ def target(): thread.join() -@pytest.mark.flaky(reruns=3) @_isolated_tk_test(success_count=0) def test_never_update(): import tkinter diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 1d6769494ef9..50100883747d 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -3,9 +3,11 @@ import pytest import matplotlib.backends.backend_webagg_core +from matplotlib.testing._markers import starts_subprocess from matplotlib.testing import subprocess_run_for_testing +@starts_subprocess @pytest.mark.parametrize("backend", ["webagg", "nbagg"]) def test_webagg_fallback(backend): pytest.importorskip("tornado") diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 188983816372..df94de730204 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -20,6 +20,10 @@ from matplotlib import _c_internal_utils from matplotlib.backend_tools import ToolToggleBase from matplotlib.testing import subprocess_run_helper as _run_helper, is_ci_environment +from matplotlib.testing._markers import starts_subprocess + + +pytestmark = starts_subprocess class _WaitForStringPopen(subprocess.Popen): @@ -240,7 +244,6 @@ def check_alt_backend(alt_backend): @pytest.mark.parametrize("env", _get_testable_interactive_backends()) @pytest.mark.parametrize("toolbar", ["toolbar2", "toolmanager"]) -@pytest.mark.flaky(reruns=_retry_count) def test_interactive_backend(env, toolbar): if env["MPLBACKEND"] == "macosx": if toolbar == "toolmanager": @@ -335,7 +338,6 @@ def _test_thread_impl(): @pytest.mark.parametrize("env", _thread_safe_backends) -@pytest.mark.flaky(reruns=_retry_count) def test_interactive_thread_safety(env): proc = _run_helper(_test_thread_impl, timeout=_test_timeout, extra_env=env) assert proc.stdout.count("CloseEvent") == 1 @@ -622,8 +624,6 @@ def _test_number_of_draws_script(): @pytest.mark.parametrize("env", _blit_backends) -# subprocesses can struggle to get the display, so rerun a few times -@pytest.mark.flaky(reruns=_retry_count) def test_blitting_events(env): proc = _run_helper( _test_number_of_draws_script, timeout=_test_timeout, extra_env=env) @@ -725,6 +725,7 @@ def interrupter(): print('SUCCESS', flush=True) +@pytest.mark.skip(reason='eliminate test from enquiries') @pytest.mark.parametrize("env", _get_testable_interactive_backends()) @pytest.mark.parametrize("target, kwargs", [ ('show', {'block': True}), @@ -773,6 +774,7 @@ def custom_signal_handler(signum, frame): print('SUCCESS', flush=True) +@pytest.mark.skip(reason='eliminate test from enquiries') @pytest.mark.skipif(sys.platform == 'win32', reason='No other signal available to send on Windows') @pytest.mark.parametrize("env", _get_testable_interactive_backends()) diff --git a/lib/matplotlib/tests/test_basic.py b/lib/matplotlib/tests/test_basic.py index f6aa1e458555..976fba23c0ef 100644 --- a/lib/matplotlib/tests/test_basic.py +++ b/lib/matplotlib/tests/test_basic.py @@ -3,6 +3,7 @@ import sys import textwrap +from matplotlib.testing._markers import starts_subprocess from matplotlib.testing import subprocess_run_for_testing @@ -28,6 +29,7 @@ def test_override_builtins(): assert overridden <= ok_to_override +@starts_subprocess def test_lazy_imports(): source = textwrap.dedent(""" import sys diff --git a/lib/matplotlib/tests/test_determinism.py b/lib/matplotlib/tests/test_determinism.py index c0e4adbef40b..9210cddc54a0 100644 --- a/lib/matplotlib/tests/test_determinism.py +++ b/lib/matplotlib/tests/test_determinism.py @@ -16,11 +16,15 @@ from matplotlib.patches import Circle, PathPatch from matplotlib.path import Path from matplotlib.testing import subprocess_run_for_testing -from matplotlib.testing._markers import needs_ghostscript, needs_usetex +from matplotlib.testing._markers import ( + needs_ghostscript, needs_usetex, starts_subprocess +) import matplotlib.testing.compare from matplotlib.text import TextPath from matplotlib.transforms import IdentityTransform +pytestmark = starts_subprocess + def _save_figure(objects='mhip', fmt="pdf", usetex=False): mpl.use(fmt) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 1bca00e072a3..0d6f40d30801 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -19,6 +19,7 @@ _get_fontconfig_fonts, _normalize_weight) from matplotlib import cbook, ft2font, pyplot as plt, rc_context, figure as mfigure from matplotlib.testing import subprocess_run_helper, subprocess_run_for_testing +from matplotlib.testing._markers import starts_subprocess has_fclist = shutil.which('fc-list') is not None @@ -289,6 +290,7 @@ def test_fontcache_thread_safe(): subprocess_run_helper(_test_threading, timeout=10) +@starts_subprocess def test_lockfilefailure(tmp_path): # The logic here: # 1. get a temp directory from pytest diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index d0a3f8c617e1..911ccf65d20e 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -7,6 +7,7 @@ import matplotlib from matplotlib.testing import subprocess_run_for_testing +from matplotlib.testing._markers import starts_subprocess @pytest.mark.parametrize('version_str, version_tuple', [ @@ -19,6 +20,7 @@ def test_parse_to_version_info(version_str, version_tuple): assert matplotlib._parse_to_version_info(version_str) == version_tuple +@starts_subprocess @pytest.mark.skipif(sys.platform == "win32", reason="chmod() doesn't work as is on Windows") @pytest.mark.skipif(sys.platform != "win32" and os.geteuid() == 0, @@ -37,6 +39,7 @@ def test_tmpconfigdir_warning(tmp_path): os.chmod(tmp_path, mode) +@starts_subprocess def test_importable_with_no_home(tmp_path): subprocess_run_for_testing( [sys.executable, "-c", diff --git a/lib/matplotlib/tests/test_preprocess_data.py b/lib/matplotlib/tests/test_preprocess_data.py index c983d78786e1..44db2733f10d 100644 --- a/lib/matplotlib/tests/test_preprocess_data.py +++ b/lib/matplotlib/tests/test_preprocess_data.py @@ -8,6 +8,8 @@ from matplotlib.axes import Axes from matplotlib.testing import subprocess_run_for_testing from matplotlib.testing.decorators import check_figures_equal +from matplotlib.testing._markers import starts_subprocess + # Notes on testing the plotting functions itself # * the individual decorated plotting functions are tested in 'test_axes.py' @@ -245,6 +247,7 @@ def funcy(ax, x, y, z, t=None): funcy.__doc__) +@starts_subprocess def test_data_parameter_replacement(): """ Test that the docstring contains the correct *data* parameter stub diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index 1cca7332aa0c..7d3921dbbef7 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -11,7 +11,10 @@ from matplotlib.testing import subprocess_run_for_testing from matplotlib import pyplot as plt +from matplotlib.testing._markers import starts_subprocess + +@starts_subprocess def test_pyplot_up_to_date(tmp_path): pytest.importorskip("black", minversion="24.1") diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 525a9ff60d1a..61ff9ced44a6 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -32,6 +32,7 @@ _validate_linestyle, _listify_validator) from matplotlib.testing import subprocess_run_for_testing +from matplotlib.testing._markers import starts_subprocess def test_rcparams(tmp_path): @@ -529,6 +530,7 @@ def test_rcparams_reset_after_fail(): assert mpl.rcParams['text.usetex'] is False +@starts_subprocess @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") def test_backend_fallback_headless_invalid_backend(tmp_path): env = {**os.environ, @@ -546,6 +548,7 @@ def test_backend_fallback_headless_invalid_backend(tmp_path): env=env, check=True, stderr=subprocess.DEVNULL) +@starts_subprocess @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") def test_backend_fallback_headless_auto_backend(tmp_path): # specify a headless mpl environment, but request a graphical (tk) backend @@ -568,6 +571,7 @@ def test_backend_fallback_headless_auto_backend(tmp_path): assert backend.strip().lower() == "agg" +@starts_subprocess @pytest.mark.skipif( sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), reason="headless") diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index c6f4e13c74c2..91d436c52e42 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -7,6 +7,8 @@ import sys from matplotlib.testing import subprocess_run_for_testing +from matplotlib.testing._markers import starts_subprocess + import pytest @@ -16,6 +18,7 @@ tinypages = Path(__file__).parent / 'data/tinypages' +@starts_subprocess def build_sphinx_html(source_dir, doctree_dir, html_dir, extra_args=None): # Build the pages with warnings turned into errors extra_args = [] if extra_args is None else extra_args diff --git a/lib/matplotlib/tests/test_streamplot.py b/lib/matplotlib/tests/test_streamplot.py index 697ee527f253..ca7843f72b58 100644 --- a/lib/matplotlib/tests/test_streamplot.py +++ b/lib/matplotlib/tests/test_streamplot.py @@ -100,6 +100,7 @@ def test_direction(): linewidth=2, density=2) +# @pytest.mark.skip(reason='Eliminating test from enquiries') @image_comparison(['streamplot_integration.png'], style='mpl20', tol=0.05) def test_integration_options(): # Linear potential flow over a lifting cylinder diff --git a/lib/matplotlib/tests/test_texmanager.py b/lib/matplotlib/tests/test_texmanager.py index 7e7b5632c7ac..4f957247bdad 100644 --- a/lib/matplotlib/tests/test_texmanager.py +++ b/lib/matplotlib/tests/test_texmanager.py @@ -9,6 +9,7 @@ from matplotlib.testing import subprocess_run_for_testing from matplotlib.testing._markers import needs_usetex from matplotlib.texmanager import TexManager +from matplotlib.testing._markers import starts_subprocess def test_fontconfig_preamble(): @@ -63,6 +64,7 @@ def test_unicode_characters(): fig.canvas.draw() +@starts_subprocess @needs_usetex def test_openin_any_paranoid(): completed = subprocess_run_for_testing(