From a3bc6df95061f20630af33c3b12f6a48a6d10acb Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 7 Jan 2020 20:03:38 -0300 Subject: [PATCH 1/9] Implement code coverage in GitHub actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This overwrites the `codecov.yml` file in the root of the repository with `codecov-upstream.yml` file (which contains the code-cov token)ยด, so PRs and branches on the repository can upload coverage. Suggestion from here: https://github.com/pytest-dev/pytest/pull/6421#issuecomment-571934112 Security concerns: the token might be misused, but only to upload bogus coverage to `codecov.io`, so the team believe this is harmless. If we decide to fallback from this decision , we just need to revoke the token. Related to #6369 --- .github/codecov-upstream.yml | 13 +++++++++++ .github/workflows/main.yml | 45 +++++++++++++++++++++++++++++++----- codecov.yml | 2 ++ tox.ini | 2 +- 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 .github/codecov-upstream.yml diff --git a/.github/codecov-upstream.yml b/.github/codecov-upstream.yml new file mode 100644 index 000000000..1256a4389 --- /dev/null +++ b/.github/codecov-upstream.yml @@ -0,0 +1,13 @@ +# this file replaces /codecov.yml when building on the main repository and PRs +coverage: + status: + project: true + patch: true + changes: true + +comment: off + +codecov: + # token from: https://codecov.io/gh/pytest-dev/pytest/settings + # use same URL to regenerate it if needed + token: "1eca3b1f-31a2-4fb8-a8c3-138b441b50a7" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f3a2bccfc..5d7493e72 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,8 +1,7 @@ -# evaluating GitHub actions for CI, disconsider failures when evaluating PRs +# evaluating GitHub actions for CI, disregard failures when evaluating PRs # # this is still missing: # - deploy -# - coverage # - upload github notes # name: main @@ -17,7 +16,6 @@ on: jobs: build: - runs-on: ${{ matrix.os }} strategy: @@ -86,6 +84,8 @@ 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 @@ -94,6 +94,8 @@ 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" @@ -118,6 +120,37 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install tox - - name: Test - run: tox -e ${{ matrix.tox_env }} + pip install tox coverage + + - name: Test without coverage + if: "matrix.skip_coverage" + run: "tox -e ${{ matrix.tox_env }}" + + - name: Test with coverage + if: "! matrix.skip_coverage" + env: + _PYTEST_TOX_COVERAGE_RUN: "coverage run -m" + COVERAGE_PROCESS_START: ".coveragerc" + _PYTEST_TOX_EXTRA_DEP: "coverage-enable-subprocess" + run: "tox -e ${{ matrix.tox_env }}" + + - name: Prepare coverage token + if: success() && !matrix.skip_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' ) + run: | + cp .github/codecov-upstream.yml codecov.yml + + - name: Combine coverage + if: success() && !matrix.skip_coverage + run: | + python -m coverage combine + python -m coverage xml + + - name: Codecov upload + if: success() && !matrix.skip_coverage + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.codecov }} + file: ./coverage.xml + flags: ${{ runner.os }} + fail_ci_if_error: false + name: ${{ matrix.name }} diff --git a/codecov.yml b/codecov.yml index a0a308588..44fd14710 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,5 @@ +# note: `.github/codecov-upstream.yml` is basically a copy of this file, please propagate +# changes as needed coverage: status: project: true diff --git a/tox.ini b/tox.ini index d33a62224..8fc69e89d 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ passenv = USER USERNAME COVERAGE_* TRAVIS PYTEST_ADDOPTS TERM setenv = _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} - # Configuration to run with coverage similar to Travis/Appveyor, e.g. + # Configuration to run with coverage similar to CI, e.g. # "tox -e py37-coverage". coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess From 5e1c6ce6301aaa2806d089b4d89e85c6efb96c8b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 12 Jan 2020 20:09:51 +0100 Subject: [PATCH 2/9] tox: linting: pass posargs --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d33a62224..bc0646d12 100644 --- a/tox.ini +++ b/tox.ini @@ -53,7 +53,7 @@ deps = skip_install = True basepython = python3 deps = pre-commit>=1.11.0 -commands = pre-commit run --all-files --show-diff-on-failure +commands = pre-commit run --all-files --show-diff-on-failure {posargs:} [testenv:docs] basepython = python3 From 117072d64c395283f8e035ed4dc44e838623c119 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 12 Jan 2020 22:17:49 +0100 Subject: [PATCH 3/9] typing: fix _pytest.config.findpaths.determine_setup --- src/_pytest/config/findpaths.py | 2 +- testing/test_config.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index f06c9cfff..728246dfc 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -108,7 +108,7 @@ CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supporte def determine_setup( - inifile: str, + inifile: Optional[str], args: List[str], rootdir_cmd_arg: Optional[str] = None, config: Optional["Config"] = None, diff --git a/testing/test_config.py b/testing/test_config.py index 9735fc176..29bd45c6c 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -859,7 +859,7 @@ class TestRootdir: assert get_common_ancestor([no_path.join("a")]) == tmpdir @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) - def test_with_ini(self, tmpdir, name): + def test_with_ini(self, tmpdir, name) -> None: inifile = tmpdir.join(name) inifile.write("[pytest]\n" if name != "setup.cfg" else "[tool:pytest]\n") @@ -874,7 +874,7 @@ class TestRootdir: assert inifile == inifile @pytest.mark.parametrize("name", "setup.cfg tox.ini".split()) - def test_pytestini_overrides_empty_other(self, tmpdir, name): + def test_pytestini_overrides_empty_other(self, tmpdir, name) -> None: inifile = tmpdir.ensure("pytest.ini") a = tmpdir.mkdir("a") a.ensure(name) @@ -882,7 +882,7 @@ class TestRootdir: assert rootdir == tmpdir assert inifile == inifile - def test_setuppy_fallback(self, tmpdir): + def test_setuppy_fallback(self, tmpdir) -> None: a = tmpdir.mkdir("a") a.ensure("setup.cfg") tmpdir.ensure("setup.py") @@ -891,14 +891,14 @@ class TestRootdir: assert inifile is None assert inicfg == {} - def test_nothing(self, tmpdir, monkeypatch): + def test_nothing(self, tmpdir, monkeypatch) -> None: monkeypatch.chdir(str(tmpdir)) rootdir, inifile, inicfg = determine_setup(None, [tmpdir]) assert rootdir == tmpdir assert inifile is None assert inicfg == {} - def test_with_specific_inifile(self, tmpdir): + def test_with_specific_inifile(self, tmpdir) -> None: inifile = tmpdir.ensure("pytest.ini") rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir]) assert rootdir == tmpdir @@ -1039,7 +1039,7 @@ class TestOverrideIniArgs: result = testdir.runpytest("--override-ini", "python_files=unittest_*.py") result.stdout.fnmatch_lines(["*1 passed in*"]) - def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch): + def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch) -> None: monkeypatch.chdir(str(tmpdir)) a = tmpdir.mkdir("a") b = tmpdir.mkdir("b") @@ -1047,7 +1047,7 @@ class TestOverrideIniArgs: assert rootdir == tmpdir assert inifile is None - def test_with_arg_outside_cwd_with_inifile(self, tmpdir): + def test_with_arg_outside_cwd_with_inifile(self, tmpdir) -> None: a = tmpdir.mkdir("a") b = tmpdir.mkdir("b") inifile = a.ensure("pytest.ini") @@ -1056,13 +1056,13 @@ class TestOverrideIniArgs: assert inifile == parsed_inifile @pytest.mark.parametrize("dirs", ([], ["does-not-exist"], ["a/does-not-exist"])) - def test_with_non_dir_arg(self, dirs, tmpdir): + def test_with_non_dir_arg(self, dirs, tmpdir) -> None: with tmpdir.ensure(dir=True).as_cwd(): rootdir, inifile, inicfg = determine_setup(None, dirs) assert rootdir == tmpdir assert inifile is None - def test_with_existing_file_in_subdir(self, tmpdir): + def test_with_existing_file_in_subdir(self, tmpdir) -> None: a = tmpdir.mkdir("a") a.ensure("exist") with tmpdir.as_cwd(): From 090e26051738314317fbe7e6fc603e1cf39d6c87 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 2 Dec 2019 18:01:08 +0200 Subject: [PATCH 4/9] master: update mypy 0.740 -> 0.761 (cherry picked from commit 16ff9f591e38d1f2a79441f177130b1d89098c6e) (cherry picked from commit 4848bbdf9a4480ec85b520c6f3224256f1346679) --- .pre-commit-config.yaml | 2 +- src/_pytest/_code/code.py | 2 +- src/_pytest/assertion/__init__.py | 2 +- src/_pytest/assertion/rewrite.py | 5 ++--- src/_pytest/compat.py | 7 ++----- src/_pytest/doctest.py | 4 +++- src/_pytest/pytester.py | 2 +- src/_pytest/reports.py | 2 +- src/_pytest/runner.py | 2 +- testing/code/test_source.py | 8 ++------ 10 files changed, 15 insertions(+), 21 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ebbd6d18..67b10c6af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: - id: pyupgrade args: [--py3-plus] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.740 + rev: v0.761 hooks: - id: mypy files: ^(src/|testing/) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index a8f117366..d1a8ec2f1 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -479,7 +479,7 @@ class ExceptionInfo(Generic[_E]): assert tup[1] is not None, "no current exception" assert tup[2] is not None, "no current exception" exc_info = (tup[0], tup[1], tup[2]) - return cls.from_exc_info(exc_info, exprinfo) + return ExceptionInfo.from_exc_info(exc_info, exprinfo) @classmethod def for_later(cls) -> "ExceptionInfo[_E]": diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 34d6701ed..f96afce6d 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -33,7 +33,7 @@ def pytest_addoption(parser): ) -def register_assert_rewrite(*names): +def register_assert_rewrite(*names) -> None: """Register one or more module names to be rewritten on import. This function will make sure that this module or all modules inside diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 77ae70411..ab5e63a1e 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -1045,14 +1045,13 @@ def try_makedirs(cache_dir) -> bool: def get_cache_dir(file_path: Path) -> Path: """Returns the cache directory to write .pyc files for the given .py file path""" - # Type ignored until added in next mypy release. - if sys.version_info >= (3, 8) and sys.pycache_prefix: # type: ignore + if sys.version_info >= (3, 8) and sys.pycache_prefix: # given: # prefix = '/tmp/pycs' # path = '/home/user/proj/test_app.py' # we want: # '/tmp/pycs/home/user/proj' - return Path(sys.pycache_prefix) / Path(*file_path.parts[1:-1]) # type: ignore + return Path(sys.pycache_prefix) / Path(*file_path.parts[1:-1]) else: # classic pycache directory return file_path.parent / "__pycache__" diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index fc810b3e5..8dd74b577 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -43,8 +43,7 @@ MODULE_NOT_FOUND_ERROR = ( if sys.version_info >= (3, 8): - # Type ignored until next mypy release. - from importlib import metadata as importlib_metadata # type: ignore + from importlib import metadata as importlib_metadata else: import importlib_metadata # noqa: F401 @@ -385,9 +384,7 @@ else: if sys.version_info >= (3, 8): - # TODO: Remove type ignore on next mypy update. - # https://github.com/python/typeshed/commit/add0b5e930a1db16560fde45a3b710eefc625709 - from functools import cached_property # type: ignore + from functools import cached_property else: class cached_property(Generic[_S, _T]): diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index c81f0bfd4..488e91ddb 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -445,7 +445,9 @@ class DoctestModule(pytest.Module): obj = getattr(obj, "fget", obj) return doctest.DocTestFinder._find_lineno(self, obj, source_lines) - def _find(self, tests, obj, name, module, source_lines, globs, seen): + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: if _is_mocked(obj): return with _patch_unwrap_mock_aware(): diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index c279fca88..930f7db29 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -832,7 +832,7 @@ class Testdir: items = [x.item for x in rec.getcalls("pytest_itemcollected")] return items, rec - def inline_run(self, *args, plugins=(), no_reraise_ctrlc=False): + def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False): """Run ``pytest.main()`` in-process, returning a HookRecorder. Runs the :py:func:`pytest.main` function to run all of pytest inside diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 4c93013d6..ad63e21a9 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -259,7 +259,7 @@ class TestReport(BaseReport): ) @classmethod - def from_item_and_call(cls, item, call): + def from_item_and_call(cls, item, call) -> "TestReport": """ Factory method to create and fill a TestReport with standard item and call info. """ diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 67e28e905..50e4d4307 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -250,7 +250,7 @@ def pytest_runtest_makereport(item, call): return TestReport.from_item_and_call(item, call) -def pytest_make_collect_report(collector): +def pytest_make_collect_report(collector) -> CollectReport: call = CallInfo.from_call(lambda: list(collector.collect()), "collect") longrepr = None if not call.excinfo: diff --git a/testing/code/test_source.py b/testing/code/test_source.py index bf52dccd7..1390d8b0a 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -318,16 +318,12 @@ class TestSourceParsingAndCompiling: @pytest.mark.parametrize("name", ["", None, "my"]) def test_compilefuncs_and_path_sanity(self, name: Optional[str]) -> None: - def check(comp, name): + def check(comp, name) -> None: co = comp(self.source, name) if not name: expected = "codegen %s:%d>" % (mypath, mylineno + 2 + 2) # type: ignore else: - expected = "codegen %r %s:%d>" % ( - name, - mypath, # type: ignore - mylineno + 2 + 2, # type: ignore - ) # type: ignore + expected = "codegen %r %s:%d>" % (name, mypath, mylineno + 2 + 2) # type: ignore fn = co.co_filename assert fn.endswith(expected) From 4ff7453b482ca99a1481b096c96d9447fb2b4b93 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 10 Jan 2020 20:18:12 +0100 Subject: [PATCH 5/9] ci: Travis: Python 3.5.1 via Trusty Python 3.5.0 caused flaky failures before (https://github.com/pytest-dev/pytest/issues/5795). This is pulled out of https://github.com/pytest-dev/pytest/pull/6435, which adds code specific for Python < 3.5.2. It only runs a specific test, while collecting everything to get coverage of the version specific code around typing. --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 35ca8a69c..5b67f15dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,8 +52,10 @@ jobs: - env: TOXENV=pypy3-xdist python: 'pypy3' - - env: TOXENV=py35-xdist - python: '3.5' + # Coverage for Python 3.5.{0,1} specific code, mostly typing related. + - env: TOXENV=py35 PYTEST_COVERAGE=1 PYTEST_ADDOPTS="-k test_raises_cyclic_reference" + python: '3.5.1' + dist: trusty # Specialized factors for py37. - env: TOXENV=py37-pluggymaster-xdist From f0c7f21312ca1445aefedcf25f2d54f2cdfe1924 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 12 Jan 2020 20:35:20 +0100 Subject: [PATCH 6/9] Remove "pragma: no cover" comments --- src/_pytest/compat.py | 2 +- testing/python/raises.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 8dd74b577..de640bdd1 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -371,7 +371,7 @@ class CaptureIO(io.TextIOWrapper): return self.buffer.getvalue().decode("UTF-8") -if sys.version_info < (3, 5, 2): # pragma: no cover +if sys.version_info < (3, 5, 2): def overload(f): # noqa: F811 return f diff --git a/testing/python/raises.py b/testing/python/raises.py index 1c701796a..cfdbe6748 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -166,7 +166,7 @@ class TestRaises: # Early versions of Python 3.5 have some bug causing the # __call__ frame to still refer to t even after everything # is done. This makes the test pass for them. - if sys.version_info < (3, 5, 2): # pragma: no cover + if sys.version_info < (3, 5, 2): del self raise ValueError From d291905825d2ff3e5bbbaa245d8fecd24c980db9 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 14 Jan 2020 07:47:21 -0300 Subject: [PATCH 7/9] Append token to codecov.yml instead of duplicating the file --- .github/codecov-upstream.yml | 13 ------------ .github/workflows/main.yml | 2 +- codecov.yml | 2 -- scripts/append_codecov_token.py | 36 +++++++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 16 deletions(-) delete mode 100644 .github/codecov-upstream.yml create mode 100644 scripts/append_codecov_token.py diff --git a/.github/codecov-upstream.yml b/.github/codecov-upstream.yml deleted file mode 100644 index 1256a4389..000000000 --- a/.github/codecov-upstream.yml +++ /dev/null @@ -1,13 +0,0 @@ -# this file replaces /codecov.yml when building on the main repository and PRs -coverage: - status: - project: true - patch: true - changes: true - -comment: off - -codecov: - # token from: https://codecov.io/gh/pytest-dev/pytest/settings - # use same URL to regenerate it if needed - token: "1eca3b1f-31a2-4fb8-a8c3-138b441b50a7" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5d7493e72..b1bae1c18 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -137,7 +137,7 @@ jobs: - name: Prepare coverage token if: success() && !matrix.skip_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' ) run: | - cp .github/codecov-upstream.yml codecov.yml + python scripts/append_codecov_token.py - name: Combine coverage if: success() && !matrix.skip_coverage diff --git a/codecov.yml b/codecov.yml index 44fd14710..a0a308588 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,5 +1,3 @@ -# note: `.github/codecov-upstream.yml` is basically a copy of this file, please propagate -# changes as needed coverage: status: project: true diff --git a/scripts/append_codecov_token.py b/scripts/append_codecov_token.py new file mode 100644 index 000000000..8eecb0fa5 --- /dev/null +++ b/scripts/append_codecov_token.py @@ -0,0 +1,36 @@ +""" +Appends the codecov token to the 'codecov.yml' file at the root of the repository. + +This is done by CI during PRs and builds on the pytest-dev repository so we can upload coverage, at least +until codecov grows some native integration like it has with Travis and AppVeyor. + +See discussion in https://github.com/pytest-dev/pytest/pull/6441 for more information. +""" +import os.path +from textwrap import dedent + + +def main(): + this_dir = os.path.dirname(__file__) + cov_file = os.path.join(this_dir, "..", "codecov.yml") + + assert os.path.isfile(cov_file), "{cov_file} does not exist".format( + cov_file=cov_file + ) + + with open(cov_file, "a") as f: + # token from: https://codecov.io/gh/pytest-dev/pytest/settings + # use same URL to regenerate it if needed + text = dedent( + """ + codecov: + token: "1eca3b1f-31a2-4fb8-a8c3-138b441b50a7" + """ + ) + f.write(text) + + print("Token updated:", cov_file) + + +if __name__ == "__main__": + main() From 21d189eb52f5042de2668f9eee48696205a87442 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 14 Jan 2020 09:18:34 -0300 Subject: [PATCH 8/9] Enable GitHub actions for 'features' --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b1bae1c18..d6e5ddd1d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,9 +10,11 @@ on: push: branches: - master + - features pull_request: branches: - master + - features jobs: build: From b9c136b809a33009ad514672666c515953957b33 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 14 Jan 2020 10:51:44 -0300 Subject: [PATCH 9/9] Use a dummy RemoteTraceback for test in Python 3.5 Windows Somehow in Python 3.5 on Windows this test fails with: File "c:\hostedtoolcache\windows\python\3.5.4\x64\Lib\multiprocessing\connection.py", line 302, in _recv_bytes overlapped=True) OSError: [WinError 6] The handle is invalid This only happens in this platform and Python version, decided to use a dummy traceback as originally done in #6412. --- testing/test_reports.py | 51 +++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/testing/test_reports.py b/testing/test_reports.py index d0bafec23..8c509ec47 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -1,3 +1,5 @@ +import sys + import pytest from _pytest._code.code import ExceptionChainRepr from _pytest.pathlib import Path @@ -314,27 +316,52 @@ class TestReportSerialization: # elsewhere and we do check the contents of the longrepr object after loading it. loaded_report.longrepr.toterminal(tw_mock) - def test_chained_exceptions_no_reprcrash( - self, testdir, tw_mock, - ): + def test_chained_exceptions_no_reprcrash(self, testdir, tw_mock): """Regression test for tracebacks without a reprcrash (#5971) This happens notably on exceptions raised by multiprocess.pool: the exception transfer from subprocess to main process creates an artificial exception, which ExceptionInfo can't obtain the ReprFileLocation from. """ - testdir.makepyfile( + # somehow in Python 3.5 on Windows this test fails with: + # File "c:\...\3.5.4\x64\Lib\multiprocessing\connection.py", line 302, in _recv_bytes + # overlapped=True) + # OSError: [WinError 6] The handle is invalid + # + # so in this platform we opted to use a mock traceback which is identical to the + # one produced by the multiprocessing module + if sys.version_info[:2] <= (3, 5) and sys.platform.startswith("win"): + testdir.makepyfile( + """ + # equivalent of multiprocessing.pool.RemoteTraceback + class RemoteTraceback(Exception): + def __init__(self, tb): + self.tb = tb + def __str__(self): + return self.tb + def test_a(): + try: + raise ValueError('value error') + except ValueError as e: + # equivalent to how multiprocessing.pool.rebuild_exc does it + e.__cause__ = RemoteTraceback('runtime error') + raise e """ - from concurrent.futures import ProcessPoolExecutor + ) + else: + testdir.makepyfile( + """ + from concurrent.futures import ProcessPoolExecutor - def func(): - raise ValueError('value error') + def func(): + raise ValueError('value error') + + def test_a(): + with ProcessPoolExecutor() as p: + p.submit(func).result() + """ + ) - def test_a(): - with ProcessPoolExecutor() as p: - p.submit(func).result() - """ - ) reprec = testdir.inline_run() reports = reprec.getreports("pytest_runtest_logreport")