From 82c972b0fe81e72408d22a0d7deafe900c2c8425 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Fri, 30 Oct 2026 16:39:11 +0100 Subject: [PATCH 1/3] fix: support pytest 9.2 fixture manager changes pytest 9.2 replaced the private `FixtureManager._arg2fixturedefs` with `_arg2node2fixturedefs` (pytest-dev/pytest#14984). The `pytest_plugin_registered` hook wrote into `_arg2fixturedefs` to swap pytest-benchmark's `benchmark` fixture for ours, so every `--codspeed` run with pytest-benchmark installed crashed with an AttributeError. On pytest 9.1+, register the override through the public `pytest.register_fixture` API at session start instead. Older pytest keeps the previous `_arg2fixturedefs` swap. pytest-benchmark itself still reads the removed `Node._nodeid` on pytest 9.2, so the two tests that run it without `--codspeed` are marked as non-strict xfail on that combination. CI now also runs pytest ~=9.0.0 and ~=8.0.0 on the latest Python to cover the legacy override paths, and installs valgrind for the pytest-benchmark-4/5 configs, which the old `== 'pytest-benchmark'` check never matched. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 18 +++++++++++++++++- src/pytest_codspeed/plugin.py | 23 +++++++++++++++++++++++ src/pytest_codspeed/utils.py | 2 ++ tests/conftest.py | 5 +++++ tests/test_pytest_plugin.py | 3 +++ 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f82262..9b0cee1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,22 @@ jobs: - version: "3.15t" pytest-version: - ">=8.1.1" + # Older pytest versions only on the latest Python + include: + # Before pytest.register_fixture (private _arg2fixturedefs override) + - config: headless + python: { version: "3.15" } + pytest-version: "~=9.0.0" + - config: pytest-benchmark-5 + python: { version: "3.15" } + pytest-version: "~=9.0.0" + # Before 8.1.1 (getfixturedefs takes a nodeid) + - config: headless + python: { version: "3.15" } + pytest-version: "~=8.0.0" + - config: pytest-benchmark-4 + python: { version: "3.15" } + pytest-version: "~=8.0.0" steps: - uses: actions/checkout@v5 @@ -57,7 +73,7 @@ jobs: uses: actions/setup-python@v6 with: python-version: "${{ matrix.python.version }}" - - if: matrix.config == 'valgrind' || matrix.config == 'pytest-benchmark' + - if: matrix.config == 'valgrind' || startsWith(matrix.config, 'pytest-benchmark') name: Install valgrind run: | sudo apt-get update diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index 2b50a8b..6b21a89 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -23,6 +23,7 @@ from pytest_codspeed.instruments import MeasurementMode, get_instrument_from_mode from pytest_codspeed.utils import ( BEFORE_PYTEST_8_1_1, + HAS_PYTEST_REGISTER_FIXTURE, IS_PYTEST_BENCHMARK_INSTALLED, IS_PYTEST_SPEED_INSTALLED, get_environment_metadata, @@ -187,6 +188,8 @@ def pytest_plugin_registered(plugin, manager: pytest.PytestPluginManager): Patch the benchmark fixture to use the codspeed one if codspeed is enabled and an alternative benchmark fixture is available """ + if HAS_PYTEST_REGISTER_FIXTURE: + return # handled in pytest_sessionstart if (IS_PYTEST_BENCHMARK_INSTALLED or IS_PYTEST_SPEED_INSTALLED) and isinstance( plugin, FixtureManager ): @@ -210,6 +213,26 @@ def pytest_plugin_registered(plugin, manager: pytest.PytestPluginManager): fixture_manager._arg2fixturedefs["benchmark"] = codspeed_benchmark_fixtures +def _benchmark_compat(codspeed_benchmark, request: pytest.FixtureRequest): + return codspeed_benchmark + + +@pytest.hookimpl(trylast=True) +def pytest_sessionstart(session: pytest.Session): + """ + Override the benchmark fixture of pytest-benchmark/pytest-speed with the codspeed + one, using the public API available since pytest 9.1 + """ + if HAS_PYTEST_REGISTER_FIXTURE and ( + IS_PYTEST_BENCHMARK_INSTALLED or IS_PYTEST_SPEED_INSTALLED + ): + codspeed_plugin = session.config.pluginmanager.get_plugin(PLUGIN_NAME) + if codspeed_plugin is not None and codspeed_plugin.is_codspeed_enabled: + pytest.register_fixture( + name="benchmark", func=_benchmark_compat, node=session + ) + + @pytest.hookimpl(trylast=True) def pytest_report_header(config: pytest.Config): plugin = get_plugin(config) diff --git a/src/pytest_codspeed/utils.py b/src/pytest_codspeed/utils.py index 6c24143..95b9256 100644 --- a/src/pytest_codspeed/utils.py +++ b/src/pytest_codspeed/utils.py @@ -19,6 +19,8 @@ IS_PYTEST_BENCHMARK_INSTALLED = importlib.util.find_spec("pytest_benchmark") is not None IS_PYTEST_SPEED_INSTALLED = importlib.util.find_spec("pytest_speed") is not None BEFORE_PYTEST_8_1_1 = pytest.version_tuple < (8, 1, 1) +# Public API since pytest 9.1, required since 9.2 removed `_arg2fixturedefs` +HAS_PYTEST_REGISTER_FIXTURE = hasattr(pytest, "register_fixture") SUPPORTS_PERF_TRAMPOLINE = sysconfig.get_config_var("PY_HAVE_PERF_TRAMPOLINE") == 1 diff --git a/tests/conftest.py b/tests/conftest.py index e8a5625..c03d074 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,6 +23,11 @@ skip_with_pytest_benchmark = pytest.mark.skipif( IS_PYTEST_BENCHMARK_INSTALLED, reason="pytest_benchmark installed" ) +xfail_pytest_benchmark_on_pytest_9_2 = pytest.mark.xfail( + IS_PYTEST_BENCHMARK_INSTALLED and pytest.version_tuple >= (9, 2), + reason="pytest-benchmark is incompatible with pytest 9.2 (Node._nodeid removal)", + strict=False, +) if IS_PYTEST_BENCHMARK_INSTALLED: pytest_plugins.append("pytest_benchmark") print( diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index 452e9da..a266649 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -6,6 +6,7 @@ skip_with_perf_trampoline, skip_without_pytest_benchmark, skip_without_valgrind, + xfail_pytest_benchmark_on_pytest_9_2, ) @@ -119,6 +120,7 @@ def test_child(self): ) +@xfail_pytest_benchmark_on_pytest_9_2 def test_plugin_disabled(pytester: pytest.Pytester) -> None: pytester.copy_example("tests/examples/test_addition_fixture.py") result = pytester.runpytest() @@ -191,6 +193,7 @@ def test_another_useless_thing(): ) +@xfail_pytest_benchmark_on_pytest_9_2 @skip_without_pytest_benchmark def test_pytest_benchmark_compatibility(pytester: pytest.Pytester) -> None: pytester.makepyfile( From 3a4c4d4a6744509b684d5ec7d1af0d436b2f8eaf Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Fri, 30 Oct 2026 16:42:27 +0100 Subject: [PATCH 2/3] ci: use ubuntu-24 for python 3.9 test --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b0cee1..330d61f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,8 @@ jobs: extra_args: --all-files tests: - runs-on: ubuntu-latest + # setup-python has no 3.9 build for Ubuntu 26.04 + runs-on: ${{ matrix.python.version == '3.9' && 'ubuntu-24.04' || 'ubuntu-latest' }} strategy: fail-fast: false matrix: From 088872f8393887f8690dc2d11a06a0ae3474a195 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Fri, 30 Oct 2026 16:45:07 +0100 Subject: [PATCH 3/3] ci: remove the runner main branch usage for macos test --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 330d61f..07f8d5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,8 +117,6 @@ jobs: with: run: uv run pytest tests/benchmarks/test_bench_fibo.py --codspeed mode: walltime - # TODO: Remove this once the runner has been released with macos support - runner-version: branch:main all-checks: runs-on: ubuntu-latest