From 1dc1eb6d09cef799d908c9d956d985987ad1443b Mon Sep 17 00:00:00 2001 From: Nicolas Simonds Date: Wed, 10 Jul 2024 17:44:42 -0700 Subject: [PATCH] fix: improve caching of parameterized fixtures The fix for Issue #6541 caused regression where cache hits became cache misses, unexpectedly. Attempt to restore the previous behavior, while also retaining the fix for the bug. Fixes: Issue #6962 --- AUTHORS | 1 + changelog/6962.bugfix.rst | 1 + src/_pytest/fixtures.py | 13 ++++++++++--- testing/python/fixtures.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 changelog/6962.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 8d31170560c..9b6cb6a9d23 100644 --- a/AUTHORS +++ b/AUTHORS @@ -306,6 +306,7 @@ Nicholas Devenish Nicholas Murphy Niclas Olofsson Nicolas Delaby +Nicolas Simonds Nico Vidal Nikolay Kondratyev Nipunn Koorapati diff --git a/changelog/6962.bugfix.rst b/changelog/6962.bugfix.rst new file mode 100644 index 00000000000..9557d7b173e --- /dev/null +++ b/changelog/6962.bugfix.rst @@ -0,0 +1 @@ +Fixed bug where parametrized fixtures were not being cached correctly, being recreated every time. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 0151a4d9c86..cda4f0c42f3 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1056,9 +1056,16 @@ def execute(self, request: SubRequest) -> FixtureValue: my_cache_key = self.cache_key(request) if self.cached_result is not None: cache_key = self.cached_result[1] - # note: comparison with `==` can fail (or be expensive) for e.g. - # numpy arrays (#6497). - if my_cache_key is cache_key: + + # note: `__eq__` is not required to return a bool, and sometimes + # doesn't, e.g., numpy arrays (#6497). Coerce the comparison + # into a bool, and if that fails, fall back to an identity check. + try: + cache_hit = bool(my_cache_key == cache_key) + except (ValueError, RuntimeError): + cache_hit = my_cache_key is cache_key + + if cache_hit: if self.cached_result[2] is not None: exc, exc_tb = self.cached_result[2] raise exc.with_traceback(exc_tb) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 5c3a6a35b34..dd0a7cc7095 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1557,6 +1557,37 @@ def test_printer_2(self): result = pytester.runpytest() result.stdout.fnmatch_lines(["* 2 passed in *"]) + def test_parameterized_fixture_caching(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + from itertools import count + + CACHE_HITS = count(0) + + def pytest_generate_tests(metafunc): + if "my_fixture" in metafunc.fixturenames: + param = "d%s" % "1" + print("param id=%d" % id(param), flush=True) + metafunc.parametrize("my_fixture", [param, "d2"], indirect=True) + + @pytest.fixture(scope='session') + def my_fixture(request): + next(CACHE_HITS) + + def test1(my_fixture): + pass + + def test2(my_fixture): + pass + + def teardown_module(): + assert next(CACHE_HITS) == 2 + """ + ) + result = pytester.runpytest() + result.stdout.no_fnmatch_line("* ERROR at teardown *") + class TestFixtureManagerParseFactories: @pytest.fixture