From c2980eb80ffc9a7ab81d1424afeac77ec46a2fe2 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 26 Jan 2020 23:36:28 +0100 Subject: [PATCH 1/8] pytester: test for _makefile joining an absolute path Ref: https://github.com/pytest-dev/pytest/pull/6578#discussion_r371035867 --- testing/test_pytester.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 869e35db3..35a06e33a 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -710,3 +710,13 @@ def test_testdir_outcomes_with_multiple_errors(testdir): result.assert_outcomes(error=2) assert result.parseoutcomes() == {"error": 2} + + +def test_makefile_joins_absolute_path(testdir: Testdir) -> None: + absfile = testdir.tmpdir / "absfile" + if sys.platform == "win32": + with pytest.raises(OSError): + testdir.makepyfile(**{str(absfile): ""}) + else: + p1 = testdir.makepyfile(**{str(absfile): ""}) + assert str(p1) == (testdir.tmpdir / absfile) + ".py" From 35ba053f00e4f8a85ed76cde67f87af35c598528 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 28 Jan 2020 01:23:44 +0100 Subject: [PATCH 2/8] typing: ignore false positive with more-itertools Fixed in https://github.com/erikrose/more-itertools/pull/374. --- src/_pytest/python_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 5e5eddc5b..24145016c 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -685,7 +685,7 @@ def raises( # noqa: F811 """ __tracebackhide__ = True for exc in filterfalse( - inspect.isclass, always_iterable(expected_exception, BASE_TYPE) + inspect.isclass, always_iterable(expected_exception, BASE_TYPE) # type: ignore[arg-type] # noqa: F821 ): msg = "exceptions must be derived from BaseException, not %s" raise TypeError(msg % type(exc)) From 12c5a6af64f3380974cb665b7d252c20d54de95d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 28 Jan 2020 01:49:45 +0100 Subject: [PATCH 3/8] typing: fix Code.path Fixes: > src/_pytest/_code/code.py:83: error: Incompatible types in assignment > (expression has type "str", variable has type "local") [assignment] --- src/_pytest/_code/code.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 94ad4292e..b176dde98 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -77,12 +77,11 @@ class Code: # maybe don't try this checking if not p.check(): raise OSError("py.path check failed.") + return p except OSError: # XXX maybe try harder like the weird logic # in the standard lib [linecache.updatecache] does? - p = self.raw.co_filename - - return p + return self.raw.co_filename @property def fullsource(self) -> Optional["Source"]: From b2e6f66438dd4a2d8e794a02810fb54241505f0f Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 28 Jan 2020 00:56:13 +0100 Subject: [PATCH 4/8] ci: GHA: run less jobs with coverage This often might be causing for jobs to take longer than 10 minutes, which is a timeout Codecov uses to wait for successful CI. Also it is good in general to have CI finish faster, of course. --- .github/workflows/main.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c55316874..242c2eb83 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,6 +52,7 @@ jobs: python: "3.5" os: windows-latest tox_env: "py35-xdist" + use_coverage: true - name: "windows-py36" python: "3.6" os: windows-latest @@ -68,6 +69,7 @@ jobs: python: "3.8" os: windows-latest tox_env: "py38" + use_coverage: true - name: "ubuntu-py35" python: "3.5" @@ -81,6 +83,7 @@ jobs: python: "3.7" os: ubuntu-latest tox_env: "py37-lsof-numpy-oldattrs-pexpect-twisted" + use_coverage: true - name: "ubuntu-py37-pluggy" python: "3.7" os: ubuntu-latest @@ -89,8 +92,6 @@ jobs: python: "3.7" os: ubuntu-latest tox_env: "py37-freeze" - # coverage does not apply for freeze test, skip it - skip_coverage: true - name: "ubuntu-py38" python: "3.8" os: ubuntu-latest @@ -99,8 +100,6 @@ jobs: python: "pypy3" os: ubuntu-latest tox_env: "pypy3-xdist" - # coverage too slow with pypy3, skip it - skip_coverage: true - name: "macos-py37" python: "3.7" @@ -110,21 +109,21 @@ jobs: python: "3.8" os: macos-latest tox_env: "py38-xdist" + use_coverage: true - name: "linting" python: "3.7" os: ubuntu-latest tox_env: "linting" - skip_coverage: true - name: "docs" python: "3.7" os: ubuntu-latest tox_env: "docs" - skip_coverage: true - name: "doctesting" python: "3.7" os: ubuntu-latest tox_env: "doctesting" + use_coverage: true steps: - uses: actions/checkout@v1 @@ -138,11 +137,11 @@ jobs: pip install tox coverage - name: Test without coverage - if: "matrix.skip_coverage" + if: "! matrix.use_coverage" run: "tox -e ${{ matrix.tox_env }}" - name: Test with coverage - if: "! matrix.skip_coverage" + if: "matrix.use_coverage" env: _PYTEST_TOX_COVERAGE_RUN: "coverage run -m" COVERAGE_PROCESS_START: ".coveragerc" @@ -150,12 +149,12 @@ jobs: run: "tox -e ${{ matrix.tox_env }}" - name: Prepare coverage token - if: (!matrix.skip_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' )) + if: (matrix.use_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' )) run: | python scripts/append_codecov_token.py - name: Report coverage - if: (!matrix.skip_coverage) + if: (matrix.use_coverage) env: CODECOV_NAME: ${{ matrix.name }} run: bash scripts/report-coverage.sh -F GHA,${{ runner.os }} From 1cf9e68dbce51f512f3ed9197a8cdc67a9171ded Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 28 Jan 2020 18:39:47 +0100 Subject: [PATCH 5/8] tests: cover absolute path handling in _compute_fixture_value --- src/_pytest/fixtures.py | 5 +++-- testing/python/fixtures.py | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 464828de4..d70b2c64a 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -550,8 +550,9 @@ class FixtureRequest: source_path = frameinfo.filename source_lineno = frameinfo.lineno source_path = py.path.local(source_path) - if source_path.relto(funcitem.config.rootdir): - source_path_str = source_path.relto(funcitem.config.rootdir) + rel_source_path = source_path.relto(funcitem.config.rootdir) + if rel_source_path: + source_path_str = rel_source_path else: source_path_str = str(source_path) msg = ( diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 8cfaae50d..d9ea5cf58 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -3662,13 +3662,30 @@ class TestParameterizedSubRequest: " test_foos.py::test_foo", "", "Requested fixture 'fix_with_param' defined in:", - "*fix.py:4", + "{}:4".format(fixfile), "Requested here:", "test_foos.py:4", "*1 failed*", ] ) + # With non-overlapping rootdir, passing tests_dir. + rootdir = testdir.mkdir("rootdir") + rootdir.chdir() + result = testdir.runpytest("--rootdir", rootdir, tests_dir) + result.stdout.fnmatch_lines( + [ + "The requested fixture has no parameter defined for test:", + " test_foos.py::test_foo", + "", + "Requested fixture 'fix_with_param' defined in:", + "{}:4".format(fixfile), + "Requested here:", + "{}:4".format(testfile), + "*1 failed*", + ] + ) + def test_pytest_fixture_setup_and_post_finalizer_hook(testdir): testdir.makeconftest( From 7c878742773d50e3bc04d1a5b0a786ce69562847 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 28 Jan 2020 19:02:35 +0100 Subject: [PATCH 6/8] source_path: py.path.local directly Via bc7282576. --- src/_pytest/fixtures.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index d70b2c64a..5d54078de 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -547,9 +547,8 @@ class FixtureRequest: if has_params: frame = inspect.stack()[3] frameinfo = inspect.getframeinfo(frame[0]) - source_path = frameinfo.filename + source_path = py.path.local(frameinfo.filename) source_lineno = frameinfo.lineno - source_path = py.path.local(source_path) rel_source_path = source_path.relto(funcitem.config.rootdir) if rel_source_path: source_path_str = rel_source_path From e25d46aae621fb8b294aeab98dfcc81f051c5f77 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 28 Jan 2020 21:44:12 +0100 Subject: [PATCH 7/8] typing: MonkeyPatch.context --- src/_pytest/monkeypatch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 090bf61d6..8aec7b818 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -4,6 +4,7 @@ import re import sys import warnings from contextlib import contextmanager +from typing import Generator import pytest from _pytest.fixtures import fixture @@ -108,7 +109,7 @@ class MonkeyPatch: self._savesyspath = None @contextmanager - def context(self): + def context(self) -> Generator["MonkeyPatch", None, None]: """ Context manager that returns a new :class:`MonkeyPatch` object which undoes any patching done inside the ``with`` block upon exit: From 80d4dd6f0bf289194f540f4ab6d6cf96776b8072 Mon Sep 17 00:00:00 2001 From: Holger Kohr Date: Sat, 25 Jan 2020 20:28:00 +0100 Subject: [PATCH 8/8] 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 aa2237c68..a3200f774 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 000000000..66d436abd --- /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 464828de4..bd8472e36 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -898,7 +898,9 @@ class FixtureDef: 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 8cfaae50d..c72437ed5 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1102,6 +1102,38 @@ class TestFixtureUsages: "*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( """