From bdbf1ee9b83cbccbbf0b85f09cd3acc15c45caca Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 4 Jun 2024 16:45:34 -0700 Subject: [PATCH 01/10] TST: Fix condition for test_tmpconfigdir_warning This checks `os.geteuid`, but this is only available on Unix, not Emscripten or WASM. --- lib/matplotlib/tests/test_matplotlib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index d0a3f8c617e1..312fa4154b66 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -19,9 +19,9 @@ def test_parse_to_version_info(version_str, version_tuple): assert matplotlib._parse_to_version_info(version_str) == version_tuple -@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, +@pytest.mark.skipif(sys.platform not in ["linux", "darwin"], + reason="chmod() doesn't work on this platform") +@pytest.mark.skipif(sys.platform in ["linux", "darwin"] and os.geteuid() == 0, reason="chmod() doesn't work as root") def test_tmpconfigdir_warning(tmp_path): """Test that a warning is emitted if a temporary configdir must be used.""" From 113e5813f6b7de49040bec639aa97f015b35952b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 4 Jun 2024 17:15:43 -0700 Subject: [PATCH 02/10] Ignore threading errors when preparing font cache In Enscripten/WASM, this module can be imported, but doesn't work, so we can't fall back to `dummy_threading` at import-time as we used to do. --- lib/matplotlib/font_manager.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index ab6b495631de..74bb99b0db89 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1090,9 +1090,12 @@ def __init__(self, size=None, weight='normal'): self.ttflist = [] # Delay the warning by 5s. - timer = threading.Timer(5, lambda: _log.warning( - 'Matplotlib is building the font cache; this may take a moment.')) - timer.start() + try: + timer = threading.Timer(5, lambda: _log.warning( + 'Matplotlib is building the font cache; this may take a moment.')) + timer.start() + except RuntimeError: + timer = None try: for fontext in ["afm", "ttf"]: for path in [*findSystemFonts(paths, fontext=fontext), @@ -1105,7 +1108,8 @@ def __init__(self, size=None, weight='normal'): _log.info("Failed to extract font properties from %s: " "%s", path, exc) finally: - timer.cancel() + if timer: + timer.cancel() def addfont(self, path): """ From 4f49ce6ec0bd8d50e31cf0dbbd51f19ab30acb74 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 4 Jun 2024 17:28:28 -0700 Subject: [PATCH 03/10] Don't attempt to load system fonts on Emscripten The file system is either sandboxed or there are simply no other fonts, so limit ourselves to our pre-shipped fonts. --- lib/matplotlib/font_manager.py | 3 +++ lib/matplotlib/tests/test_font_manager.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 74bb99b0db89..81f41a0b8f54 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -291,6 +291,9 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): if sys.platform == 'win32': installed_fonts = _get_win32_installed_fonts() fontpaths = [] + elif sys.platform == 'emscripten': + installed_fonts = [] + fontpaths = [] else: installed_fonts = _get_fontconfig_fonts() if sys.platform == 'darwin': diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 24421b8e30b3..e0e3cbda1ed2 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -21,7 +21,7 @@ from matplotlib.testing import subprocess_run_helper, subprocess_run_for_testing -has_fclist = shutil.which('fc-list') is not None +has_fclist = sys.platform != 'emscripten' and shutil.which('fc-list') is not None def test_font_priority(): From 983f6ede334a6864b48ef87e7b135fa879b4f059 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 26 Sep 2024 06:14:03 -0400 Subject: [PATCH 04/10] agg: Explicitly cast dimensions when creating Python buffer On WASM, which is wholly 32-bit, casting unsigned int to signed long is a narrowing conversion, which it seems to treat as an error. The Agg buffer cannot be over `(1<<23)` in width or height, so this cast is safe even with the smaller sizes. --- src/_backend_agg_wrapper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 3dd50b31f64a..11d45773d186 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -254,12 +254,12 @@ PYBIND11_MODULE(_backend_agg, m, py::mod_gil_not_used()) .def_buffer([](RendererAgg *renderer) -> py::buffer_info { std::vector shape { - renderer->get_height(), - renderer->get_width(), + static_cast(renderer->get_height()), + static_cast(renderer->get_width()), 4 }; std::vector strides { - renderer->get_width() * 4, + static_cast(renderer->get_width() * 4), 4, 1 }; From e12ffade3a43d4cbadcf70ff03dcf0304fdc97fa Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 Nov 2024 04:33:59 -0500 Subject: [PATCH 05/10] wasm: Skip more subprocess & thread tests --- lib/matplotlib/animation.py | 2 ++ lib/matplotlib/dviread.py | 4 ++-- lib/matplotlib/testing/__init__.py | 7 ++++++- lib/matplotlib/testing/decorators.py | 2 ++ lib/matplotlib/tests/test_animation.py | 4 ++++ lib/matplotlib/tests/test_dviread.py | 3 ++- lib/matplotlib/tests/test_figure.py | 5 +++++ lib/matplotlib/tests/test_font_manager.py | 2 ++ lib/matplotlib/texmanager.py | 8 ++++---- 9 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 8756cb0c1439..7267644b8b19 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -369,6 +369,8 @@ def bin_path(cls): @classmethod def isAvailable(cls): """Return whether a MovieWriter subclass is actually available.""" + if sys.platform == 'emscripten': + return False return shutil.which(cls.bin_path()) is not None diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index 9e8b6a5facf5..20c19e8a5b19 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -1135,7 +1135,7 @@ def find_tex_file(filename): try: lk = _LuatexKpsewhich() - except FileNotFoundError: + except (FileNotFoundError, OSError): lk = None # Fallback to directly calling kpsewhich, as below. if lk: @@ -1155,7 +1155,7 @@ def find_tex_file(filename): path = (cbook._check_and_log_subprocess(['kpsewhich', filename], _log, **kwargs) .rstrip('\n')) - except (FileNotFoundError, RuntimeError): + except (FileNotFoundError, OSError, RuntimeError): path = None if path: diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 904ee5d73db4..b2dbabdf997f 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -87,9 +87,14 @@ def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None, Raises ------ + pytest.skip + If running on emscripten, which does not support subprocesses. pytest.xfail If platform is Cygwin and subprocess reports a fork() failure. """ + if sys.platform == 'emscripten': + import pytest + pytest.skip('emscripten does not support subprocesses') if capture_output: stdout = stderr = subprocess.PIPE try: @@ -187,7 +192,7 @@ def _has_tex_package(package): try: mpl.dviread.find_tex_file(f"{package}.sty") return True - except FileNotFoundError: + except (FileNotFoundError, OSError): return False diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 17509449e768..f99d8a4866cd 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -138,6 +138,8 @@ def copy_baseline(self, baseline, extension): try: if 'microsoft' in uname().release.lower(): raise OSError # On WSL, symlink breaks silently + if sys.platform == 'emscripten': + raise OSError os.symlink(orig_expected_path, expected_fname) except OSError: # On Windows, symlink *may* be unavailable. shutil.copyfile(orig_expected_path, expected_fname) diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index 114e38996a10..b34dc01e41cb 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -271,6 +271,8 @@ def test_no_length_frames(anim): anim.save('unused.null', writer=NullMovieWriter()) +@pytest.mark.skipif(sys.platform == 'emscripten', + reason='emscripten does not support subprocesses') def test_movie_writer_registry(): assert len(animation.writers._registered) > 0 mpl.rcParams['animation.ffmpeg_path'] = "not_available_ever_xxxx" @@ -522,6 +524,8 @@ def test_disable_cache_warning(anim): def test_movie_writer_invalid_path(anim): if sys.platform == "win32": match_str = r"\[WinError 3] .*\\\\foo\\\\bar\\\\aardvark'" + elif sys.platform == "emscripten": + match_str = r"\[Errno 44] .*'/foo" else: match_str = r"\[Errno 2] .*'/foo" with pytest.raises(FileNotFoundError, match=match_str): diff --git a/lib/matplotlib/tests/test_dviread.py b/lib/matplotlib/tests/test_dviread.py index 7b7ff151be18..2aa3abe12e4b 100644 --- a/lib/matplotlib/tests/test_dviread.py +++ b/lib/matplotlib/tests/test_dviread.py @@ -1,6 +1,7 @@ import json from pathlib import Path import shutil +import sys import matplotlib.dviread as dr import pytest @@ -60,7 +61,7 @@ def test_PsfontsMap(monkeypatch): fontmap[b'%'] -@pytest.mark.skipif(shutil.which("kpsewhich") is None, +@pytest.mark.skipif(sys.platform == "emscripten" or shutil.which("kpsewhich") is None, reason="kpsewhich is not available") def test_dviread(): dirpath = Path(__file__).parent / 'baseline_images/dviread' diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index c5890a2963b3..702b51ca590a 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -3,6 +3,7 @@ import io import pickle import platform +import sys from threading import Timer from types import SimpleNamespace import warnings @@ -1605,6 +1606,8 @@ def test_add_axes_kwargs(): plt.close() +@pytest.mark.skipif(sys.platform == 'emscripten', + reason='emscripten does not support threads') def test_ginput(recwarn): # recwarn undoes warn filters at exit. warnings.filterwarnings("ignore", "cannot show the figure") fig, ax = plt.subplots() @@ -1627,6 +1630,8 @@ def multi_presses(): np.testing.assert_allclose(fig.ginput(3), [(.3, .4), (.5, .6)]) +@pytest.mark.skipif(sys.platform == 'emscripten', + reason='emscripten does not support threads') def test_waitforbuttonpress(recwarn): # recwarn undoes warn filters at exit. warnings.filterwarnings("ignore", "cannot show the figure") fig = plt.figure() diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index e0e3cbda1ed2..ef8499c7b26f 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -229,6 +229,8 @@ def _model_handler(_): plt.close() +@pytest.mark.skipif(sys.platform == 'emscripten', + reason='emscripten does not support subprocesses') @pytest.mark.skipif(not hasattr(os, "register_at_fork"), reason="Cannot register at_fork handlers") def test_fork(): diff --git a/lib/matplotlib/texmanager.py b/lib/matplotlib/texmanager.py index 020a26e31cbe..993f9b0aeb6e 100644 --- a/lib/matplotlib/texmanager.py +++ b/lib/matplotlib/texmanager.py @@ -255,10 +255,6 @@ def _run_checked_subprocess(cls, command, tex, *, cwd=None): report = subprocess.check_output( command, cwd=cwd if cwd is not None else cls._texcache, stderr=subprocess.STDOUT) - except FileNotFoundError as exc: - raise RuntimeError( - f'Failed to process string with tex because {command[0]} ' - 'could not be found') from exc except subprocess.CalledProcessError as exc: raise RuntimeError( '{prog} was not able to process the following string:\n' @@ -271,6 +267,10 @@ def _run_checked_subprocess(cls, command, tex, *, cwd=None): tex=tex.encode('unicode_escape'), exc=exc.output.decode('utf-8', 'backslashreplace')) ) from None + except (FileNotFoundError, OSError) as exc: + raise RuntimeError( + f'Failed to process string with tex because {command[0]} ' + 'could not be found') from exc _log.debug(report) return report From 957586a42bbbfc4e12ae16f97b9c91e8df1696b4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 6 Nov 2024 05:20:05 -0500 Subject: [PATCH 06/10] Skip tests that expect LinAlgError on WASM --- lib/matplotlib/tests/test_mlab.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/tests/test_mlab.py b/lib/matplotlib/tests/test_mlab.py index 109a6d542450..82f877b4cc01 100644 --- a/lib/matplotlib/tests/test_mlab.py +++ b/lib/matplotlib/tests/test_mlab.py @@ -884,6 +884,8 @@ def test_single_dataset_element(self): with pytest.raises(ValueError): mlab.GaussianKDE([42]) + @pytest.mark.skipif(sys.platform == 'emscripten', + reason="WASM doesn't support floating-point exceptions") def test_silverman_multidim_dataset(self): """Test silverman's for a multi-dimensional array.""" x1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) @@ -897,6 +899,8 @@ def test_silverman_singledim_dataset(self): y_expected = 0.76770389927475502 assert_almost_equal(mygauss.covariance_factor(), y_expected, 7) + @pytest.mark.skipif(sys.platform == 'emscripten', + reason="WASM doesn't support floating-point exceptions") def test_scott_multidim_dataset(self): """Test scott's output for a multi-dimensional array.""" x1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) From 172aa8eebfd6f5248b132821f2c192d504a485d6 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 6 Nov 2024 00:25:44 -0500 Subject: [PATCH 07/10] ci: Add a build for wasm This adds a `pyproject.toml` config for it, so you can replicate locally with cibuildwheel. --- .github/labeler.yml | 4 ++- .github/workflows/nightlies.yml | 40 +++++++++++------------ .github/workflows/wasm.yml | 58 +++++++++++++++++++++++++++++++++ pyproject.toml | 20 ++++++++++++ 4 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/wasm.yml diff --git a/.github/labeler.yml b/.github/labeler.yml index 75adfed57f43..77b79146b47f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,7 +1,9 @@ --- "CI: Run cibuildwheel": - changed-files: - - any-glob-to-any-file: ['.github/workflows/cibuildwheel.yml'] + - any-glob-to-any-file: + - '.github/workflows/cibuildwheel.yml' + - '.github/workflows/wasm.yml' "CI: Run cygwin": - changed-files: - any-glob-to-any-file: ['.github/workflows/cygwin.yml'] diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml index 393ce2e73472..b534aa3ef5b0 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -31,28 +31,28 @@ jobs: run: | PROJECT_REPO="matplotlib/matplotlib" BRANCH="main" - WORKFLOW_NAME="cibuildwheel.yml" ARTIFACT_PATTERN="cibw-wheels-*" - gh run --repo "${PROJECT_REPO}" \ - list --branch "${BRANCH}" \ - --workflow "${WORKFLOW_NAME}" \ - --json event,status,conclusion,databaseId > runs.json - RUN_ID=$( - jq --compact-output \ - '[ - .[] | - # Filter on "push" events to main (merged PRs) ... - select(.event == "push") | - # that have completed successfully ... - select(.status == "completed" and .conclusion == "success") - ] | - # and get ID of latest build of wheels. - sort_by(.databaseId) | reverse | .[0].databaseId' runs.json - ) - gh run --repo "${PROJECT_REPO}" view "${RUN_ID}" - gh run --repo "${PROJECT_REPO}" \ - download "${RUN_ID}" --pattern "${ARTIFACT_PATTERN}" + for WORKFLOW_NAME in cibuildwheel.yml wasm.yml; do + gh run --repo "${PROJECT_REPO}" \ + list --branch "${BRANCH}" \ + --workflow "${WORKFLOW_NAME}" \ + --json event,status,conclusion,databaseId > runs.json + RUN_ID=$( + jq --compact-output \ + '[ + .[] | + # Filter on "push" events to main (merged PRs) ... + select(.event == "push") | + # that have completed successfully ... + select(.status == "completed" and .conclusion == "success") + ] | + # and get ID of latest build of wheels. + sort_by(.databaseId) | reverse | .[0].databaseId' runs.json + ) + gh run --repo "${PROJECT_REPO}" view "${RUN_ID}" + gh run --repo "${PROJECT_REPO}" download "${RUN_ID}" --pattern "${ARTIFACT_PATTERN}" + done mkdir dist mv ${ARTIFACT_PATTERN}/*.whl dist/ diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml new file mode 100644 index 000000000000..38872d6439d0 --- /dev/null +++ b/.github/workflows/wasm.yml @@ -0,0 +1,58 @@ +--- +name: Build wasm wheels + +on: + # Save CI by only running this on release branches or tags. + push: + branches: + - main + - v[0-9]+.[0-9]+.x + tags: + - v* + # Also allow running this action on PRs if requested by applying the + # "Run cibuildwheel" label. + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + +permissions: + contents: read + +jobs: + build_wasm: + if: >- + github.event_name == 'push' || + github.event_name == 'pull_request' && ( + ( + github.event.action == 'labeled' && + github.event.label.name == 'CI: Run cibuildwheel' + ) || + contains(github.event.pull_request.labels.*.name, + 'CI: Run cibuildwheel') + ) + name: Build wasm + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + name: Install Python + with: + python-version: '3.13' + + - name: Build wheels for wasm + uses: pypa/cibuildwheel@faf86a6ed7efa889faf6996aa23820831055001a # v2.23.3 + env: + CIBW_PLATFORM: "pyodide" + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: cibw-wheels-wasm + path: ./wheelhouse/*.whl + if-no-files-found: error diff --git a/pyproject.toml b/pyproject.toml index cf8503a0f3fe..2ac70de5e580 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -329,3 +329,23 @@ testpaths = ["lib"] addopts = [ "--import-mode=importlib", ] + +[tool.cibuildwheel.pyodide] +test-requires = "pytest" +test-command = [ + # Wheels are built without test images, so copy them into the testing directory. + "basedir=$(python -c 'import pathlib, matplotlib; print(pathlib.Path(matplotlib.__file__).parent.parent)')", + "cp -a {package}/lib/matplotlib/tests/data $basedir/matplotlib/tests/", + """ + for subdir in matplotlib mpl_toolkits/axes_grid1 mpl_toolkits/axisartist mpl_toolkits/mplot3d; do + cp -a {package}/lib/${subdir}/tests/baseline_images $basedir/${subdir}/tests/ + done""", + # Test installed, not repository, copy as we aren't using an editable install. + "pytest -p no:cacheprovider --pyargs matplotlib mpl_toolkits.axes_grid1 mpl_toolkits.axisartist mpl_toolkits.mplot3d", +] +[tool.cibuildwheel.pyodide.environment] +# Exceptions are needed for pybind11: +# https://github.com/pybind/pybind11/pull/5298 +CFLAGS = "-fexceptions" +CXXFLAGS = "-fexceptions" +LDFLAGS = "-fexceptions" From 0a25b48be6a7257d3765ae11fb3c2a950a413e89 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 6 Nov 2024 01:20:52 -0500 Subject: [PATCH 08/10] Open symbol visibility for FreeType on wasm --- subprojects/packagefiles/freetype-2.6.1-meson/meson.build | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/subprojects/packagefiles/freetype-2.6.1-meson/meson.build b/subprojects/packagefiles/freetype-2.6.1-meson/meson.build index 9a5180ef7586..1fd4bc44e7b5 100644 --- a/subprojects/packagefiles/freetype-2.6.1-meson/meson.build +++ b/subprojects/packagefiles/freetype-2.6.1-meson/meson.build @@ -179,11 +179,17 @@ ft_config_headers += [configure_file(input: 'include/freetype/config/ftoption.h. output: 'ftoption.h', configuration: conf)] +if cc.get_id() == 'emscripten' + kwargs = {} +else + kwargs = {'gnu_symbol_visibility': 'inlineshidden'} +endif + libfreetype = static_library('freetype', base_sources, include_directories: incbase, dependencies: deps, c_args: c_args, - gnu_symbol_visibility: 'inlineshidden', + kwargs: kwargs ) freetype_dep = declare_dependency( From 3d2f2a6d8161da5c705fa5b93db2be1533845faf Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 7 Nov 2024 18:53:55 -0500 Subject: [PATCH 09/10] TST: Avoid using os.devnull for path tests On wasm, this file doesn't support seeking, which is sometimes necessary depending on file type. --- lib/matplotlib/tests/test_backend_pdf.py | 5 ++--- lib/matplotlib/tests/test_image.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index f126fb543e78..a3e9895d8e4d 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -1,7 +1,6 @@ import datetime import decimal import io -import os from pathlib import Path import numpy as np @@ -291,8 +290,8 @@ def test_text_urls_tex(): assert annot.Rect[1] == decimal.Decimal('0.7') * 72 -def test_pdfpages_fspath(): - with PdfPages(Path(os.devnull)) as pdf: +def test_pdfpages_fspath(tmp_path): + with PdfPages(tmp_path / 'unused.pdf') as pdf: pdf.savefig(plt.figure()) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 00c223c59362..e3d425ba3460 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -215,8 +215,8 @@ def test_imsave_rgba_origin(origin): @pytest.mark.parametrize("fmt", ["png", "pdf", "ps", "eps", "svg"]) -def test_imsave_fspath(fmt): - plt.imsave(Path(os.devnull), np.array([[0, 1]]), format=fmt) +def test_imsave_fspath(fmt, tmp_path): + plt.imsave(tmp_path / f'unused.{fmt}', np.array([[0, 1]]), format=fmt) def test_imsave_color_alpha(): From 358733ec86da4e83deb8040d6107e96e814b8da6 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 16 May 2025 19:30:26 -0400 Subject: [PATCH 10/10] Increase some tolerances for wasm --- lib/matplotlib/tests/test_axes.py | 4 ++-- lib/matplotlib/tests/test_legend.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f03183b20323..8969daede433 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8289,7 +8289,7 @@ def test_normal_axes(): ] for nn, b in enumerate(bbaxis): targetbb = mtransforms.Bbox.from_bounds(*target[nn]) - assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=2) + assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=1) target = [ [150.0, 119.999, 930.0, 11.111], @@ -8307,7 +8307,7 @@ def test_normal_axes(): target = [85.5138, 75.88888, 1021.11, 1017.11] targetbb = mtransforms.Bbox.from_bounds(*target) - assert_array_almost_equal(bbtb.bounds, targetbb.bounds, decimal=2) + assert_array_almost_equal(bbtb.bounds, targetbb.bounds, decimal=1) # test that get_position roundtrips to get_window_extent axbb = ax.get_position().transformed(fig.transFigure).bounds diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index 9b100037cc41..c18b52563042 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -513,10 +513,10 @@ def test_figure_legend_outside(): leg = fig.legend(loc='outside ' + todo) fig.draw_without_rendering() - assert_allclose(axs.get_window_extent().extents, - axbb[nn]) - assert_allclose(leg.get_window_extent().extents, - legbb[nn]) + assert_allclose(axs.get_window_extent().extents, axbb[nn], + rtol=1e-4) + assert_allclose(leg.get_window_extent().extents, legbb[nn], + rtol=1e-4) @image_comparison(['legend_stackplot.png'], pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy