From 80d4dd6f0bf289194f540f4ab6d6cf96776b8072 Mon Sep 17 00:00:00 2001 From: Holger Kohr Date: Sat, 25 Jan 2020 20:28:00 +0100 Subject: [PATCH] Replace `==` with `is` for comparison of cache keys Closes #6497 --- AUTHORS | 1 + changelog/6497.bugfix.rst | 4 ++++ src/_pytest/fixtures.py | 4 +++- testing/python/fixtures.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 changelog/6497.bugfix.rst diff --git a/AUTHORS b/AUTHORS index aa2237c6837..a3200f774cf 100644 --- a/AUTHORS +++ b/AUTHORS @@ -112,6 +112,7 @@ Guido Wesdorp Guoqiang Zhang Harald Armin Massa Henk-Jaap Wagenaar +Holger Kohr Hugo van Kemenade Hui Wang (coldnight) Ian Bicking diff --git a/changelog/6497.bugfix.rst b/changelog/6497.bugfix.rst new file mode 100644 index 00000000000..66d436abd99 --- /dev/null +++ b/changelog/6497.bugfix.rst @@ -0,0 +1,4 @@ +Fix bug in the comparison of request key with cached key in fixture. + +A construct ``if key == cached_key:`` can fail either because ``==`` is explicitly disallowed, or for, e.g., NumPy arrays, where the result of ``a == b`` cannot generally be converted to `bool`. +The implemented fix replaces `==` with ``is``. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 464828de4c9..bd8472e366d 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -898,7 +898,9 @@ def execute(self, request): cached_result = getattr(self, "cached_result", None) if cached_result is not None: result, cache_key, err = cached_result - if my_cache_key == cache_key: + # note: comparison with `==` can fail (or be expensive) for e.g. + # numpy arrays (#6497) + if my_cache_key is cache_key: if err is not None: _, val, tb = err raise val.with_traceback(tb) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 8cfaae50d65..c72437ed56f 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1102,6 +1102,38 @@ def test_nothing(badscope): "*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'" ) + @pytest.mark.parametrize("scope", ["function", "session"]) + def test_parameters_without_eq_semantics(self, scope, testdir): + testdir.makepyfile( + """ + class NoEq1: # fails on `a == b` statement + def __eq__(self, _): + raise RuntimeError + + class NoEq2: # fails on `if a == b:` statement + def __eq__(self, _): + class NoBool: + def __bool__(self): + raise RuntimeError + return NoBool() + + import pytest + @pytest.fixture(params=[NoEq1(), NoEq2()], scope={scope!r}) + def no_eq(request): + return request.param + + def test1(no_eq): + pass + + def test2(no_eq): + pass + """.format( + scope=scope + ) + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*4 passed*"]) + def test_funcarg_parametrized_and_used_twice(self, testdir): testdir.makepyfile( """