diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..f3a2bccfc --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,123 @@ +# evaluating GitHub actions for CI, disconsider failures when evaluating PRs +# +# this is still missing: +# - deploy +# - coverage +# - upload github notes +# +name: main + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + name: [ + "windows-py35", + "windows-py36", + "windows-py37", + "windows-py37-pluggy", + "windows-py38", + + "ubuntu-py35", + "ubuntu-py36", + "ubuntu-py37", + "ubuntu-py37-pluggy", + "ubuntu-py37-freeze", + "ubuntu-py38", + "ubuntu-pypy3", + + "macos-py37", + "macos-py38", + + "linting", + ] + + include: + - name: "windows-py35" + python: "3.5" + os: windows-latest + tox_env: "py35-xdist" + - name: "windows-py36" + python: "3.6" + os: windows-latest + tox_env: "py36-xdist" + - name: "windows-py37" + python: "3.7" + os: windows-latest + tox_env: "py37-twisted-numpy" + - name: "windows-py37-pluggy" + python: "3.7" + os: windows-latest + tox_env: "py37-pluggymaster-xdist" + - name: "windows-py38" + python: "3.8" + os: windows-latest + tox_env: "py38" + + - name: "ubuntu-py35" + python: "3.5" + os: ubuntu-latest + tox_env: "py35-xdist" + - name: "ubuntu-py36" + python: "3.6" + os: ubuntu-latest + tox_env: "py36-xdist" + - name: "ubuntu-py37" + python: "3.7" + os: ubuntu-latest + tox_env: "py37-lsof-numpy-oldattrs-pexpect-twisted" + - name: "ubuntu-py37-pluggy" + python: "3.7" + os: ubuntu-latest + tox_env: "py37-pluggymaster-xdist" + - name: "ubuntu-py37-freeze" + python: "3.7" + os: ubuntu-latest + tox_env: "py37-freeze" + - name: "ubuntu-py38" + python: "3.8" + os: ubuntu-latest + tox_env: "py38-xdist" + - name: "ubuntu-pypy3" + python: "pypy3" + os: ubuntu-latest + tox_env: "pypy3-xdist" + + - name: "macos-py37" + python: "3.7" + os: macos-latest + tox_env: "py37-xdist" + - name: "macos-py38" + python: "3.8" + os: macos-latest + tox_env: "py38-xdist" + + - name: "linting" + python: "3.7" + os: ubuntu-latest + tox_env: "linting,docs,doctesting" + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + - name: Test + run: tox -e ${{ matrix.tox_env }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b2368cf8f..99d728ffa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: rev: v1.0.0 hooks: - id: blacken-docs - additional_dependencies: [black==19.3b0] + additional_dependencies: [black==19.10b0] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.2.3 hooks: diff --git a/.travis.yml b/.travis.yml index e3edbfe9b..35ca8a69c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,9 +80,9 @@ jobs: addons: apt: packages: - # required by publish_gh_release_notes + # required by publish-gh-release-notes - pandoc - after_deploy: tox -e publish_gh_release_notes + after_deploy: tox -e publish-gh-release-notes deploy: provider: pypi user: nicoddemus diff --git a/AUTHORS b/AUTHORS index 50c2d478a..3289b9913 100644 --- a/AUTHORS +++ b/AUTHORS @@ -238,6 +238,7 @@ Samuele Pedroni Sankt Petersbug Segev Finer Serhii Mozghovyi +Seth Junot Simon Gomizelj Skylar Downes Srinivas Reddy Thatiparthy diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a3ae731e4..455998b78 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -51,7 +51,8 @@ Fix bugs Look through the `GitHub issues for bugs `_. -:ref:`Talk ` to developers to find out how you can fix specific bugs. +:ref:`Talk ` to developers to find out how you can fix specific bugs. To indicate that you are going +to work on a particular issue, add a comment to that effect on the specific issue. Don't forget to check the issue trackers of your favourite plugins, too! diff --git a/LICENSE b/LICENSE index 477af2d2e..d14fb7ff4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2004-2019 Holger Krekel and others +Copyright (c) 2004-2020 Holger Krekel and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.rst b/README.rst index 53e643a5b..3235dd4c2 100644 --- a/README.rst +++ b/README.rst @@ -112,12 +112,12 @@ Support pytest -------------- `Open Collective`_ is an online funding platform for open and transparent communities. -It provide tools to raise money and share your finances in full transparency. +It provides tools to raise money and share your finances in full transparency. It is the platform of choice for individuals and companies that want to make one-time or monthly donations directly to the project. -See more datails in the `pytest collective`_. +See more details in the `pytest collective`_. .. _Open Collective: https://opencollective.com .. _pytest collective: https://opencollective.com/pytest @@ -145,7 +145,7 @@ Tidelift will coordinate the fix and disclosure. License ------- -Copyright Holger Krekel and others, 2004-2019. +Copyright Holger Krekel and others, 2004-2020. Distributed under the terms of the `MIT`_ license, pytest is free and open source software. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f18ce0887..a6d856d91 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -39,6 +39,9 @@ jobs: py37-pluggymaster-xdist: python.version: '3.7' tox.env: 'py37-pluggymaster-xdist' + py38-xdist: + python.version: '3.8' + tox.env: 'py38-xdist' maxParallel: 10 steps: diff --git a/changelog/5971.bugfix.rst b/changelog/5971.bugfix.rst new file mode 100644 index 000000000..dbc79dd96 --- /dev/null +++ b/changelog/5971.bugfix.rst @@ -0,0 +1,2 @@ +Fix a ``pytest-xdist`` crash when dealing with exceptions raised in subprocesses created by the +``multiprocessing`` module. diff --git a/changelog/6350.trivial.rst b/changelog/6350.trivial.rst new file mode 100644 index 000000000..c43fb4267 --- /dev/null +++ b/changelog/6350.trivial.rst @@ -0,0 +1 @@ +Optimized automatic renaming of test parameter IDs. diff --git a/changelog/6532.bugfix.rst b/changelog/6532.bugfix.rst new file mode 100644 index 000000000..b5c7cf771 --- /dev/null +++ b/changelog/6532.bugfix.rst @@ -0,0 +1 @@ +Fix parsing of outcomes containing multiple errors with ``testdir`` results (regression in 5.3.0). diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index e44ab212e..7bcf899d9 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -20,6 +20,10 @@ Release announcements release-5.1.0 release-5.0.1 release-5.0.0 + release-4.6.9 + release-4.6.8 + release-4.6.7 + release-4.6.6 release-4.6.5 release-4.6.4 release-4.6.3 diff --git a/doc/en/announce/release-2.3.0.rst b/doc/en/announce/release-2.3.0.rst index 5fb253670..1b9d0dcc1 100644 --- a/doc/en/announce/release-2.3.0.rst +++ b/doc/en/announce/release-2.3.0.rst @@ -3,13 +3,13 @@ pytest-2.3: improved fixtures / better unittest integration pytest-2.3 comes with many major improvements for fixture/funcarg management and parametrized testing in Python. It is now easier, more efficient and -more predicatable to re-run the same tests with different fixture +more predictable to re-run the same tests with different fixture instances. Also, you can directly declare the caching "scope" of fixtures so that dependent tests throughout your whole test suite can re-use database or other expensive fixture objects with ease. Lastly, it's possible for fixture functions (formerly known as funcarg factories) to use other fixtures, allowing for a completely modular and -re-useable fixture design. +re-usable fixture design. For detailed info and tutorial-style examples, see: diff --git a/doc/en/announce/release-2.5.0.rst b/doc/en/announce/release-2.5.0.rst index 29064e05e..bc83fdc12 100644 --- a/doc/en/announce/release-2.5.0.rst +++ b/doc/en/announce/release-2.5.0.rst @@ -91,7 +91,7 @@ holger krekel it might be the cause for other finalizers to fail. - fix ordering when mock.patch or other standard decorator-wrappings - are used with test methods. This fixues issue346 and should + are used with test methods. This fixes issue346 and should help with random "xdist" collection failures. Thanks to Ronny Pfannschmidt and Donald Stufft for helping to isolate it. diff --git a/doc/en/announce/release-2.7.0.rst b/doc/en/announce/release-2.7.0.rst index 8952ff50f..cf798ff2c 100644 --- a/doc/en/announce/release-2.7.0.rst +++ b/doc/en/announce/release-2.7.0.rst @@ -35,7 +35,7 @@ holger krekel - fix issue435: make reload() work when assert rewriting is active. Thanks Daniel Hahler. -- fix issue616: conftest.py files and their contained fixutres are now +- fix issue616: conftest.py files and their contained fixtures are now properly considered for visibility, independently from the exact current working directory and test arguments that are used. Many thanks to Eric Siegerman and his PR235 which contains diff --git a/doc/en/announce/release-4.6.6.rst b/doc/en/announce/release-4.6.6.rst new file mode 100644 index 000000000..c47a31695 --- /dev/null +++ b/doc/en/announce/release-4.6.6.rst @@ -0,0 +1,20 @@ +pytest-4.6.6 +======================================= + +pytest 4.6.6 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Michael Goerz + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-4.6.7.rst b/doc/en/announce/release-4.6.7.rst new file mode 100644 index 000000000..0e6cf6a95 --- /dev/null +++ b/doc/en/announce/release-4.6.7.rst @@ -0,0 +1,19 @@ +pytest-4.6.7 +======================================= + +pytest 4.6.7 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Bruno Oliveira +* Daniel Hahler + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-4.6.8.rst b/doc/en/announce/release-4.6.8.rst new file mode 100644 index 000000000..3c04e5dbe --- /dev/null +++ b/doc/en/announce/release-4.6.8.rst @@ -0,0 +1,20 @@ +pytest-4.6.8 +======================================= + +pytest 4.6.8 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Ryan Mast + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-4.6.9.rst b/doc/en/announce/release-4.6.9.rst new file mode 100644 index 000000000..ae0478c52 --- /dev/null +++ b/doc/en/announce/release-4.6.9.rst @@ -0,0 +1,21 @@ +pytest-4.6.9 +======================================= + +pytest 4.6.9 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Felix Yan +* Hugo + + +Happy testing, +The pytest Development Team diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 46fa240e0..4703c7e67 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -1,3 +1,5 @@ +.. _`changelog`: + ========= Changelog ========= @@ -815,6 +817,38 @@ Improved Documentation - `#5416 `_: Fix PytestUnknownMarkWarning in run/skip example. +pytest 4.6.9 (2020-01-04) +========================= + +Bug Fixes +--------- + +- `#6301 `_: Fix assertion rewriting for egg-based distributions and ``editable`` installs (``pip install --editable``). + + +pytest 4.6.8 (2019-12-19) +========================= + +Features +-------- + +- `#5471 `_: JUnit XML now includes a timestamp and hostname in the testsuite tag. + + + +Bug Fixes +--------- + +- `#5430 `_: junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase. + + + +Trivial/Internal Changes +------------------------ + +- `#6345 `_: Pin ``colorama`` to ``0.4.1`` only for Python 3.4 so newer Python versions can still receive colorama updates. + + pytest 4.6.7 (2019-12-05) ========================= @@ -5343,7 +5377,7 @@ time or change existing behaviors in order to make them less surprising/more use Thanks Ronny Pfannschmidt for most of the merging work. - "-r" option now accepts "a" to include all possible reports, similar - to passing "fEsxXw" explicitly (isse960). + to passing "fEsxXw" explicitly (issue960). Thanks Abhijeet Kasurde for the PR. - avoid python3.5 deprecation warnings by introducing version @@ -5633,7 +5667,7 @@ time or change existing behaviors in order to make them less surprising/more use - fix issue435: make reload() work when assert rewriting is active. Thanks Daniel Hahler. -- fix issue616: conftest.py files and their contained fixutres are now +- fix issue616: conftest.py files and their contained fixtures are now properly considered for visibility, independently from the exact current working directory and test arguments that are used. Many thanks to Eric Siegerman and his PR235 which contains @@ -7180,7 +7214,7 @@ Bug fixes: - streamlined plugin loading: order is now as documented in customize.html: setuptools, ENV, commandline, conftest. - also setuptools entry point names are turned to canonical namees ("pytest_*") + also setuptools entry point names are turned to canonical names ("pytest_*") - automatically skip tests that need 'capfd' but have no os.dup diff --git a/doc/en/conf.py b/doc/en/conf.py index 7e8a2f209..3fb6002bc 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -66,7 +66,7 @@ master_doc = "contents" # General information about the project. project = "pytest" -copyright = "2015–2019, holger krekel and pytest-dev team" +copyright = "2015–2020, holger krekel and pytest-dev team" # The language for content autogenerated by Sphinx. Refer to documentation @@ -289,7 +289,7 @@ man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu" epub_title = "pytest" epub_author = "holger krekel at merlinux eu" epub_publisher = "holger krekel at merlinux eu" -epub_copyright = "2013, holger krekel et alii" +epub_copyright = "2013-2020, holger krekel et alii" # The language of the text. It defaults to the language option # or en if the language is not set. diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 08305a5cc..db06a4015 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -1035,15 +1035,19 @@ file: # content of conftest.py - import pytest - import tempfile import os + import shutil + import tempfile + + import pytest @pytest.fixture() def cleandir(): newpath = tempfile.mkdtemp() os.chdir(newpath) + yield + shutil.rmtree(newpath) and declare its use in a test module via a ``usefixtures`` marker: diff --git a/doc/en/historical-notes.rst b/doc/en/historical-notes.rst index c8403728f..ba96d32ab 100644 --- a/doc/en/historical-notes.rst +++ b/doc/en/historical-notes.rst @@ -111,7 +111,7 @@ More details can be found in the `original PR `__. -Python 3.4's last release is scheduled for -`March 2019 `__. pytest is one of -the participating projects of the https://python3statement.org. +It is demanding on the maintainers of an open source project to support many Python versions, as +there's extra cost of keeping code compatible between all versions, while holding back on +features only made possible on newer Python versions. -The **pytest 4.6** series is the last to support Python 2.7 and 3.4, and was released in -**June 2019**. **pytest 5.0** and onwards will support only Python 3.5+. +In case of Python 2 and 3, the difference between the languages makes it even more prominent, +because many new Python 3 features cannot be used in a Python 2/3 compatible code base. -Thanks to the `python_requires`_ ``setuptools`` option, -Python 2.7 and Python 3.4 users using a modern ``pip`` version -will install the last pytest ``4.6`` version automatically even if ``5.0`` or later +Python 2.7 EOL has been reached `in 2020 `__, with +the last release planned for mid-April, 2020. + +Python 3.4 EOL has been reached `in 2019 `__, with the last release made in March, 2019. + +For those reasons, in Jun 2019 it was decided that **pytest 4.6** series will be the last to support Python 2.7 and 3.4. + +What this means for general users +--------------------------------- + +Thanks to the `python_requires`_ setuptools option, +Python 2.7 and Python 3.4 users using a modern pip version +will install the last pytest 4.6.X version automatically even if 5.0 or later versions are available on PyPI. -While pytest ``5.0`` will be the new mainstream and development version, until **January 2020** -the pytest core team plans to make bug-fix releases of the pytest ``4.6`` series by -back-porting patches to the ``4.6-maintenance`` branch that affect Python 2 users. +Users should ensure they are using the latest pip and setuptools versions for this to work. -**After 2020**, the core team will no longer actively backport patches, but the ``4.6-maintenance`` -branch will continue to exist so the community itself can contribute patches. The core team will -be happy to accept those patches and make new ``4.6`` releases **until mid-2020**. +Maintenance of 4.6.X versions +----------------------------- + +Until January 2020, the pytest core team ported many bug-fixes from the main release into the +``4.6-maintenance`` branch, with several 4.6.X releases being made along the year. + +From now on, the core team will **no longer actively backport patches**, but the ``4.6-maintenance`` +branch will continue to exist so the community itself can contribute patches. + +The core team will be happy to accept those patches, and make new 4.6.X releases **until mid-2020** +(but consider that date as a ballpark, after that date the team might still decide to make new releases +for critical bugs). .. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires -Technical Aspects ------------------ +Technical aspects +~~~~~~~~~~~~~~~~~ -The technical aspects of the Python 2.7 and 3.4 support plan (such as when releases will occurr, how to backport fixes, etc) is described in issue `#5275 `__. +(This section is a transcript from `#5275 `__). + +In this section we describe the technical aspects of the Python 2.7 and 3.4 support plan. + +What goes into 4.6.X releases ++++++++++++++++++++++++++++++ + +New 4.6.X releases will contain bug fixes only. + +When will 4.6.X releases happen ++++++++++++++++++++++++++++++++ + +New 4.6.X releases will happen after we have a few bugs in place to release, or if a few weeks have +passed (say a single bug has been fixed a month after the latest 4.6.X release). + +No hard rules here, just ballpark. + +Who will handle applying bug fixes +++++++++++++++++++++++++++++++++++ + +We core maintainers expect that people still using Python 2.7/3.4 and being affected by +bugs to step up and provide patches and/or port bug fixes from the active branches. + +We will be happy to guide users interested in doing so, so please don't hesitate to ask. + +**Backporting changes into 4.6** + +Please follow these instructions: + +#. ``git fetch --all --prune`` + +#. ``git checkout origin/4.6-maintenance -b backport-XXXX`` # use the PR number here + +#. Locate the merge commit on the PR, in the *merged* message, for example: + + nicoddemus merged commit 0f8b462 into pytest-dev:features + +#. ``git cherry-pick -m1 REVISION`` # use the revision you found above (``0f8b462``). + +#. Open a PR targeting ``4.6-maintenance``: + + * Prefix the message with ``[4.6]`` so it is an obvious backport + * Delete the PR body, it usually contains a duplicate commit message. + +**Providing new PRs to 4.6** + +Fresh pull requests to ``4.6-maintenance`` will be accepted provided that +the equivalent code in the active branches does not contain that bug (for example, a bug is specific +to Python 2 only). + +Bug fixes that also happen in the mainstream version should be first fixed +there, and then backported as per instructions above. diff --git a/doc/en/reference.rst b/doc/en/reference.rst index a056fdf28..50e32d660 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -1,3 +1,5 @@ +.. _`reference`: + API Reference ============= diff --git a/doc/en/sponsor.rst b/doc/en/sponsor.rst index 1023928b3..8362a7f0a 100644 --- a/doc/en/sponsor.rst +++ b/doc/en/sponsor.rst @@ -17,7 +17,7 @@ It provide tools to raise money and share your finances in full transparency. It is the platform of choice for individuals and companies that want to make one-time or monthly donations directly to the project. -See more datails in the `pytest collective`_. +See more details in the `pytest collective`_. .. _Tidelift: https://tidelift.com diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index 4b8be4469..013564c2d 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -198,7 +198,7 @@ the regular expression ``".*U.*mode is deprecated"``. Ensuring code triggers a deprecation warning -------------------------------------------- -You can also call a global helper for checking +You can also use :func:`pytest.deprecated_call` for checking that a certain function call triggers a ``DeprecationWarning`` or ``PendingDeprecationWarning``: @@ -207,13 +207,18 @@ that a certain function call triggers a ``DeprecationWarning`` or import pytest - def test_global(): - pytest.deprecated_call(myfunction, 17) + def test_myfunction_deprecated(): + with pytest.deprecated_call(): + myfunction(17) + +This test will fail if ``myfunction`` does not issue a deprecation warning +when called with a ``17`` argument. By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be -caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide -them. If you wish to record them in your own code, use the -command ``warnings.simplefilter('always')``: +caught when using :func:`pytest.warns` or :ref:`recwarn ` because +the default Python warnings filters hide +them. If you wish to record them in your own code, use +``warnings.simplefilter('always')``: .. code-block:: python @@ -223,19 +228,13 @@ command ``warnings.simplefilter('always')``: def test_deprecation(recwarn): warnings.simplefilter("always") - warnings.warn("deprecated", DeprecationWarning) + myfunction(17) assert len(recwarn) == 1 assert recwarn.pop(DeprecationWarning) -You can also use it as a contextmanager: - -.. code-block:: python - - def test_global(): - with pytest.deprecated_call(): - myobject.deprecated_method() - +The :ref:`recwarn ` fixture automatically ensures to reset the warnings +filter at the end of the test, so no global state is leaked. .. _`asserting warnings`: diff --git a/scripts/publish_gh_release_notes.py b/scripts/publish-gh-release-notes.py similarity index 94% rename from scripts/publish_gh_release_notes.py rename to scripts/publish-gh-release-notes.py index 23f7b40ad..34ccaa63d 100644 --- a/scripts/publish_gh_release_notes.py +++ b/scripts/publish-gh-release-notes.py @@ -6,7 +6,13 @@ This script is meant to be executed after a successful deployment in Travis. Uses the following environment variables: * GIT_TAG: the name of the tag of the current commit. -* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions. It should be encrypted using: +* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions. + + Create one at: + + https://github.com/settings/tokens + + It should be encrypted using: $travis encrypt GH_RELEASE_NOTES_TOKEN= -r pytest-dev/pytest @@ -33,7 +39,7 @@ def publish_github_release(slug, token, tag_name, body): def parse_changelog(tag_name): - p = Path(__file__).parent.parent / "CHANGELOG.rst" + p = Path(__file__).parent.parent / "doc/en/changelog.rst" changelog_lines = p.read_text(encoding="UTF-8").splitlines() title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)") diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 16c94afb9..ab5e63a1e 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -143,10 +143,12 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder): exec(co, module.__dict__) def _early_rewrite_bailout(self, name, state): - """This is a fast way to get out of rewriting modules. Profiling has - shown that the call to PathFinder.find_spec (inside of the find_spec - from this class) is a major slowdown, so, this method tries to - filter what we're sure won't be rewritten before getting to it. + """This is a fast way to get out of rewriting modules. + + Profiling has shown that the call to PathFinder.find_spec (inside of + the find_spec from this class) is a major slowdown, so, this method + tries to filter what we're sure won't be rewritten before getting to + it. """ if self.session is not None and not self._session_paths_checked: self._session_paths_checked = True diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 605dd5eb7..d2313f280 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -433,9 +433,14 @@ class RunResult: for line in reversed(self.outlines): if rex_session_duration.search(line): outcomes = rex_outcome.findall(line) - return {noun: int(count) for (count, noun) in outcomes} - - raise ValueError("Pytest terminal summary report not found") + ret = {noun: int(count) for (count, noun) in outcomes} + break + else: + raise ValueError("Pytest terminal summary report not found") + if "errors" in ret: + assert "error" not in ret + ret["error"] = ret.pop("errors") + return ret def assert_outcomes( self, diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 1b01f4faa..281fa3cfe 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -6,6 +6,7 @@ import os import sys import warnings from collections import Counter +from collections import defaultdict from collections.abc import Sequence from functools import partial from textwrap import dedent @@ -1244,14 +1245,23 @@ def idmaker(argnames, parametersets, idfn=None, ids=None, config=None, item=None _idvalset(valindex, parameterset, argnames, idfn, ids, config=config, item=item) for valindex, parameterset in enumerate(parametersets) ] - if len(set(ids)) != len(ids): - # The ids are not unique - duplicates = [testid for testid in ids if ids.count(testid) > 1] - counters = Counter() - for index, testid in enumerate(ids): - if testid in duplicates: - ids[index] = testid + str(counters[testid]) - counters[testid] += 1 + + # All IDs must be unique! + unique_ids = set(ids) + if len(unique_ids) != len(ids): + + # Record the number of occurrences of each test ID + test_id_counts = Counter(ids) + + # Map the test ID to its next suffix + test_id_suffixes = defaultdict(int) + + # Suffix non-unique IDs to make them unique + for index, test_id in enumerate(ids): + if test_id_counts[test_id] > 1: + ids[index] = "{}{}".format(test_id, test_id_suffixes[test_id]) + test_id_suffixes[test_id] += 1 + return ids diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 95236450d..ad63e21a9 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -374,8 +374,11 @@ def _report_to_json(report): ] return result - def serialize_repr_crash(reprcrash): - return reprcrash.__dict__.copy() + def serialize_repr_crash(reprcrash: Optional[ReprFileLocation]): + if reprcrash is not None: + return reprcrash.__dict__.copy() + else: + return None def serialize_longrepr(rep): result = { @@ -455,8 +458,11 @@ def _report_kwargs_from_json(reportdict): ] return ReprTraceback(**repr_traceback_dict) - def deserialize_repr_crash(repr_crash_dict): - return ReprFileLocation(**repr_crash_dict) + def deserialize_repr_crash(repr_crash_dict: Optional[dict]): + if repr_crash_dict is not None: + return ReprFileLocation(**repr_crash_dict) + else: + return None if ( reportdict["longrepr"] diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 3dab13b4b..b747ac9ee 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -676,3 +676,25 @@ def test_run_result_repr(): repr(r) == "" ) + + +def test_testdir_outcomes_with_multiple_errors(testdir): + p1 = testdir.makepyfile( + """ + import pytest + + @pytest.fixture + def bad_fixture(): + raise Exception("bad") + + def test_error1(bad_fixture): + pass + + def test_error2(bad_fixture): + pass + """ + ) + result = testdir.runpytest(str(p1)) + result.assert_outcomes(error=2) + + assert result.parseoutcomes() == {"error": 2} diff --git a/testing/test_reports.py b/testing/test_reports.py index ff813543c..d0bafec23 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -305,6 +305,8 @@ class TestReportSerialization: data = report._to_json() loaded_report = report_class._from_json(data) + + assert loaded_report.failed check_longrepr(loaded_report.longrepr) # make sure we don't blow up on ``toterminal`` call; we don't test the actual output because it is very @@ -312,6 +314,60 @@ 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, + ): + """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( + """ + from concurrent.futures import ProcessPoolExecutor + + def func(): + raise ValueError('value error') + + def test_a(): + with ProcessPoolExecutor() as p: + p.submit(func).result() + """ + ) + reprec = testdir.inline_run() + + reports = reprec.getreports("pytest_runtest_logreport") + + def check_longrepr(longrepr): + assert isinstance(longrepr, ExceptionChainRepr) + assert len(longrepr.chain) == 2 + entry1, entry2 = longrepr.chain + tb1, fileloc1, desc1 = entry1 + tb2, fileloc2, desc2 = entry2 + + assert "RemoteTraceback" in str(tb1) + assert "ValueError: value error" in str(tb2) + + assert fileloc1 is None + assert fileloc2.message == "ValueError: value error" + + # 3 reports: setup/call/teardown: get the call report + assert len(reports) == 3 + report = reports[1] + + assert report.failed + check_longrepr(report.longrepr) + + data = report._to_json() + loaded_report = TestReport._from_json(data) + + assert loaded_report.failed + check_longrepr(loaded_report.longrepr) + + # for same reasons as previous test, ensure we don't blow up here + loaded_report.longrepr.toterminal(tw_mock) + class TestHooks: """Test that the hooks are working correctly for plugins""" diff --git a/testing/test_runner.py b/testing/test_runner.py index 301e11898..df0b7b0cf 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -468,10 +468,7 @@ reporttypes = [reports.BaseReport, reports.TestReport, reports.CollectReport] "reporttype", reporttypes, ids=[x.__name__ for x in reporttypes] ) def test_report_extra_parameters(reporttype): - if hasattr(inspect, "signature"): - args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:] - else: - args = inspect.getargspec(reporttype.__init__)[0][1:] + args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:] basekw = dict.fromkeys(args, []) report = reporttype(newthing=1, **basekw) assert report.newthing == 1 diff --git a/tox.ini b/tox.ini index 71f795316..d33a62224 100644 --- a/tox.ini +++ b/tox.ini @@ -127,7 +127,7 @@ deps = towncrier commands = python scripts/release.py {posargs} -[testenv:publish_gh_release_notes] +[testenv:publish-gh-release-notes] description = create GitHub release after deployment basepython = python3 usedevelop = True @@ -135,7 +135,7 @@ passenv = GH_RELEASE_NOTES_TOKEN TRAVIS_TAG TRAVIS_REPO_SLUG deps = github3.py pypandoc -commands = python scripts/publish_gh_release_notes.py +commands = python scripts/publish-gh-release-notes.py {posargs} [pytest]