Skip to content

Commit d38a997

Browse files
committed
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
1 parent 16cdacc commit d38a997

File tree

4 files changed

+43
-3
lines changed

4 files changed

+43
-3
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ Nicholas Devenish
306306
Nicholas Murphy
307307
Niclas Olofsson
308308
Nicolas Delaby
309+
Nicolas Simonds
309310
Nico Vidal
310311
Nikolay Kondratyev
311312
Nipunn Koorapati

changelog/6962.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed bug where parametrized fixtures were not being cached correctly, being recreated every time.

src/_pytest/fixtures.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,9 +1056,16 @@ def execute(self, request: SubRequest) -> FixtureValue:
10561056
my_cache_key = self.cache_key(request)
10571057
if self.cached_result is not None:
10581058
cache_key = self.cached_result[1]
1059-
# note: comparison with `==` can fail (or be expensive) for e.g.
1060-
# numpy arrays (#6497).
1061-
if my_cache_key is cache_key:
1059+
1060+
# note: `__eq__` is not required to return a bool, and sometimes
1061+
# doesn't, e.g., numpy arrays (#6497). Coerce the comparison
1062+
# into a bool, and if that fails, fall back to an identity check.
1063+
try:
1064+
cache_hit = bool(my_cache_key == cache_key)
1065+
except (ValueError, RuntimeError):
1066+
cache_hit = my_cache_key is cache_key
1067+
1068+
if cache_hit:
10621069
if self.cached_result[2] is not None:
10631070
exc, exc_tb = self.cached_result[2]
10641071
raise exc.with_traceback(exc_tb)

testing/python/fixtures.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,37 @@ def test_printer_2(self):
15571557
result = pytester.runpytest()
15581558
result.stdout.fnmatch_lines(["* 2 passed in *"])
15591559

1560+
def test_parameterized_fixture_caching(self, pytester: Pytester) -> None:
1561+
pytester.makepyfile(
1562+
"""
1563+
import pytest
1564+
from itertools import count
1565+
1566+
CACHE_MISSES = count(0)
1567+
1568+
def pytest_generate_tests(metafunc):
1569+
if "my_fixture" in metafunc.fixturenames:
1570+
param = "d%s" % "1"
1571+
print("param id=%d" % id(param), flush=True)
1572+
metafunc.parametrize("my_fixture", [param, "d2"], indirect=True)
1573+
1574+
@pytest.fixture(scope='session')
1575+
def my_fixture(request):
1576+
next(CACHE_MISSES)
1577+
1578+
def test1(my_fixture):
1579+
pass
1580+
1581+
def test2(my_fixture):
1582+
pass
1583+
1584+
def teardown_module():
1585+
assert next(CACHE_MISSES) == 2
1586+
"""
1587+
)
1588+
result = pytester.runpytest()
1589+
result.stdout.no_fnmatch_line("* ERROR at teardown *")
1590+
15601591

15611592
class TestFixtureManagerParseFactories:
15621593
@pytest.fixture

0 commit comments

Comments
 (0)