From 799325713239638ba59f957860d3ef244b6660f7 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 07:17:19 +1000 Subject: [PATCH 01/19] animation: skip auto_layout on draw_idle --- ultraplot/figure.py | 20 +++++++++++ ultraplot/tests/test_animation.py | 60 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 ultraplot/tests/test_animation.py diff --git a/ultraplot/figure.py b/ultraplot/figure.py index 01d449d36..613b80fb3 100644 --- a/ultraplot/figure.py +++ b/ultraplot/figure.py @@ -476,6 +476,12 @@ def _canvas_preprocess(self, *args, **kwargs): else: return + skip_autolayout = getattr(fig, "_skip_autolayout", False) + if skip_autolayout and getattr(fig, "_layout_initialized", False): + fig._skip_autolayout = False + return func(self, *args, **kwargs) + fig._skip_autolayout = False + # Adjust layout # NOTE: The authorized_context is needed because some backends disable # constrained layout or tight layout before printing the figure. @@ -484,6 +490,7 @@ def _canvas_preprocess(self, *args, **kwargs): ctx3 = rc.context(fig._render_context) # draw with figure-specific setting with ctx1, ctx2, ctx3: fig.auto_layout() + fig._layout_initialized = True return func(self, *args, **kwargs) # Add preprocessor @@ -797,6 +804,8 @@ def __init__( self._subplot_counter = 0 # avoid add_subplot() returning an existing subplot self._is_adjusting = False self._is_authorized = False + self._layout_initialized = False + self._skip_autolayout = False self._includepanels = None self._render_context = {} rc_kw, rc_mode = _pop_rc(kwargs) @@ -3134,6 +3143,17 @@ def set_canvas(self, canvas): # method = '_draw' if callable(getattr(canvas, '_draw', None)) else 'draw' _add_canvas_preprocessor(canvas, "print_figure", cache=False) # saves, inlines _add_canvas_preprocessor(canvas, method, cache=True) # renderer displays + + orig_draw_idle = getattr(type(canvas), "draw_idle", None) + if orig_draw_idle is not None: + + def _draw_idle(self, *args, **kwargs): + fig = self.figure + if fig is not None: + fig._skip_autolayout = True + return orig_draw_idle(self, *args, **kwargs) + + canvas.draw_idle = _draw_idle.__get__(canvas) super().set_canvas(canvas) def _is_same_size(self, figsize, eps=None): diff --git a/ultraplot/tests/test_animation.py b/ultraplot/tests/test_animation.py new file mode 100644 index 000000000..6e8ad2efc --- /dev/null +++ b/ultraplot/tests/test_animation.py @@ -0,0 +1,60 @@ +from unittest.mock import MagicMock + +import numpy as np +import pytest +from matplotlib.animation import FuncAnimation + +import ultraplot as uplt + + +def test_auto_layout_not_called_on_every_frame(): + """ + Test that auto_layout is not called on every frame of a FuncAnimation. + """ + fig, ax = uplt.subplots() + fig.auto_layout = MagicMock() + + x = np.linspace(0, 2 * np.pi, 100) + y = np.sin(x) + (line,) = ax.plot(x, y) + + def update(frame): + line.set_ydata(np.sin(x + frame / 10.0)) + return (line,) + + ani = FuncAnimation(fig, update, frames=10, blit=False) + # The animation is not actually run, but the initial draw will call auto_layout once + fig.canvas.draw() + + assert fig.auto_layout.call_count == 1 + + +def test_draw_idle_skips_auto_layout_after_first_draw(): + """ + draw_idle should not re-run auto_layout after the initial draw. + """ + fig, ax = uplt.subplots() + fig.auto_layout = MagicMock() + + fig.canvas.draw() + assert fig.auto_layout.call_count == 1 + + fig.canvas.draw_idle() + assert fig.auto_layout.call_count == 1 + + +def test_layout_array_no_crash(): + """ + Test that using layout_array with FuncAnimation does not crash. + """ + layout = [[1, 1], [2, 3]] + fig, axs = uplt.subplots(array=layout) + + def update(frame): + for ax in axs: + ax.clear() + ax.plot(np.sin(np.linspace(0, 2 * np.pi) + frame / 10.0)) + + ani = FuncAnimation(fig, update, frames=10) + # The test passes if no exception is raised + fig.canvas.draw() From 93f9672be0e86ebf87826d05b5c9ba00fe60af41 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 07:49:52 +1000 Subject: [PATCH 02/19] layout: skip auto_layout unless layout is dirty --- ultraplot/axes/base.py | 2 ++ ultraplot/figure.py | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/ultraplot/axes/base.py b/ultraplot/axes/base.py index ef9d59b43..b4462ed32 100644 --- a/ultraplot/axes/base.py +++ b/ultraplot/axes/base.py @@ -3371,6 +3371,8 @@ def format( ultraplot.gridspec.SubplotGrid.format ultraplot.config.Configurator.context """ + if self.figure is not None: + self.figure._layout_dirty = True skip_figure = kwargs.pop("skip_figure", False) # internal keyword arg params = _pop_params(kwargs, self.figure._format_signature) diff --git a/ultraplot/figure.py b/ultraplot/figure.py index 613b80fb3..c6b1401ba 100644 --- a/ultraplot/figure.py +++ b/ultraplot/figure.py @@ -477,7 +477,12 @@ def _canvas_preprocess(self, *args, **kwargs): return skip_autolayout = getattr(fig, "_skip_autolayout", False) - if skip_autolayout and getattr(fig, "_layout_initialized", False): + layout_dirty = getattr(fig, "_layout_dirty", False) + if ( + skip_autolayout + and getattr(fig, "_layout_initialized", False) + and not layout_dirty + ): fig._skip_autolayout = False return func(self, *args, **kwargs) fig._skip_autolayout = False @@ -489,8 +494,10 @@ def _canvas_preprocess(self, *args, **kwargs): ctx2 = fig._context_authorized() # skip backend set_constrained_layout() ctx3 = rc.context(fig._render_context) # draw with figure-specific setting with ctx1, ctx2, ctx3: - fig.auto_layout() - fig._layout_initialized = True + if not fig._layout_initialized or layout_dirty: + fig.auto_layout() + fig._layout_initialized = True + fig._layout_dirty = False return func(self, *args, **kwargs) # Add preprocessor @@ -805,6 +812,7 @@ def __init__( self._is_adjusting = False self._is_authorized = False self._layout_initialized = False + self._layout_dirty = True self._skip_autolayout = False self._includepanels = None self._render_context = {} @@ -1555,6 +1563,7 @@ def _add_figure_panel( """ Add a figure panel. """ + self._layout_dirty = True # Interpret args and enforce sensible keyword args side = _translate_loc(side, "panel", default="right") if side in ("left", "right"): @@ -1588,6 +1597,7 @@ def _add_subplot(self, *args, **kwargs): """ The driver function for adding single subplots. """ + self._layout_dirty = True # Parse arguments kwargs = self._parse_proj(**kwargs) @@ -2558,6 +2568,7 @@ def format( ultraplot.gridspec.SubplotGrid.format ultraplot.config.Configurator.context """ + self._layout_dirty = True # Initiate context block axs = axs or self._subplot_dict.values() skip_axes = kwargs.pop("skip_axes", False) # internal keyword arg @@ -3220,6 +3231,7 @@ def set_size_inches(self, w, h=None, *, forward=True, internal=False, eps=None): super().set_size_inches(figsize, forward=forward) if not samesize: # gridspec positions will resolve differently self.gridspec.update() + self._layout_dirty = True def _iter_axes(self, hidden=False, children=False, panels=True): """ From ecf6010b0e26333c21ad43f60bcdb5634d034b4f Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 09:20:22 +1000 Subject: [PATCH 03/19] layout: avoid dirtying layout on backend size updates --- ultraplot/figure.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ultraplot/figure.py b/ultraplot/figure.py index c6b1401ba..5536f22fc 100644 --- a/ultraplot/figure.py +++ b/ultraplot/figure.py @@ -3231,7 +3231,8 @@ def set_size_inches(self, w, h=None, *, forward=True, internal=False, eps=None): super().set_size_inches(figsize, forward=forward) if not samesize: # gridspec positions will resolve differently self.gridspec.update() - self._layout_dirty = True + if not backend and not internal: + self._layout_dirty = True def _iter_axes(self, hidden=False, children=False, panels=True): """ From eb70a984bf4e92467b2152be4f17f105fc3c6a82 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 09:43:40 +1000 Subject: [PATCH 04/19] ci: append coverage data with xdist --- .github/workflows/build-ultraplot.yml | 4 ++-- .github/workflows/test-map.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 9899017df..0fed7f5ba 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -55,9 +55,9 @@ jobs: - name: Test Ultraplot run: | if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -n auto --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${TEST_NODEIDS} + pytest -n auto --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ${TEST_NODEIDS} else - pytest -n auto --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot + pytest -n auto --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ultraplot fi - name: Upload coverage reports to Codecov diff --git a/.github/workflows/test-map.yml b/.github/workflows/test-map.yml index f5c23e1e5..0955476f2 100644 --- a/.github/workflows/test-map.yml +++ b/.github/workflows/test-map.yml @@ -36,7 +36,7 @@ jobs: - name: Generate test coverage map run: | mkdir -p .ci - pytest -n auto --cov=ultraplot --cov-branch --cov-context=test --cov-report= ultraplot + pytest -n auto --cov=ultraplot --cov-branch --cov-context=test --cov-report= --cov-append ultraplot python tools/ci/build_test_map.py --coverage-file .coverage --output .ci/test-map.json --root . - name: Cache test map From 0d3bf5b9fe0891db50ad9b6dda124ceaeb6134ae Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 09:50:09 +1000 Subject: [PATCH 05/19] ci: force pytest-cov plugin with xdist --- .github/workflows/build-ultraplot.yml | 4 ++-- .github/workflows/test-map.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 0fed7f5ba..917e51897 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -55,9 +55,9 @@ jobs: - name: Test Ultraplot run: | if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -n auto --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ${TEST_NODEIDS} + pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ${TEST_NODEIDS} else - pytest -n auto --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ultraplot + pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ultraplot fi - name: Upload coverage reports to Codecov diff --git a/.github/workflows/test-map.yml b/.github/workflows/test-map.yml index 0955476f2..8c42adeb7 100644 --- a/.github/workflows/test-map.yml +++ b/.github/workflows/test-map.yml @@ -36,7 +36,7 @@ jobs: - name: Generate test coverage map run: | mkdir -p .ci - pytest -n auto --cov=ultraplot --cov-branch --cov-context=test --cov-report= --cov-append ultraplot + pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-context=test --cov-report= --cov-append ultraplot python tools/ci/build_test_map.py --coverage-file .coverage --output .ci/test-map.json --root . - name: Cache test map From ab165b5862e04a32320ea8bb2e4af2a2bb6eabf6 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 09:54:41 +1000 Subject: [PATCH 06/19] ci: fall back to full tests when selection is empty --- .github/workflows/build-ultraplot.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 917e51897..a411f14d1 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -56,6 +56,13 @@ jobs: run: | if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ${TEST_NODEIDS} + status=$? + if [ "$status" -eq 5 ]; then + echo "No tests collected from selected nodeids; running full suite." + pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ultraplot + else + exit "$status" + fi else pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ultraplot fi @@ -152,6 +159,19 @@ jobs: --mpl-generate-summary=html \ --mpl-default-style="./ultraplot.yml" \ ${TEST_NODEIDS} + status=$? + if [ "$status" -eq 5 ]; then + echo "No tests collected from selected nodeids; running full suite." + pytest -W ignore \ + --mpl \ + --mpl-baseline-path=./ultraplot/tests/baseline \ + --mpl-results-path=./results/ \ + --mpl-generate-summary=html \ + --mpl-default-style="./ultraplot.yml" \ + ultraplot/tests + else + exit "$status" + fi else pytest -W ignore \ --mpl \ From 008ddb11c7c72fa25c1d89b9e1d2320b68cd1035 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 09:58:05 +1000 Subject: [PATCH 07/19] ci: handle empty selected tests under bash -e --- .github/workflows/build-ultraplot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index a411f14d1..aaadea9cc 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -54,6 +54,7 @@ jobs: - name: Test Ultraplot run: | + set +e if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ${TEST_NODEIDS} status=$? @@ -66,6 +67,9 @@ jobs: else pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ultraplot fi + status=$? + set -e + exit "$status" - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 @@ -152,6 +156,7 @@ jobs: mkdir -p results python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then + set +e pytest -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ @@ -172,6 +177,9 @@ jobs: else exit "$status" fi + status=$? + set -e + exit "$status" else pytest -W ignore \ --mpl \ From 4d6c0a2fe61a5cc74fc50a439a01488220030d20 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 10:06:23 +1000 Subject: [PATCH 08/19] ci: fall back to full baseline generation on empty selection --- .github/workflows/build-ultraplot.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index aaadea9cc..b770e6663 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -131,10 +131,24 @@ jobs: # Generate the baseline images and hash library python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then + set +e pytest -W ignore \ --mpl-generate-path=./ultraplot/tests/baseline/ \ --mpl-default-style="./ultraplot.yml" \ ${TEST_NODEIDS} + status=$? + if [ "$status" -eq 5 ]; then + echo "No tests collected from selected nodeids on base; running full suite." + pytest -W ignore \ + --mpl-generate-path=./ultraplot/tests/baseline/ \ + --mpl-default-style="./ultraplot.yml" \ + ultraplot/tests + else + exit "$status" + fi + status=$? + set -e + exit "$status" else pytest -W ignore \ --mpl-generate-path=./ultraplot/tests/baseline/ \ From b2ea1a34479640f9fb52ee4326fad2a87fdb0ad0 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 10:09:51 +1000 Subject: [PATCH 09/19] ci: treat missing nodeids as empty selection --- .github/workflows/build-ultraplot.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index b770e6663..8412bc95d 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -58,7 +58,7 @@ jobs: if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ${TEST_NODEIDS} status=$? - if [ "$status" -eq 5 ]; then + if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ultraplot else @@ -137,7 +137,7 @@ jobs: --mpl-default-style="./ultraplot.yml" \ ${TEST_NODEIDS} status=$? - if [ "$status" -eq 5 ]; then + if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids on base; running full suite." pytest -W ignore \ --mpl-generate-path=./ultraplot/tests/baseline/ \ @@ -179,7 +179,7 @@ jobs: --mpl-default-style="./ultraplot.yml" \ ${TEST_NODEIDS} status=$? - if [ "$status" -eq 5 ]; then + if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." pytest -W ignore \ --mpl \ From a959cd6825868ab08fc82a0050f17934e74401c2 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 10:24:15 +1000 Subject: [PATCH 10/19] ci: run coverage without xdist to avoid worker gaps --- .github/workflows/build-ultraplot.yml | 25 ++++++++++++++++++++++--- .github/workflows/test-map.yml | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 8412bc95d..7669fb10e 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -56,16 +56,35 @@ jobs: run: | set +e if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ${TEST_NODEIDS} + pytest -n auto ${TEST_NODEIDS} status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." - pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ultraplot + pytest -n auto ultraplot else exit "$status" fi else - pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml --cov-append ultraplot + pytest -n auto ultraplot + fi + status=$? + set -e + exit "$status" + + - name: Coverage Ultraplot (single process) + run: | + set +e + if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then + pytest -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${TEST_NODEIDS} + status=$? + if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then + echo "No tests collected from selected nodeids; running full suite." + pytest -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot + else + exit "$status" + fi + else + pytest -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot fi status=$? set -e diff --git a/.github/workflows/test-map.yml b/.github/workflows/test-map.yml index 8c42adeb7..77bb352db 100644 --- a/.github/workflows/test-map.yml +++ b/.github/workflows/test-map.yml @@ -36,7 +36,7 @@ jobs: - name: Generate test coverage map run: | mkdir -p .ci - pytest -n auto -p pytest_cov --cov=ultraplot --cov-branch --cov-context=test --cov-report= --cov-append ultraplot + pytest -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-context=test --cov-report= ultraplot python tools/ci/build_test_map.py --coverage-file .coverage --output .ci/test-map.json --root . - name: Cache test map From c9a0f0f8da44125db717df2e69f4d32a3bf76ee5 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 10:26:14 +1000 Subject: [PATCH 11/19] ci: quiet pytest output --- .github/workflows/build-ultraplot.yml | 18 +++++++++--------- .github/workflows/test-map.yml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 7669fb10e..7b655ac4c 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -56,16 +56,16 @@ jobs: run: | set +e if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -n auto ${TEST_NODEIDS} + pytest -q --tb=short -n auto ${TEST_NODEIDS} status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." - pytest -n auto ultraplot + pytest -q --tb=short -n auto ultraplot else exit "$status" fi else - pytest -n auto ultraplot + pytest -q --tb=short -n auto ultraplot fi status=$? set -e @@ -75,16 +75,16 @@ jobs: run: | set +e if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${TEST_NODEIDS} + pytest -q --tb=short -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${TEST_NODEIDS} status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." - pytest -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot + pytest -q --tb=short -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot else exit "$status" fi else - pytest -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot + pytest -q --tb=short -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot fi status=$? set -e @@ -190,7 +190,7 @@ jobs: python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then set +e - pytest -W ignore \ + pytest -q --tb=short -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ --mpl-results-path=./results/ \ @@ -200,7 +200,7 @@ jobs: status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." - pytest -W ignore \ + pytest -q --tb=short -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ --mpl-results-path=./results/ \ @@ -214,7 +214,7 @@ jobs: set -e exit "$status" else - pytest -W ignore \ + pytest -q --tb=short -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ --mpl-results-path=./results/ \ diff --git a/.github/workflows/test-map.yml b/.github/workflows/test-map.yml index 77bb352db..dc761a83c 100644 --- a/.github/workflows/test-map.yml +++ b/.github/workflows/test-map.yml @@ -36,7 +36,7 @@ jobs: - name: Generate test coverage map run: | mkdir -p .ci - pytest -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-context=test --cov-report= ultraplot + pytest -q --tb=short -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-context=test --cov-report= ultraplot python tools/ci/build_test_map.py --coverage-file .coverage --output .ci/test-map.json --root . - name: Cache test map From e3ba31a78b2874f78a1e39e0ec05a58f366a6733 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 10:27:34 +1000 Subject: [PATCH 12/19] ci: suppress pytest warnings output --- .github/workflows/build-ultraplot.yml | 18 +++++++++--------- .github/workflows/test-map.yml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 7b655ac4c..11c96d842 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -56,16 +56,16 @@ jobs: run: | set +e if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -q --tb=short -n auto ${TEST_NODEIDS} + pytest -q --tb=short --disable-warnings -n auto ${TEST_NODEIDS} status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." - pytest -q --tb=short -n auto ultraplot + pytest -q --tb=short --disable-warnings -n auto ultraplot else exit "$status" fi else - pytest -q --tb=short -n auto ultraplot + pytest -q --tb=short --disable-warnings -n auto ultraplot fi status=$? set -e @@ -75,16 +75,16 @@ jobs: run: | set +e if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -q --tb=short -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${TEST_NODEIDS} + pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${TEST_NODEIDS} status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." - pytest -q --tb=short -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot + pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot else exit "$status" fi else - pytest -q --tb=short -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot + pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot fi status=$? set -e @@ -190,7 +190,7 @@ jobs: python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then set +e - pytest -q --tb=short -W ignore \ + pytest -q --tb=short --disable-warnings -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ --mpl-results-path=./results/ \ @@ -200,7 +200,7 @@ jobs: status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." - pytest -q --tb=short -W ignore \ + pytest -q --tb=short --disable-warnings -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ --mpl-results-path=./results/ \ @@ -214,7 +214,7 @@ jobs: set -e exit "$status" else - pytest -q --tb=short -W ignore \ + pytest -q --tb=short --disable-warnings -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ --mpl-results-path=./results/ \ diff --git a/.github/workflows/test-map.yml b/.github/workflows/test-map.yml index dc761a83c..a1e9ff107 100644 --- a/.github/workflows/test-map.yml +++ b/.github/workflows/test-map.yml @@ -36,7 +36,7 @@ jobs: - name: Generate test coverage map run: | mkdir -p .ci - pytest -q --tb=short -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-context=test --cov-report= ultraplot + pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-context=test --cov-report= ultraplot python tools/ci/build_test_map.py --coverage-file .coverage --output .ci/test-map.json --root . - name: Cache test map From 9de5e83ddd4fa058924f97badac1e663ae135b1f Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 10:41:02 +1000 Subject: [PATCH 13/19] ci: stabilize pytest exit handling --- .github/workflows/build-ultraplot.yml | 58 ++++++++++----------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 11c96d842..511cb16b8 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -54,40 +54,32 @@ jobs: - name: Test Ultraplot run: | - set +e + status=0 if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -q --tb=short --disable-warnings -n auto ${TEST_NODEIDS} - status=$? + pytest -q --tb=short --disable-warnings -n auto ${TEST_NODEIDS} || status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." - pytest -q --tb=short --disable-warnings -n auto ultraplot - else - exit "$status" + status=0 + pytest -q --tb=short --disable-warnings -n auto ultraplot || status=$? fi else - pytest -q --tb=short --disable-warnings -n auto ultraplot + pytest -q --tb=short --disable-warnings -n auto ultraplot || status=$? fi - status=$? - set -e exit "$status" - name: Coverage Ultraplot (single process) run: | - set +e + status=0 if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${TEST_NODEIDS} - status=$? + pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${TEST_NODEIDS} || status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." - pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot - else - exit "$status" + status=0 + pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot || status=$? fi else - pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot + pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot || status=$? fi - status=$? - set -e exit "$status" - name: Upload coverage reports to Codecov @@ -150,26 +142,22 @@ jobs: # Generate the baseline images and hash library python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - set +e - pytest -W ignore \ + status=0 + pytest -q --tb=short --disable-warnings -W ignore \ --mpl-generate-path=./ultraplot/tests/baseline/ \ --mpl-default-style="./ultraplot.yml" \ - ${TEST_NODEIDS} - status=$? + ${TEST_NODEIDS} || status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids on base; running full suite." - pytest -W ignore \ + status=0 + pytest -q --tb=short --disable-warnings -W ignore \ --mpl-generate-path=./ultraplot/tests/baseline/ \ --mpl-default-style="./ultraplot.yml" \ - ultraplot/tests - else - exit "$status" + ultraplot/tests || status=$? fi - status=$? - set -e exit "$status" else - pytest -W ignore \ + pytest -q --tb=short --disable-warnings -W ignore \ --mpl-generate-path=./ultraplot/tests/baseline/ \ --mpl-default-style="./ultraplot.yml" \ ultraplot/tests @@ -189,29 +177,25 @@ jobs: mkdir -p results python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - set +e + status=0 pytest -q --tb=short --disable-warnings -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ --mpl-results-path=./results/ \ --mpl-generate-summary=html \ --mpl-default-style="./ultraplot.yml" \ - ${TEST_NODEIDS} - status=$? + ${TEST_NODEIDS} || status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." + status=0 pytest -q --tb=short --disable-warnings -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ --mpl-results-path=./results/ \ --mpl-generate-summary=html \ --mpl-default-style="./ultraplot.yml" \ - ultraplot/tests - else - exit "$status" + ultraplot/tests || status=$? fi - status=$? - set -e exit "$status" else pytest -q --tb=short --disable-warnings -W ignore \ From bcffb193a3b985b4170f6eb20b1e1604fbab776b Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 10:49:44 +1000 Subject: [PATCH 14/19] ci: retry pytest without xdist on nonzero exit --- .github/workflows/build-ultraplot.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 511cb16b8..7bea86df0 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -65,6 +65,20 @@ jobs: else pytest -q --tb=short --disable-warnings -n auto ultraplot || status=$? fi + if [ "$status" -ne 0 ]; then + echo "Parallel pytest exited with $status; retrying in single process." + status=0 + if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then + pytest -q --tb=short --disable-warnings -n 0 ${TEST_NODEIDS} || status=$? + if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then + echo "No tests collected from selected nodeids; running full suite." + status=0 + pytest -q --tb=short --disable-warnings -n 0 ultraplot || status=$? + fi + else + pytest -q --tb=short --disable-warnings -n 0 ultraplot || status=$? + fi + fi exit "$status" - name: Coverage Ultraplot (single process) From 2591a82e3906b586f197cc1579e072bac16518cf Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 11:00:13 +1000 Subject: [PATCH 15/19] ci: run main test step without xdist --- .github/workflows/build-ultraplot.yml | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 7bea86df0..c4ba0442d 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -56,28 +56,14 @@ jobs: run: | status=0 if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -q --tb=short --disable-warnings -n auto ${TEST_NODEIDS} || status=$? + pytest -q --tb=short --disable-warnings -n 0 ${TEST_NODEIDS} || status=$? if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then echo "No tests collected from selected nodeids; running full suite." status=0 - pytest -q --tb=short --disable-warnings -n auto ultraplot || status=$? - fi - else - pytest -q --tb=short --disable-warnings -n auto ultraplot || status=$? - fi - if [ "$status" -ne 0 ]; then - echo "Parallel pytest exited with $status; retrying in single process." - status=0 - if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -q --tb=short --disable-warnings -n 0 ${TEST_NODEIDS} || status=$? - if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then - echo "No tests collected from selected nodeids; running full suite." - status=0 - pytest -q --tb=short --disable-warnings -n 0 ultraplot || status=$? - fi - else pytest -q --tb=short --disable-warnings -n 0 ultraplot || status=$? fi + else + pytest -q --tb=short --disable-warnings -n 0 ultraplot || status=$? fi exit "$status" From f0b0622e780d64f7e6295879c97e9582d8b038d6 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 12:33:33 +1000 Subject: [PATCH 16/19] ci: filter missing nodeids before pytest --- .github/workflows/build-ultraplot.yml | 123 ++++++++++++++++++++------ 1 file changed, 98 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index c4ba0442d..eac585ad5 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -55,12 +55,28 @@ jobs: - name: Test Ultraplot run: | status=0 + filter_nodeids() { + local filtered="" + for nodeid in ${TEST_NODEIDS}; do + local path="${nodeid%%::*}" + if [ -f "$path" ]; then + filtered="${filtered} ${nodeid}" + fi + done + echo "${filtered}" + } if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -q --tb=short --disable-warnings -n 0 ${TEST_NODEIDS} || status=$? - if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then - echo "No tests collected from selected nodeids; running full suite." - status=0 + FILTERED_NODEIDS="$(filter_nodeids)" + if [ -z "${FILTERED_NODEIDS}" ]; then + echo "No valid nodeids found; running full suite." pytest -q --tb=short --disable-warnings -n 0 ultraplot || status=$? + else + pytest -q --tb=short --disable-warnings -n 0 ${FILTERED_NODEIDS} || status=$? + if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then + echo "No tests collected from selected nodeids; running full suite." + status=0 + pytest -q --tb=short --disable-warnings -n 0 ultraplot || status=$? + fi fi else pytest -q --tb=short --disable-warnings -n 0 ultraplot || status=$? @@ -70,12 +86,28 @@ jobs: - name: Coverage Ultraplot (single process) run: | status=0 + filter_nodeids() { + local filtered="" + for nodeid in ${TEST_NODEIDS}; do + local path="${nodeid%%::*}" + if [ -f "$path" ]; then + filtered="${filtered} ${nodeid}" + fi + done + echo "${filtered}" + } if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${TEST_NODEIDS} || status=$? - if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then - echo "No tests collected from selected nodeids; running full suite." - status=0 + FILTERED_NODEIDS="$(filter_nodeids)" + if [ -z "${FILTERED_NODEIDS}" ]; then + echo "No valid nodeids found; running full suite." pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot || status=$? + else + pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${FILTERED_NODEIDS} || status=$? + if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then + echo "No tests collected from selected nodeids; running full suite." + status=0 + pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot || status=$? + fi fi else pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot || status=$? @@ -143,17 +175,36 @@ jobs: python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then status=0 - pytest -q --tb=short --disable-warnings -W ignore \ - --mpl-generate-path=./ultraplot/tests/baseline/ \ - --mpl-default-style="./ultraplot.yml" \ - ${TEST_NODEIDS} || status=$? - if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then - echo "No tests collected from selected nodeids on base; running full suite." - status=0 + filter_nodeids() { + local filtered="" + for nodeid in ${TEST_NODEIDS}; do + local path="${nodeid%%::*}" + if [ -f "$path" ]; then + filtered="${filtered} ${nodeid}" + fi + done + echo "${filtered}" + } + FILTERED_NODEIDS="$(filter_nodeids)" + if [ -z "${FILTERED_NODEIDS}" ]; then + echo "No valid nodeids found; running full suite." pytest -q --tb=short --disable-warnings -W ignore \ --mpl-generate-path=./ultraplot/tests/baseline/ \ --mpl-default-style="./ultraplot.yml" \ ultraplot/tests || status=$? + else + pytest -q --tb=short --disable-warnings -W ignore \ + --mpl-generate-path=./ultraplot/tests/baseline/ \ + --mpl-default-style="./ultraplot.yml" \ + ${FILTERED_NODEIDS} || status=$? + if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then + echo "No tests collected from selected nodeids on base; running full suite." + status=0 + pytest -q --tb=short --disable-warnings -W ignore \ + --mpl-generate-path=./ultraplot/tests/baseline/ \ + --mpl-default-style="./ultraplot.yml" \ + ultraplot/tests || status=$? + fi fi exit "$status" else @@ -178,16 +229,19 @@ jobs: python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then status=0 - pytest -q --tb=short --disable-warnings -W ignore \ - --mpl \ - --mpl-baseline-path=./ultraplot/tests/baseline \ - --mpl-results-path=./results/ \ - --mpl-generate-summary=html \ - --mpl-default-style="./ultraplot.yml" \ - ${TEST_NODEIDS} || status=$? - if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then - echo "No tests collected from selected nodeids; running full suite." - status=0 + filter_nodeids() { + local filtered="" + for nodeid in ${TEST_NODEIDS}; do + local path="${nodeid%%::*}" + if [ -f "$path" ]; then + filtered="${filtered} ${nodeid}" + fi + done + echo "${filtered}" + } + FILTERED_NODEIDS="$(filter_nodeids)" + if [ -z "${FILTERED_NODEIDS}" ]; then + echo "No valid nodeids found; running full suite." pytest -q --tb=short --disable-warnings -W ignore \ --mpl \ --mpl-baseline-path=./ultraplot/tests/baseline \ @@ -195,6 +249,25 @@ jobs: --mpl-generate-summary=html \ --mpl-default-style="./ultraplot.yml" \ ultraplot/tests || status=$? + else + pytest -q --tb=short --disable-warnings -W ignore \ + --mpl \ + --mpl-baseline-path=./ultraplot/tests/baseline \ + --mpl-results-path=./results/ \ + --mpl-generate-summary=html \ + --mpl-default-style="./ultraplot.yml" \ + ${FILTERED_NODEIDS} || status=$? + if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then + echo "No tests collected from selected nodeids; running full suite." + status=0 + pytest -q --tb=short --disable-warnings -W ignore \ + --mpl \ + --mpl-baseline-path=./ultraplot/tests/baseline \ + --mpl-results-path=./results/ \ + --mpl-generate-summary=html \ + --mpl-default-style="./ultraplot.yml" \ + ultraplot/tests || status=$? + fi fi exit "$status" else From 9736984bbc015c11579aa49d9c6128ae7781b8f2 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 13:13:23 +1000 Subject: [PATCH 17/19] ci: bump cache keys for test map and baselines --- .github/workflows/build-ultraplot.yml | 4 ++-- .github/workflows/main.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index eac585ad5..d93bb63cc 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -152,9 +152,9 @@ jobs: with: path: ./ultraplot/tests/baseline # The directory to cache # Key is based on OS, Python/Matplotlib versions, and the base commit SHA - key: ${{ runner.os }}-baseline-base-${{ github.event.pull_request.base.sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }} + key: ${{ runner.os }}-baseline-base-v2-${{ github.event.pull_request.base.sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }} restore-keys: | - ${{ runner.os }}-baseline-base-${{ github.event.pull_request.base.sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}- + ${{ runner.os }}-baseline-base-v2-${{ github.event.pull_request.base.sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}- # Conditional Baseline Generation (Only runs on cache miss) - name: Generate baseline from main diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c035214ed..c383287cb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,9 +40,9 @@ jobs: uses: actions/cache/restore@v4 with: path: .ci/test-map.json - key: test-map-${{ github.event.pull_request.base.sha }} + key: test-map-v2-${{ github.event.pull_request.base.sha }} restore-keys: | - test-map- + test-map-v2- - name: Select impacted tests id: select From ed69c4819c583d918b7360425b066190a7ea079c Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 15:46:40 +1000 Subject: [PATCH 18/19] ci: rely on coverage step for test gating --- .github/workflows/build-ultraplot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index d93bb63cc..89b19ff6f 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -53,6 +53,7 @@ jobs: pip install --no-build-isolation --no-deps . - name: Test Ultraplot + continue-on-error: true run: | status=0 filter_nodeids() { From e119fbb0582693c293df8705e4a8bbd44637164b Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sun, 25 Jan 2026 16:14:04 +1000 Subject: [PATCH 19/19] ci: drop coverage step from build workflow --- .github/workflows/build-ultraplot.yml | 38 --------------------------- 1 file changed, 38 deletions(-) diff --git a/.github/workflows/build-ultraplot.yml b/.github/workflows/build-ultraplot.yml index 89b19ff6f..6d6b51deb 100644 --- a/.github/workflows/build-ultraplot.yml +++ b/.github/workflows/build-ultraplot.yml @@ -53,7 +53,6 @@ jobs: pip install --no-build-isolation --no-deps . - name: Test Ultraplot - continue-on-error: true run: | status=0 filter_nodeids() { @@ -84,43 +83,6 @@ jobs: fi exit "$status" - - name: Coverage Ultraplot (single process) - run: | - status=0 - filter_nodeids() { - local filtered="" - for nodeid in ${TEST_NODEIDS}; do - local path="${nodeid%%::*}" - if [ -f "$path" ]; then - filtered="${filtered} ${nodeid}" - fi - done - echo "${filtered}" - } - if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then - FILTERED_NODEIDS="$(filter_nodeids)" - if [ -z "${FILTERED_NODEIDS}" ]; then - echo "No valid nodeids found; running full suite." - pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot || status=$? - else - pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ${FILTERED_NODEIDS} || status=$? - if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then - echo "No tests collected from selected nodeids; running full suite." - status=0 - pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot || status=$? - fi - fi - else - pytest -q --tb=short --disable-warnings -n 0 -p pytest_cov --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot || status=$? - fi - exit "$status" - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - slug: Ultraplot/ultraplot - compare-baseline: name: Compare baseline Python ${{ inputs.python-version }} with MPL ${{ inputs.matplotlib-version }} runs-on: ubuntu-latest