diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 00000000000..f7840d537cf --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,51 @@ +name: backport + +on: + # Note that `pull_request_target` has security implications: + # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + # In particular: + # - Only allow triggers that can be used only be trusted users + # - Don't execute any code from the target branch + # - Don't use cache + pull_request_target: + types: [labeled] + +# Set permissions at the job level. +permissions: {} + +jobs: + backport: + if: startsWith(github.event.label.name, 'backport ') && github.event.pull_request.merged + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + persist-credentials: true + + - name: Create backport PR + run: | + set -eux + + git config --global user.name "pytest bot" + git config --global user.email "pytestbot@gmail.com" + + label='${{ github.event.label.name }}' + target_branch="${label#backport }" + backport_branch=backport-${{ github.event.number }}-to-"${target_branch}" + subject="[$target_branch] $(gh pr view --json title -q .title ${{ github.event.number }})" + + git checkout origin/"${target_branch}" -b "${backport_branch}" + git cherry-pick -x --mainline 1 ${{ github.event.pull_request.merge_commit_sha }} + git commit --amend --message "$subject" + git push --set-upstream origin --force-with-lease "${backport_branch}" + gh pr create \ + --base "${target_branch}" \ + --title "${subject}" \ + --body "Backport of PR #${{ github.event.number }} to $target_branch branch. PR created by backport workflow." + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000000..ecfa3071f58 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,60 @@ +name: deploy + +on: + push: + tags: + # These tags are protected, see: + # https://github.com/pytest-dev/pytest/settings/tag_protection + - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+" + + +# Set permissions at the job level. +permissions: {} + +jobs: + + deploy: + if: github.repository == 'pytest-dev/pytest' + + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: write + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Build and Check Package + uses: hynek/build-and-inspect-python-package@v1.5 + + - name: Download Package + uses: actions/download-artifact@v3 + with: + name: Packages + path: dist + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.pypi_token }} + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.7" + + - name: Install tox + run: | + python -m pip install --upgrade pip + pip install --upgrade tox + + - name: Publish GitHub release notes + env: + GH_RELEASE_NOTES_TOKEN: ${{ github.token }} + run: | + sudo apt-get install pandoc + tox -e publish-gh-release-notes diff --git a/.github/workflows/main.yml b/.github/workflows/test.yml similarity index 71% rename from .github/workflows/main.yml rename to .github/workflows/test.yml index 92e2dc6be7c..cd1ffdbf9d4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/test.yml @@ -1,10 +1,11 @@ -name: main +name: test on: push: branches: - main - "[0-9]+.[0-9]+.x" + - "test-me-*" tags: - "[0-9]+.[0-9]+.[0-9]+" - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+" @@ -17,13 +18,18 @@ on: env: PYTEST_ADDOPTS: "--color=yes" +# Cancel running jobs for the same workflow and branch. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + # Set permissions at the job level. permissions: {} jobs: build: runs-on: ${{ matrix.os }} - timeout-minutes: 30 + timeout-minutes: 45 permissions: contents: read @@ -31,24 +37,26 @@ jobs: fail-fast: false matrix: name: [ - "windows-py36", "windows-py37", "windows-py37-pluggy", "windows-py38", "windows-py39", "windows-py310", + "windows-py311", - "ubuntu-py36", "ubuntu-py37", "ubuntu-py37-pluggy", "ubuntu-py37-freeze", "ubuntu-py38", "ubuntu-py39", "ubuntu-py310", + "ubuntu-py311", "ubuntu-pypy3", "macos-py37", "macos-py38", + "macos-py39", + "macos-py310", "docs", "doctesting", @@ -56,10 +64,6 @@ jobs: ] include: - - name: "windows-py36" - python: "3.6" - os: windows-latest - tox_env: "py36-xdist" - name: "windows-py37" python: "3.7" os: windows-latest @@ -67,7 +71,7 @@ jobs: - name: "windows-py37-pluggy" python: "3.7" os: windows-latest - tox_env: "py37-pluggymain-xdist" + tox_env: "py37-pluggymain-pylib-xdist" - name: "windows-py38" python: "3.8" os: windows-latest @@ -78,14 +82,14 @@ jobs: os: windows-latest tox_env: "py39-xdist" - name: "windows-py310" - python: "3.10-dev" + python: "3.10" os: windows-latest tox_env: "py310-xdist" + - name: "windows-py311" + python: "3.11-dev" + os: windows-latest + tox_env: "py311" - - name: "ubuntu-py36" - python: "3.6" - os: ubuntu-latest - tox_env: "py36-xdist" - name: "ubuntu-py37" python: "3.7" os: ubuntu-latest @@ -94,7 +98,7 @@ jobs: - name: "ubuntu-py37-pluggy" python: "3.7" os: ubuntu-latest - tox_env: "py37-pluggymain-xdist" + tox_env: "py37-pluggymain-pylib-xdist" - name: "ubuntu-py37-freeze" python: "3.7" os: ubuntu-latest @@ -108,9 +112,14 @@ jobs: os: ubuntu-latest tox_env: "py39-xdist" - name: "ubuntu-py310" - python: "3.10-dev" + python: "3.10" os: ubuntu-latest tox_env: "py310-xdist" + - name: "ubuntu-py311" + python: "3.11-dev" + os: ubuntu-latest + tox_env: "py311" + use_coverage: true - name: "ubuntu-pypy3" python: "pypy-3.7" os: ubuntu-latest @@ -125,9 +134,17 @@ jobs: os: macos-latest tox_env: "py38-xdist" use_coverage: true + - name: "macos-py39" + python: "3.9" + os: macos-latest + tox_env: "py39-xdist" + - name: "macos-py310" + python: "3.10" + os: macos-latest + tox_env: "py310-xdist" - name: "plugins" - python: "3.7" + python: "3.9" os: ubuntu-latest tox_env: "plugins" @@ -142,13 +159,13 @@ jobs: use_coverage: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 persist-credentials: false - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} @@ -171,51 +188,16 @@ jobs: - name: Upload coverage to Codecov if: "matrix.use_coverage" - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 + continue-on-error: true with: fail_ci_if_error: true files: ./coverage.xml verbose: true - deploy: - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest' - + check-package: runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: write - - needs: [build] - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: "3.7" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install --upgrade build tox - - - name: Build package - run: | - python -m build - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_token }} - - - name: Publish GitHub release notes - env: - GH_RELEASE_NOTES_TOKEN: ${{ github.token }} - run: | - sudo apt-get install pandoc - tox -e publish-gh-release-notes + - uses: actions/checkout@v3 + - name: Build and Check Package + uses: hynek/build-and-inspect-python-package@v1.5 diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index 17c6364f4bd..193469072ff 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -12,6 +12,7 @@ permissions: {} jobs: createPullRequest: + if: github.repository_owner == 'pytest-dev' runs-on: ubuntu-latest permissions: contents: write diff --git a/.gitignore b/.gitignore index 935da3b9a2e..3cac2474a59 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ coverage.xml .project .settings .vscode +__pycache__/ # generated by pip pip-wheel-metadata/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 20cede3b7bb..df0c6e338b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,18 @@ +default_language_version: + python: "3.10" repos: - repo: https://github.com/psf/black - rev: 21.11b1 + rev: 22.12.0 hooks: - id: black args: [--safe, --quiet] - repo: https://github.com/asottile/blacken-docs - rev: v1.12.0 + rev: v1.12.1 hooks: - id: blacken-docs additional_dependencies: [black==20.8b1] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -20,47 +22,56 @@ repos: - id: debug-statements exclude: _pytest/(debugging|hookspec).py language_version: python3 +- repo: https://github.com/PyCQA/autoflake + rev: v1.7.6 + hooks: + - id: autoflake + name: autoflake + args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"] + language: python + files: \.py$ - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 + rev: 5.0.4 hooks: - id: flake8 language_version: python3 additional_dependencies: - - flake8-typing-imports==1.9.0 + - flake8-typing-imports==1.12.0 - flake8-docstrings==1.5.0 - repo: https://github.com/asottile/reorder_python_imports - rev: v2.6.0 + rev: v3.8.5 hooks: - id: reorder-python-imports - args: ['--application-directories=.:src', --py36-plus] + args: ['--application-directories=.:src', --py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.29.1 + rev: v3.3.1 hooks: - id: pyupgrade - args: [--py36-plus] + args: [--py37-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.20.0 + rev: v2.1.0 hooks: - id: setup-cfg-fmt - args: [--max-py-version=3.10] + args: ["--max-py-version=3.11", "--include-version-classifiers"] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.9.0 hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.910-1 + rev: v0.982 hooks: - id: mypy files: ^(src/|testing/) args: [] additional_dependencies: - iniconfig>=1.1.0 - - py>=1.8.2 - attrs>=19.2.0 - packaging - tomli - - types-atomicwrites - types-pkg_resources + # for mypy running on python>=3.11 since exceptiongroup is only a dependency + # on <3.11 + - exceptiongroup>=1.0.0rc8 - repo: local hooks: - id: rst @@ -93,7 +104,7 @@ repos: types: [python] - id: py-path-deprecated name: py.path usage is deprecated - exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py + exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py|src/_pytest/legacypath.py language: pygrep entry: \bpy\.path\.local types: [python] diff --git a/.readthedocs.yml b/.readthedocs.yml index bc44d38b4c7..b506c5f4039 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,9 +2,12 @@ version: 2 python: install: - - requirements: doc/en/requirements.txt - - method: pip - path: . + # Install pytest first, then doc/en/requirements.txt. + # This order is important to honor any pins in doc/en/requirements.txt + # when the pinned library is also a dependency of pytest. + - method: pip + path: . + - requirements: doc/en/requirements.txt build: os: ubuntu-20.04 diff --git a/AUTHORS b/AUTHORS index c57502ac22b..697cca65be5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,6 +15,7 @@ Alan Velasco Alexander Johnson Alexander King Alexei Kozlenok +Alice Purcell Allan Feldman Aly Sivji Amir Elkess @@ -44,6 +45,7 @@ Aron Coyle Aron Curzon Aviral Verma Aviv Palivoda +Babak Keyvani Barney Gale Ben Gartner Ben Webb @@ -62,9 +64,11 @@ Ceridwen Charles Cloud Charles Machalow Charnjit SiNGH (CCSJ) +Cheuk Ting Ho Chris Lamb Chris NeJame Chris Rose +Chris Wheeler Christian Boelsen Christian Fetzer Christian Neumüller @@ -83,6 +87,8 @@ Damian Skrzypczak Daniel Grana Daniel Hahler Daniel Nuri +Daniel Sánchez Castelló +Daniel Valenzuela Zenteno Daniel Wandschneider Daniele Procida Danielle Jenkins @@ -124,6 +130,7 @@ Feng Ma Florian Bruhin Florian Dahlitz Floris Bruynooghe +Gabriel Landau Gabriel Reis Garvit Shubham Gene Wood @@ -149,6 +156,7 @@ Ian Bicking Ian Lesperance Ilya Konstantinov Ionuț Turturică +Itxaso Aizpurua Iwan Briquemont Jaap Broekhuizen Jakob van Santen @@ -163,7 +171,9 @@ Jeff Rackauckas Jeff Widman Jenni Rinker John Eddie Ayson +John Litborn John Towler +Jon Parise Jon Sonesen Jonas Obrist Jordan Guymon @@ -183,9 +193,14 @@ Katarzyna Jachim Katarzyna Król Katerina Koukiou Keri Volans +Kevin C Kevin Cox +Kevin Hierro Carrasco Kevin J. Foley +Kian Eliasi +Kian-Meng Ang Kodi B. Arfer +Kojo Idrissa Kostis Anagnostopoulos Kristoffer Nordström Kyle Altendorf @@ -243,6 +258,7 @@ Nicholas Murphy Niclas Olofsson Nicolas Delaby Nikolay Kondratyev +Nipunn Koorapati Olga Matoula Oleg Pidsadnyi Oleg Sushchenko @@ -253,6 +269,8 @@ Ondřej Súkup Oscar Benjamin Parth Patel Patrick Hayes +Paul Müller +Paul Reece Pauli Virtanen Pavel Karateev Paweł Adamczak @@ -268,6 +286,7 @@ Prashant Sharma Pulkit Goyal Punyashloka Biswal Quentin Pradet +q0w Ralf Schmitt Ram Rachum Ralph Giles @@ -287,17 +306,20 @@ Ruaridh Williamson Russel Winder Ryan Wooden Saiprasad Kale +Samuel Colvin Samuel Dion-Girardeau Samuel Searles-Bryant Samuele Pedroni Sanket Duthade Sankt Petersbug +Saravanan Padmanaban Segev Finer Serhii Mozghovyi Seth Junot Shantanu Jain Shubham Adep Simon Gomizelj +Simon Holesch Simon Kerr Skylar Downes Srinivas Reddy Thatiparthy @@ -315,26 +337,32 @@ Taneli Hukkinen Tanvi Mehta Tarcisio Fischer Tareq Alayan +Tatiana Ovary Ted Xiao Terje Runde Thomas Grainger Thomas Hisch Tim Hoffmann Tim Strazny +TJ Bruno +Tobias Diez Tom Dalton Tom Viner Tomáš Gavenčiak Tomer Keren +Tony Narlock Tor Colvin Trevor Bekolay Tyler Goodlet Tzu-ping Chung Vasily Kuznetsov Victor Maryama +Victor Rodriguez Victor Uriarte Vidar T. Fauske Virgil Dupras Vitaly Lashmanov +Vivaan Verma Vlad Dragos Vlad Radziuk Vladyslav Rachek @@ -348,7 +376,12 @@ Xixi Zhao Xuan Luong Xuecong Liao Yoav Caspi +Yuliang Shao +Yusuke Kadowaki +Yuval Shimon Zac Hatfield-Dodds Zachary Kneupper +Zachary OBrien +Zhouxin Qiu Zoltán Máté Zsolt Cserna diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 24bca723c8b..791f988306f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -50,6 +50,8 @@ Fix bugs -------- Look through the `GitHub issues for bugs `_. +See also the `"status: easy" issues `_ +that are friendly to new contributors. :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. @@ -221,7 +223,7 @@ changes you want to review and merge. Pull requests are stored on Once you send a pull request, we can discuss its potential modifications and even add more commits to it later on. There's an excellent tutorial on how Pull Requests work in the -`GitHub Help Center `_. +`GitHub Help Center `_. Here is a simple overview, with pytest-specific bits: @@ -242,6 +244,11 @@ Here is a simple overview, with pytest-specific bits: be released in micro releases whereas features will be released in minor releases and incompatible changes in major releases. + You will need the tags to test locally, so be sure you have the tags from the main repository. If you suspect you don't, set the main repository as upstream and fetch the tags:: + + $ git remote add upstream https://github.com/pytest-dev/pytest + $ git fetch upstream --tags + If you need some help with Git, follow this quick start guide: https://git.wiki.kernel.org/index.php/QuickStart @@ -378,7 +385,7 @@ them. Backporting bug fixes for the next patch release ------------------------------------------------ -Pytest makes feature release every few weeks or months. In between, patch releases +Pytest makes a feature release every few weeks or months. In between, patch releases are made to the previous feature release, containing bug fixes only. The bug fixes usually fix regressions, but may be any change that should reach users before the next feature release. @@ -387,10 +394,17 @@ Suppose for example that the latest release was 1.2.3, and you want to include a bug fix in 1.2.4 (check https://github.com/pytest-dev/pytest/releases for the actual latest release). The procedure for this is: -#. First, make sure the bug is fixed the ``main`` branch, with a regular pull +#. First, make sure the bug is fixed in the ``main`` branch, with a regular pull request, as described above. An exception to this is if the bug fix is not applicable to ``main`` anymore. +Automatic method: + +Add a ``backport 1.2.x`` label to the PR you want to backport. This will create +a backport PR against the ``1.2.x`` branch. + +Manual method: + #. ``git checkout origin/1.2.x -b backport-XXXX`` # use the main PR number here #. Locate the merge commit on the PR, in the *merged* message, for example: diff --git a/README.rst b/README.rst index 14733765173..034034a40b8 100644 --- a/README.rst +++ b/README.rst @@ -20,8 +20,8 @@ :target: https://codecov.io/gh/pytest-dev/pytest :alt: Code coverage Status -.. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg - :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain +.. image:: https://github.com/pytest-dev/pytest/workflows/test/badge.svg + :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Atest .. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/main.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/main @@ -100,7 +100,7 @@ Features - Can run `unittest `_ (or trial), `nose `_ test suites out of the box -- Python 3.6+ and PyPy3 +- Python 3.7+ or PyPy3 - Rich plugin architecture, with over 850+ `external plugins `_ and thriving community diff --git a/RELEASING.rst b/RELEASING.rst index 25ce90d0f65..b018dc48932 100644 --- a/RELEASING.rst +++ b/RELEASING.rst @@ -37,7 +37,7 @@ breaking changes or new features. For a new minor release, first create a new maintenance branch from ``main``:: - git fetch --all + git fetch upstream git branch 7.1.x upstream/main git push upstream 7.1.x @@ -63,7 +63,7 @@ Major releases 1. Create a new maintenance branch from ``main``:: - git fetch --all + git fetch upstream git branch 8.0.x upstream/main git push upstream 8.0.x @@ -136,29 +136,31 @@ Both automatic and manual processes described above follow the same steps from t #. After all tests pass and the PR has been approved, tag the release commit in the ``release-MAJOR.MINOR.PATCH`` branch and push it. This will publish to PyPI:: - git fetch --all + git fetch upstream git tag MAJOR.MINOR.PATCH upstream/release-MAJOR.MINOR.PATCH - git push git@github.com:pytest-dev/pytest.git MAJOR.MINOR.PATCH + git push upstream MAJOR.MINOR.PATCH Wait for the deploy to complete, then make sure it is `available on PyPI `_. -#. Merge the PR. +#. Merge the PR. **Make sure it's not squash-merged**, so that the tagged commit ends up in the main branch. #. Cherry-pick the CHANGELOG / announce files to the ``main`` branch:: - git fetch --all --prune + git fetch upstream git checkout upstream/main -b cherry-pick-release git cherry-pick -x -m1 upstream/MAJOR.MINOR.x #. Open a PR for ``cherry-pick-release`` and merge it once CI passes. No need to wait for approvals if there were no conflicts on the previous step. -#. For major and minor releases, tag the release cherry-pick merge commit in main with +#. For major and minor releases (or the first prerelease of it), tag the release cherry-pick merge commit in main with a dev tag for the next feature release:: git checkout main git pull git tag MAJOR.{MINOR+1}.0.dev0 - git push git@github.com:pytest-dev/pytest.git MAJOR.{MINOR+1}.0.dev0 + git push upstream MAJOR.{MINOR+1}.0.dev0 + +#. For major and minor releases, change the default version in the `Read the Docs Settings `_ to the new branch. #. Send an email announcement with the contents from:: diff --git a/changelog/4320.doc.rst b/changelog/4320.doc.rst deleted file mode 100644 index 70a4a743f36..00000000000 --- a/changelog/4320.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Improved docs for `pytester.copy_example`. diff --git a/changelog/5105.doc.rst b/changelog/5105.doc.rst deleted file mode 100644 index 71c1edc9fc4..00000000000 --- a/changelog/5105.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Add automatically generated :ref:`plugin-list`. The list is updated on a periodic schedule. diff --git a/changelog/5196.feature.rst b/changelog/5196.feature.rst deleted file mode 100644 index 5e6312b482d..00000000000 --- a/changelog/5196.feature.rst +++ /dev/null @@ -1,3 +0,0 @@ -Tests are now ordered by definition order in more cases. - -In a class hierarchy, tests from base classes are now consistently ordered before tests defined on their subclasses (reverse MRO order). diff --git a/changelog/7124.bugfix.rst b/changelog/7124.bugfix.rst deleted file mode 100644 index 191925d7a24..00000000000 --- a/changelog/7124.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed an issue where ``__main__.py`` would raise an ``ImportError`` when ``--doctest-modules`` was provided. diff --git a/changelog/7132.feature.rst b/changelog/7132.feature.rst deleted file mode 100644 index 9fb735ee153..00000000000 --- a/changelog/7132.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added two environment variables :envvar:`PYTEST_THEME` and :envvar:`PYTEST_THEME_MODE` to let the users customize the pygments theme used. diff --git a/changelog/7259.breaking.rst b/changelog/7259.breaking.rst deleted file mode 100644 index 48ecbcbb730..00000000000 --- a/changelog/7259.breaking.rst +++ /dev/null @@ -1,9 +0,0 @@ -The :ref:`Node.reportinfo() ` function first return value type has been expanded from `py.path.local | str` to `os.PathLike[str] | str`. - -Most plugins which refer to `reportinfo()` only define it as part of a custom :class:`pytest.Item` implementation. -Since `py.path.local` is a `os.PathLike[str]`, these plugins are unaffacted. - -Plugins and users which call `reportinfo()`, use the first return value and interact with it as a `py.path.local`, would need to adjust by calling `py.path.local(fspath)`. -Although preferably, avoid the legacy `py.path.local` and use `pathlib.Path`, or use `item.location` or `item.path`, instead. - -Note: pytest was not able to provide a deprecation period for this change. diff --git a/changelog/7259.deprecation.rst b/changelog/7259.deprecation.rst deleted file mode 100644 index ae857c0d826..00000000000 --- a/changelog/7259.deprecation.rst +++ /dev/null @@ -1,12 +0,0 @@ -``py.path.local`` arguments for hooks have been deprecated. See :ref:`the deprecation note ` for full details. - -``py.path.local`` arguments to Node constructors have been deprecated. See :ref:`the deprecation note ` for full details. - -.. note:: - The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the - new attribute being ``path``) is **the opposite** of the situation for hooks - (the old argument being ``path``). - - This is an unfortunate artifact due to historical reasons, which should be - resolved in future versions as we slowly get rid of the :pypi:`py` - dependency (see :issue:`9283` for a longer discussion). diff --git a/changelog/7259.feature.rst b/changelog/7259.feature.rst deleted file mode 100644 index dd03b48969d..00000000000 --- a/changelog/7259.feature.rst +++ /dev/null @@ -1,7 +0,0 @@ -Added :meth:`cache.mkdir() `, which is similar to the existing :meth:`cache.makedir() `, -but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``. - -Added a ``paths`` type to :meth:`parser.addini() `, -as in ``parser.addini("mypaths", "my paths", type="paths")``, -which is similar to the existing ``pathlist``, -but returns a list of :class:`pathlib.Path` instead of legacy ``py.path.local``. diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst deleted file mode 100644 index ea8c7c0b4f9..00000000000 --- a/changelog/7469.deprecation.rst +++ /dev/null @@ -1,13 +0,0 @@ -Directly constructing the following classes is now deprecated: - -- ``_pytest.mark.structures.Mark`` -- ``_pytest.mark.structures.MarkDecorator`` -- ``_pytest.mark.structures.MarkGenerator`` -- ``_pytest.python.Metafunc`` -- ``_pytest.runner.CallInfo`` -- ``_pytest._code.ExceptionInfo`` -- ``_pytest.config.argparsing.Parser`` -- ``_pytest.config.argparsing.OptionGroup`` -- ``_pytest.pytester.HookRecorder`` - -These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8. diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst deleted file mode 100644 index 4694e97e9d9..00000000000 --- a/changelog/7469.feature.rst +++ /dev/null @@ -1,25 +0,0 @@ -The types of objects used in pytest's API are now exported so they may be used in type annotations. - -The newly-exported types are: - -- ``pytest.Config`` for :class:`Config `. -- ``pytest.Mark`` for :class:`marks `. -- ``pytest.MarkDecorator`` for :class:`mark decorators `. -- ``pytest.MarkGenerator`` for the :class:`pytest.mark ` singleton. -- ``pytest.Metafunc`` for the :class:`metafunc ` argument to the :func:`pytest_generate_tests ` hook. -- ``pytest.CallInfo`` for the :class:`CallInfo ` type passed to various hooks. -- ``pytest.PytestPluginManager`` for :class:`PytestPluginManager `. -- ``pytest.ExceptionInfo`` for the :class:`ExceptionInfo ` type returned from :func:`pytest.raises` and passed to various hooks. -- ``pytest.Parser`` for the :class:`Parser ` type passed to the :func:`pytest_addoption ` hook. -- ``pytest.OptionGroup`` for the :class:`OptionGroup ` type returned from the :func:`parser.addgroup ` method. -- ``pytest.HookRecorder`` for the :class:`HookRecorder ` type returned from :class:`~pytest.Pytester`. -- ``pytest.RecordedHookCall`` for the :class:`RecordedHookCall ` type returned from :class:`~pytest.HookRecorder`. -- ``pytest.RunResult`` for the :class:`RunResult ` type returned from :class:`~pytest.Pytester`. -- ``pytest.LineMatcher`` for the :class:`LineMatcher ` type used in :class:`~pytest.RunResult` and others. -- ``pytest.TestReport`` for the :class:`TestReport ` type used in various hooks. -- ``pytest.CollectReport`` for the :class:`CollectReport ` type used in various hooks. - -Constructing most of them directly is not supported; they are only meant for use in type annotations. -Doing so will emit a deprecation warning, and may become a hard-error in pytest 8.0. - -Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy. diff --git a/changelog/7480.improvement.rst b/changelog/7480.improvement.rst deleted file mode 100644 index 296e6e7fb54..00000000000 --- a/changelog/7480.improvement.rst +++ /dev/null @@ -1,5 +0,0 @@ -A deprecation scheduled to be removed in a major version X (e.g. pytest 7, 8, 9, ...) now uses warning category `PytestRemovedInXWarning`, -a subclass of :class:`~pytest.PytestDeprecationWarning`, -instead of :class:`PytestDeprecationWarning` directly. - -See :ref:`backwards-compatibility` for more details. diff --git a/changelog/7856.feature.rst b/changelog/7856.feature.rst deleted file mode 100644 index 22ed4c83bc3..00000000000 --- a/changelog/7856.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -:ref:`--import-mode=importlib ` now works with features that -depend on modules being on :py:data:`sys.modules`, such as :mod:`pickle` and :mod:`dataclasses`. diff --git a/changelog/7864.improvement.rst b/changelog/7864.improvement.rst deleted file mode 100644 index 195632346fe..00000000000 --- a/changelog/7864.improvement.rst +++ /dev/null @@ -1,4 +0,0 @@ -Improved error messages when parsing warning filters. - -Previously pytest would show an internal traceback, which besides being ugly sometimes would hide the cause -of the problem (for example an ``ImportError`` while importing a specific warning type). diff --git a/changelog/8061.bugfix.rst b/changelog/8061.bugfix.rst deleted file mode 100644 index 5686af66321..00000000000 --- a/changelog/8061.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed failing ``staticmethod`` test cases if they are inherited from a parent test class. diff --git a/changelog/8133.trivial.rst b/changelog/8133.trivial.rst deleted file mode 100644 index 7ceaae59765..00000000000 --- a/changelog/8133.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Migrate to ``setuptools_scm`` 6.x to use ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST`` for more robust release tooling. diff --git a/changelog/8144.feature.rst b/changelog/8144.feature.rst deleted file mode 100644 index fe576eeffa8..00000000000 --- a/changelog/8144.feature.rst +++ /dev/null @@ -1,16 +0,0 @@ -The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument: - -- :func:`pytest_ignore_collect <_pytest.hookspec.pytest_ignore_collect>` - The ``collection_path`` parameter (equivalent to existing ``path`` parameter). -- :func:`pytest_collect_file <_pytest.hookspec.pytest_collect_file>` - The ``file_path`` parameter (equivalent to existing ``path`` parameter). -- :func:`pytest_pycollect_makemodule <_pytest.hookspec.pytest_pycollect_makemodule>` - The ``module_path`` parameter (equivalent to existing ``path`` parameter). -- :func:`pytest_report_header <_pytest.hookspec.pytest_report_header>` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter). -- :func:`pytest_report_collectionfinish <_pytest.hookspec.pytest_report_collectionfinish>` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter). - -.. note:: - The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the - new attribute being ``path``) is **the opposite** of the situation for hooks - (the old argument being ``path``). - - This is an unfortunate artifact due to historical reasons, which should be - resolved in future versions as we slowly get rid of the :pypi:`py` - dependency (see :issue:`9283` for a longer discussion). diff --git a/changelog/8174.trivial.rst b/changelog/8174.trivial.rst deleted file mode 100644 index 7649764618f..00000000000 --- a/changelog/8174.trivial.rst +++ /dev/null @@ -1,6 +0,0 @@ -The following changes have been made to internal pytest types/functions: - -- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``. -- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``. -- The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``. -- The ``_pytest.python.path_matches_patterns()`` function takes ``Path`` instead of ``py.path.local``. diff --git a/changelog/8192.bugfix.rst b/changelog/8192.bugfix.rst deleted file mode 100644 index 8920b200a21..00000000000 --- a/changelog/8192.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -``testdir.makefile`` now silently accepts values which don't start with ``.`` to maintain backward compatibility with older pytest versions. - -``pytester.makefile`` now issues a clearer error if the ``.`` is missing in the ``ext`` argument. diff --git a/changelog/8242.deprecation.rst b/changelog/8242.deprecation.rst deleted file mode 100644 index 3875c5867e3..00000000000 --- a/changelog/8242.deprecation.rst +++ /dev/null @@ -1,7 +0,0 @@ -Raising :class:`unittest.SkipTest` to skip collection of tests during the -pytest collection phase is deprecated. Use :func:`pytest.skip` instead. - -Note: This deprecation only relates to using :class:`unittest.SkipTest` during test -collection. You are probably not doing that. Ordinary usage of -:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` / -:func:`unittest.skip` in unittest test cases is fully supported. diff --git a/changelog/8246.breaking.rst b/changelog/8246.breaking.rst deleted file mode 100644 index ba9e5c8beea..00000000000 --- a/changelog/8246.breaking.rst +++ /dev/null @@ -1 +0,0 @@ -``--version`` now writes version information to ``stdout`` rather than ``stderr``. diff --git a/changelog/8248.trivial.rst b/changelog/8248.trivial.rst deleted file mode 100644 index 3044071fa81..00000000000 --- a/changelog/8248.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Internal Restructure: let ``python.PyObjMixin`` inherit from ``nodes.Node`` to carry over typing information. diff --git a/changelog/8251.feature.rst b/changelog/8251.feature.rst deleted file mode 100644 index fe157cc98b7..00000000000 --- a/changelog/8251.feature.rst +++ /dev/null @@ -1,11 +0,0 @@ -Implement ``Node.path`` as a ``pathlib.Path``. Both the old ``fspath`` and this new attribute gets set no matter whether ``path`` or ``fspath`` (deprecated) is passed to the constructor. It is a replacement for the ``fspath`` attribute (which represents the same path as ``py.path.local``). While ``fspath`` is not deprecated yet -due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo`, we expect to deprecate it in a future release. - -.. note:: - The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the - new attribute being ``path``) is **the opposite** of the situation for hooks - (the old argument being ``path``). - - This is an unfortunate artifact due to historical reasons, which should be - resolved in future versions as we slowly get rid of the :pypi:`py` - dependency (see :issue:`9283` for a longer discussion). diff --git a/changelog/8258.bugfix.rst b/changelog/8258.bugfix.rst deleted file mode 100644 index 6518ec0b738..00000000000 --- a/changelog/8258.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixed issue where pytest's ``faulthandler`` support would not dump traceback on crashes -if the :mod:`faulthandler` module was already enabled during pytest startup (using -``python -X dev -m pytest`` for example). diff --git a/changelog/8315.deprecation.rst b/changelog/8315.deprecation.rst deleted file mode 100644 index b204dcedd9b..00000000000 --- a/changelog/8315.deprecation.rst +++ /dev/null @@ -1,5 +0,0 @@ -Several behaviors of :meth:`Parser.addoption ` are now -scheduled for removal in pytest 8 (deprecated since pytest 2.4.0): - -- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. -- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. diff --git a/changelog/8317.bugfix.rst b/changelog/8317.bugfix.rst deleted file mode 100644 index 7312880a11f..00000000000 --- a/changelog/8317.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed an issue where illegal directory characters derived from ``getpass.getuser()`` raised an ``OSError``. diff --git a/changelog/8335.improvement.rst b/changelog/8335.improvement.rst deleted file mode 100644 index f6c0e3343f0..00000000000 --- a/changelog/8335.improvement.rst +++ /dev/null @@ -1,10 +0,0 @@ -Improved :func:`pytest.approx` assertion messages for sequences of numbers. - -The assertion messages now dumps a table with the index and the error of each diff. -Example:: - - > assert [1, 2, 3, 4] == pytest.approx([1, 3, 3, 5]) - E assert comparison failed for 2 values: - E Index | Obtained | Expected - E 1 | 2 | 3 +- 3.0e-06 - E 3 | 4 | 5 +- 5.0e-06 diff --git a/changelog/8337.doc.rst b/changelog/8337.doc.rst deleted file mode 100644 index f2483a6b481..00000000000 --- a/changelog/8337.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Recommend `numpy.testing `__ module on :func:`pytest.approx` documentation. diff --git a/changelog/8367.bugfix.rst b/changelog/8367.bugfix.rst deleted file mode 100644 index f4b03670108..00000000000 --- a/changelog/8367.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``Class.from_parent`` so it forwards extra keyword arguments to the constructor. diff --git a/changelog/8377.bugfix.rst b/changelog/8377.bugfix.rst deleted file mode 100644 index 3df54a949af..00000000000 --- a/changelog/8377.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -The test selection options ``pytest -k`` and ``pytest -m`` now support matching -names containing forward slash (``/``) characters. diff --git a/changelog/8384.bugfix.rst b/changelog/8384.bugfix.rst deleted file mode 100644 index 3b70987490e..00000000000 --- a/changelog/8384.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -The ``@pytest.mark.skip`` decorator now correctly handles its arguments. When the ``reason`` argument is accidentally given both positional and as a keyword (e.g. because it was confused with ``skipif``), a ``TypeError`` now occurs. Before, such tests were silently skipped, and the positional argument ignored. Additionally, ``reason`` is now documented correctly as positional or keyword (rather than keyword-only). diff --git a/changelog/8394.bugfix.rst b/changelog/8394.bugfix.rst deleted file mode 100644 index a0fb5bb71fd..00000000000 --- a/changelog/8394.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Use private names for internal fixtures that handle classic setup/teardown so that they don't show up with the default ``--fixtures`` invocation (but they still show up with ``--fixtures -v``). diff --git a/changelog/8403.improvement.rst b/changelog/8403.improvement.rst deleted file mode 100644 index ec392245f67..00000000000 --- a/changelog/8403.improvement.rst +++ /dev/null @@ -1,5 +0,0 @@ -By default, pytest will truncate long strings in assert errors so they don't clutter the output too much, -currently at ``240`` characters by default. - -However, in some cases the longer output helps, or is even crucial, to diagnose a failure. Using ``-v`` will -now increase the truncation threshold to ``2400`` characters, and ``-vv`` or higher will disable truncation entirely. diff --git a/changelog/8421.feature.rst b/changelog/8421.feature.rst deleted file mode 100644 index c729ca3950a..00000000000 --- a/changelog/8421.feature.rst +++ /dev/null @@ -1 +0,0 @@ -:func:`pytest.approx` now works on :class:`~decimal.Decimal` within mappings/dicts and sequences/lists. diff --git a/changelog/8432.trivial.rst b/changelog/8432.trivial.rst deleted file mode 100644 index af4c7a22617..00000000000 --- a/changelog/8432.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Improve error message when :func:`pytest.skip` is used at module level without passing `allow_module_level=True`. diff --git a/changelog/8447.deprecation.rst b/changelog/8447.deprecation.rst deleted file mode 100644 index 8386e3b7480..00000000000 --- a/changelog/8447.deprecation.rst +++ /dev/null @@ -1,4 +0,0 @@ -Defining a custom pytest node type which is both an :class:`pytest.Item ` and a :class:`pytest.Collector ` (e.g. :class:`pytest.File `) now issues a warning. -It was never sanely supported and triggers hard to debug errors. - -See :ref:`the deprecation note ` for full details. diff --git a/changelog/8456.bugfix.rst b/changelog/8456.bugfix.rst deleted file mode 100644 index da9370b7b1b..00000000000 --- a/changelog/8456.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -The :confval:`required_plugins` config option now works correctly when pre-releases of plugins are installed, rather than falsely claiming that those plugins aren't installed at all. diff --git a/changelog/8464.bugfix.rst b/changelog/8464.bugfix.rst deleted file mode 100644 index 017c4c078fb..00000000000 --- a/changelog/8464.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``-c `` now also properly defines ``rootdir`` as the directory that contains ````. diff --git a/changelog/8503.bugfix.rst b/changelog/8503.bugfix.rst deleted file mode 100644 index 26f660bbf3f..00000000000 --- a/changelog/8503.bugfix.rst +++ /dev/null @@ -1,4 +0,0 @@ -:meth:`pytest.MonkeyPatch.syspath_prepend` no longer fails when -``setuptools`` is not installed. -It now only calls :func:`pkg_resources.fixup_namespace_packages` if -``pkg_resources`` was previously imported, because it is not needed otherwise. diff --git a/changelog/8509.improvement.rst b/changelog/8509.improvement.rst deleted file mode 100644 index 982888c1fea..00000000000 --- a/changelog/8509.improvement.rst +++ /dev/null @@ -1,5 +0,0 @@ -Fixed issue where :meth:`unittest.TestCase.setUpClass` is not called when a test has `/` in its name since pytest 6.2.0. - -This refers to the path part in pytest node IDs, e.g. ``TestClass::test_it`` in the node ID ``tests/test_file.py::TestClass::test_it``. - -Now, instead of assuming that the test name does not contain ``/``, it is assumed that test path does not contain ``::``. We plan to hopefully make both of these work in the future. diff --git a/changelog/8548.bugfix.rst b/changelog/8548.bugfix.rst deleted file mode 100644 index 9201169fc0b..00000000000 --- a/changelog/8548.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Introduce fix to handle precision width in ``log-cli-format`` in turn to fix output coloring for certain formats. diff --git a/changelog/8592.deprecation.rst b/changelog/8592.deprecation.rst deleted file mode 100644 index 733ad3586e5..00000000000 --- a/changelog/8592.deprecation.rst +++ /dev/null @@ -1,3 +0,0 @@ -:func:`pytest_cmdline_preparse <_pytest.hookspec.pytest_cmdline_preparse>` has been officially deprecated. It will be removed in a future release. Use :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load_initial_conftests>` instead. - -See :ref:`the deprecation note ` for full details. diff --git a/changelog/8606.feature.rst b/changelog/8606.feature.rst deleted file mode 100644 index d918ecd1ec1..00000000000 --- a/changelog/8606.feature.rst +++ /dev/null @@ -1,5 +0,0 @@ -pytest invocations with ``--fixtures-per-test`` and ``--fixtures`` have been enriched with: - -- Fixture location path printed with the fixture name. -- First section of the fixture's docstring printed under the fixture name. -- Whole of fixture's docstring printed under the fixture name using ``--verbose`` option. diff --git a/changelog/8645.deprecation.rst b/changelog/8645.deprecation.rst deleted file mode 100644 index 722a5d764de..00000000000 --- a/changelog/8645.deprecation.rst +++ /dev/null @@ -1,4 +0,0 @@ -:func:`pytest.warns(None) ` is now deprecated because many people used -it to mean "this code does not emit warnings", but it actually had the effect of -checking that the code emits at least one warning of any type - like ``pytest.warns()`` -or ``pytest.warns(Warning)``. diff --git a/changelog/8655.doc.rst b/changelog/8655.doc.rst deleted file mode 100644 index 65051a74319..00000000000 --- a/changelog/8655.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Help text for ``--pdbcls`` more accurately reflects the option's behavior. diff --git a/changelog/8733.breaking.rst b/changelog/8733.breaking.rst deleted file mode 100644 index fa2a43ac655..00000000000 --- a/changelog/8733.breaking.rst +++ /dev/null @@ -1,5 +0,0 @@ -Drop a workaround for `pyreadline `__ that made it work with ``--pdb``. - -The workaround was introduced in `#1281 `__ in 2015, however since then -`pyreadline seems to have gone unmaintained `__, is `generating -warnings `__, and will stop working on Python 3.10. diff --git a/changelog/8761.feature.rst b/changelog/8761.feature.rst deleted file mode 100644 index 88288c81002..00000000000 --- a/changelog/8761.feature.rst +++ /dev/null @@ -1 +0,0 @@ -New :ref:`version-tuple` attribute, which makes it simpler for users to do something depending on the pytest version (such as declaring hooks which are introduced in later versions). diff --git a/changelog/8789.feature.rst b/changelog/8789.feature.rst deleted file mode 100644 index 23215c97ef2..00000000000 --- a/changelog/8789.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Switch TOML parser from ``toml`` to ``tomli`` for TOML v1.0.0 support in ``pyproject.toml``. diff --git a/changelog/8796.bugfix.rst b/changelog/8796.bugfix.rst deleted file mode 100644 index 1e83f4e2613..00000000000 --- a/changelog/8796.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed internal error when skipping doctests. diff --git a/changelog/8803.improvement.rst b/changelog/8803.improvement.rst deleted file mode 100644 index 1e4db257dfc..00000000000 --- a/changelog/8803.improvement.rst +++ /dev/null @@ -1,9 +0,0 @@ -It is now possible to add colors to custom log levels on cli log. - -By using :func:`add_color_level <_pytest.logging.add_color_level>` from a ``pytest_configure`` hook, colors can be added:: - - logging_plugin = config.pluginmanager.get_plugin('logging-plugin') - logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, 'cyan') - logging_plugin.log_cli_handler.formatter.add_color_level(logging.SPAM, 'blue') - -See :ref:`log_colors` for more information. diff --git a/changelog/8818.trivial.rst b/changelog/8818.trivial.rst deleted file mode 100644 index e8c7a24c61b..00000000000 --- a/changelog/8818.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Ensure ``regendoc`` opts out of ``TOX_ENV`` cachedir selection to ensure independent example test runs. diff --git a/changelog/8822.improvement.rst b/changelog/8822.improvement.rst deleted file mode 100644 index a89bcd6baa7..00000000000 --- a/changelog/8822.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -When showing fixture paths in `--fixtures` or `--fixtures-by-test`, fixtures coming from pytest itself now display an elided path, rather than the full path to the file in the `site-packages` directory. diff --git a/changelog/8898.improvement.rst b/changelog/8898.improvement.rst deleted file mode 100644 index d725157cfc6..00000000000 --- a/changelog/8898.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Complex numbers are now treated like floats and integers when generating parameterization IDs. diff --git a/changelog/8913.trivial.rst b/changelog/8913.trivial.rst deleted file mode 100644 index 0d971c475a5..00000000000 --- a/changelog/8913.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -The private ``CallSpec2._arg2scopenum`` attribute has been removed after an internal refactoring. diff --git a/changelog/8920.feature.rst b/changelog/8920.feature.rst deleted file mode 100644 index 05bdab6da75..00000000000 --- a/changelog/8920.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added :class:`pytest.Stash`, a facility for plugins to store their data on :class:`~pytest.Config` and :class:`~_pytest.nodes.Node`\s in a type-safe and conflict-free manner. -See :ref:`plugin-stash` for details. diff --git a/changelog/8948.deprecation.rst b/changelog/8948.deprecation.rst deleted file mode 100644 index a39a92bc70e..00000000000 --- a/changelog/8948.deprecation.rst +++ /dev/null @@ -1,5 +0,0 @@ -:func:`pytest.skip(msg=...) `, :func:`pytest.fail(msg=...) ` and :func:`pytest.exit(msg=...) ` -signatures now accept a ``reason`` argument instead of ``msg``. Using ``msg`` still works, but is deprecated and will be removed in a future release. - -This was changed for consistency with :func:`pytest.mark.skip ` and :func:`pytest.mark.xfail ` which both accept -``reason`` as an argument. diff --git a/changelog/8953.feature.rst b/changelog/8953.feature.rst deleted file mode 100644 index aa60fa19ca2..00000000000 --- a/changelog/8953.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -:class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a -``warnings`` argument to assert the total number of warnings captured. diff --git a/changelog/8954.feature.rst b/changelog/8954.feature.rst deleted file mode 100644 index 7edb430075e..00000000000 --- a/changelog/8954.feature.rst +++ /dev/null @@ -1 +0,0 @@ -``--debug`` flag now accepts a :class:`str` file to route debug logs into, remains defaulted to `pytestdebug.log`. diff --git a/changelog/8967.trivial.rst b/changelog/8967.trivial.rst deleted file mode 100644 index d5f773241f5..00000000000 --- a/changelog/8967.trivial.rst +++ /dev/null @@ -1,2 +0,0 @@ -:func:`pytest_assertion_pass <_pytest.hookspec.pytest_assertion_pass>` is no longer considered experimental and -future changes to it will be considered more carefully. diff --git a/changelog/8983.bugfix.rst b/changelog/8983.bugfix.rst deleted file mode 100644 index 403d421d6bd..00000000000 --- a/changelog/8983.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -The test selection options ``pytest -k`` and ``pytest -m`` now support matching names containing backslash (`\\`) characters. -Backslashes are treated literally, not as escape characters (the values being matched against are already escaped). diff --git a/changelog/8990.bugfix.rst b/changelog/8990.bugfix.rst deleted file mode 100644 index ab63eb9a75e..00000000000 --- a/changelog/8990.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix `pytest -vv` crashing with an internal exception `AttributeError: 'str' object has no attribute 'relative_to'` in some cases. diff --git a/changelog/9023.feature.rst b/changelog/9023.feature.rst deleted file mode 100644 index 86a819a84e0..00000000000 --- a/changelog/9023.feature.rst +++ /dev/null @@ -1,4 +0,0 @@ - -Full diffs are now always shown for equality assertions of iterables when -`CI` or ``BUILD_NUMBER`` is found in the environment, even when ``-v`` isn't -used. diff --git a/changelog/9061.breaking.rst b/changelog/9061.breaking.rst deleted file mode 100644 index bf639e13214..00000000000 --- a/changelog/9061.breaking.rst +++ /dev/null @@ -1,15 +0,0 @@ -Using :func:`pytest.approx` in a boolean context now raises an error hinting at the proper usage. - -It is apparently common for users to mistakenly use ``pytest.approx`` like this: - -.. code-block:: python - - assert pytest.approx(actual, expected) - -While the correct usage is: - -.. code-block:: python - - assert actual == pytest.approx(expected) - -The new error message helps catch those mistakes. diff --git a/changelog/9062.improvement.rst b/changelog/9062.improvement.rst deleted file mode 100644 index bfc7cf00a46..00000000000 --- a/changelog/9062.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -``--stepwise-skip`` now implicitly enables ``--stepwise`` and can be used on its own. diff --git a/changelog/9077.bugfix.rst b/changelog/9077.bugfix.rst deleted file mode 100644 index fcee5d385df..00000000000 --- a/changelog/9077.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed confusing error message when ``request.fspath`` / ``request.path`` was accessed from a session-scoped fixture. diff --git a/changelog/9113.feature.rst b/changelog/9113.feature.rst deleted file mode 100644 index f16e6ea63fd..00000000000 --- a/changelog/9113.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -:class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a -``deselected`` argument to assert the total number of deselected tests. diff --git a/changelog/9114.feature.rst b/changelog/9114.feature.rst deleted file mode 100644 index 0a576c3b735..00000000000 --- a/changelog/9114.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added :confval:`pythonpath` setting that adds listed paths to :data:`sys.path` for the duration of the test session. If you currently use the pytest-pythonpath or pytest-srcpaths plugins, you should be able to replace them with built-in `pythonpath` setting. diff --git a/changelog/9131.bugfix.rst b/changelog/9131.bugfix.rst deleted file mode 100644 index 837bb596db5..00000000000 --- a/changelog/9131.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the URL used by ``--pastebin`` to use `bpa.st `__. diff --git a/changelog/9163.bugfix.rst b/changelog/9163.bugfix.rst deleted file mode 100644 index fb559d10fe8..00000000000 --- a/changelog/9163.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -The end line number and end column offset are now properly set for rewritten assert statements. diff --git a/changelog/9169.bugfix.rst b/changelog/9169.bugfix.rst deleted file mode 100644 index 83fce0a3893..00000000000 --- a/changelog/9169.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Support for the ``files`` API from ``importlib.resources`` within rewritten files. diff --git a/changelog/9202.trivial.rst b/changelog/9202.trivial.rst deleted file mode 100644 index 916d75074b9..00000000000 --- a/changelog/9202.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Add github action to upload coverage report to codecov instead of bash uploader. diff --git a/changelog/9205.improvement.rst b/changelog/9205.improvement.rst deleted file mode 100644 index edfc3008965..00000000000 --- a/changelog/9205.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -:meth:`pytest.Cache.set` now preserves key order when saving dicts. diff --git a/changelog/9210.doc.rst b/changelog/9210.doc.rst deleted file mode 100644 index 02a850c08c4..00000000000 --- a/changelog/9210.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Remove incorrect docs about ``confcutdir`` being a configuration option: it can only be set through the ``--confcutdir`` command-line option. diff --git a/changelog/9225.trivial.rst b/changelog/9225.trivial.rst deleted file mode 100644 index 979f7fd0ce3..00000000000 --- a/changelog/9225.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Changed the command used to create sdist and wheel artifacts: using the build package instead of setup.py. diff --git a/changelog/9242.doc.rst b/changelog/9242.doc.rst deleted file mode 100644 index ef2afc744bf..00000000000 --- a/changelog/9242.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Upgrade readthedocs configuration to use a `newer Ubuntu version `__` with better unicode support for PDF docs. diff --git a/changelog/9272.bugfix.rst b/changelog/9272.bugfix.rst deleted file mode 100644 index 0f242d40891..00000000000 --- a/changelog/9272.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -The nose compatibility module-level fixtures `setup()` and `teardown()` are now only called once per module, instead of for each test function. -They are now called even if object-level `setup`/`teardown` is defined. diff --git a/changelog/9277.breaking.rst b/changelog/9277.breaking.rst deleted file mode 100644 index 0296dcd7c99..00000000000 --- a/changelog/9277.breaking.rst +++ /dev/null @@ -1,3 +0,0 @@ -The ``pytest.Instance`` collector type has been removed. -Importing ``pytest.Instance`` or ``_pytest.python.Instance`` returns a dummy type and emits a deprecation warning. -See :ref:`instance-collector-deprecation` for details. diff --git a/changelog/9308.breaking.rst b/changelog/9308.breaking.rst deleted file mode 100644 index b03a854aa2f..00000000000 --- a/changelog/9308.breaking.rst +++ /dev/null @@ -1,22 +0,0 @@ -**PytestRemovedIn7Warning deprecation warnings are now errors by default.** - -Following our plan to remove deprecated features with as little disruption as -possible, all warnings of type ``PytestRemovedIn7Warning`` now generate errors -instead of warning messages by default. - -**The affected features will be effectively removed in pytest 7.1**, so please consult the -:ref:`deprecations` section in the docs for directions on how to update existing code. - -In the pytest ``7.0.X`` series, it is possible to change the errors back into warnings as a -stopgap measure by adding this to your ``pytest.ini`` file: - -.. code-block:: ini - - [pytest] - filterwarnings = - ignore::pytest.PytestRemovedIn7Warning - -But this will stop working when pytest ``7.1`` is released. - -**If you have concerns** about the removal of a specific feature, please add a -comment to :issue:`9308`. diff --git a/changelog/9341.doc.rst b/changelog/9341.doc.rst deleted file mode 100644 index 1b97a581247..00000000000 --- a/changelog/9341.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Various methods commonly used for :ref:`non-python tests` are now correctly documented in the reference docs. They were undocumented previously. diff --git a/changelog/9351.trivial.rst b/changelog/9351.trivial.rst deleted file mode 100644 index 4fd8ac82704..00000000000 --- a/changelog/9351.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Correct minor typos in doc/en/example/special.rst. diff --git a/doc/en/_templates/globaltoc.html b/doc/en/_templates/globaltoc.html index 7c595e7ebf2..09d970b64ed 100644 --- a/doc/en/_templates/globaltoc.html +++ b/doc/en/_templates/globaltoc.html @@ -17,7 +17,6 @@

About the project

  • Changelog
  • Contributing
  • Backwards Compatibility
  • -
  • Python 2.7 and 3.4 Support
  • Sponsor
  • pytest for Enterprise
  • License
  • @@ -30,5 +29,3 @@

    About the project

    {%- endif %}
    -Index -
    diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index f0c84cb4c47..fa57ca5c8af 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,16 @@ Release announcements :maxdepth: 2 + release-7.2.2 + release-7.2.1 + release-7.2.0 + release-7.1.3 + release-7.1.2 + release-7.1.1 + release-7.1.0 + release-7.0.1 + release-7.0.0 + release-7.0.0rc1 release-6.2.5 release-6.2.4 release-6.2.3 diff --git a/doc/en/announce/release-2.5.0.rst b/doc/en/announce/release-2.5.0.rst index bc83fdc122c..c6cdcdd8a83 100644 --- a/doc/en/announce/release-2.5.0.rst +++ b/doc/en/announce/release-2.5.0.rst @@ -11,7 +11,7 @@ clear information about the circumstances and a simple example which reproduces the problem. The issue tracker is of course not empty now. We have many remaining -"enhacement" issues which we'll hopefully can tackle in 2014 with your +"enhancement" issues which we'll hopefully can tackle in 2014 with your help. For those who use older Python versions, please note that pytest is not diff --git a/doc/en/announce/release-7.0.0.rst b/doc/en/announce/release-7.0.0.rst new file mode 100644 index 00000000000..3ce4335564f --- /dev/null +++ b/doc/en/announce/release-7.0.0.rst @@ -0,0 +1,74 @@ +pytest-7.0.0 +======================================= + +The pytest team is proud to announce the 7.0.0 release! + +This release contains new features, improvements, bug fixes, and breaking changes, so users +are encouraged to take a look at the CHANGELOG carefully: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all of the contributors to this release: + +* Adam J. Stewart +* Alexander King +* Amin Alaee +* Andrew Neitsch +* Anthony Sottile +* Ben Davies +* Bernát Gábor +* Brian Okken +* Bruno Oliveira +* Cristian Vera +* Dan Alvizu +* David Szotten +* Eddie +* Emmanuel Arias +* Emmanuel Meric de Bellefon +* Eric Liu +* Florian Bruhin +* GergelyKalmar +* Graeme Smecher +* Harshna +* Hugo van Kemenade +* Jakub Kulík +* James Myatt +* Jeff Rasley +* Kale Kundert +* Kian Meng, Ang +* Miro Hrončok +* Naveen-Pratap +* Oleg Höfling +* Olga Matoula +* Ran Benita +* Ronny Pfannschmidt +* Simon K +* Srip +* Sören Wegener +* Taneli Hukkinen +* Terje Runde +* Thomas Grainger +* Thomas Hisch +* William Jamir Silva +* Yuval Shimon +* Zac Hatfield-Dodds +* andrewdotn +* denivyruck +* ericluoliu +* oleg.hoefling +* symonk +* ziebam +* Éloi Rivard +* Éric + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.0.0rc1.rst b/doc/en/announce/release-7.0.0rc1.rst new file mode 100644 index 00000000000..a5bf0ed3c44 --- /dev/null +++ b/doc/en/announce/release-7.0.0rc1.rst @@ -0,0 +1,74 @@ +pytest-7.0.0rc1 +======================================= + +The pytest team is proud to announce the 7.0.0rc1 prerelease! + +This is a prerelease, not intended for production use, but to test the upcoming features and improvements +in order to catch any major problems before the final version is released to the major public. + +We appreciate your help testing this out before the final release, making sure to report any +regressions to our issue tracker: + +https://github.com/pytest-dev/pytest/issues + +When doing so, please include the string ``[prerelease]`` in the title. + +You can upgrade from PyPI via: + + pip install pytest==7.0.0rc1 + +Users are encouraged to take a look at the CHANGELOG carefully: + + https://docs.pytest.org/en/7.0.x/changelog.html + +Thanks to all the contributors to this release: + +* Adam J. Stewart +* Alexander King +* Amin Alaee +* Andrew Neitsch +* Anthony Sottile +* Ben Davies +* Bernát Gábor +* Brian Okken +* Bruno Oliveira +* Cristian Vera +* David Szotten +* Eddie +* Emmanuel Arias +* Emmanuel Meric de Bellefon +* Eric Liu +* Florian Bruhin +* GergelyKalmar +* Graeme Smecher +* Harshna +* Hugo van Kemenade +* Jakub Kulík +* James Myatt +* Jeff Rasley +* Kale Kundert +* Miro Hrončok +* Naveen-Pratap +* Oleg Höfling +* Ran Benita +* Ronny Pfannschmidt +* Simon K +* Srip +* Sören Wegener +* Taneli Hukkinen +* Terje Runde +* Thomas Grainger +* Thomas Hisch +* William Jamir Silva +* Zac Hatfield-Dodds +* andrewdotn +* denivyruck +* ericluoliu +* oleg.hoefling +* symonk +* ziebam +* Éloi Rivard + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.0.1.rst b/doc/en/announce/release-7.0.1.rst new file mode 100644 index 00000000000..5accfbad0d4 --- /dev/null +++ b/doc/en/announce/release-7.0.1.rst @@ -0,0 +1,20 @@ +pytest-7.0.1 +======================================= + +pytest 7.0.1 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/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.1.0.rst b/doc/en/announce/release-7.1.0.rst new file mode 100644 index 00000000000..3361e1c8a32 --- /dev/null +++ b/doc/en/announce/release-7.1.0.rst @@ -0,0 +1,48 @@ +pytest-7.1.0 +======================================= + +The pytest team is proud to announce the 7.1.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in the changelog: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all of the contributors to this release: + +* Akuli +* Andrew Svetlov +* Anthony Sottile +* Brett Holman +* Bruno Oliveira +* Chris NeJame +* Dan Alvizu +* Elijah DeLee +* Emmanuel Arias +* Fabian Egli +* Florian Bruhin +* Gabor Szabo +* Hasan Ramezani +* Hugo van Kemenade +* Kian Meng, Ang +* Kojo Idrissa +* Masaru Tsuchiyama +* Olga Matoula +* P. L. Lim +* Ran Benita +* Tobias Deiminger +* Yuval Shimon +* eduardo naufel schettino +* Éric + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.1.1.rst b/doc/en/announce/release-7.1.1.rst new file mode 100644 index 00000000000..d271c4557a2 --- /dev/null +++ b/doc/en/announce/release-7.1.1.rst @@ -0,0 +1,18 @@ +pytest-7.1.1 +======================================= + +pytest 7.1.1 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/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.1.2.rst b/doc/en/announce/release-7.1.2.rst new file mode 100644 index 00000000000..ba33cdc694b --- /dev/null +++ b/doc/en/announce/release-7.1.2.rst @@ -0,0 +1,23 @@ +pytest-7.1.2 +======================================= + +pytest 7.1.2 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/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* Hugo van Kemenade +* Kian Eliasi +* Ran Benita +* Zac Hatfield-Dodds + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.1.3.rst b/doc/en/announce/release-7.1.3.rst new file mode 100644 index 00000000000..4cb1b271704 --- /dev/null +++ b/doc/en/announce/release-7.1.3.rst @@ -0,0 +1,28 @@ +pytest-7.1.3 +======================================= + +pytest 7.1.3 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/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* Gergely Kalmár +* Nipunn Koorapati +* Pax +* Sviatoslav Sydorenko +* Tim Hoffmann +* Tony Narlock +* Wolfremium +* Zach OBrien +* aizpurua23a + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.2.0.rst b/doc/en/announce/release-7.2.0.rst new file mode 100644 index 00000000000..eca84aeb669 --- /dev/null +++ b/doc/en/announce/release-7.2.0.rst @@ -0,0 +1,93 @@ +pytest-7.2.0 +======================================= + +The pytest team is proud to announce the 7.2.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in the changelog: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all of the contributors to this release: + +* Aaron Berdy +* Adam Turner +* Albert Villanova del Moral +* Alice Purcell +* Anthony Sottile +* Anton Yakutovich +* Babak Keyvani +* Brandon Chinn +* Bruno Oliveira +* Chanvin Xiao +* Cheuk Ting Ho +* Chris Wheeler +* EmptyRabbit +* Ezio Melotti +* Florian Best +* Florian Bruhin +* Fredrik Berndtsson +* Gabriel Landau +* Gergely Kalmár +* Hugo van Kemenade +* James Gerity +* John Litborn +* Jon Parise +* Kevin C +* Kian Eliasi +* MatthewFlamm +* Miro Hrončok +* Nate Meyvis +* Neil Girdhar +* Nhieuvu1802 +* Nipunn Koorapati +* Ofek Lev +* Paul Müller +* Paul Reece +* Pax +* Pete Baughman +* Peyman Salehi +* Philipp A +* Ran Benita +* Robert O'Shea +* Ronny Pfannschmidt +* Rowin +* Ruth Comer +* Samuel Colvin +* Samuel Gaist +* Sandro Tosi +* Shantanu +* Simon K +* Stephen Rosen +* Sviatoslav Sydorenko +* Tatiana Ovary +* Thierry Moisan +* Thomas Grainger +* Tim Hoffmann +* Tobias Diez +* Tony Narlock +* Vivaan Verma +* Wolfremium +* Zac Hatfield-Dodds +* Zach OBrien +* aizpurua23a +* gresm +* holesch +* itxasos23 +* johnkangw +* skhomuti +* sommersoft +* wodny +* zx.qiu + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.2.1.rst b/doc/en/announce/release-7.2.1.rst new file mode 100644 index 00000000000..80ac7aff07f --- /dev/null +++ b/doc/en/announce/release-7.2.1.rst @@ -0,0 +1,25 @@ +pytest-7.2.1 +======================================= + +pytest 7.2.1 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/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Valenzuela +* Kadino +* Prerak Patel +* Ronny Pfannschmidt +* Santiago Castro +* s-padmanaban + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.2.2.rst b/doc/en/announce/release-7.2.2.rst new file mode 100644 index 00000000000..b34a6ff5c1e --- /dev/null +++ b/doc/en/announce/release-7.2.2.rst @@ -0,0 +1,25 @@ +pytest-7.2.2 +======================================= + +pytest 7.2.2 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/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Bruno Oliveira +* Garvit Shubham +* Mahesh Vashishtha +* Ramsey +* Ronny Pfannschmidt +* Teejay +* q0w +* vin01 + + +Happy testing, +The pytest Development Team diff --git a/doc/en/backwards-compatibility.rst b/doc/en/backwards-compatibility.rst index 3a0ff126164..64bcbf5bd49 100644 --- a/doc/en/backwards-compatibility.rst +++ b/doc/en/backwards-compatibility.rst @@ -77,3 +77,18 @@ Deprecation Roadmap Features currently deprecated and removed in previous releases can be found in :ref:`deprecations`. We track future deprecation and removal of features using milestones and the `deprecation `_ and `removal `_ labels on GitHub. + + +Python version support +====================== + +Released pytest versions support all Python versions that are actively maintained at the time of the release: + +============== =================== +pytest version min. Python version +============== =================== +7.1+ 3.7+ +6.2 - 7.0 3.6+ +5.0 - 6.1 3.5+ +3.3 - 4.6 2.7, 3.4+ +============== =================== diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index e22a874b4d5..453be4ad320 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -18,11 +18,11 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a $ pytest --fixtures -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collected 0 items - cache -- ../../../..$PYTHON_SITE/_pytest/cacheprovider.py:520 + cache -- .../_pytest/cacheprovider.py:509 Return a cache object that can persist state between testing sessions. cache.get(key, default) @@ -33,39 +33,93 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a Values can be any object handled by the json stdlib module. - capsys -- ../../../..$PYTHON_SITE/_pytest/capture.py:903 + capsys -- .../_pytest/capture.py:905 Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsys.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. - capsysbinary -- ../../../..$PYTHON_SITE/_pytest/capture.py:920 + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_output(capsys): + print("hello") + captured = capsys.readouterr() + assert captured.out == "hello\n" + + capsysbinary -- .../_pytest/capture.py:933 Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``bytes`` objects. - capfd -- ../../../..$PYTHON_SITE/_pytest/capture.py:937 + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_output(capsysbinary): + print("hello") + captured = capsysbinary.readouterr() + assert captured.out == b"hello\n" + + capfd -- .../_pytest/capture.py:961 Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. - capfdbinary -- ../../../..$PYTHON_SITE/_pytest/capture.py:954 + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfd): + os.system('echo "hello"') + captured = capfd.readouterr() + assert captured.out == "hello\n" + + capfdbinary -- .../_pytest/capture.py:989 Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``byte`` objects. - doctest_namespace [session scope] -- ../../../..$PYTHON_SITE/_pytest/doctest.py:728 + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfdbinary): + os.system('echo "hello"') + captured = capfdbinary.readouterr() + assert captured.out == b"hello\n" + + doctest_namespace [session scope] -- .../_pytest/doctest.py:738 Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. - pytestconfig [session scope] -- ../../../..$PYTHON_SITE/_pytest/fixtures.py:1372 + Usually this fixture is used in conjunction with another ``autouse`` fixture: + + .. code-block:: python + + @pytest.fixture(autouse=True) + def add_np(doctest_namespace): + doctest_namespace["np"] = numpy + + For more details: :ref:`doctest_namespace`. + + pytestconfig [session scope] -- .../_pytest/fixtures.py:1356 Session-scoped fixture that returns the session's :class:`pytest.Config` object. @@ -75,7 +129,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a if pytestconfig.getoption("verbose") > 0: ... - record_property -- ../../../..$PYTHON_SITE/_pytest/junitxml.py:282 + record_property -- .../_pytest/junitxml.py:282 Add extra properties to the calling test. User properties become part of the test report and are available to the @@ -89,13 +143,13 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a def test_function(record_property): record_property("example_key", 1) - record_xml_attribute -- ../../../..$PYTHON_SITE/_pytest/junitxml.py:305 + record_xml_attribute -- .../_pytest/junitxml.py:305 Add extra xml attributes to the tag for the calling test. The fixture is callable with ``name, value``. The value is automatically XML-encoded. - record_testsuite_property [session scope] -- ../../../..$PYTHON_SITE/_pytest/junitxml.py:343 + record_testsuite_property [session scope] -- .../_pytest/junitxml.py:343 Record a new ```` tag as child of the root ````. This is suitable to writing global information regarding the entire test @@ -109,15 +163,40 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a record_testsuite_property("ARCH", "PPC") record_testsuite_property("STORAGE_TYPE", "CEPH") - ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. + :param name: + The property name. + :param value: + The property value. Will be converted to a string. .. warning:: Currently this fixture **does not work** with the - `pytest-xdist `__ plugin. See issue - `#7767 `__ for details. + `pytest-xdist `__ plugin. See + :issue:`7767` for details. + + tmpdir_factory [session scope] -- .../_pytest/legacypath.py:302 + Return a :class:`pytest.TempdirFactory` instance for the test session. + + tmpdir -- .../_pytest/legacypath.py:309 + Return a temporary directory path object which is unique to each test + function invocation, created as a sub directory of the base temporary + directory. + + By default, a new base temporary directory is created each test session, + and old bases are removed after 3 sessions, to aid in debugging. If + ``--basetemp`` is used then it is cleared each session. See :ref:`base + temporary directory`. - caplog -- ../../../..$PYTHON_SITE/_pytest/logging.py:491 + The returned object is a `legacy_path`_ object. + + .. note:: + These days, it is preferred to use ``tmp_path``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + + .. _legacy_path: https://py.readthedocs.io/en/latest/path.html + + caplog -- .../_pytest/logging.py:491 Access and control log capturing. Captured logs are available through the following properties/methods:: @@ -128,52 +207,40 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a * caplog.record_tuples -> list of (logger_name, level, message) tuples * caplog.clear() -> clear captured records and formatted log output string - monkeypatch -- ../../../..$PYTHON_SITE/_pytest/monkeypatch.py:29 + monkeypatch -- .../_pytest/monkeypatch.py:29 A convenient fixture for monkey-patching. - The fixture provides these methods to modify objects, dictionaries or - os.environ:: + The fixture provides these methods to modify objects, dictionaries, or + :data:`os.environ`: - monkeypatch.setattr(obj, name, value, raising=True) - monkeypatch.delattr(obj, name, raising=True) - monkeypatch.setitem(mapping, name, value) - monkeypatch.delitem(obj, name, raising=True) - monkeypatch.setenv(name, value, prepend=None) - monkeypatch.delenv(name, raising=True) - monkeypatch.syspath_prepend(path) - monkeypatch.chdir(path) + * :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` + * :meth:`monkeypatch.delattr(obj, name, raising=True) ` + * :meth:`monkeypatch.setitem(mapping, name, value) ` + * :meth:`monkeypatch.delitem(obj, name, raising=True) ` + * :meth:`monkeypatch.setenv(name, value, prepend=None) ` + * :meth:`monkeypatch.delenv(name, raising=True) ` + * :meth:`monkeypatch.syspath_prepend(path) ` + * :meth:`monkeypatch.chdir(path) ` + * :meth:`monkeypatch.context() ` All modifications will be undone after the requesting test function or - fixture has finished. The ``raising`` parameter determines if a KeyError - or AttributeError will be raised if the set/deletion operation has no target. + fixture has finished. The ``raising`` parameter determines if a :class:`KeyError` + or :class:`AttributeError` will be raised if the set/deletion operation does not have the + specified target. + + To undo modifications done by the fixture in a contained scope, + use :meth:`context() `. - recwarn -- ../../../..$PYTHON_SITE/_pytest/recwarn.py:29 + recwarn -- .../_pytest/recwarn.py:30 Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. - See https://docs.python.org/library/how-to/capture-warnings.html for information + See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information on warning categories. - tmpdir_factory [session scope] -- ../../../..$PYTHON_SITE/_pytest/tmpdir.py:210 - Return a :class:`pytest.TempdirFactory` instance for the test session. - - tmp_path_factory [session scope] -- ../../../..$PYTHON_SITE/_pytest/tmpdir.py:217 + tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:188 Return a :class:`pytest.TempPathFactory` instance for the test session. - tmpdir -- ../../../..$PYTHON_SITE/_pytest/tmpdir.py:232 - Return a temporary directory path object which is unique to each test - function invocation, created as a sub directory of the base temporary - directory. - - By default, a new base temporary directory is created each test session, - and old bases are removed after 3 sessions, to aid in debugging. If - ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. - - The returned object is a `legacy_path`_ object. - - .. _legacy_path: https://py.readthedocs.io/en/latest/path.html - - tmp_path -- ../../../..$PYTHON_SITE/_pytest/tmpdir.py:250 + tmp_path -- .../_pytest/tmpdir.py:203 Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 6cec2935d9a..020e6289e48 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,920 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 7.2.2 (2023-03-03) +========================= + +Bug Fixes +--------- + +- `#10533 `_: Fixed :func:`pytest.approx` handling of dictionaries containing one or more values of `0.0`. + + +- `#10592 `_: Fixed crash if `--cache-show` and `--help` are passed at the same time. + + +- `#10597 `_: Fixed bug where a fixture method named ``teardown`` would be called as part of ``nose`` teardown stage. + + +- `#10626 `_: Fixed crash if ``--fixtures`` and ``--help`` are passed at the same time. + + +- `#10660 `_: Fixed :py:func:`pytest.raises` to return a 'ContextManager' so that type-checkers could narrow + :code:`pytest.raises(...) if ... else nullcontext()` down to 'ContextManager' rather than 'object'. + + + +Improved Documentation +---------------------- + +- `#10690 `_: Added `CI` and `BUILD_NUMBER` environment variables to the documentation. + + +- `#10721 `_: Fixed entry-points declaration in the documentation example using Hatch. + + +- `#10753 `_: Changed wording of the module level skip to be very explicit + about not collecting tests and not executing the rest of the module. + + +pytest 7.2.1 (2023-01-13) +========================= + +Bug Fixes +--------- + +- `#10452 `_: Fix 'importlib.abc.TraversableResources' deprecation warning in Python 3.12. + + +- `#10457 `_: If a test is skipped from inside a fixture, the test summary now shows the test location instead of the fixture location. + + +- `#10506 `_: Fix bug where sometimes pytest would use the file system root directory as :ref:`rootdir ` on Windows. + + +- `#10607 `_: Fix a race condition when creating junitxml reports, which could occur when multiple instances of pytest execute in parallel. + + +- `#10641 `_: Fix a race condition when creating or updating the stepwise plugin's cache, which could occur when multiple xdist worker nodes try to simultaneously update the stepwise plugin's cache. + + +pytest 7.2.0 (2022-10-23) +========================= + +Deprecations +------------ + +- `#10012 `_: Update :class:`pytest.PytestUnhandledCoroutineWarning` to a deprecation; it will raise an error in pytest 8. + + +- `#10396 `_: pytest no longer depends on the ``py`` library. ``pytest`` provides a vendored copy of ``py.error`` and ``py.path`` modules but will use the ``py`` library if it is installed. If you need other ``py.*`` modules, continue to install the deprecated ``py`` library separately, otherwise it can usually be removed as a dependency. + + +- `#4562 `_: Deprecate configuring hook specs/impls using attributes/marks. + + Instead use :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec`. + For more details, see the :ref:`docs `. + + +- `#9886 `_: The functionality for running tests written for ``nose`` has been officially deprecated. + + This includes: + + * Plain ``setup`` and ``teardown`` functions and methods: this might catch users by surprise, as ``setup()`` and ``teardown()`` are not pytest idioms, but part of the ``nose`` support. + * Setup/teardown using the `@with_setup `_ decorator. + + For more details, consult the :ref:`deprecation docs `. + + .. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup + +- `#7337 `_: A deprecation warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`. The plan is to make returning non-`None` from tests an error in the future. + + +Features +-------- + +- `#9897 `_: Added shell-style wildcard support to ``testpaths``. + + + +Improvements +------------ + +- `#10218 `_: ``@pytest.mark.parametrize()`` (and similar functions) now accepts any ``Sequence[str]`` for the argument names, + instead of just ``list[str]`` and ``tuple[str, ...]``. + + (Note that ``str``, which is itself a ``Sequence[str]``, is still treated as a + comma-delimited name list, as before). + + +- `#10381 `_: The ``--no-showlocals`` flag has been added. This can be passed directly to tests to override ``--showlocals`` declared through ``addopts``. + + +- `#3426 `_: Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expressed instead. + + +- `#8508 `_: Introduce multiline display for warning matching via :py:func:`pytest.warns` and + enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`. + + +- `#8646 `_: Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing + error. We now raise immediately with a more helpful message. + + +- `#9741 `_: On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML. + + :mod:`tomli` is no longer a dependency on Python 3.11. + + +- `#9742 `_: Display assertion message without escaped newline characters with ``-vv``. + + +- `#9823 `_: Improved error message that is shown when no collector is found for a given file. + + +- `#9873 `_: Some coloring has been added to the short test summary. + + +- `#9883 `_: Normalize the help description of all command-line options. + + +- `#9920 `_: Display full crash messages in ``short test summary info``, when running in a CI environment. + + +- `#9987 `_: Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``. + + + +Bug Fixes +--------- + +- `#10150 `_: :data:`sys.stdin` now contains all expected methods of a file-like object when capture is enabled. + + +- `#10382 `_: Do not break into pdb when ``raise unittest.SkipTest()`` appears top-level in a file. + + +- `#7792 `_: Marks are now inherited according to the full MRO in test classes. Previously, if a test class inherited from two or more classes, only marks from the first super-class would apply. + + When inheriting marks from super-classes, marks from the sub-classes are now ordered before marks from the super-classes, in MRO order. Previously it was the reverse. + + When inheriting marks from super-classes, the `pytestmark` attribute of the sub-class now only contains the marks directly applied to it. Previously, it also contained marks from its super-classes. Please note that this attribute should not normally be accessed directly; use :func:`pytest.Node.iter_markers` instead. + + +- `#9159 `_: Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``. + + +- `#9877 `_: Ensure ``caplog.get_records(when)`` returns current/correct data after invoking ``caplog.clear()``. + + + +Improved Documentation +---------------------- + +- `#10344 `_: Update information on writing plugins to use ``pyproject.toml`` instead of ``setup.py``. + + +- `#9248 `_: The documentation is now built using Sphinx 5.x (up from 3.x previously). + + +- `#9291 `_: Update documentation on how :func:`pytest.warns` affects :class:`DeprecationWarning`. + + + +Trivial/Internal Changes +------------------------ + +- `#10313 `_: Made ``_pytest.doctest.DoctestItem`` export ``pytest.DoctestItem`` for + type check and runtime purposes. Made `_pytest.doctest` use internal APIs + to avoid circular imports. + + +- `#9906 `_: Made ``_pytest.compat`` re-export ``importlib_metadata`` in the eyes of type checkers. + + +- `#9910 `_: Fix default encoding warning (``EncodingWarning``) in ``cacheprovider`` + + +- `#9984 `_: Improve the error message when we attempt to access a fixture that has been + torn down. + Add an additional sentence to the docstring explaining when it's not a good + idea to call ``getfixturevalue``. + + +pytest 7.1.3 (2022-08-31) +========================= + +Bug Fixes +--------- + +- `#10060 `_: When running with ``--pdb``, ``TestCase.tearDown`` is no longer called for tests when the *class* has been skipped via ``unittest.skip`` or ``pytest.mark.skip``. + + +- `#10190 `_: Invalid XML characters in setup or teardown error messages are now properly escaped for JUnit XML reports. + + +- `#10230 `_: Ignore ``.py`` files created by ``pyproject.toml``-based editable builds introduced in `pip 21.3 `__. + + +- `#3396 `_: Doctests now respect the ``--import-mode`` flag. + + +- `#9514 `_: Type-annotate ``FixtureRequest.param`` as ``Any`` as a stop gap measure until :issue:`8073` is fixed. + + +- `#9791 `_: Fixed a path handling code in ``rewrite.py`` that seems to work fine, but was incorrect and fails in some systems. + + +- `#9917 `_: Fixed string representation for :func:`pytest.approx` when used to compare tuples. + + + +Improved Documentation +---------------------- + +- `#9937 `_: Explicit note that :fixture:`tmpdir` fixture is discouraged in favour of :fixture:`tmp_path`. + + + +Trivial/Internal Changes +------------------------ + +- `#10114 `_: Replace `atomicwrites `__ dependency on windows with `os.replace`. + + +pytest 7.1.2 (2022-04-23) +========================= + +Bug Fixes +--------- + +- `#9726 `_: An unnecessary ``numpy`` import inside :func:`pytest.approx` was removed. + + +- `#9820 `_: Fix comparison of ``dataclasses`` with ``InitVar``. + + +- `#9869 `_: Increase ``stacklevel`` for the ``NODE_CTOR_FSPATH_ARG`` deprecation to point to the + user's code, not pytest. + + +- `#9871 `_: Fix a bizarre (and fortunately rare) bug where the `temp_path` fixture could raise + an internal error while attempting to get the current user's username. + + +pytest 7.1.1 (2022-03-17) +========================= + +Bug Fixes +--------- + +- `#9767 `_: Fixed a regression in pytest 7.1.0 where some conftest.py files outside of the source tree (e.g. in the `site-packages` directory) were not picked up. + + +pytest 7.1.0 (2022-03-13) +========================= + +Breaking Changes +---------------- + +- `#8838 `_: As per our policy, the following features have been deprecated in the 6.X series and are now + removed: + + * ``pytest._fillfuncargs`` function. + + * ``pytest_warning_captured`` hook - use ``pytest_warning_recorded`` instead. + + * ``-k -foobar`` syntax - use ``-k 'not foobar'`` instead. + + * ``-k foobar:`` syntax. + + * ``pytest.collect`` module - import from ``pytest`` directly. + + For more information consult + `Deprecations and Removals `__ in the docs. + + +- `#9437 `_: Dropped support for Python 3.6, which reached `end-of-life `__ at 2021-12-23. + + + +Improvements +------------ + +- `#5192 `_: Fixed test output for some data types where ``-v`` would show less information. + + Also, when showing diffs for sequences, ``-q`` would produce full diffs instead of the expected diff. + + +- `#9362 `_: pytest now avoids specialized assert formatting when it is detected that the default ``__eq__`` is overridden in ``attrs`` or ``dataclasses``. + + +- `#9536 `_: When ``-vv`` is given on command line, show skipping and xfail reasons in full instead of truncating them to fit the terminal width. + + +- `#9644 `_: More information about the location of resources that led Python to raise :class:`ResourceWarning` can now + be obtained by enabling :mod:`tracemalloc`. + + See :ref:`resource-warnings` for more information. + + +- `#9678 `_: More types are now accepted in the ``ids`` argument to ``@pytest.mark.parametrize``. + Previously only `str`, `float`, `int` and `bool` were accepted; + now `bytes`, `complex`, `re.Pattern`, `Enum` and anything with a `__name__` are also accepted. + + +- `#9692 `_: :func:`pytest.approx` now raises a :class:`TypeError` when given an unordered sequence (such as :class:`set`). + + Note that this implies that custom classes which only implement ``__iter__`` and ``__len__`` are no longer supported as they don't guarantee order. + + + +Bug Fixes +--------- + +- `#8242 `_: The deprecation of raising :class:`unittest.SkipTest` to skip collection of + tests during the pytest collection phase is reverted - this is now a supported + feature again. + + +- `#9493 `_: Symbolic link components are no longer resolved in conftest paths. + This means that if a conftest appears twice in collection tree, using symlinks, it will be executed twice. + For example, given + + tests/real/conftest.py + tests/real/test_it.py + tests/link -> tests/real + + running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``. + This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details). + + +- `#9626 `_: Fixed count of selected tests on terminal collection summary when there were errors or skipped modules. + + If there were errors or skipped modules on collection, pytest would mistakenly subtract those from the selected count. + + +- `#9645 `_: Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites. + + +- `#9708 `_: :fixture:`pytester` now requests a :fixture:`monkeypatch` fixture instead of creating one internally. This solves some issues with tests that involve pytest environment variables. + + +- `#9730 `_: Malformed ``pyproject.toml`` files now produce a clearer error message. + + +pytest 7.0.1 (2022-02-11) +========================= + +Bug Fixes +--------- + +- `#9608 `_: Fix invalid importing of ``importlib.readers`` in Python 3.9. + + +- `#9610 `_: Restore `UnitTestFunction.obj` to return unbound rather than bound method. + Fixes a crash during a failed teardown in unittest TestCases with non-default `__init__`. + Regressed in pytest 7.0.0. + + +- `#9636 `_: The ``pythonpath`` plugin was renamed to ``python_path``. This avoids a conflict with the ``pytest-pythonpath`` plugin. + + +- `#9642 `_: Fix running tests by id with ``::`` in the parametrize portion. + + +- `#9643 `_: Delay issuing a :class:`~pytest.PytestWarning` about diamond inheritance involving :class:`~pytest.Item` and + :class:`~pytest.Collector` so it can be filtered using :ref:`standard warning filters `. + + +pytest 7.0.0 (2022-02-03) +========================= + +(**Please see the full set of changes for this release also in the 7.0.0rc1 notes below**) + +Deprecations +------------ + +- `#9488 `_: If custom subclasses of nodes like :class:`pytest.Item` override the + ``__init__`` method, they should take ``**kwargs``. See + :ref:`uncooperative-constructors-deprecated` for details. + + Note that a deprection warning is only emitted when there is a conflict in the + arguments pytest expected to pass. This deprecation was already part of pytest + 7.0.0rc1 but wasn't documented. + + + +Bug Fixes +--------- + +- `#9355 `_: Fixed error message prints function decorators when using assert in Python 3.8 and above. + + +- `#9396 `_: Ensure :attr:`pytest.Config.inifile` is available during the :func:`pytest_cmdline_main <_pytest.hookspec.pytest_cmdline_main>` hook (regression during ``7.0.0rc1``). + + + +Improved Documentation +---------------------- + +- `#9404 `_: Added extra documentation on alternatives to common misuses of `pytest.warns(None)` ahead of its deprecation. + + +- `#9505 `_: Clarify where the configuration files are located. To avoid confusions documentation mentions + that configuration file is located in the root of the repository. + + + +Trivial/Internal Changes +------------------------ + +- `#9521 `_: Add test coverage to assertion rewrite path. + + +pytest 7.0.0rc1 (2021-12-06) +============================ + +Breaking Changes +---------------- + +- `#7259 `_: The :ref:`Node.reportinfo() ` function first return value type has been expanded from `py.path.local | str` to `os.PathLike[str] | str`. + + Most plugins which refer to `reportinfo()` only define it as part of a custom :class:`pytest.Item` implementation. + Since `py.path.local` is a `os.PathLike[str]`, these plugins are unaffacted. + + Plugins and users which call `reportinfo()`, use the first return value and interact with it as a `py.path.local`, would need to adjust by calling `py.path.local(fspath)`. + Although preferably, avoid the legacy `py.path.local` and use `pathlib.Path`, or use `item.location` or `item.path`, instead. + + Note: pytest was not able to provide a deprecation period for this change. + + +- `#8246 `_: ``--version`` now writes version information to ``stdout`` rather than ``stderr``. + + +- `#8733 `_: Drop a workaround for `pyreadline `__ that made it work with ``--pdb``. + + The workaround was introduced in `#1281 `__ in 2015, however since then + `pyreadline seems to have gone unmaintained `__, is `generating + warnings `__, and will stop working on Python 3.10. + + +- `#9061 `_: Using :func:`pytest.approx` in a boolean context now raises an error hinting at the proper usage. + + It is apparently common for users to mistakenly use ``pytest.approx`` like this: + + .. code-block:: python + + assert pytest.approx(actual, expected) + + While the correct usage is: + + .. code-block:: python + + assert actual == pytest.approx(expected) + + The new error message helps catch those mistakes. + + +- `#9277 `_: The ``pytest.Instance`` collector type has been removed. + Importing ``pytest.Instance`` or ``_pytest.python.Instance`` returns a dummy type and emits a deprecation warning. + See :ref:`instance-collector-deprecation` for details. + + +- `#9308 `_: **PytestRemovedIn7Warning deprecation warnings are now errors by default.** + + Following our plan to remove deprecated features with as little disruption as + possible, all warnings of type ``PytestRemovedIn7Warning`` now generate errors + instead of warning messages by default. + + **The affected features will be effectively removed in pytest 7.1**, so please consult the + :ref:`deprecations` section in the docs for directions on how to update existing code. + + In the pytest ``7.0.X`` series, it is possible to change the errors back into warnings as a + stopgap measure by adding this to your ``pytest.ini`` file: + + .. code-block:: ini + + [pytest] + filterwarnings = + ignore::pytest.PytestRemovedIn7Warning + + But this will stop working when pytest ``7.1`` is released. + + **If you have concerns** about the removal of a specific feature, please add a + comment to :issue:`9308`. + + + +Deprecations +------------ + +- `#7259 `_: ``py.path.local`` arguments for hooks have been deprecated. See :ref:`the deprecation note ` for full details. + + ``py.path.local`` arguments to Node constructors have been deprecated. See :ref:`the deprecation note ` for full details. + + .. note:: + The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the + new attribute being ``path``) is **the opposite** of the situation for hooks + (the old argument being ``path``). + + This is an unfortunate artifact due to historical reasons, which should be + resolved in future versions as we slowly get rid of the :pypi:`py` + dependency (see :issue:`9283` for a longer discussion). + + +- `#7469 `_: Directly constructing the following classes is now deprecated: + + - ``_pytest.mark.structures.Mark`` + - ``_pytest.mark.structures.MarkDecorator`` + - ``_pytest.mark.structures.MarkGenerator`` + - ``_pytest.python.Metafunc`` + - ``_pytest.runner.CallInfo`` + - ``_pytest._code.ExceptionInfo`` + - ``_pytest.config.argparsing.Parser`` + - ``_pytest.config.argparsing.OptionGroup`` + - ``_pytest.pytester.HookRecorder`` + + These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8. + + +- `#8242 `_: Raising :class:`unittest.SkipTest` to skip collection of tests during the + pytest collection phase is deprecated. Use :func:`pytest.skip` instead. + + Note: This deprecation only relates to using :class:`unittest.SkipTest` during test + collection. You are probably not doing that. Ordinary usage of + :class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` / + :func:`unittest.skip` in unittest test cases is fully supported. + + .. note:: This deprecation has been reverted in pytest 7.1.0. + + +- `#8315 `_: Several behaviors of :meth:`Parser.addoption ` are now + scheduled for removal in pytest 8 (deprecated since pytest 2.4.0): + + - ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. + - ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. + + +- `#8447 `_: Defining a custom pytest node type which is both an :class:`pytest.Item ` and a :class:`pytest.Collector ` (e.g. :class:`pytest.File `) now issues a warning. + It was never sanely supported and triggers hard to debug errors. + + See :ref:`the deprecation note ` for full details. + + +- `#8592 `_: :hook:`pytest_cmdline_preparse` has been officially deprecated. It will be removed in a future release. Use :hook:`pytest_load_initial_conftests` instead. + + See :ref:`the deprecation note ` for full details. + + +- `#8645 `_: :func:`pytest.warns(None) ` is now deprecated because many people used + it to mean "this code does not emit warnings", but it actually had the effect of + checking that the code emits at least one warning of any type - like ``pytest.warns()`` + or ``pytest.warns(Warning)``. + + +- `#8948 `_: :func:`pytest.skip(msg=...) `, :func:`pytest.fail(msg=...) ` and :func:`pytest.exit(msg=...) ` + signatures now accept a ``reason`` argument instead of ``msg``. Using ``msg`` still works, but is deprecated and will be removed in a future release. + + This was changed for consistency with :func:`pytest.mark.skip ` and :func:`pytest.mark.xfail ` which both accept + ``reason`` as an argument. + +- `#8174 `_: The following changes have been made to types reachable through :attr:`pytest.ExceptionInfo.traceback`: + + - The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``. + - The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``. + + There was no deprecation period for this change (sorry!). + + +Features +-------- + +- `#5196 `_: Tests are now ordered by definition order in more cases. + + In a class hierarchy, tests from base classes are now consistently ordered before tests defined on their subclasses (reverse MRO order). + + +- `#7132 `_: Added two environment variables :envvar:`PYTEST_THEME` and :envvar:`PYTEST_THEME_MODE` to let the users customize the pygments theme used. + + +- `#7259 `_: Added :meth:`cache.mkdir() `, which is similar to the existing :meth:`cache.makedir() `, + but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``. + + Added a ``paths`` type to :meth:`parser.addini() `, + as in ``parser.addini("mypaths", "my paths", type="paths")``, + which is similar to the existing ``pathlist``, + but returns a list of :class:`pathlib.Path` instead of legacy ``py.path.local``. + + +- `#7469 `_: The types of objects used in pytest's API are now exported so they may be used in type annotations. + + The newly-exported types are: + + - ``pytest.Config`` for :class:`Config `. + - ``pytest.Mark`` for :class:`marks `. + - ``pytest.MarkDecorator`` for :class:`mark decorators `. + - ``pytest.MarkGenerator`` for the :class:`pytest.mark ` singleton. + - ``pytest.Metafunc`` for the :class:`metafunc ` argument to the :hook:`pytest_generate_tests` hook. + - ``pytest.CallInfo`` for the :class:`CallInfo ` type passed to various hooks. + - ``pytest.PytestPluginManager`` for :class:`PytestPluginManager `. + - ``pytest.ExceptionInfo`` for the :class:`ExceptionInfo ` type returned from :func:`pytest.raises` and passed to various hooks. + - ``pytest.Parser`` for the :class:`Parser ` type passed to the :hook:`pytest_addoption` hook. + - ``pytest.OptionGroup`` for the :class:`OptionGroup ` type returned from the :func:`parser.addgroup ` method. + - ``pytest.HookRecorder`` for the :class:`HookRecorder ` type returned from :class:`~pytest.Pytester`. + - ``pytest.RecordedHookCall`` for the :class:`RecordedHookCall ` type returned from :class:`~pytest.HookRecorder`. + - ``pytest.RunResult`` for the :class:`RunResult ` type returned from :class:`~pytest.Pytester`. + - ``pytest.LineMatcher`` for the :class:`LineMatcher ` type used in :class:`~pytest.RunResult` and others. + - ``pytest.TestReport`` for the :class:`TestReport ` type used in various hooks. + - ``pytest.CollectReport`` for the :class:`CollectReport ` type used in various hooks. + + Constructing most of them directly is not supported; they are only meant for use in type annotations. + Doing so will emit a deprecation warning, and may become a hard-error in pytest 8.0. + + Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy. + + +- `#7856 `_: :ref:`--import-mode=importlib ` now works with features that + depend on modules being on :py:data:`sys.modules`, such as :mod:`pickle` and :mod:`dataclasses`. + + +- `#8144 `_: The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument: + + - :hook:`pytest_ignore_collect` - The ``collection_path`` parameter (equivalent to existing ``path`` parameter). + - :hook:`pytest_collect_file` - The ``file_path`` parameter (equivalent to existing ``path`` parameter). + - :hook:`pytest_pycollect_makemodule` - The ``module_path`` parameter (equivalent to existing ``path`` parameter). + - :hook:`pytest_report_header` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter). + - :hook:`pytest_report_collectionfinish` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter). + + .. note:: + The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the + new attribute being ``path``) is **the opposite** of the situation for hooks + (the old argument being ``path``). + + This is an unfortunate artifact due to historical reasons, which should be + resolved in future versions as we slowly get rid of the :pypi:`py` + dependency (see :issue:`9283` for a longer discussion). + + +- `#8251 `_: Implement ``Node.path`` as a ``pathlib.Path``. Both the old ``fspath`` and this new attribute gets set no matter whether ``path`` or ``fspath`` (deprecated) is passed to the constructor. It is a replacement for the ``fspath`` attribute (which represents the same path as ``py.path.local``). While ``fspath`` is not deprecated yet + due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo`, we expect to deprecate it in a future release. + + .. note:: + The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the + new attribute being ``path``) is **the opposite** of the situation for hooks + (the old argument being ``path``). + + This is an unfortunate artifact due to historical reasons, which should be + resolved in future versions as we slowly get rid of the :pypi:`py` + dependency (see :issue:`9283` for a longer discussion). + + +- `#8421 `_: :func:`pytest.approx` now works on :class:`~decimal.Decimal` within mappings/dicts and sequences/lists. + + +- `#8606 `_: pytest invocations with ``--fixtures-per-test`` and ``--fixtures`` have been enriched with: + + - Fixture location path printed with the fixture name. + - First section of the fixture's docstring printed under the fixture name. + - Whole of fixture's docstring printed under the fixture name using ``--verbose`` option. + + +- `#8761 `_: New :ref:`version-tuple` attribute, which makes it simpler for users to do something depending on the pytest version (such as declaring hooks which are introduced in later versions). + + +- `#8789 `_: Switch TOML parser from ``toml`` to ``tomli`` for TOML v1.0.0 support in ``pyproject.toml``. + + +- `#8920 `_: Added :class:`pytest.Stash`, a facility for plugins to store their data on :class:`~pytest.Config` and :class:`~_pytest.nodes.Node`\s in a type-safe and conflict-free manner. + See :ref:`plugin-stash` for details. + + +- `#8953 `_: :class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a + ``warnings`` argument to assert the total number of warnings captured. + + +- `#8954 `_: ``--debug`` flag now accepts a :class:`str` file to route debug logs into, remains defaulted to `pytestdebug.log`. + + +- `#9023 `_: Full diffs are now always shown for equality assertions of iterables when + `CI` or ``BUILD_NUMBER`` is found in the environment, even when ``-v`` isn't + used. + + +- `#9113 `_: :class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a + ``deselected`` argument to assert the total number of deselected tests. + + +- `#9114 `_: Added :confval:`pythonpath` setting that adds listed paths to :data:`sys.path` for the duration of the test session. If you currently use the pytest-pythonpath or pytest-srcpaths plugins, you should be able to replace them with built-in `pythonpath` setting. + + + +Improvements +------------ + +- `#7480 `_: A deprecation scheduled to be removed in a major version X (e.g. pytest 7, 8, 9, ...) now uses warning category `PytestRemovedInXWarning`, + a subclass of :class:`~pytest.PytestDeprecationWarning`, + instead of :class:`PytestDeprecationWarning` directly. + + See :ref:`backwards-compatibility` for more details. + + +- `#7864 `_: Improved error messages when parsing warning filters. + + Previously pytest would show an internal traceback, which besides being ugly sometimes would hide the cause + of the problem (for example an ``ImportError`` while importing a specific warning type). + + +- `#8335 `_: Improved :func:`pytest.approx` assertion messages for sequences of numbers. + + The assertion messages now dumps a table with the index and the error of each diff. + Example:: + + > assert [1, 2, 3, 4] == pytest.approx([1, 3, 3, 5]) + E assert comparison failed for 2 values: + E Index | Obtained | Expected + E 1 | 2 | 3 +- 3.0e-06 + E 3 | 4 | 5 +- 5.0e-06 + + +- `#8403 `_: By default, pytest will truncate long strings in assert errors so they don't clutter the output too much, + currently at ``240`` characters by default. + + However, in some cases the longer output helps, or is even crucial, to diagnose a failure. Using ``-v`` will + now increase the truncation threshold to ``2400`` characters, and ``-vv`` or higher will disable truncation entirely. + + +- `#8509 `_: Fixed issue where :meth:`unittest.TestCase.setUpClass` is not called when a test has `/` in its name since pytest 6.2.0. + + This refers to the path part in pytest node IDs, e.g. ``TestClass::test_it`` in the node ID ``tests/test_file.py::TestClass::test_it``. + + Now, instead of assuming that the test name does not contain ``/``, it is assumed that test path does not contain ``::``. We plan to hopefully make both of these work in the future. + + +- `#8803 `_: It is now possible to add colors to custom log levels on cli log. + + By using :func:`add_color_level <_pytest.logging.add_color_level>` from a ``pytest_configure`` hook, colors can be added:: + + logging_plugin = config.pluginmanager.get_plugin('logging-plugin') + logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, 'cyan') + logging_plugin.log_cli_handler.formatter.add_color_level(logging.SPAM, 'blue') + + See :ref:`log_colors` for more information. + + +- `#8822 `_: When showing fixture paths in `--fixtures` or `--fixtures-by-test`, fixtures coming from pytest itself now display an elided path, rather than the full path to the file in the `site-packages` directory. + + +- `#8898 `_: Complex numbers are now treated like floats and integers when generating parameterization IDs. + + +- `#9062 `_: ``--stepwise-skip`` now implicitly enables ``--stepwise`` and can be used on its own. + + +- `#9205 `_: :meth:`pytest.Cache.set` now preserves key order when saving dicts. + + + +Bug Fixes +--------- + +- `#7124 `_: Fixed an issue where ``__main__.py`` would raise an ``ImportError`` when ``--doctest-modules`` was provided. + + +- `#8061 `_: Fixed failing ``staticmethod`` test cases if they are inherited from a parent test class. + + +- `#8192 `_: ``testdir.makefile`` now silently accepts values which don't start with ``.`` to maintain backward compatibility with older pytest versions. + + ``pytester.makefile`` now issues a clearer error if the ``.`` is missing in the ``ext`` argument. + + +- `#8258 `_: Fixed issue where pytest's ``faulthandler`` support would not dump traceback on crashes + if the :mod:`faulthandler` module was already enabled during pytest startup (using + ``python -X dev -m pytest`` for example). + + +- `#8317 `_: Fixed an issue where illegal directory characters derived from ``getpass.getuser()`` raised an ``OSError``. + + +- `#8367 `_: Fix ``Class.from_parent`` so it forwards extra keyword arguments to the constructor. + + +- `#8377 `_: The test selection options ``pytest -k`` and ``pytest -m`` now support matching + names containing forward slash (``/``) characters. + + +- `#8384 `_: The ``@pytest.mark.skip`` decorator now correctly handles its arguments. When the ``reason`` argument is accidentally given both positional and as a keyword (e.g. because it was confused with ``skipif``), a ``TypeError`` now occurs. Before, such tests were silently skipped, and the positional argument ignored. Additionally, ``reason`` is now documented correctly as positional or keyword (rather than keyword-only). + + +- `#8394 `_: Use private names for internal fixtures that handle classic setup/teardown so that they don't show up with the default ``--fixtures`` invocation (but they still show up with ``--fixtures -v``). + + +- `#8456 `_: The :confval:`required_plugins` config option now works correctly when pre-releases of plugins are installed, rather than falsely claiming that those plugins aren't installed at all. + + +- `#8464 `_: ``-c `` now also properly defines ``rootdir`` as the directory that contains ````. + + +- `#8503 `_: :meth:`pytest.MonkeyPatch.syspath_prepend` no longer fails when + ``setuptools`` is not installed. + It now only calls :func:`pkg_resources.fixup_namespace_packages` if + ``pkg_resources`` was previously imported, because it is not needed otherwise. + + +- `#8548 `_: Introduce fix to handle precision width in ``log-cli-format`` in turn to fix output coloring for certain formats. + + +- `#8796 `_: Fixed internal error when skipping doctests. + + +- `#8983 `_: The test selection options ``pytest -k`` and ``pytest -m`` now support matching names containing backslash (`\\`) characters. + Backslashes are treated literally, not as escape characters (the values being matched against are already escaped). + + +- `#8990 `_: Fix `pytest -vv` crashing with an internal exception `AttributeError: 'str' object has no attribute 'relative_to'` in some cases. + + +- `#9077 `_: Fixed confusing error message when ``request.fspath`` / ``request.path`` was accessed from a session-scoped fixture. + + +- `#9131 `_: Fixed the URL used by ``--pastebin`` to use `bpa.st `__. + + +- `#9163 `_: The end line number and end column offset are now properly set for rewritten assert statements. + + +- `#9169 `_: Support for the ``files`` API from ``importlib.resources`` within rewritten files. + + +- `#9272 `_: The nose compatibility module-level fixtures `setup()` and `teardown()` are now only called once per module, instead of for each test function. + They are now called even if object-level `setup`/`teardown` is defined. + + + +Improved Documentation +---------------------- + +- `#4320 `_: Improved docs for `pytester.copy_example`. + + +- `#5105 `_: Add automatically generated :ref:`plugin-list`. The list is updated on a periodic schedule. + + +- `#8337 `_: Recommend `numpy.testing `__ module on :func:`pytest.approx` documentation. + + +- `#8655 `_: Help text for ``--pdbcls`` more accurately reflects the option's behavior. + + +- `#9210 `_: Remove incorrect docs about ``confcutdir`` being a configuration option: it can only be set through the ``--confcutdir`` command-line option. + + +- `#9242 `_: Upgrade readthedocs configuration to use a `newer Ubuntu version `__` with better unicode support for PDF docs. + + +- `#9341 `_: Various methods commonly used for :ref:`non-python tests` are now correctly documented in the reference docs. They were undocumented previously. + + + +Trivial/Internal Changes +------------------------ + +- `#8133 `_: Migrate to ``setuptools_scm`` 6.x to use ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST`` for more robust release tooling. + + +- `#8174 `_: The following changes have been made to internal pytest types/functions: + + - The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``. + - The ``_pytest.python.path_matches_patterns()`` function takes ``Path`` instead of ``py.path.local``. + - The ``_pytest._code.Traceback.cut()`` function accepts any ``os.PathLike[str]``, not just ``py.path.local``. + + +- `#8248 `_: Internal Restructure: let ``python.PyObjMixin`` inherit from ``nodes.Node`` to carry over typing information. + + +- `#8432 `_: Improve error message when :func:`pytest.skip` is used at module level without passing `allow_module_level=True`. + + +- `#8818 `_: Ensure ``regendoc`` opts out of ``TOX_ENV`` cachedir selection to ensure independent example test runs. + + +- `#8913 `_: The private ``CallSpec2._arg2scopenum`` attribute has been removed after an internal refactoring. + + +- `#8967 `_: :hook:`pytest_assertion_pass` is no longer considered experimental and + future changes to it will be considered more carefully. + + +- `#9202 `_: Add github action to upload coverage report to codecov instead of bash uploader. + + +- `#9225 `_: Changed the command used to create sdist and wheel artifacts: using the build package instead of setup.py. + + +- `#9351 `_: Correct minor typos in doc/en/example/special.rst. + + pytest 6.2.5 (2021-08-29) ========================= @@ -62,7 +976,7 @@ Bug Fixes the ``tmp_path``/``tmpdir`` fixture). Now the directories are created with private permissions. - pytest used to silenty use a pre-existing ``/tmp/pytest-of-`` directory, + pytest used to silently use a pre-existing ``/tmp/pytest-of-`` directory, even if owned by another user. This means another user could pre-create such a directory and gain control of another user's temporary directory. Now such a condition results in an error. @@ -377,8 +1291,8 @@ Deprecations if you use this and want a replacement. -- :issue:`7255`: The :func:`pytest_warning_captured <_pytest.hookspec.pytest_warning_captured>` hook is deprecated in favor - of :func:`pytest_warning_recorded <_pytest.hookspec.pytest_warning_recorded>`, and will be removed in a future version. +- :issue:`7255`: The :hook:`pytest_warning_captured` hook is deprecated in favor + of :hook:`pytest_warning_recorded`, and will be removed in a future version. - :issue:`7648`: The ``gethookproxy()`` and ``isinitpath()`` methods of ``FSCollector`` and ``Package`` are deprecated; @@ -486,7 +1400,7 @@ Trivial/Internal Changes process, pytest now ignores builtin attributes (like ``__class__``, ``__delattr__`` and ``__new__``) without consulting the :confval:`python_classes` and :confval:`python_functions` configuration options and without passing them to plugins - using the :func:`pytest_pycollect_makeitem <_pytest.hookspec.pytest_pycollect_makeitem>` hook. + using the :hook:`pytest_pycollect_makeitem` hook. pytest 6.0.2 (2020-09-04) @@ -828,7 +1742,7 @@ Improvements is not displayed by default for passing tests. This change makes the mistake visible during testing. - You may supress this behavior temporarily or permanently by setting + You may suppress this behavior temporarily or permanently by setting ``logging.raiseExceptions = False``. @@ -1275,7 +2189,7 @@ Bug Fixes - :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest <_pytest.pytester.Testdir.runpytest>` etc. -- :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :func:`pytest_sessionfinish <_pytest.hookspec.pytest_sessionfinish>` hook. This includes quitting from a debugger. +- :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :hook:`pytest_sessionfinish` hook. This includes quitting from a debugger. - :issue:`6752`: When :py:func:`pytest.raises` is used as a function (as opposed to a context manager), @@ -1283,7 +2197,7 @@ Bug Fixes it was swallowed and ignored (regression in pytest 5.1.0). -- :issue:`6801`: Do not display empty lines inbetween traceback for unexpected exceptions with doctests. +- :issue:`6801`: Do not display empty lines in between traceback for unexpected exceptions with doctests. - :issue:`6802`: The :fixture:`testdir fixture ` works within doctests now. @@ -1387,7 +2301,7 @@ Improvements - :issue:`6231`: Improve check for misspelling of :ref:`pytest.mark.parametrize ref`. -- :issue:`6257`: Handle :py:func:`pytest.exit` being used via :py:func:`~_pytest.hookspec.pytest_internalerror`, e.g. when quitting pdb from post mortem. +- :issue:`6257`: Handle :func:`pytest.exit` being used via :hook:`pytest_internalerror`, e.g. when quitting pdb from post mortem. @@ -1945,7 +2859,8 @@ Important This release is a Python3.5+ only release. -For more details, see our :std:doc:`Python 2.7 and 3.4 support plan `. +For more details, see our `Python 2.7 and 3.4 support plan +`_. Removals -------- @@ -2021,7 +2936,7 @@ Deprecations Features -------- -- :issue:`3457`: New :func:`~_pytest.hookspec.pytest_assertion_pass` +- :issue:`3457`: New :hook:`pytest_assertion_pass` hook, called with context information when an assertion *passes*. This hook is still **experimental** so use it with caution. @@ -2169,7 +3084,11 @@ Features - :issue:`6870`: New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``. - Remark: while this is technically a new feature and according to our :ref:`policy ` it should not have been backported, we have opened an exception in this particular case because it fixes a serious interaction with ``pytest-xdist``, so it can also be considered a bugfix. + Remark: while this is technically a new feature and according to our + `policy `_ + it should not have been backported, we have opened an exception in this + particular case because it fixes a serious interaction with ``pytest-xdist``, + so it can also be considered a bugfix. Trivial/Internal Changes ------------------------ @@ -2341,7 +3260,8 @@ Important The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**. -For more details, see our :std:doc:`Python 2.7 and 3.4 support plan `. +For more details, see our `Python 2.7 and 3.4 support plan +`_. Features @@ -4152,7 +5072,7 @@ Bug Fixes - Fixed a bug where stdout and stderr were logged twice by junitxml when a test was marked xfail. (:issue:`3491`) -- Fix ``usefixtures`` mark applyed to unittest tests by correctly instantiating +- Fix ``usefixtures`` mark applied to unittest tests by correctly instantiating ``FixtureInfo``. (:issue:`3498`) - Fix assertion rewriter compatibility with libraries that monkey patch @@ -4541,9 +5461,9 @@ Features - Console output falls back to "classic" mode when capturing is disabled (``-s``), otherwise the output gets garbled to the point of being useless. (:issue:`3038`) -- New :func:`~_pytest.hookspec.pytest_runtest_logfinish` +- New :hook:`pytest_runtest_logfinish` hook which is called when a test item has finished executing, analogous to - :func:`~_pytest.hookspec.pytest_runtest_logstart`. + :hook:`pytest_runtest_logstart`. (:issue:`3101`) - Improve performance when collecting tests using many fixtures. (:issue:`3107`) @@ -5509,7 +6429,7 @@ Bug Fixes Thanks :user:`adborden` for the report and :user:`nicoddemus` for the PR. * Clean up unittest TestCase objects after tests are complete (:issue:`1649`). - Thanks :user:`d_b_w` for the report and PR. + Thanks :user:`d-b-w` for the report and PR. 3.0.3 (2016-09-28) @@ -5524,7 +6444,7 @@ Bug Fixes Thanks :user:`nicoddemus` for the PR. * Fix pkg_resources import error in Jython projects (:issue:`1853`). - Thanks :user:`raquel-ucl` for the PR. + Thanks :user:`raquelalegre` for the PR. * Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception in Python 3 (:issue:`1944`). @@ -7533,7 +8453,7 @@ Bug fixes: or through plugin hooks. Also introduce a "--strict" option which will treat unregistered markers as errors allowing to avoid typos and maintain a well described set of markers - for your test suite. See exaples at http://pytest.org/en/stable/how-to/mark.html + for your test suite. See examples at http://pytest.org/en/stable/how-to/mark.html and its links. - issue50: introduce "-m marker" option to select tests based on markers (this is a stricter and more predictable version of '-k' in that "-m" @@ -7845,7 +8765,7 @@ Bug fixes: - fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny) - fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson) - fix py.code.compile(source) to generate unique filenames -- fix assertion re-interp problems on PyPy, by defering code +- fix assertion re-interp problems on PyPy, by deferring code compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot) - fix py.path.local.pyimport() to work with directories - streamline py.path.local.mkdtemp implementation and usage @@ -7919,7 +8839,7 @@ Bug fixes: - improve support for raises and other dynamically compiled code by manipulating python's linecache.cache instead of the previous rather hacky way of creating custom code objects. This makes - it seemlessly work on Jython and PyPy where it previously didn't. + it seamlessly work on Jython and PyPy where it previously didn't. - fix issue96: make capturing more resilient against Control-C interruptions (involved somewhat substantial refactoring diff --git a/doc/en/conf.py b/doc/en/conf.py index bcf01aa4713..5184ee7b1e5 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -38,6 +38,7 @@ autodoc_member_order = "bysource" autodoc_typehints = "description" +autodoc_typehints_description_target = "documented" todo_include_todos = 1 latex_engine = "lualatex" @@ -162,11 +163,11 @@ _repo = "https://github.com/pytest-dev/pytest" extlinks = { - "bpo": ("https://bugs.python.org/issue%s", "bpo-"), - "pypi": ("https://pypi.org/project/%s/", ""), - "issue": (f"{_repo}/issues/%s", "issue #"), - "pull": (f"{_repo}/pull/%s", "pull request #"), - "user": ("https://github.com/%s", "@"), + "bpo": ("https://bugs.python.org/issue%s", "bpo-%s"), + "pypi": ("https://pypi.org/project/%s/", "%s"), + "issue": (f"{_repo}/issues/%s", "issue #%s"), + "pull": (f"{_repo}/pull/%s", "pull request #%s"), + "user": ("https://github.com/%s", "@%s"), } @@ -247,7 +248,7 @@ html_domain_indices = True # If false, no index is generated. -html_use_index = True +html_use_index = False # If true, the index is split into individual pages for each letter. # html_split_index = False @@ -320,7 +321,9 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)] +man_pages = [ + ("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1) +] # -- Options for Epub output --------------------------------------------------- @@ -382,7 +385,6 @@ ] -# Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { "pluggy": ("https://pluggy.readthedocs.io/en/stable", None), "python": ("https://docs.python.org/3", None), @@ -390,11 +392,8 @@ "pip": ("https://pip.pypa.io/en/stable", None), "tox": ("https://tox.wiki/en/stable", None), "virtualenv": ("https://virtualenv.pypa.io/en/stable", None), - "django": ( - "http://docs.djangoproject.com/en/stable", - "http://docs.djangoproject.com/en/stable/_objects", - ), "setuptools": ("https://setuptools.pypa.io/en/stable", None), + "packaging": ("https://packaging.python.org/en/latest", None), } @@ -422,8 +421,6 @@ def filter(self, record: logging.LogRecord) -> bool: def setup(app: "sphinx.application.Sphinx") -> None: - # from sphinx.ext.autodoc import cut_lines - # app.connect('autodoc-process-docstring', cut_lines(4, what=['module'])) app.add_crossref_type( "fixture", "fixture", @@ -445,6 +442,13 @@ def setup(app: "sphinx.application.Sphinx") -> None: indextemplate="pair: %s; global variable interpreted by pytest", ) + app.add_crossref_type( + directivename="hook", + rolename="hook", + objname="pytest hook", + indextemplate="pair: %s; hook", + ) + configure_logging(app) # Make Sphinx mark classes with "final" when decorated with @final. diff --git a/doc/en/contents.rst b/doc/en/contents.rst index 049d44ba9d8..ae42884f658 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -85,7 +85,6 @@ Further topics backwards-compatibility deprecations - py27-py34-deprecation contributing development_guide diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index ea9ef2aa35d..a73c11fb846 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -16,7 +16,114 @@ Deprecated Features ------------------- Below is a complete list of all pytest features which are considered deprecated. Using those features will issue -:class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. +:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. + + +.. _nose-deprecation: + +Support for tests written for nose +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.2 + +Support for running tests written for `nose `__ is now deprecated. + +``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills +over the code base (see :issue:`9886` for more details). + +setup/teardown +^^^^^^^^^^^^^^ + +One thing that might catch users by surprise is that plain ``setup`` and ``teardown`` methods are not pytest native, +they are in fact part of the ``nose`` support. + + +.. code-block:: python + + class Test: + def setup(self): + self.resource = make_resource() + + def teardown(self): + self.resource.close() + + def test_foo(self): + ... + + def test_bar(self): + ... + + + +Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`xunit-method-setup`), so the above should be changed to: + +.. code-block:: python + + class Test: + def setup_method(self): + self.resource = make_resource() + + def teardown_method(self): + self.resource.close() + + def test_foo(self): + ... + + def test_bar(self): + ... + + +This is easy to do in an entire code base by doing a simple find/replace. + +@with_setup +^^^^^^^^^^^ + +Code using `@with_setup `_ such as this: + +.. code-block:: python + + from nose.tools import with_setup + + + def setup_some_resource(): + ... + + + def teardown_some_resource(): + ... + + + @with_setup(setup_some_resource, teardown_some_resource) + def test_foo(): + ... + +Will also need to be ported to a supported pytest style. One way to do it is using a fixture: + +.. code-block:: python + + import pytest + + + def setup_some_resource(): + ... + + + def teardown_some_resource(): + ... + + + @pytest.fixture + def some_resource(): + setup_some_resource() + yield + teardown_some_resource() + + + def test_foo(some_resource): + ... + + +.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup .. _instance-collector-deprecation: @@ -56,6 +163,10 @@ Plugins which implement custom items and collectors are encouraged to replace ``fspath`` parameters (``py.path.local``) with ``path`` parameters (``pathlib.Path``), and drop any other usage of the ``py`` library if possible. +If possible, plugins with custom items should use :ref:`cooperative +constructors ` to avoid hardcoding +arguments they only pass on to the superclass. + .. note:: The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the new attribute being ``path``) is **the opposite** of the situation for @@ -74,6 +185,50 @@ no matter what argument was used in the constructor. We expect to deprecate the .. _legacy-path-hooks-deprecated: +Configuring hook specs/impls using markers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before pluggy, pytest's plugin library, was its own package and had a clear API, +pytest just used ``pytest.mark`` to configure hooks. + +The :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec` decorators +have been available since years and should be used instead. + +.. code-block:: python + + @pytest.mark.tryfirst + def pytest_runtest_call(): + ... + + + # or + def pytest_runtest_call(): + ... + + + pytest_runtest_call.tryfirst = True + +should be changed to: + +.. code-block:: python + + @pytest.hookimpl(tryfirst=True) + def pytest_runtest_call(): + ... + +Changed ``hookimpl`` attributes: + +* ``tryfirst`` +* ``trylast`` +* ``optionalhook`` +* ``hookwrapper`` + +Changed ``hookwrapper`` attributes: + +* ``firstresult`` +* ``historic`` + + ``py.path.local`` arguments for hooks replaced with ``pathlib.Path`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -81,11 +236,11 @@ no matter what argument was used in the constructor. We expect to deprecate the In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments: -* :func:`pytest_ignore_collect(collection_path: pathlib.Path) <_pytest.hookspec.pytest_ignore_collect>` as equivalent to ``path`` -* :func:`pytest_collect_file(file_path: pathlib.Path) <_pytest.hookspec.pytest_collect_file>` as equivalent to ``path`` -* :func:`pytest_pycollect_makemodule(module_path: pathlib.Path) <_pytest.hookspec.pytest_pycollect_makemodule>` as equivalent to ``path`` -* :func:`pytest_report_header(start_path: pathlib.Path) <_pytest.hookspec.pytest_report_header>` as equivalent to ``startdir`` -* :func:`pytest_report_collectionfinish(start_path: pathlib.Path) <_pytest.hookspec.pytest_report_collectionfinish>` as equivalent to ``startdir`` +* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) ` as equivalent to ``path`` +* :hook:`pytest_collect_file(file_path: pathlib.Path) ` as equivalent to ``path`` +* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) ` as equivalent to ``path`` +* :hook:`pytest_report_header(start_path: pathlib.Path) ` as equivalent to ``startdir`` +* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) ` as equivalent to ``startdir`` The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments. @@ -127,7 +282,7 @@ Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit`` Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit` is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these -functions and the``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument. +functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument. .. code-block:: python @@ -157,8 +312,8 @@ Implementing the ``pytest_cmdline_preparse`` hook .. deprecated:: 7.0 -Implementing the :func:`pytest_cmdline_preparse <_pytest.hookspec.pytest_cmdline_preparse>` hook has been officially deprecated. -Implement the :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load_initial_conftests>` hook instead. +Implementing the :hook:`pytest_cmdline_preparse` hook has been officially deprecated. +Implement the :hook:`pytest_load_initial_conftests` hook instead. .. code-block:: python @@ -191,6 +346,40 @@ Instead, a separate collector node should be used, which collects the item. See .. _example pr fixing inheritance: https://github.com/asmeurer/pytest-flakes/pull/40/files +.. _uncooperative-constructors-deprecated: + +Constructors of custom :class:`pytest.Node` subclasses should take ``**kwargs`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.0 + +If custom subclasses of nodes like :class:`pytest.Item` override the +``__init__`` method, they should take ``**kwargs``. Thus, + +.. code-block:: python + + class CustomItem(pytest.Item): + def __init__(self, name, parent, additional_arg): + super().__init__(name, parent) + self.additional_arg = additional_arg + +should be turned into: + +.. code-block:: python + + class CustomItem(pytest.Item): + def __init__(self, *, additional_arg, **kwargs): + super().__init__(**kwargs) + self.additional_arg = additional_arg + +to avoid hard-coding the arguments pytest can pass to the superclass. +See :ref:`non-python tests` for a full example. + +For cases without conflicts, no deprecation warning is emitted. For cases with +conflicts (such as :class:`pytest.File` now taking ``path`` instead of +``fspath``, as :ref:`outlined above `), a +deprecation warning is now raised. + Backward compatibilities in ``Parser.addoption`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -203,28 +392,56 @@ scheduled for removal in pytest 8 (deprecated since pytest 2.4.0): - ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. -Raising ``unittest.SkipTest`` during collection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Using ``pytest.warns(None)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 7.0 -Raising :class:`unittest.SkipTest` to skip collection of tests during the -pytest collection phase is deprecated. Use :func:`pytest.skip` instead. +:func:`pytest.warns(None) ` is now deprecated because it was frequently misused. +Its correct usage was checking that the code emits at least one warning of any type - like ``pytest.warns()`` +or ``pytest.warns(Warning)``. -Note: This deprecation only relates to using `unittest.SkipTest` during test -collection. You are probably not doing that. Ordinary usage of -:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` / -:func:`unittest.skip` in unittest test cases is fully supported. +See :ref:`warns use cases` for examples. -Using ``pytest.warns(None)`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. deprecated:: 7.0 +Returning non-None value in test functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:func:`pytest.warns(None) ` is now deprecated because many people used -it to mean "this code does not emit warnings", but it actually had the effect of -checking that the code emits at least one warning of any type - like ``pytest.warns()`` -or ``pytest.warns(Warning)``. +.. deprecated:: 7.2 + +A :class:`pytest.PytestReturnNotNoneWarning` is now emitted if a test function returns something other than `None`. + +This prevents a common mistake among beginners that expect that returning a `bool` would cause a test to pass or fail, for example: + +.. code-block:: python + + @pytest.mark.parametrize( + ["a", "b", "result"], + [ + [1, 2, 5], + [2, 3, 8], + [5, 3, 18], + ], + ) + def test_foo(a, b, result): + return foo(a, b) == result + +Given that pytest ignores the return value, this might be surprising that it will never fail. + +The proper fix is to change the `return` to an `assert`: + +.. code-block:: python + + @pytest.mark.parametrize( + ["a", "b", "result"], + [ + [1, 2, 5], + [2, 3, 8], + [5, 3, 18], + ], + ) + def test_foo(a, b, result): + assert foo(a, b) == result The ``--strict`` command-line option @@ -250,29 +467,42 @@ The ``yield_fixture`` function/decorator It has been so for a very long time, so can be search/replaced safely. -The ``pytest_warning_captured`` hook -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 6.0 +Removed Features +---------------- -This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``. +As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after +an appropriate period of deprecation has passed. -Use the ``pytest_warning_recored`` hook instead, which replaces the ``item`` parameter -by a ``nodeid`` parameter. The ``pytest.collect`` module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 6.0 +.. versionremoved:: 7.0 The ``pytest.collect`` module is no longer part of the public API, all its names should now be imported from ``pytest`` directly instead. + +The ``pytest_warning_captured`` hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0 +.. versionremoved:: 7.0 + +This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``. + +Use the ``pytest_warning_recorded`` hook instead, which replaces the ``item`` parameter +by a ``nodeid`` parameter. + + + The ``pytest._fillfuncargs`` function ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 6.0 +.. versionremoved:: 7.0 This function was kept for backward compatibility with an older plugin. @@ -281,12 +511,6 @@ it, use `function._request._fillfixtures()` instead, though note this is not a public API and may break in the future. -Removed Features ----------------- - -As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after -an appropriate period of deprecation has passed. - ``--no-print-logs`` command-line option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -324,8 +548,8 @@ at some point, depending on the plans for the plugins and number of users using .. versionremoved:: 6.0 -The ``pytest_collect_directory`` has not worked properly for years (it was called -but the results were ignored). Users may consider using :func:`pytest_collection_modifyitems <_pytest.hookspec.pytest_collection_modifyitems>` instead. +The ``pytest_collect_directory`` hook has not worked properly for years (it was called +but the results were ignored). Users may consider using :hook:`pytest_collection_modifyitems` instead. TerminalReporter.writer ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/example/attic.rst b/doc/en/example/attic.rst index 2ea87006204..2b1f2766dce 100644 --- a/doc/en/example/attic.rst +++ b/doc/en/example/attic.rst @@ -25,7 +25,7 @@ example: specifying and selecting acceptance tests self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True) def run(self, *cmd): - """ called by test code to execute an acceptance test. """ + """called by test code to execute an acceptance test.""" self.tmpdir.chdir() return subprocess.check_output(cmd).decode() diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_flat.svg b/doc/en/example/fixtures/test_fixtures_order_autouse_flat.svg new file mode 100644 index 00000000000..03c4598272a --- /dev/null +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_flat.svg @@ -0,0 +1,56 @@ + + + + + order + + a + + b + + c + + autouse + + d + + e + + f + + g + + test_order + diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/doc/en/example/fixtures/test_fixtures_order_dependencies.py index b3512c2a64d..e76e3f93c62 100644 --- a/doc/en/example/fixtures/test_fixtures_order_dependencies.py +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies.py @@ -17,7 +17,7 @@ def b(a, order): @pytest.fixture -def c(a, b, order): +def c(b, order): order.append("c") diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 402b8e34626..55fd1f576cf 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -45,7 +45,7 @@ You can then restrict a test run to only run tests marked with ``webtest``: $ pytest -v -m webtest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 3 deselected / 1 selected @@ -60,7 +60,7 @@ Or the inverse, running all tests except the webtest ones: $ pytest -v -m "not webtest" =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 1 deselected / 3 selected @@ -82,7 +82,7 @@ tests based on their module, class, method, or function name: $ pytest -v test_server.py::TestClass::test_method =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 1 item @@ -97,7 +97,7 @@ You can also select on the class: $ pytest -v test_server.py::TestClass =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 1 item @@ -112,7 +112,7 @@ Or select multiple nodes: $ pytest -v test_server.py::TestClass test_server.py::test_send_http =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 2 items @@ -156,7 +156,7 @@ The expression matching is now case-insensitive. $ pytest -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 3 deselected / 1 selected @@ -171,7 +171,7 @@ And you can also run all tests except the ones that match the keyword: $ pytest -k "not send_http" -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 1 deselected / 3 selected @@ -188,7 +188,7 @@ Or to select "http" and "quick" tests: $ pytest -k "http or quick" -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 2 deselected / 2 selected @@ -246,9 +246,9 @@ You can ask which markers exist for your test suite - the list includes our just @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures - @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead. - @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead. For an example on how to add and work with markers from a plugin, see @@ -346,7 +346,7 @@ Custom marker and command line option to control test runs Plugins can provide custom markers and implement specific behaviour based on it. This is a self-contained example which adds a command line option and a parametrized test function marker to run tests -specifies via named environments: +specified via named environments: .. code-block:: python @@ -375,7 +375,7 @@ specifies via named environments: envnames = [mark.args[0] for mark in item.iter_markers(name="env")] if envnames: if item.config.getoption("-E") not in envnames: - pytest.skip("test requires env in {!r}".format(envnames)) + pytest.skip(f"test requires env in {envnames!r}") A test file using this local plugin: @@ -397,8 +397,7 @@ the test needs: $ pytest -E stage2 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -412,8 +411,7 @@ and here is one that specifies exactly the environment needed: $ pytest -E stage1 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -440,9 +438,9 @@ The ``--markers`` option always gives you a list of available markers: @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures - @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead. - @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead. .. _`passing callables to custom markers`: @@ -530,7 +528,7 @@ test function. From a conftest file we can read it like this: def pytest_runtest_setup(item): for mark in item.iter_markers(name="glob"): - print("glob args={} kwargs={}".format(mark.args, mark.kwargs)) + print(f"glob args={mark.args} kwargs={mark.kwargs}") sys.stdout.flush() Let's run this without capturing output and see what we get: @@ -560,6 +558,7 @@ for your particular platform, you could use the following plugin: # content of conftest.py # import sys + import pytest ALL = set("darwin linux win32".split()) @@ -569,7 +568,7 @@ for your particular platform, you could use the following plugin: supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers()) plat = sys.platform if supported_platforms and plat not in supported_platforms: - pytest.skip("cannot run on platform {}".format(plat)) + pytest.skip(f"cannot run on platform {plat}") then tests will be skipped if they were specified for a different platform. Let's do a little test file to show how this looks like: @@ -605,15 +604,14 @@ then you will see two tests skipped and two executed tests as expected: $ pytest -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items test_plat.py s.s. [100%] ========================= short test summary info ========================== - SKIPPED [2] conftest.py:12: cannot run on platform linux + SKIPPED [2] conftest.py:13: cannot run on platform linux ======================= 2 passed, 2 skipped in 0.12s ======================= Note that if you specify a platform via the marker-command line option like this: @@ -622,8 +620,7 @@ Note that if you specify a platform via the marker-command line option like this $ pytest -m linux =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items / 3 deselected / 1 selected @@ -686,8 +683,7 @@ We can now use the ``-m option`` to select one set: $ pytest -m interface --tb=short =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items / 2 deselected / 2 selected @@ -713,8 +709,7 @@ or to select both "event" and "interface" tests: $ pytest -m "interface or event" --tb=short =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items / 1 deselected / 3 selected diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index 876c9c872f1..efb701b1f16 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -9,7 +9,7 @@ Working with non-python tests A basic example for specifying tests in Yaml files -------------------------------------------------------------- -.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py +.. _`pytest-yamlwsgi`: https://pypi.org/project/pytest-yamlwsgi/ Here is an example ``conftest.py`` (extracted from Ali Afshar's special purpose `pytest-yamlwsgi`_ plugin). This ``conftest.py`` will collect ``test*.yaml`` files and will execute the yaml-formatted content as custom tests: @@ -28,8 +28,7 @@ now execute the test specification: nonpython $ pytest test_simple.yaml =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project/nonpython collected 2 items @@ -65,7 +64,7 @@ consulted when reporting in ``verbose`` mode: nonpython $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project/nonpython collecting ... collected 2 items @@ -91,8 +90,7 @@ interesting to just look at the collection tree: nonpython $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project/nonpython collected 2 items diff --git a/doc/en/example/nonpython/conftest.py b/doc/en/example/nonpython/conftest.py index 7ec4134036c..bc39a1f6b20 100644 --- a/doc/en/example/nonpython/conftest.py +++ b/doc/en/example/nonpython/conftest.py @@ -18,8 +18,8 @@ def collect(self): class YamlItem(pytest.Item): - def __init__(self, name, parent, spec): - super().__init__(name, parent) + def __init__(self, *, spec, **kwargs): + super().__init__(**kwargs) self.spec = spec def runtest(self): diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 87ed1420694..df2859b14e0 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -160,8 +160,7 @@ objects, they are still using the default pytest representation: $ pytest test_time.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 8 items @@ -223,8 +222,7 @@ this is a fully self-contained example which you can run with: $ pytest test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items @@ -238,17 +236,16 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia $ pytest --collect-only test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items - - - - + + + + ======================== 4 tests collected in 0.12s ======================== @@ -317,8 +314,7 @@ Let's first see how it looks like at collection time: $ pytest test_backends.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -416,7 +412,7 @@ The result of this test will be successful: $ pytest -v test_indirect_list.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 1 item @@ -506,8 +502,12 @@ Running it results in some skips if we don't have all the python interpreters in .. code-block:: pytest . $ pytest -rs -q multipython.py - ........................... [100%] - 27 passed in 0.12s + sssssssssssssssssssssssssss [100%] + ========================= short test summary info ========================== + SKIPPED [9] multipython.py:69: 'python3.5' not found + SKIPPED [9] multipython.py:69: 'python3.6' not found + SKIPPED [9] multipython.py:69: 'python3.7' not found + 27 skipped in 0.12s Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- @@ -567,15 +567,14 @@ If you run this with reporting for skips enabled: $ pytest -rs test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items test_module.py .s [100%] ========================= short test summary info ========================== - SKIPPED [1] conftest.py:12: could not import 'opt2': No module named 'opt2' + SKIPPED [1] test_module.py:3: could not import 'opt2': No module named 'opt2' ======================= 1 passed, 1 skipped in 0.12s ======================= You'll see that we don't have an ``opt2`` module and thus the second test run @@ -629,7 +628,7 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker: $ pytest -v -m basic =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 24 items / 21 deselected / 3 selected @@ -658,18 +657,15 @@ Use :func:`pytest.raises` with the :ref:`pytest.mark.parametrize ref` decorator to write parametrized tests in which some tests raise exceptions and others do not. -It is helpful to define a no-op context manager ``does_not_raise`` to serve -as a complement to ``raises``. For example: +It may be helpful to use ``nullcontext`` as a complement to ``raises``. -.. code-block:: python +For example: - from contextlib import contextmanager - import pytest +.. code-block:: python + from contextlib import nullcontext as does_not_raise - @contextmanager - def does_not_raise(): - yield + import pytest @pytest.mark.parametrize( @@ -688,22 +684,3 @@ as a complement to ``raises``. For example: In the example above, the first three test cases should run unexceptionally, while the fourth should raise ``ZeroDivisionError``. - -If you're only supporting Python 3.7+, you can simply use ``nullcontext`` -to define ``does_not_raise``: - -.. code-block:: python - - from contextlib import nullcontext as does_not_raise - -Or, if you're supporting Python 3.3+ you can use: - -.. code-block:: python - - from contextlib import ExitStack as does_not_raise - -Or, if desired, you can ``pip install contextlib2`` and use: - -.. code-block:: python - - from contextlib2 import nullcontext as does_not_raise diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 8b7f05bb76d..b9c2386ac51 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -147,15 +147,14 @@ The test collection would look like this: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project, configfile: pytest.ini collected 2 items - - + + ======================== 2 tests collected in 0.12s ======================== @@ -209,16 +208,15 @@ You can always peek at the collection tree without running tests like this: . $ pytest --collect-only pythoncollection.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project, configfile: pytest.ini collected 3 items - - + + ======================== 3 tests collected in 0.12s ======================== @@ -291,8 +289,7 @@ file will be left out: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project, configfile: pytest.ini collected 0 items diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index ff814bd754a..4c1ae1c05af 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -9,8 +9,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: assertion $ pytest failure_demo.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project/assertion collected 44 items @@ -156,7 +155,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > assert [0, 1, 2] == [0, 1, 3] E assert [0, 1, 2] == [0, 1, 3] E At index 2 diff: 2 != 3 - E Use -v to get the full diff + E Use -v to get more diff failure_demo.py:63: AssertionError ______________ TestSpecialisedExplanations.test_eq_list_long _______________ @@ -169,7 +168,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > assert a == b E assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...] E At index 100 diff: 1 != 2 - E Use -v to get the full diff + E Use -v to get more diff failure_demo.py:68: AssertionError _________________ TestSpecialisedExplanations.test_eq_dict _________________ @@ -216,7 +215,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > assert [1, 2] == [1, 2, 3] E assert [1, 2] == [1, 2, 3] E Right contains one more item: 3 - E Use -v to get the full diff + E Use -v to get more diff failure_demo.py:77: AssertionError _________________ TestSpecialisedExplanations.test_in_list _________________ diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 3b9963f09d6..e62060f191c 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -232,8 +232,7 @@ directory with the above conftest.py: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 0 items @@ -297,8 +296,7 @@ and when running it will see a skipped "slow" test: $ pytest -rs # "-rs" means report details on the little 's' =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -314,8 +312,7 @@ Or run it including the ``slow`` marked test: $ pytest --runslow =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -345,7 +342,7 @@ Example: def checkconfig(x): __tracebackhide__ = True if not hasattr(x, "config"): - pytest.fail("not configured: {}".format(x)) + pytest.fail(f"not configured: {x}") def test_something(): @@ -379,6 +376,7 @@ this to make sure unexpected exception types aren't hidden: .. code-block:: python import operator + import pytest @@ -389,7 +387,7 @@ this to make sure unexpected exception types aren't hidden: def checkconfig(x): __tracebackhide__ = operator.methodcaller("errisinstance", ConfigException) if not hasattr(x, "config"): - raise ConfigException("not configured: {}".format(x)) + raise ConfigException(f"not configured: {x}") def test_something(): @@ -458,8 +456,7 @@ which will add the string to the test header accordingly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y project deps: mylib-1.1 rootdir: /home/sweet/project collected 0 items @@ -487,7 +484,7 @@ which will add info only when run with "--v": $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache info1: did you know that ... did you? @@ -502,8 +499,7 @@ and nothing when run plainly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 0 items @@ -542,8 +538,7 @@ Now we can profile which test functions execute the slowest: $ pytest --durations=3 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items @@ -571,6 +566,7 @@ an ``incremental`` marker which is to be used on classes: # content of conftest.py from typing import Dict, Tuple + import pytest # store history of failures per test class name and per index in parametrize (if parametrize used) @@ -614,7 +610,7 @@ an ``incremental`` marker which is to be used on classes: test_name = _test_failed_incremental[cls_name].get(parametrize_index, None) # if name found, test has failed for the combination of class name & test name if test_name is not None: - pytest.xfail("previous test failed ({})".format(test_name)) + pytest.xfail(f"previous test failed ({test_name})") These two hook implementations work together to abort incremental-marked @@ -648,8 +644,7 @@ If we run this: $ pytest -rx =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items @@ -666,8 +661,7 @@ If we run this: test_step.py:11: AssertionError ========================= short test summary info ========================== - XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification) + XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification) ================== 1 failed, 2 passed, 1 xfailed in 0.12s ================== We'll see that ``test_deletion`` was not executed because ``test_modification`` @@ -732,8 +726,7 @@ We can run this: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 7 items @@ -810,9 +803,10 @@ case we just write some information out to a ``failures`` file: # content of conftest.py - import pytest import os.path + import pytest + @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): @@ -851,8 +845,7 @@ and run them: $ pytest test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -958,8 +951,7 @@ and run it: $ pytest -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items @@ -1076,6 +1068,7 @@ like ``pytest-timeout`` they must be imported explicitly and passed on to pytest # contents of app_main.py import sys + import pytest_timeout # Third party plugin if len(sys.argv) > 1 and sys.argv[1] == "--pytest": diff --git a/doc/en/explanation/flaky.rst b/doc/en/explanation/flaky.rst index 50121c7a761..ccf3fbb2b0c 100644 --- a/doc/en/explanation/flaky.rst +++ b/doc/en/explanation/flaky.rst @@ -94,7 +94,7 @@ Mark Lapierre discusses the `Pros and Cons of Quarantined Tests `_ and rerun failed tests. +Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests `_ and rerun failed tests. diff --git a/doc/en/explanation/goodpractices.rst b/doc/en/explanation/goodpractices.rst index 32a14991ae2..6b32875d3d1 100644 --- a/doc/en/explanation/goodpractices.rst +++ b/doc/en/explanation/goodpractices.rst @@ -12,41 +12,26 @@ For development, we recommend you use :mod:`venv` for virtual environments and as well as the ``pytest`` package itself. This ensures your code and dependencies are isolated from your system Python installation. -Next, place a ``pyproject.toml`` file in the root of your package: +Create a ``pyproject.toml`` file in the root of your repository as described in +:doc:`packaging:tutorials/packaging-projects`. +The first few lines should look like this: .. code-block:: toml [build-system] - requires = ["setuptools>=42", "wheel"] - build-backend = "setuptools.build_meta" + requires = ["hatchling"] + build-backend = "hatchling.build" -and a ``setup.cfg`` file containing your package's metadata with the following minimum content: - -.. code-block:: ini - - [metadata] - name = PACKAGENAME - - [options] - packages = find: + [project] + name = "PACKAGENAME" where ``PACKAGENAME`` is the name of your package. -.. note:: - - If your pip version is older than ``21.3``, you'll also need a ``setup.py`` file: - - .. code-block:: python - - from setuptools import setup - - setup() - You can then install your package in "editable" mode by running from the same directory: .. code-block:: bash - pip install -e . + pip install -e . which lets you change your source code (both tests and application) and rerun tests at will. @@ -89,11 +74,11 @@ to keep tests separate from actual application code (often a good idea): .. code-block:: text pyproject.toml - setup.cfg - mypkg/ - __init__.py - app.py - view.py + src/ + mypkg/ + __init__.py + app.py + view.py tests/ test_app.py test_view.py @@ -103,83 +88,56 @@ This has the following benefits: * Your tests can run against an installed version after executing ``pip install .``. * Your tests can run against the local copy with an editable install after executing ``pip install --editable .``. -* If you don't use an editable install and are relying on the fact that Python by default puts the current - directory in ``sys.path`` to import your package, you can execute ``python -m pytest`` to execute the tests against the - local copy directly, without using ``pip``. -.. note:: +For new projects, we recommend to use ``importlib`` :ref:`import mode ` +(see which-import-mode_ for a detailed explanation). +To this end, add the following to your ``pyproject.toml``: - See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and - ``python -m pytest``. +.. code-block:: toml -Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode ` -(which is the default): your test files must have **unique names**, because -``pytest`` will import them as *top-level* modules since there are no packages -to derive a full package name from. In other words, the test files in the example above will -be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to -``sys.path``. + [tool.pytest.ini_options] + addopts = [ + "--import-mode=importlib", + ] -If you need to have test modules with the same name, you might add ``__init__.py`` files to your -``tests`` folder and subfolders, changing them to packages: +.. _src-layout: -.. code-block:: text +Generally, but especially if you use the default import mode ``prepend``, +it is **strongly** suggested to use a ``src`` layout. +Here, your application root package resides in a sub-directory of your root, +i.e. ``src/mypkg/`` instead of ``mypkg``. - pyproject.toml - setup.cfg - mypkg/ - ... - tests/ - __init__.py - foo/ - __init__.py - test_view.py - bar/ - __init__.py - test_view.py +This layout prevents a lot of common pitfalls and has many benefits, +which are better explained in this excellent `blog post`_ by Ionel Cristian Mărieș. -Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing -you to have modules with the same name. But now this introduces a subtle problem: in order to load -the test modules from the ``tests`` directory, pytest prepends the root of the repository to -``sys.path``, which adds the side-effect that now ``mypkg`` is also importable. +.. _blog post: https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure> -This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment, -because you want to test the *installed* version of your package, not the local code from the repository. +.. note:: -.. _`src-layout`: + If you do not use an editable install and use the ``src`` layout as above you need to extend the Python's + search path for module files to execute the tests against the local copy directly. You can do it in an + ad-hoc manner by setting the ``PYTHONPATH`` environment variable: -In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a -sub-directory of your root: + .. code-block:: bash -.. code-block:: text + PYTHONPATH=src pytest - pyproject.toml - setup.cfg - src/ - mypkg/ - __init__.py - app.py - view.py - tests/ - __init__.py - foo/ - __init__.py - test_view.py - bar/ - __init__.py - test_view.py + or in a permanent manner by using the :confval:`pythonpath` configuration variable and adding the + following to your ``pyproject.toml``: + .. code-block:: toml -This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent -`blog post by Ionel Cristian Mărieș `_. + [tool.pytest.ini_options] + pythonpath = "src" .. note:: - The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't have - any of the drawbacks above because ``sys.path`` is not changed when importing - test modules, so users that run - into this issue are strongly encouraged to try it and report if the new option works well for them. - The ``src`` directory layout is still strongly recommended however. + If you do not use an editable install and not use the ``src`` layout (``mypkg`` directly in the root + directory) you can rely on the fact that Python by default puts the current directory in ``sys.path`` to + import your package and run ``python -m pytest`` to execute the tests against the local copy directly. + See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and + ``python -m pytest``. Tests as part of application code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -191,12 +149,11 @@ want to distribute them along with your application: .. code-block:: text pyproject.toml - setup.cfg - mypkg/ + [src/]mypkg/ __init__.py app.py view.py - test/ + tests/ __init__.py test_app.py test_view.py @@ -254,6 +211,56 @@ Note that this layout also works in conjunction with the ``src`` layout mentione much less surprising. +.. _which-import-mode: + +Choosing an import mode +^^^^^^^^^^^^^^^^^^^^^^^ + +For historical reasons, pytest defaults to the ``prepend`` :ref:`import mode ` +instead of the ``importlib`` import mode we recommend for new projects. +The reason lies in the way the ``prepend`` mode works: + +Since there are no packages to derive a full package name from, +``pytest`` will import your test files as *top-level* modules. +The test files in the first example (:ref:`src layout `) would be imported as +``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to ``sys.path``. + +This results in a drawback compared to the import mode ``importlib``: +your test files must have **unique names**. + +If you need to have test modules with the same name, +as a workaround you might add ``__init__.py`` files to your ``tests`` folder and subfolders, +changing them to packages: + +.. code-block:: text + + pyproject.toml + mypkg/ + ... + tests/ + __init__.py + foo/ + __init__.py + test_view.py + bar/ + __init__.py + test_view.py + +Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, +allowing you to have modules with the same name. +But now this introduces a subtle problem: +in order to load the test modules from the ``tests`` directory, +pytest prepends the root of the repository to ``sys.path``, +which adds the side-effect that now ``mypkg`` is also importable. + +This is problematic if you are using a tool like tox_ to test your package in a virtual environment, +because you want to test the *installed* version of your package, +not the local code from the repository. + +The ``importlib`` import mode does not have any of the drawbacks above, +because ``sys.path`` is not changed when importing test modules. + + .. _`buildout`: http://www.buildout.org/en/latest/ .. _`use tox`: @@ -263,8 +270,8 @@ tox Once you are done with your work and want to make sure that your actual package passes all tests you may want to look into :doc:`tox `, the -virtualenv test automation tool and its :doc:`pytest support `. -tox helps you to setup virtualenv environments with pre-defined +virtualenv test automation tool. +``tox`` helps you to setup virtualenv environments with pre-defined dependencies and then executing a pre-configured test command with options. It will run tests against the installed package and not against your source code checkout, helping to detect packaging diff --git a/doc/en/explanation/pythonpath.rst b/doc/en/explanation/pythonpath.rst index 2330356b863..5b533f47fdc 100644 --- a/doc/en/explanation/pythonpath.rst +++ b/doc/en/explanation/pythonpath.rst @@ -16,7 +16,7 @@ import process can be controlled through the ``--import-mode`` command-line flag these values: * ``prepend`` (default): the directory path containing each module will be inserted into the *beginning* - of :py:data:`sys.path` if not already there, and then imported with the :func:`__import__ <__import__>` builtin. + of :py:data:`sys.path` if not already there, and then imported with the :func:`importlib.import_module ` function. This requires test module names to be unique when the test directory tree is not arranged in packages, because the modules will put in :py:data:`sys.modules` after importing. @@ -24,7 +24,7 @@ these values: This is the classic mechanism, dating back from the time Python 2 was still supported. * ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already - there, and imported with ``__import__``. + there, and imported with :func:`importlib.import_module `. This better allows to run test modules against installed versions of a package even if the package under test has the same import root. For example: @@ -43,12 +43,21 @@ these values: Same as ``prepend``, requires test module names to be unique when the test directory tree is not arranged in packages, because the modules will put in :py:data:`sys.modules` after importing. -* ``importlib``: new in pytest-6.0, this mode uses :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`. +* ``importlib``: new in pytest-6.0, this mode uses more fine control mechanisms provided by :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`. - For this reason this doesn't require test module names to be unique, but also makes test - modules non-importable by each other. + For this reason this doesn't require test module names to be unique. + + One drawback however is that test modules are non-importable by each other. Also, utility + modules in the tests directories are not automatically importable because the tests directory is no longer + added to :py:data:`sys.path`. + + Initially we intended to make ``importlib`` the default in future releases, however it is clear now that + it has its own set of drawbacks so the default will remain ``prepend`` for the foreseeable future. + +.. seealso:: + + The :confval:`pythonpath` configuration variable. - We intend to make ``importlib`` the default in future releases, depending on feedback. ``prepend`` and ``append`` import modes scenarios ------------------------------------------------- diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index a013645d1f5..b36c2e3ccae 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -9,7 +9,7 @@ Get Started Install ``pytest`` ---------------------------------------- -``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3. +``pytest`` requires: Python 3.7+ or PyPy3. 1. Run the following command in your command line: @@ -22,7 +22,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 6.3.0.dev685+g581b021aa.d20210922 + pytest 7.2.2 .. _`simpletest`: @@ -47,8 +47,7 @@ The test $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item diff --git a/doc/en/history.rst b/doc/en/history.rst index 796a42486ca..bb5aa493022 100644 --- a/doc/en/history.rst +++ b/doc/en/history.rst @@ -139,7 +139,7 @@ project: which adds ``pytest`` (rather than ``py.test``) as the recommended command-line entry point -Due to this history, it's diffcult to answer the question when pytest was started. +Due to this history, it's difficult to answer the question when pytest was started. It depends what point should really be seen as the start of it all. One possible interpretation is to pick Europython 2004, i.e. around June/July 2004. diff --git a/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst index ab6cbdee7cf..1b10c131389 100644 --- a/doc/en/how-to/assert.rst +++ b/doc/en/how-to/assert.rst @@ -29,8 +29,7 @@ you will see the return value of the function call: $ pytest test_assert1.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -184,8 +183,7 @@ if you run this module: $ pytest test_assert2.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -203,7 +201,7 @@ if you run this module: E '1' E Extra items in the right set: E '5' - E Use -v to get the full diff + E Use -v to get more diff test_assert2.py:4: AssertionError ========================= short test summary info ========================== @@ -240,7 +238,7 @@ file which provides an alternative explanation for ``Foo`` objects: if isinstance(left, Foo) and isinstance(right, Foo) and op == "==": return [ "Comparing Foo instances:", - " vals: {} != {}".format(left.val, right.val), + f" vals: {left.val} != {right.val}", ] now, given this test module: diff --git a/doc/en/how-to/bash-completion.rst b/doc/en/how-to/bash-completion.rst index 245dfd6d9a8..117ff7ec13b 100644 --- a/doc/en/how-to/bash-completion.rst +++ b/doc/en/how-to/bash-completion.rst @@ -5,7 +5,7 @@ How to set up bash completion ============================= When using bash as your shell, ``pytest`` can use argcomplete -(https://argcomplete.readthedocs.io/) for auto-completion. +(https://kislyuk.github.io/argcomplete/) for auto-completion. For this ``argcomplete`` needs to be installed **and** enabled. Install argcomplete using: diff --git a/doc/en/how-to/cache.rst b/doc/en/how-to/cache.rst index 1ba048d955f..8554a984c72 100644 --- a/doc/en/how-to/cache.rst +++ b/doc/en/how-to/cache.rst @@ -86,8 +86,7 @@ If you then run it with ``--lf``: $ pytest --lf =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items run-last-failure: rerun previous 2 failures @@ -133,8 +132,7 @@ of ``FF`` and dots): $ pytest --ff =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 50 items run-last-failure: rerun previous 2 failures first @@ -201,7 +199,6 @@ across pytest invocations: # content of test_caching.py import pytest - import time def expensive_computation(): @@ -236,7 +233,7 @@ If you run this command for the first time, you can see the print statement: > assert mydata == 23 E assert 42 == 23 - test_caching.py:20: AssertionError + test_caching.py:19: AssertionError -------------------------- Captured stdout setup --------------------------- running expensive computation... ========================= short test summary info ========================== @@ -259,7 +256,7 @@ the cache and nothing will be printed: > assert mydata == 23 E assert 42 == 23 - test_caching.py:20: AssertionError + test_caching.py:19: AssertionError ========================= short test summary info ========================== FAILED test_caching.py::test_function - assert 42 == 23 1 failed in 0.12s @@ -277,8 +274,7 @@ You can always peek at the content of the cache using the $ pytest --cache-show =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project cachedir: /home/sweet/project/.pytest_cache --------------------------- cache values for '*' --------------------------- @@ -300,8 +296,7 @@ filtering: $ pytest --cache-show example/* =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project cachedir: /home/sweet/project/.pytest_cache ----------------------- cache values for 'example/*' ----------------------- diff --git a/doc/en/how-to/capture-stdout-stderr.rst b/doc/en/how-to/capture-stdout-stderr.rst index 14628df6164..9ccea719b64 100644 --- a/doc/en/how-to/capture-stdout-stderr.rst +++ b/doc/en/how-to/capture-stdout-stderr.rst @@ -83,8 +83,7 @@ of the failing function and hide the other one: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst index 4c89905e2c6..0390230b8ea 100644 --- a/doc/en/how-to/capture-warnings.rst +++ b/doc/en/how-to/capture-warnings.rst @@ -28,8 +28,7 @@ Running pytest now produces this output: $ pytest test_show_warnings.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -43,8 +42,20 @@ Running pytest now produces this output: -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html ======================= 1 passed, 1 warning in 0.12s ======================= -The ``-W`` flag can be passed to control which warnings will be displayed or even turn -them into errors: +.. _`controlling-warnings`: + +Controlling warnings +-------------------- + +Similar to Python's `warning filter`_ and :option:`-W option ` flag, pytest provides +its own ``-W`` flag to control which warnings are ignored, displayed, or turned into +errors. See the `warning filter`_ documentation for more +advanced use-cases. + +.. _`warning filter`: https://docs.python.org/3/library/warnings.html#warning-filter + +This code sample shows how to treat any ``UserWarning`` category class of warning +as an error: .. code-block:: pytest @@ -97,9 +108,18 @@ all other warnings into errors. When a warning matches more than one option in the list, the action for the last matching option is performed. -Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own -:option:`-W option ` and :func:`warnings.simplefilter`, so please refer to those sections in the Python -documentation for other examples and advanced usage. + +.. note:: + + The ``-W`` flag and the ``filterwarnings`` ini option use warning filters that are + similar in structure, but each configuration option interprets its filter + differently. For example, *message* in ``filterwarnings`` is a string containing a + regular expression that the start of the warning message must match, + case-insensitively, while *message* in ``-W`` is a literal string that the start of + the warning message must contain (case-insensitively), ignoring any whitespace at + the start or end of message. Consult the `warning filter`_ documentation for more + details. + .. _`filterwarnings`: @@ -170,11 +190,14 @@ using an external system. DeprecationWarning and PendingDeprecationWarning ------------------------------------------------ - By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings from user code and third-party libraries, as recommended by :pep:`565`. This helps users keep their code modern and avoid breakages when deprecated warnings are effectively removed. +However, in the specific case where users capture any type of warnings in their test, either with +:func:`pytest.warns`, :func:`pytest.deprecated_call` or using the :ref:`recwarn ` fixture, +no warning will be displayed at all. + Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over (such as third-party libraries), in which case you might use the warning filters options (ini or marks) to ignore those warnings. @@ -191,6 +214,9 @@ For example: This will ignore all warnings of type ``DeprecationWarning`` where the start of the message matches the regular expression ``".*U.*mode is deprecated"``. +See :ref:`@pytest.mark.filterwarnings ` and +:ref:`Controlling warnings ` for more examples. + .. note:: If warnings are configured at the interpreter level, using @@ -239,14 +265,15 @@ when called with a ``17`` argument. Asserting warnings with the warns function ------------------------------------------ - - You can check that code raises a particular warning using :func:`pytest.warns`, -which works in a similar manner to :ref:`raises `: +which works in a similar manner to :ref:`raises ` (except that +:ref:`raises ` does not capture all exceptions, only the +``expected_exception``): .. code-block:: python import warnings + import pytest @@ -254,21 +281,35 @@ which works in a similar manner to :ref:`raises `: with pytest.warns(UserWarning): warnings.warn("my warning", UserWarning) -The test will fail if the warning in question is not raised. The keyword -argument ``match`` to assert that the exception matches a text or regex:: +The test will fail if the warning in question is not raised. Use the keyword +argument ``match`` to assert that the warning matches a text or regex. +To match a literal string that may contain regular expression metacharacters like ``(`` or ``.``, the pattern can +first be escaped with ``re.escape``. + +Some examples: - >>> with warns(UserWarning, match='must be 0 or None'): +.. code-block:: pycon + + + >>> with warns(UserWarning, match="must be 0 or None"): ... warnings.warn("value must be 0 or None", UserWarning) + ... - >>> with warns(UserWarning, match=r'must be \d+$'): + >>> with warns(UserWarning, match=r"must be \d+$"): ... warnings.warn("value must be 42", UserWarning) + ... - >>> with warns(UserWarning, match=r'must be \d+$'): + >>> with warns(UserWarning, match=r"must be \d+$"): ... warnings.warn("this is not here", UserWarning) + ... Traceback (most recent call last): ... Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... + >>> with warns(UserWarning, match=re.escape("issue with foo() func")): + ... warnings.warn("issue with foo() func") + ... + You can also call :func:`pytest.warns` on a function or code string: .. code-block:: python @@ -345,6 +386,49 @@ warnings, or index into it to get a particular recorded warning. Full API: :class:`~_pytest.recwarn.WarningsRecorder`. +.. _`warns use cases`: + +Additional use cases of warnings in tests +----------------------------------------- + +Here are some use cases involving warnings that often come up in tests, and suggestions on how to deal with them: + +- To ensure that **at least one** of the indicated warnings is issued, use: + +.. code-block:: python + + def test_warning(): + with pytest.warns((RuntimeWarning, UserWarning)): + ... + +- To ensure that **only** certain warnings are issued, use: + +.. code-block:: python + + def test_warning(recwarn): + ... + assert len(recwarn) == 1 + user_warning = recwarn.pop(UserWarning) + assert issubclass(user_warning.category, UserWarning) + +- To ensure that **no** warnings are emitted, use: + +.. code-block:: python + + def test_warning(): + with warnings.catch_warnings(): + warnings.simplefilter("error") + ... + +- To suppress warnings, use: + +.. code-block:: python + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + ... + + .. _custom_failure_messages: Custom failure messages @@ -404,3 +488,18 @@ Please read our :ref:`backwards-compatibility` to learn how we proceed about dep features. The full list of warnings is listed in :ref:`the reference documentation `. + + +.. _`resource-warnings`: + +Resource Warnings +----------------- + +Additional information of the source of a :class:`ResourceWarning` can be obtained when captured by pytest if +:mod:`tracemalloc` module is enabled. + +One convenient way to enable :mod:`tracemalloc` when running tests is to set the :envvar:`PYTHONTRACEMALLOC` to a large +enough number of frames (say ``20``, but that number is application dependent). + +For more information, consult the `Python Development Mode `__ +section in the Python documentation. diff --git a/doc/en/how-to/doctest.rst b/doc/en/how-to/doctest.rst index 29e689e1290..021ba274fbc 100644 --- a/doc/en/how-to/doctest.rst +++ b/doc/en/how-to/doctest.rst @@ -30,8 +30,7 @@ then you can just invoke ``pytest`` directly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -59,8 +58,7 @@ and functions, including from test modules: $ pytest --doctest-modules =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -128,14 +126,17 @@ pytest also introduces new options: in expected doctest output. * ``NUMBER``: when enabled, floating-point numbers only need to match as far as - the precision you have written in the expected doctest output. For example, - the following output would only need to match to 2 decimal places:: + the precision you have written in the expected doctest output. The numbers are + compared using :func:`pytest.approx` with relative tolerance equal to the + precision. For example, the following output would only need to match to 2 + decimal places when comparing ``3.14`` to + ``pytest.approx(math.pi, rel=10**-2)``:: >>> math.pi 3.14 - If you wrote ``3.1416`` then the actual output would need to match to 4 - decimal places; and so on. + If you wrote ``3.1416`` then the actual output would need to match to + approximately 4 decimal places; and so on. This avoids false positives caused by limited floating-point precision, like this:: @@ -241,7 +242,6 @@ which can then be used in your doctests directly: >>> len(a) 10 """ - pass Note that like the normal ``conftest.py``, the fixtures are discovered in the directory tree conftest is in. Meaning that if you put your doctest with your source code, the relevant conftest.py needs to be in the same directory tree. diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index e0ac36e792d..3acf39d0fa0 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -398,9 +398,10 @@ access the fixture function: .. code-block:: python # content of conftest.py - import pytest import smtplib + import pytest + @pytest.fixture(scope="module") def smtp_connection(): @@ -432,8 +433,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this: $ pytest test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -610,10 +610,10 @@ Here's what that might look like: .. code-block:: python # content of test_emaillib.py - import pytest - from emaillib import Email, MailAdminClient + import pytest + @pytest.fixture def mail_admin(): @@ -631,6 +631,7 @@ Here's what that might look like: def receiving_user(mail_admin): user = mail_admin.create_user() yield user + user.clear_mailbox() mail_admin.delete_user(user) @@ -684,10 +685,10 @@ Here's how the previous example would look using the ``addfinalizer`` method: .. code-block:: python # content of test_emaillib.py - import pytest - from emaillib import Email, MailAdminClient + import pytest + @pytest.fixture def mail_admin(): @@ -737,6 +738,87 @@ does offer some nuances for when you're in a pinch. . [100%] 1 passed in 0.12s +Note on finalizer order +"""""""""""""""""""""""" + +Finalizers are executed in a first-in-last-out order. +For yield fixtures, the first teardown code to run is from the right-most fixture, i.e. the last test parameter. + + +.. code-block:: python + + # content of test_finalizers.py + import pytest + + + def test_bar(fix_w_yield1, fix_w_yield2): + print("test_bar") + + + @pytest.fixture + def fix_w_yield1(): + yield + print("after_yield_1") + + + @pytest.fixture + def fix_w_yield2(): + yield + print("after_yield_2") + + +.. code-block:: pytest + + $ pytest -s test_finalizers.py + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + collected 1 item + + test_finalizers.py test_bar + .after_yield_2 + after_yield_1 + + + ============================ 1 passed in 0.12s ============================= + +For finalizers, the first fixture to run is last call to `request.addfinalizer`. + +.. code-block:: python + + # content of test_finalizers.py + from functools import partial + import pytest + + + @pytest.fixture + def fix_w_finalizers(request): + request.addfinalizer(partial(print, "finalizer_2")) + request.addfinalizer(partial(print, "finalizer_1")) + + + def test_bar(fix_w_finalizers): + print("test_bar") + + +.. code-block:: pytest + + $ pytest -s test_finalizers.py + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + collected 1 item + + test_finalizers.py test_bar + .finalizer_1 + finalizer_2 + + + ============================ 1 passed in 0.12s ============================= + +This is so because yield fixtures use `addfinalizer` behind the scenes: when the fixture executes, `addfinalizer` registers a function that resumes the generator, which in turn calls the teardown code. + + .. _`safe teardowns`: Safe teardowns @@ -753,10 +835,10 @@ above): .. code-block:: python # content of test_emaillib.py - import pytest - from emaillib import Email, MailAdminClient + import pytest + @pytest.fixture def setup(): @@ -1031,16 +1113,17 @@ read an optional server URL from the test module which uses our fixture: .. code-block:: python # content of conftest.py - import pytest import smtplib + import pytest + @pytest.fixture(scope="module") def smtp_connection(request): server = getattr(request.module, "smtpserver", "smtp.gmail.com") smtp_connection = smtplib.SMTP(server, 587, timeout=5) yield smtp_connection - print("finalizing {} ({})".format(smtp_connection, server)) + print(f"finalizing {smtp_connection} ({server})") smtp_connection.close() We use the ``request.module`` attribute to optionally obtain an @@ -1194,15 +1277,16 @@ through the special :py:class:`request ` object: .. code-block:: python # content of conftest.py - import pytest import smtplib + import pytest + @pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"]) def smtp_connection(request): smtp_connection = smtplib.SMTP(request.param, 587, timeout=5) yield smtp_connection - print("finalizing {}".format(smtp_connection)) + print(f"finalizing {smtp_connection}") smtp_connection.close() The main change is the declaration of ``params`` with @@ -1331,16 +1415,17 @@ Running the above tests results in the following test IDs being used: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project - collected 11 items + collected 12 items + + @@ -1352,7 +1437,7 @@ Running the above tests results in the following test IDs being used: - ======================= 11 tests collected in 0.12s ======================== + ======================= 12 tests collected in 0.12s ======================== .. _`fixture-parametrize-marks`: @@ -1384,7 +1469,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``: $ pytest test_fixture_marks.py -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 3 items @@ -1434,7 +1519,7 @@ Here we declare an ``app`` fixture which receives the previously defined $ pytest -v test_appsetup.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 2 items @@ -1505,7 +1590,7 @@ to show the setup/teardown flow: def test_2(otherarg, modarg): - print(" RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg)) + print(f" RUN test2 with otherarg {otherarg} and modarg {modarg}") Let's run the tests in verbose mode and with looking at the print-output: @@ -1514,7 +1599,7 @@ Let's run the tests in verbose mode and with looking at the print-output: $ pytest -v -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 8 items @@ -1606,6 +1691,7 @@ and declare its use in a test module via a ``usefixtures`` marker: # content of test_setenv.py import os + import pytest @@ -1686,8 +1772,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest @@ -1702,8 +1786,6 @@ Given the tests file structure is: assert username == 'username' subfolder/ - __init__.py - conftest.py # content of tests/subfolder/conftest.py import pytest @@ -1712,8 +1794,8 @@ Given the tests file structure is: def username(username): return 'overridden-' + username - test_something.py - # content of tests/subfolder/test_something.py + test_something_else.py + # content of tests/subfolder/test_something_else.py def test_username(username): assert username == 'overridden-username' @@ -1729,8 +1811,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest @@ -1772,8 +1852,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest @@ -1810,8 +1888,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest diff --git a/doc/en/how-to/logging.rst b/doc/en/how-to/logging.rst index 2e8734fa6a3..c99835a2baf 100644 --- a/doc/en/how-to/logging.rst +++ b/doc/en/how-to/logging.rst @@ -73,7 +73,6 @@ messages. This is supported by the ``caplog`` fixture: def test_foo(caplog): caplog.set_level(logging.INFO) - pass By default the level is set on the root logger, however as a convenience it is also possible to set the log level of any @@ -83,7 +82,6 @@ logger: def test_foo(caplog): caplog.set_level(logging.CRITICAL, logger="root.baz") - pass The log levels set are restored automatically at the end of the test. @@ -161,9 +159,7 @@ the records for the ``setup`` and ``call`` stages during teardown like so: x.message for x in caplog.get_records(when) if x.levelno == logging.WARNING ] if messages: - pytest.fail( - "warning messages encountered during testing: {}".format(messages) - ) + pytest.fail(f"warning messages encountered during testing: {messages}") @@ -180,8 +176,8 @@ logging records as they are emitted directly into the console. You can specify the logging level for which log records with equal or higher level are printed to the console by passing ``--log-cli-level``. This setting -accepts the logging level names as seen in python's documentation or an integer -as the logging level num. +accepts the logging level names or numeric values as seen in +:ref:`logging's documentation `. Additionally, you can also specify ``--log-cli-format`` and ``--log-cli-date-format`` which mirror and default to ``--log-format`` and @@ -198,11 +194,12 @@ option names are: If you need to record the whole test suite logging calls to a file, you can pass ``--log-file=/path/to/log/file``. This log file is opened in write mode which means that it will be overwritten at each run tests session. +Note that relative paths for the log-file location, whether passed on the CLI or declared in a +config file, are always resolved relative to the current working directory. You can also specify the logging level for the log file by passing -``--log-file-level``. This setting accepts the logging level names as seen in -python's documentation(ie, uppercased level names) or an integer as the logging -level num. +``--log-file-level``. This setting accepts the logging level names or numeric +values as seen in :ref:`logging's documentation `. Additionally, you can also specify ``--log-file-format`` and ``--log-file-date-format`` which are equal to ``--log-format`` and diff --git a/doc/en/how-to/monkeypatch.rst b/doc/en/how-to/monkeypatch.rst index 9c61233f7e5..81edd00bda5 100644 --- a/doc/en/how-to/monkeypatch.rst +++ b/doc/en/how-to/monkeypatch.rst @@ -3,7 +3,7 @@ How to monkeypatch/mock modules and environments ================================================================ -.. currentmodule:: _pytest.monkeypatch +.. currentmodule:: pytest Sometimes tests need to invoke functionality which depends on global settings or which invokes code which cannot be easily @@ -14,17 +14,16 @@ environment variable, or to modify ``sys.path`` for importing. The ``monkeypatch`` fixture provides these helper methods for safely patching and mocking functionality in tests: -.. code-block:: python +* :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` +* :meth:`monkeypatch.delattr(obj, name, raising=True) ` +* :meth:`monkeypatch.setitem(mapping, name, value) ` +* :meth:`monkeypatch.delitem(obj, name, raising=True) ` +* :meth:`monkeypatch.setenv(name, value, prepend=None) ` +* :meth:`monkeypatch.delenv(name, raising=True) ` +* :meth:`monkeypatch.syspath_prepend(path) ` +* :meth:`monkeypatch.chdir(path) ` +* :meth:`monkeypatch.context() ` - monkeypatch.setattr(obj, name, value, raising=True) - monkeypatch.setattr("somemodule.obj.name", value, raising=True) - monkeypatch.delattr(obj, name, raising=True) - monkeypatch.setitem(mapping, name, value) - monkeypatch.delitem(obj, name, raising=True) - monkeypatch.setenv(name, value, prepend=None) - monkeypatch.delenv(name, raising=True) - monkeypatch.syspath_prepend(path) - monkeypatch.chdir(path) All modifications will be undone after the requesting test function or fixture has finished. The ``raising`` @@ -55,13 +54,16 @@ during a test. 5. Use :py:meth:`monkeypatch.syspath_prepend ` to modify ``sys.path`` which will also call ``pkg_resources.fixup_namespace_packages`` and :py:func:`importlib.invalidate_caches`. +6. Use :py:meth:`monkeypatch.context ` to apply patches only in a specific scope, which can help +control teardown of complex fixtures or patches to the stdlib. + See the `monkeypatch blog post`_ for some introduction material and a discussion of its motivation. .. _`monkeypatch blog post`: https://tetamap.wordpress.com//2009/03/03/monkeypatching-in-unit-tests-done-right/ -Simple example: monkeypatching functions ----------------------------------------- +Monkeypatching functions +------------------------ Consider a scenario where you are working with user directories. In the context of testing, you do not want your test to depend on the running user. ``monkeypatch`` @@ -436,7 +438,7 @@ separate fixtures for each potential mock and reference them in the needed tests _ = app.create_connection_string() -.. currentmodule:: _pytest.monkeypatch +.. currentmodule:: pytest API Reference ------------- diff --git a/doc/en/how-to/nose.rst b/doc/en/how-to/nose.rst index 4bf8b06c324..a736dfa55a7 100644 --- a/doc/en/how-to/nose.rst +++ b/doc/en/how-to/nose.rst @@ -5,6 +5,9 @@ How to run tests written for nose ``pytest`` has basic support for running tests written for nose_. +.. warning:: + This functionality has been deprecated and is likely to be removed in ``pytest 8.x``. + .. _nosestyle: Usage @@ -23,8 +26,8 @@ make use of pytest's capabilities. Supported nose Idioms ---------------------- -* setup and teardown at module/class/method level -* SkipTest exceptions and markers +* ``setup()`` and ``teardown()`` at module/class/method level: any function or method called ``setup`` will be called during the setup phase for each test, same for ``teardown``. +* ``SkipTest`` exceptions and markers * setup/teardown decorators * ``__test__`` attribute on modules/classes/functions * general usage of nose utilities diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 38de25edc7b..7d900ce3fdb 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -12,8 +12,9 @@ Examples for modifying traceback printing: .. code-block:: bash - pytest --showlocals # show local variables in tracebacks - pytest -l # show local variables (shortcut) + pytest --showlocals # show local variables in tracebacks + pytest -l # show local variables (shortcut) + pytest --no-showlocals # hide local variables (if addopts enables them) pytest --tb=auto # (default) 'long' tracebacks for the first and last # entry, but 'short' style for the other entries @@ -84,7 +85,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo > assert fruits1 == fruits2 E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi'] E At index 2 diff: 'grapes' != 'orange' - E Use -v to get the full diff + E Use -v to get more diff test_verbosity_example.py:8: AssertionError ____________________________ test_numbers_fail _____________________________ @@ -99,7 +100,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo E {'1': 1, '2': 2, '3': 3, '4': 4} E Right contains 4 more items: E {'10': 10, '20': 20, '30': 30, '40': 40} - E Use -v to get the full diff + E Use -v to get more diff test_verbosity_example.py:14: AssertionError ___________________________ test_long_text_fail ____________________________ @@ -323,8 +324,7 @@ Example: $ pytest -ra =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 6 items @@ -349,8 +349,7 @@ Example: test_example.py:14: AssertionError ========================= short test summary info ========================== SKIPPED [1] test_example.py:22: skipping this test - XFAIL test_example.py::test_xfail - reason: xfailing this test + XFAIL test_example.py::test_xfail - reason: xfailing this test XPASS test_example.py::test_xpass always xfail ERROR test_example.py::test_error - assert 0 FAILED test_example.py::test_fail - assert 0 @@ -381,8 +380,7 @@ More than one character can be used, so for example to only see failed and skipp $ pytest -rfs =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 6 items @@ -417,8 +415,7 @@ captured output: $ pytest -rpP =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 6 items diff --git a/doc/en/how-to/parametrize.rst b/doc/en/how-to/parametrize.rst index 240016601be..a0c9968428c 100644 --- a/doc/en/how-to/parametrize.rst +++ b/doc/en/how-to/parametrize.rst @@ -56,8 +56,7 @@ them in turn: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items @@ -168,8 +167,7 @@ Let's run this: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items diff --git a/doc/en/how-to/plugins.rst b/doc/en/how-to/plugins.rst index cae737e96ed..7d5bcd85a31 100644 --- a/doc/en/how-to/plugins.rst +++ b/doc/en/how-to/plugins.rst @@ -21,7 +21,7 @@ there is no need to activate it. Here is a little annotated list for some popular plugins: * :pypi:`pytest-django`: write tests - for :std:doc:`django ` apps, using pytest integration. + for `django `_ apps, using pytest integration. * :pypi:`pytest-twisted`: write tests for `twisted `_ apps, starting a reactor and @@ -51,8 +51,8 @@ Here is a little annotated list for some popular plugins: * :pypi:`pytest-flakes`: check source code with pyflakes. -* :pypi:`oejskit`: - a plugin to run javascript unittests in live browsers. +* :pypi:`allure-pytest`: + report test results via `allure-framework `_. To see a complete list of all plugins with their latest testing status against different pytest and Python versions, please visit diff --git a/doc/en/how-to/skipping.rst b/doc/en/how-to/skipping.rst index 9b74628d59f..1fc6f5e0889 100644 --- a/doc/en/how-to/skipping.rst +++ b/doc/en/how-to/skipping.rst @@ -69,6 +69,7 @@ It is also possible to skip the whole module using .. code-block:: python import sys + import pytest if not sys.platform.startswith("win"): @@ -84,14 +85,14 @@ It is also possible to skip the whole module using If you wish to skip something conditionally then you can use ``skipif`` instead. Here is an example of marking a test function to be skipped -when run on an interpreter earlier than Python3.6: +when run on an interpreter earlier than Python3.10: .. code-block:: python import sys - @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") + @pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher") def test_function(): ... @@ -369,7 +370,7 @@ Here is a simple test file with the several usages: Running it with the report-on-xfail option gives this output: -.. FIXME: Use $ instead of ! again to reenable regendoc once it's fixed: +.. FIXME: Use $ instead of ! again to re-enable regendoc once it's fixed: https://github.com/pytest-dev/pytest/issues/8807 .. code-block:: pytest @@ -409,6 +410,7 @@ test instances when using parametrize: .. code-block:: python import sys + import pytest diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst index 3e95116dca9..a2f936d3352 100644 --- a/doc/en/how-to/tmp_path.rst +++ b/doc/en/how-to/tmp_path.rst @@ -36,8 +36,7 @@ Running this would result in a passed test except for the last $ pytest test_tmp_path.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -105,8 +104,21 @@ The ``tmpdir`` and ``tmpdir_factory`` fixtures The ``tmpdir`` and ``tmpdir_factory`` fixtures are similar to ``tmp_path`` and ``tmp_path_factory``, but use/return legacy `py.path.local`_ objects -rather than standard :class:`pathlib.Path` objects. These days, prefer to -use ``tmp_path`` and ``tmp_path_factory``. +rather than standard :class:`pathlib.Path` objects. + +.. note:: + These days, it is preferred to use ``tmp_path`` and ``tmp_path_factory``. + + In order to help modernize old code bases, one can run pytest with the legacypath + plugin disabled: + + .. code-block:: bash + + pytest -p no:legacypath + + This will trigger errors on tests using the legacy paths. + It can also be permanently set as part of the :confval:`addopts` parameter in the + config file. See :fixture:`tmpdir ` :fixture:`tmpdir_factory ` API for details. @@ -122,6 +134,10 @@ the system temporary directory. The base name will be ``pytest-NUM`` where ``NUM`` will be incremented with each test run. Moreover, entries older than 3 temporary directories will be removed. +The number of entries currently cannot be changed, but using the ``--basetemp`` +option will remove the directory before every run, effectively meaning the temporary directories +of only the most recent run will be kept. + You can override the default temporary directory setting like this: .. code-block:: bash diff --git a/doc/en/how-to/unittest.rst b/doc/en/how-to/unittest.rst index e2a23a1a785..37caf6e9fb7 100644 --- a/doc/en/how-to/unittest.rst +++ b/doc/en/how-to/unittest.rst @@ -27,12 +27,15 @@ Almost all ``unittest`` features are supported: * ``setUpClass/tearDownClass``; * ``setUpModule/tearDownModule``; +.. _`pytest-subtests`: https://github.com/pytest-dev/pytest-subtests .. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol +Additionally, :ref:`subtests ` are supported by the +`pytest-subtests`_ plugin. + Up to this point pytest does not have support for the following features: * `load_tests protocol`_; -* :ref:`subtests `; Benefits out of the box ----------------------- @@ -115,6 +118,7 @@ fixture definition: # content of test_unittest_db.py import unittest + import pytest @@ -136,8 +140,7 @@ the ``self.db`` values in the traceback: $ pytest test_unittest_db.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -154,7 +157,7 @@ the ``self.db`` values in the traceback: E AssertionError: .DummyDB object at 0xdeadbeef0001> E assert 0 - test_unittest_db.py:10: AssertionError + test_unittest_db.py:11: AssertionError ___________________________ MyTest.test_method2 ____________________________ self = @@ -164,7 +167,7 @@ the ``self.db`` values in the traceback: E AssertionError: .DummyDB object at 0xdeadbeef0001> E assert 0 - test_unittest_db.py:13: AssertionError + test_unittest_db.py:14: AssertionError ========================= short test summary info ========================== FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: `. +So pytest offers a better way to do this, :attr:`item.stash <_pytest.nodes.Node.stash>`. To use the "stash" in your plugins, first create "stash keys" somewhere at the top level of your plugin: diff --git a/doc/en/how-to/writing_plugins.rst b/doc/en/how-to/writing_plugins.rst index 28ed8f5f76d..4eee1b2e120 100644 --- a/doc/en/how-to/writing_plugins.rst +++ b/doc/en/how-to/writing_plugins.rst @@ -147,29 +147,32 @@ Making your plugin installable by others If you want to make your plugin externally available, you may define a so-called entry point for your distribution so -that ``pytest`` finds your plugin module. Entry points are -a feature that is provided by :std:doc:`setuptools:index`. pytest looks up -the ``pytest11`` entrypoint to discover its -plugins and you can thus make your plugin available by defining -it in your setuptools-invocation: +that ``pytest`` finds your plugin module. Entry points are +a feature that is provided by :std:doc:`setuptools `. -.. sourcecode:: python +pytest looks up the ``pytest11`` entrypoint to discover its +plugins, thus you can make your plugin available by defining +it in your ``pyproject.toml`` file. + +.. sourcecode:: toml + + # sample ./pyproject.toml file + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" - # sample ./setup.py file - from setuptools import setup + [project] + name = "myproject" + classifiers = [ + "Framework :: Pytest", + ] - setup( - name="myproject", - packages=["myproject"], - # the following makes a plugin available to pytest - entry_points={"pytest11": ["name_of_plugin = myproject.pluginmodule"]}, - # custom PyPI classifier for pytest plugins - classifiers=["Framework :: Pytest"], - ) + [project.entry-points.pytest11] + myproject = "myproject.pluginmodule" If a package is installed this way, ``pytest`` will load ``myproject.pluginmodule`` as a plugin which can define -:ref:`hooks `. +:ref:`hooks `. Confirm registration with ``pytest --trace-config`` .. note:: @@ -367,7 +370,7 @@ string value of ``Hello World!`` if we do not supply a value or ``Hello def _hello(name=None): if not name: name = request.config.getoption("name") - return "Hello {name}!".format(name=name) + return f"Hello {name}!" return _hello @@ -445,8 +448,7 @@ in our ``pytest.ini`` to tell pytest where to look for example files. $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project, configfile: pytest.ini collected 2 items diff --git a/doc/en/how-to/xunit_setup.rst b/doc/en/how-to/xunit_setup.rst index 5a97b2c85f1..3de6681ff8f 100644 --- a/doc/en/how-to/xunit_setup.rst +++ b/doc/en/how-to/xunit_setup.rst @@ -32,7 +32,7 @@ which will usually be called once for all the functions: .. code-block:: python def setup_module(module): - """ setup any state specific to the execution of the given module.""" + """setup any state specific to the execution of the given module.""" def teardown_module(module): @@ -63,6 +63,8 @@ and after all test methods of the class are called: setup_class. """ +.. _xunit-method-setup: + Method and function level setup/teardown ----------------------------------------------- diff --git a/doc/en/index.rst b/doc/en/index.rst index 2f771aa3c42..c277cc30721 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -2,9 +2,9 @@ .. sidebar:: Next Open Trainings - - `Professional Testing with Python `_, via `Python Academy `_, February 1st to 3rd, 2022, Leipzig (Germany) and remote. + - `Professional Testing with Python `_, via `Python Academy `_, March 7th to 9th 2023 (3 day in-depth training), Remote - Also see `previous talks and blogposts `_. + Also see :doc:`previous talks and blogposts `. .. _features: @@ -17,12 +17,10 @@ The ``pytest`` framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries. -**Pythons**: ``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3. +``pytest`` requires: Python 3.7+ or PyPy3. **PyPI package name**: :pypi:`pytest` -**Documentation as PDF**: `download latest `_ - A quick example --------------- @@ -44,8 +42,7 @@ To execute it: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y - cachedir: .pytest_cache + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -79,7 +76,7 @@ Features - Can run :ref:`unittest ` (including trial) and :ref:`nose ` test suites out of the box -- Python 3.6+ and PyPy 3 +- Python 3.7+ or PyPy 3 - Rich plugin architecture, with over 800+ :ref:`external plugins ` and thriving community @@ -99,11 +96,6 @@ Bugs/Requests Please use the `GitHub issue tracker `_ to submit bugs or request features. -Changelog ---------- - -Consult the :ref:`Changelog ` page for fixes and enhancements of each version. - Support pytest -------------- @@ -136,13 +128,3 @@ Security pytest has never been associated with a security vulnerability, but in any case, to report a security vulnerability please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. - - -License -------- - -Copyright Holger Krekel and others, 2004. - -Distributed under the terms of the `MIT`_ license, pytest is free and open source software. - -.. _`MIT`: https://github.com/pytest-dev/pytest/blob/main/LICENSE diff --git a/doc/en/py27-py34-deprecation.rst b/doc/en/py27-py34-deprecation.rst deleted file mode 100644 index 660b078e30e..00000000000 --- a/doc/en/py27-py34-deprecation.rst +++ /dev/null @@ -1,99 +0,0 @@ -Python 2.7 and 3.4 support -========================== - -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. - -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. - -Python 2.7 EOL has been reached :pep:`in 2020 <0373#maintenance-releases>`, with -the last release made in April, 2020. - -Python 3.4 EOL has been reached :pep:`in 2019 <0429#release-schedule>`, 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. - -Users should ensure they are using the latest pip and setuptools versions for this to work. - -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.x`` 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.x`` -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 -~~~~~~~~~~~~~~~~~ - -(This section is a transcript from :issue:`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: - -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.x -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.x``: - - * 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.x`` 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/customize.rst b/doc/en/reference/customize.rst index 22ce24b31e0..b794d646b8e 100644 --- a/doc/en/reference/customize.rst +++ b/doc/en/reference/customize.rst @@ -20,8 +20,7 @@ Configuration file formats -------------------------- Many :ref:`pytest settings ` can be set in a *configuration file*, which -by convention resides on the root of your repository or in your -tests folder. +by convention resides in the root directory of your repository. A quick example of the configuration files supported by pytest: @@ -30,9 +29,11 @@ pytest.ini ``pytest.ini`` files take precedence over other files, even when empty. +Alternatively, the hidden version ``.pytest.ini`` can be used. + .. code-block:: ini - # pytest.ini + # pytest.ini or .pytest.ini [pytest] minversion = 6.0 addopts = -ra -q @@ -89,7 +90,7 @@ and can also be used to hold pytest configuration if they have a ``[pytest]`` se setup.cfg ~~~~~~~~~ -``setup.cfg`` files are general purpose configuration files, used originally by :doc:`distutils `, and can also be used to hold pytest configuration +``setup.cfg`` files are general purpose configuration files, used originally by :doc:`distutils `, and can also be used to hold pytest configuration if they have a ``[tool:pytest]`` section. .. code-block:: ini diff --git a/doc/en/reference/fixtures.rst b/doc/en/reference/fixtures.rst index 35b79021209..01f825222ea 100644 --- a/doc/en/reference/fixtures.rst +++ b/doc/en/reference/fixtures.rst @@ -208,7 +208,7 @@ the one defined in ``tests/test_top.py`` would be unavailable to it because it would have to step down a level (step inside a circle) to find it. The first fixture the test finds is the one that will be used, so -:ref:`fixtures can be overriden ` if you need to change or +:ref:`fixtures can be overridden ` if you need to change or extend what one does for a particular scope. You can also use the ``conftest.py`` file to implement @@ -335,7 +335,7 @@ For example: .. literalinclude:: /example/fixtures/test_fixtures_order_dependencies.py -If we map out what depends on what, we get something that look like this: +If we map out what depends on what, we get something that looks like this: .. image:: /example/fixtures/test_fixtures_order_dependencies.* :align: center @@ -401,6 +401,9 @@ the graph would look like this: Because ``c`` can now be put above ``d`` in the graph, pytest can once again linearize the graph to this: +.. image:: /example/fixtures/test_fixtures_order_autouse_flat.* + :align: center + In this example, ``c`` makes ``b`` and ``a`` effectively autouse fixtures as well. diff --git a/doc/en/reference/index.rst b/doc/en/reference/index.rst index d9648400317..ee1b2e6214d 100644 --- a/doc/en/reference/index.rst +++ b/doc/en/reference/index.rst @@ -8,8 +8,8 @@ Reference guides .. toctree:: :maxdepth: 1 + reference fixtures - plugin_list customize - reference exit-codes + plugin_list diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index ebf40091369..3dff88e5913 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -11,990 +11,1152 @@ automatically. Packages classified as inactive are excluded. creating a PDF, because otherwise the table gets far too wide for the page. -This list contains 963 plugins. +This list contains 1118 plugins. .. only:: not latex - =============================================== ======================================================================================================================================================================== ============== ===================== ================================================ - name summary last release status requires - =============================================== ======================================================================================================================================================================== ============== ===================== ================================================ - :pypi:`pytest-accept` A pytest-plugin for updating doctest outputs Nov 22, 2021 N/A pytest (>=6,<7) - :pypi:`pytest-adaptavist` pytest plugin for generating test execution results within Jira Test Management (tm4j) Nov 30, 2021 N/A pytest (>=5.4.0) - :pypi:`pytest-addons-test` 用于测试pytest的插件 Aug 02, 2021 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-adf` Pytest plugin for writing Azure Data Factory integration tests May 10, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-adf-azure-identity` Pytest plugin for writing Azure Data Factory integration tests Mar 06, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-agent` Service that exposes a REST API that can be used to interract remotely with Pytest. It is shipped with a dashboard that enables running tests in a more convenient way. Nov 25, 2021 N/A N/A - :pypi:`pytest-aggreport` pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. Mar 07, 2021 4 - Beta pytest (>=6.2.2) - :pypi:`pytest-aio` Pytest plugin for testing async python code Oct 20, 2021 4 - Beta pytest - :pypi:`pytest-aiofiles` pytest fixtures for writing aiofiles tests with pyfakefs May 14, 2017 5 - Production/Stable N/A - :pypi:`pytest-aiohttp` pytest plugin for aiohttp support Dec 05, 2017 N/A pytest - :pypi:`pytest-aiohttp-client` Pytest \`client\` fixture for the Aiohttp Nov 01, 2020 N/A pytest (>=6) - :pypi:`pytest-aioresponses` py.test integration for aioresponses Jul 29, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-aioworkers` A plugin to test aioworkers project with pytest Dec 04, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-airflow` pytest support for airflow. Apr 03, 2019 3 - Alpha pytest (>=4.4.0) - :pypi:`pytest-airflow-utils` Nov 15, 2021 N/A N/A - :pypi:`pytest-alembic` A pytest plugin for verifying alembic migrations. Dec 02, 2021 N/A pytest (>=1.0) - :pypi:`pytest-allclose` Pytest fixture extending Numpy's allclose function Jul 30, 2019 5 - Production/Stable pytest - :pypi:`pytest-allure-adaptor` Plugin for py.test to generate allure xml reports Jan 10, 2018 N/A pytest (>=2.7.3) - :pypi:`pytest-allure-adaptor2` Plugin for py.test to generate allure xml reports Oct 14, 2020 N/A pytest (>=2.7.3) - :pypi:`pytest-allure-dsl` pytest plugin to test case doc string dls instructions Oct 25, 2020 4 - Beta pytest - :pypi:`pytest-allure-spec-coverage` The pytest plugin aimed to display test coverage of the specs(requirements) in Allure Oct 26, 2021 N/A pytest - :pypi:`pytest-alphamoon` Static code checks used at Alphamoon Oct 21, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-android` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Feb 21, 2019 3 - Alpha pytest - :pypi:`pytest-anki` A pytest plugin for testing Anki add-ons Oct 14, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-annotate` pytest-annotate: Generate PyAnnotate annotations from your pytest tests. Nov 29, 2021 3 - Alpha pytest (<7.0.0,>=3.2.0) - :pypi:`pytest-ansible` Plugin for py.test to simplify calling ansible modules from tests or fixtures May 25, 2021 5 - Production/Stable N/A - :pypi:`pytest-ansible-playbook` Pytest fixture which runs given ansible playbook file. Mar 08, 2019 4 - Beta N/A - :pypi:`pytest-ansible-playbook-runner` Pytest fixture which runs given ansible playbook file. Dec 02, 2020 4 - Beta pytest (>=3.1.0) - :pypi:`pytest-antilru` Bust functools.lru_cache when running pytest to avoid test pollution Apr 11, 2019 5 - Production/Stable pytest - :pypi:`pytest-anyio` The pytest anyio plugin is built into anyio. You don't need this package. Jun 29, 2021 N/A pytest - :pypi:`pytest-anything` Pytest fixtures to assert anything and something Feb 18, 2021 N/A N/A - :pypi:`pytest-aoc` Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Nov 23, 2021 N/A pytest ; extra == 'test' - :pypi:`pytest-api` PyTest-API Python Web Framework built for testing purposes. May 04, 2021 N/A N/A - :pypi:`pytest-apistellar` apistellar plugin for pytest. Jun 18, 2019 N/A N/A - :pypi:`pytest-appengine` AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A - :pypi:`pytest-appium` Pytest plugin for appium Dec 05, 2019 N/A N/A - :pypi:`pytest-approvaltests` A plugin to use approvaltests with pytest Feb 07, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-argus` pyest results colection plugin Jun 24, 2021 5 - Production/Stable pytest (>=6.2.4) - :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Dec 06, 2018 4 - Beta pytest - :pypi:`pytest-asgi-server` Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) - :pypi:`pytest-asptest` test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A - :pypi:`pytest-assertutil` pytest-assertutil May 10, 2019 N/A N/A - :pypi:`pytest-assert-utils` Useful assertion utilities for use with pytest Sep 21, 2021 3 - Alpha N/A - :pypi:`pytest-assume` A pytest plugin that allows multiple failures per test Jun 24, 2021 N/A pytest (>=2.7) - :pypi:`pytest-ast-back-to-python` A plugin for pytest devs to view how assertion rewriting recodes the AST Sep 29, 2019 4 - Beta N/A - :pypi:`pytest-astropy` Meta-package containing dependencies for testing Sep 21, 2021 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Dec 18, 2019 3 - Alpha pytest (>=2.8) - :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest - :pypi:`pytest-asyncio` Pytest support for asyncio. Oct 15, 2021 4 - Beta pytest (>=5.4.0) - :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Oct 12, 2021 4 - Beta N/A - :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) - :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) - :pypi:`pytest-async-sqlalchemy` Database testing fixtures using the SQLAlchemy asyncio API Oct 07, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-atomic` Skip rest of tests if previous test failed. Nov 24, 2018 4 - Beta N/A - :pypi:`pytest-attrib` pytest plugin to select tests based on attributes similar to the nose-attrib plugin May 24, 2016 4 - Beta N/A - :pypi:`pytest-austin` Austin plugin for pytest Oct 11, 2020 4 - Beta N/A - :pypi:`pytest-autochecklog` automatically check condition and log all the checks Apr 25, 2015 4 - Beta N/A - :pypi:`pytest-automation` pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. Oct 01, 2021 N/A pytest - :pypi:`pytest-automock` Pytest plugin for automatical mocks creation Apr 22, 2020 N/A pytest ; extra == 'dev' - :pypi:`pytest-auto-parametrize` pytest plugin: avoid repeating arguments in parametrize Oct 02, 2016 3 - Alpha N/A - :pypi:`pytest-autotest` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Aug 25, 2021 N/A pytest - :pypi:`pytest-avoidance` Makes pytest skip tests that don not need rerunning May 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A - :pypi:`pytest-aws-config` Protect your AWS credentials in unit tests May 28, 2021 N/A N/A - :pypi:`pytest-axe` pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) - :pypi:`pytest-azurepipelines` Formatting PyTest output for Azure Pipelines UI Jul 23, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-bandit` A bandit plugin for pytest Feb 23, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-base-url` pytest plugin for URL based testing Jun 19, 2020 5 - Production/Stable pytest (>=2.7.3) - :pypi:`pytest-bdd` BDD for pytest Oct 25, 2021 6 - Mature pytest (>=4.3) - :pypi:`pytest-bdd-splinter` Common steps for pytest bdd and splinter integration Aug 12, 2019 5 - Production/Stable pytest (>=4.0.0) - :pypi:`pytest-bdd-web` A simple plugin to use with pytest Jan 02, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-bdd-wrappers` Feb 11, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-beakerlib` A pytest plugin that reports test results to the BeakerLib framework Mar 17, 2017 5 - Production/Stable pytest - :pypi:`pytest-beds` Fixtures for testing Google Appengine (GAE) apps Jun 07, 2016 4 - Beta N/A - :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A - :pypi:`pytest-benchmark` A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. Apr 17, 2021 5 - Production/Stable pytest (>=3.8) - :pypi:`pytest-bg-process` Pytest plugin to initialize background process Aug 17, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-bigchaindb` A BigchainDB plugin for pytest. Aug 17, 2021 4 - Beta N/A - :pypi:`pytest-bigquery-mock` Provides a mock fixture for python bigquery client Aug 05, 2021 N/A pytest (>=5.0) - :pypi:`pytest-black` A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A - :pypi:`pytest-black-multipy` Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' - :pypi:`pytest-blame` A pytest plugin helps developers to debug by providing useful commits history. May 04, 2019 N/A pytest (>=4.4.0) - :pypi:`pytest-blender` Blender Pytest plugin. Oct 29, 2021 N/A pytest (==6.2.5) ; extra == 'dev' - :pypi:`pytest-blink1` Pytest plugin to emit notifications via the Blink(1) RGB LED Jan 07, 2018 4 - Beta N/A - :pypi:`pytest-blockage` Disable network requests during a test run. Feb 13, 2019 N/A pytest - :pypi:`pytest-blocker` pytest plugin to mark a test as blocker and skip all other tests Sep 07, 2015 4 - Beta N/A - :pypi:`pytest-board` Local continuous test runner with pytest and watchdog. Jan 20, 2019 N/A N/A - :pypi:`pytest-bpdb` A py.test plug-in to enable drop to bpdb debugger on test failure. Jan 19, 2015 2 - Pre-Alpha N/A - :pypi:`pytest-bravado` Pytest-bravado automatically generates from OpenAPI specification client fixtures. Jul 19, 2021 N/A N/A - :pypi:`pytest-breakword` Use breakword with pytest Aug 04, 2021 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-breed-adapter` A simple plugin to connect with breed-server Nov 07, 2018 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-briefcase` A pytest plugin for running tests on a Briefcase project. Jun 14, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-browser` A pytest plugin for console based browser test selection just after the collection phase Dec 10, 2016 3 - Alpha N/A - :pypi:`pytest-browsermob-proxy` BrowserMob proxy plugin for py.test. Jun 11, 2013 4 - Beta N/A - :pypi:`pytest-browserstack-local` \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. Feb 09, 2018 N/A N/A - :pypi:`pytest-bug` Pytest plugin for marking tests as a bug Jun 02, 2020 5 - Production/Stable pytest (>=3.6.0) - :pypi:`pytest-bugtong-tag` pytest-bugtong-tag is a plugin for pytest Apr 23, 2021 N/A N/A - :pypi:`pytest-bugzilla` py.test bugzilla integration plugin May 05, 2010 4 - Beta N/A - :pypi:`pytest-bugzilla-notifier` A plugin that allows you to execute create, update, and read information from BugZilla bugs Jun 15, 2018 4 - Beta pytest (>=2.9.2) - :pypi:`pytest-buildkite` Plugin for pytest that automatically publishes coverage and pytest report annotations to Buildkite. Jul 13, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-builtin-types` Nov 17, 2021 N/A pytest - :pypi:`pytest-bwrap` Run your tests in Bubblewrap sandboxes Oct 26, 2018 3 - Alpha N/A - :pypi:`pytest-cache` pytest plugin with mechanisms for caching across test runs Jun 04, 2013 3 - Alpha N/A - :pypi:`pytest-cache-assert` Cache assertion data to simplify regression testing of complex serializable data Nov 03, 2021 4 - Beta pytest (>=5) - :pypi:`pytest-cagoule` Pytest plugin to only run tests affected by changes Jan 01, 2020 3 - Alpha N/A - :pypi:`pytest-camel-collect` Enable CamelCase-aware pytest class collection Aug 02, 2020 N/A pytest (>=2.9) - :pypi:`pytest-canonical-data` A plugin which allows to compare results with canonical results, based on previous runs May 08, 2020 2 - Pre-Alpha pytest (>=3.5.0) - :pypi:`pytest-caprng` A plugin that replays pRNG state on failure. May 02, 2018 4 - Beta N/A - :pypi:`pytest-capture-deprecatedwarnings` pytest plugin to capture all deprecatedwarnings and put them in one file Apr 30, 2019 N/A N/A - :pypi:`pytest-capturelogs` A sample Python project Sep 11, 2021 3 - Alpha N/A - :pypi:`pytest-cases` Separate test code from test cases in pytest. Nov 08, 2021 5 - Production/Stable N/A - :pypi:`pytest-cassandra` Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A - :pypi:`pytest-catchlog` py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) - :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest May 06, 2021 N/A N/A - :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A - :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A - :pypi:`pytest-change-report` turn . into √,turn F into x Sep 14, 2020 N/A pytest - :pypi:`pytest-chdir` A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) - :pypi:`pytest-checkdocs` check the README when running tests Jul 31, 2021 5 - Production/Stable pytest (>=4.6) ; extra == 'testing' - :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Jul 22, 2020 5 - Production/Stable pytest (>=2.9.2) - :pypi:`pytest-check-links` Check links in files Jul 29, 2020 N/A pytest (>=4.6) - :pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest - :pypi:`pytest-circleci` py.test plugin for CircleCI May 03, 2019 N/A N/A - :pypi:`pytest-circleci-parallelized` Parallelize pytest across CircleCI workers. Mar 26, 2019 N/A N/A - :pypi:`pytest-ckan` Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8 Apr 28, 2020 4 - Beta pytest - :pypi:`pytest-clarity` A plugin providing an alternative, colourful diff output for failing assertions. Jun 11, 2021 N/A N/A - :pypi:`pytest-cldf` Easy quality control for CLDF datasets using pytest May 06, 2019 N/A N/A - :pypi:`pytest-click` Py.test plugin for Click Aug 29, 2020 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-clld` Nov 29, 2021 N/A pytest (>=3.6) - :pypi:`pytest-cloud` Distributed tests planner plugin for pytest testing framework. Oct 05, 2020 6 - Mature N/A - :pypi:`pytest-cloudflare-worker` pytest plugin for testing cloudflare workers Mar 30, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-cobra` PyTest plugin for testing Smart Contracts for Ethereum blockchain. Jun 29, 2019 3 - Alpha pytest (<4.0.0,>=3.7.1) - :pypi:`pytest-codeblocks` Test code blocks in your READMEs Oct 13, 2021 4 - Beta pytest (>=6) - :pypi:`pytest-codecheckers` pytest plugin to add source code sanity checks (pep8 and friends) Feb 13, 2010 N/A N/A - :pypi:`pytest-codecov` Pytest plugin for uploading pytest-cov results to codecov.io Oct 27, 2021 4 - Beta pytest (>=4.6.0) - :pypi:`pytest-codegen` Automatically create pytest test signatures Aug 23, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-codestyle` pytest plugin to run pycodestyle Mar 23, 2020 3 - Alpha N/A - :pypi:`pytest-collect-formatter` Formatter for pytest collect output Mar 29, 2021 5 - Production/Stable N/A - :pypi:`pytest-collect-formatter2` Formatter for pytest collect output May 31, 2021 5 - Production/Stable N/A - :pypi:`pytest-colordots` Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A - :pypi:`pytest-commander` An interactive GUI test runner for PyTest Aug 17, 2021 N/A pytest (<7.0.0,>=6.2.4) - :pypi:`pytest-common-subject` pytest framework for testing different aspects of a common method Nov 12, 2020 N/A pytest (>=3.6,<7) - :pypi:`pytest-concurrent` Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-config` Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A - :pypi:`pytest-confluence-report` Package stands for pytest plugin to upload results into Confluence page. Nov 06, 2020 N/A N/A - :pypi:`pytest-console-scripts` Pytest plugin for testing console scripts Sep 28, 2021 4 - Beta N/A - :pypi:`pytest-consul` pytest plugin with fixtures for testing consul aware apps Nov 24, 2018 3 - Alpha pytest - :pypi:`pytest-container` Pytest fixtures for writing container based tests Nov 19, 2021 3 - Alpha pytest (>=3.10) - :pypi:`pytest-contextfixture` Define pytest fixtures as context managers. Mar 12, 2013 4 - Beta N/A - :pypi:`pytest-contexts` A plugin to run tests written with the Contexts framework using pytest May 19, 2021 4 - Beta N/A - :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 May 24, 2021 5 - Production/Stable pytest (>=3.3.0) - :pypi:`pytest-couchdbkit` py.test extension for per-test couchdb databases using couchdbkit Apr 17, 2012 N/A N/A - :pypi:`pytest-count` count erros and send email Jan 12, 2018 4 - Beta N/A - :pypi:`pytest-cov` Pytest plugin for measuring coverage. Oct 04, 2021 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-cover` Pytest plugin for measuring coverage. Forked from \`pytest-cov\`. Aug 01, 2015 5 - Production/Stable N/A - :pypi:`pytest-coverage` Jun 17, 2015 N/A N/A - :pypi:`pytest-coverage-context` Coverage dynamic context support for PyTest, including sub-processes Jan 04, 2021 4 - Beta pytest (>=6.1.0) - :pypi:`pytest-cov-exclude` Pytest plugin for excluding tests based on coverage data Apr 29, 2016 4 - Beta pytest (>=2.8.0,<2.9.0); extra == 'dev' - :pypi:`pytest-cpp` Use pytest's runner to discover and execute C++ tests Dec 03, 2021 5 - Production/Stable pytest (!=5.4.0,!=5.4.1) - :pypi:`pytest-cram` Run cram tests with pytest. Aug 08, 2020 N/A N/A - :pypi:`pytest-crate` Manages CrateDB instances during your integration tests May 28, 2019 3 - Alpha pytest (>=4.0) - :pypi:`pytest-cricri` A Cricri plugin for pytest. Jan 27, 2018 N/A pytest - :pypi:`pytest-crontab` add crontab task in crontab Dec 09, 2019 N/A N/A - :pypi:`pytest-csv` CSV output for pytest. Apr 22, 2021 N/A pytest (>=6.0) - :pypi:`pytest-curio` Pytest support for curio. Oct 07, 2020 N/A N/A - :pypi:`pytest-curl-report` pytest plugin to generate curl command line report Dec 11, 2016 4 - Beta N/A - :pypi:`pytest-custom-concurrency` Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A - :pypi:`pytest-custom-exit-code` Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) - :pypi:`pytest-custom-nodeid` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 07, 2021 N/A N/A - :pypi:`pytest-custom-report` Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest - :pypi:`pytest-custom-scheduling` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 01, 2021 N/A N/A - :pypi:`pytest-cython` A plugin for testing Cython extension modules Jan 26, 2021 4 - Beta pytest (>=2.7.3) - :pypi:`pytest-darker` A pytest plugin for checking of modified code using Darker Aug 16, 2020 N/A pytest (>=6.0.1) ; extra == 'test' - :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A - :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A - :pypi:`pytest-databricks` Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest - :pypi:`pytest-datadir` pytest plugin for test data directories and files Oct 22, 2019 5 - Production/Stable pytest (>=2.7.0) - :pypi:`pytest-datadir-mgr` Manager for test data providing downloads, caching of generated files, and a context for temp directories. Aug 16, 2021 5 - Production/Stable pytest - :pypi:`pytest-datadir-ng` Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Dec 25, 2019 5 - Production/Stable pytest - :pypi:`pytest-data-file` Fixture "data" and "case_data" for test from yaml file Dec 04, 2019 N/A N/A - :pypi:`pytest-datafiles` py.test plugin to create a 'tmpdir' containing predefined files/directories. Oct 07, 2018 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-datafixtures` Data fixtures for pytest made simple Dec 05, 2020 5 - Production/Stable N/A - :pypi:`pytest-data-from-files` pytest plugin to provide data from files loaded automatically Oct 13, 2021 4 - Beta pytest - :pypi:`pytest-dataplugin` A pytest plugin for managing an archive of test data. Sep 16, 2017 1 - Planning N/A - :pypi:`pytest-datarecorder` A py.test plugin recording and comparing test output. Apr 20, 2020 5 - Production/Stable pytest - :pypi:`pytest-datatest` A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration). Oct 15, 2020 4 - Beta pytest (>=3.3) - :pypi:`pytest-db` Session scope fixture "db" for mysql query or change Dec 04, 2019 N/A N/A - :pypi:`pytest-dbfixtures` Databases fixtures plugin for py.test. Dec 07, 2016 4 - Beta N/A - :pypi:`pytest-db-plugin` Nov 27, 2021 N/A pytest (>=5.0) - :pypi:`pytest-dbt-adapter` A pytest plugin for testing dbt adapter plugins Nov 24, 2021 N/A pytest (<7,>=6) - :pypi:`pytest-dbus-notification` D-BUS notifications for pytest results. Mar 05, 2014 5 - Production/Stable N/A - :pypi:`pytest-deadfixtures` A simple plugin to list unused fixtures in pytest Jul 23, 2020 5 - Production/Stable N/A - :pypi:`pytest-deepcov` deepcov Mar 30, 2021 N/A N/A - :pypi:`pytest-defer` Aug 24, 2021 N/A N/A - :pypi:`pytest-demo-plugin` pytest示例插件 May 15, 2021 N/A N/A - :pypi:`pytest-dependency` Manage dependencies of tests Feb 14, 2020 4 - Beta N/A - :pypi:`pytest-depends` Tests that depend on other tests Apr 05, 2020 5 - Production/Stable pytest (>=3) - :pypi:`pytest-deprecate` Mark tests as testing a deprecated feature with a warning note. Jul 01, 2019 N/A N/A - :pypi:`pytest-describe` Describe-style plugin for pytest Nov 13, 2021 4 - Beta pytest (>=4.0.0) - :pypi:`pytest-describe-it` plugin for rich text descriptions Jul 19, 2019 4 - Beta pytest - :pypi:`pytest-devpi-server` DevPI server fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-diamond` pytest plugin for diamond Aug 31, 2015 4 - Beta N/A - :pypi:`pytest-dicom` pytest plugin to provide DICOM fixtures Dec 19, 2018 3 - Alpha pytest - :pypi:`pytest-dictsdiff` Jul 26, 2019 N/A N/A - :pypi:`pytest-diff` A simple plugin to use with pytest Mar 30, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-disable` pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A - :pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. Mar 20, 2021 3 - Alpha pytest (!=6.0.0,<7,>=3.3.2) - :pypi:`pytest-django` A Django plugin for pytest. Dec 02, 2021 5 - Production/Stable pytest (>=5.4.0) - :pypi:`pytest-django-ahead` A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9) - :pypi:`pytest-djangoapp` Nice pytest plugin to help you with Django pluggable application testing. Aug 04, 2021 4 - Beta N/A - :pypi:`pytest-django-cache-xdist` A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A - :pypi:`pytest-django-casperjs` Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A - :pypi:`pytest-django-dotenv` Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-django-factories` Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A - :pypi:`pytest-django-gcir` A Django plugin for pytest. Mar 06, 2018 5 - Production/Stable N/A - :pypi:`pytest-django-haystack` Cleanup your Haystack indexes between tests Sep 03, 2017 5 - Production/Stable pytest (>=2.3.4) - :pypi:`pytest-django-ifactory` A model instance factory for pytest-django Jan 13, 2021 3 - Alpha N/A - :pypi:`pytest-django-lite` The bare minimum to integrate py.test with Django. Jan 30, 2014 N/A N/A - :pypi:`pytest-django-liveserver-ssl` Jul 30, 2021 3 - Alpha N/A - :pypi:`pytest-django-model` A Simple Way to Test your Django Models Feb 14, 2019 4 - Beta N/A - :pypi:`pytest-django-ordering` A pytest plugin for preserving the order in which Django runs tests. Jul 25, 2019 5 - Production/Stable pytest (>=2.3.0) - :pypi:`pytest-django-queries` Generate performance reports from your django database performance tests. Mar 01, 2021 N/A N/A - :pypi:`pytest-djangorestframework` A djangorestframework plugin for pytest Aug 11, 2019 4 - Beta N/A - :pypi:`pytest-django-rq` A pytest plugin to help writing unit test for django-rq Apr 13, 2020 4 - Beta N/A - :pypi:`pytest-django-sqlcounts` py.test plugin for reporting the number of SQLs executed per django testcase. Jun 16, 2015 4 - Beta N/A - :pypi:`pytest-django-testing-postgresql` Use a temporary PostgreSQL database with pytest-django Dec 05, 2019 3 - Alpha N/A - :pypi:`pytest-doc` A documentation plugin for py.test. Jun 28, 2015 5 - Production/Stable N/A - :pypi:`pytest-docgen` An RST Documentation Generator for pytest-based test suites Apr 17, 2020 N/A N/A - :pypi:`pytest-docker` Simple pytest fixtures for Docker and docker-compose based tests Jun 14, 2021 N/A pytest (<7.0,>=4.0) - :pypi:`pytest-docker-butla` Jun 16, 2019 3 - Alpha N/A - :pypi:`pytest-dockerc` Run, manage and stop Docker Compose project from Docker API Oct 09, 2020 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-docker-compose` Manages Docker containers during your integration tests Jan 26, 2021 5 - Production/Stable pytest (>=3.3) - :pypi:`pytest-docker-db` A plugin to use docker databases for pytests Mar 20, 2021 5 - Production/Stable pytest (>=3.1.1) - :pypi:`pytest-docker-fixtures` pytest docker fixtures Nov 23, 2021 3 - Alpha N/A - :pypi:`pytest-docker-git-fixtures` Pytest fixtures for testing with git scm. Mar 11, 2021 4 - Beta pytest - :pypi:`pytest-docker-pexpect` pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest - :pypi:`pytest-docker-postgresql` A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-docker-py` Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) - :pypi:`pytest-docker-registry-fixtures` Pytest fixtures for testing with docker registries. Mar 04, 2021 4 - Beta pytest - :pypi:`pytest-docker-tools` Docker integration tests for pytest Jul 23, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) - :pypi:`pytest-docs` Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-docstyle` pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A - :pypi:`pytest-doctest-custom` A py.test plugin for customizing string representations of doctest results. Jul 25, 2016 4 - Beta N/A - :pypi:`pytest-doctest-ellipsis-markers` Setup additional values for ELLIPSIS_MARKER for doctests Jan 12, 2018 4 - Beta N/A - :pypi:`pytest-doctest-import` A simple pytest plugin to import names and add them to the doctest namespace. Nov 13, 2018 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Nov 16, 2021 3 - Alpha pytest (>=4.6) - :pypi:`pytest-doctest-ufunc` A plugin to run doctests in docstrings of Numpy ufuncs Aug 02, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-dolphin` Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) - :pypi:`pytest-doorstop` A pytest plugin for adding test results into doorstop items. Jun 09, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-dotenv` A py.test plugin that parses environment files before running tests Jun 16, 2020 4 - Beta pytest (>=5.0.0) - :pypi:`pytest-drf` A Django REST framework plugin for pytest. Nov 12, 2020 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-drivings` Tool to allow webdriver automation to be ran locally or remotely Jan 13, 2021 N/A N/A - :pypi:`pytest-drop-dup-tests` A Pytest plugin to drop duplicated tests during collection May 23, 2020 4 - Beta pytest (>=2.7) - :pypi:`pytest-dummynet` A py.test plugin providing access to a dummynet. Oct 13, 2021 5 - Production/Stable pytest - :pypi:`pytest-dump2json` A pytest plugin for dumping test results to json. Jun 29, 2015 N/A N/A - :pypi:`pytest-duration-insights` Jun 25, 2021 N/A N/A - :pypi:`pytest-dynamicrerun` A pytest plugin to rerun tests dynamically based off of test outcome and output. Aug 15, 2020 4 - Beta N/A - :pypi:`pytest-dynamodb` DynamoDB fixtures for pytest Jun 03, 2021 5 - Production/Stable pytest - :pypi:`pytest-easy-addoption` pytest-easy-addoption: Easy way to work with pytest addoption Jan 22, 2020 N/A N/A - :pypi:`pytest-easy-api` Simple API testing with pytest Mar 26, 2018 N/A N/A - :pypi:`pytest-easyMPI` Package that supports mpi tests in pytest Oct 21, 2020 N/A N/A - :pypi:`pytest-easyread` pytest plugin that makes terminal printouts of the reports easier to read Nov 17, 2017 N/A N/A - :pypi:`pytest-easy-server` Pytest plugin for easy testing against servers May 01, 2021 4 - Beta pytest (<5.0.0,>=4.3.1) ; python_version < "3.5" - :pypi:`pytest-ec2` Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A - :pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Jan 08, 2020 5 - Production/Stable N/A - :pypi:`pytest-elasticsearch` Elasticsearch fixtures and fixture factories for Pytest. May 12, 2021 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) - :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest - :pypi:`pytest-embedded` pytest embedded plugin Nov 29, 2021 N/A pytest (>=6.2.0) - :pypi:`pytest-embedded-idf` pytest embedded plugin for esp-idf project Nov 29, 2021 N/A N/A - :pypi:`pytest-embedded-jtag` pytest embedded plugin for testing with jtag Nov 29, 2021 N/A N/A - :pypi:`pytest-embedded-qemu` pytest embedded plugin for qemu, not target chip Nov 29, 2021 N/A N/A - :pypi:`pytest-embedded-qemu-idf` pytest embedded plugin for esp-idf project by qemu, not target chip Jun 29, 2021 N/A N/A - :pypi:`pytest-embedded-serial` pytest embedded plugin for testing serial ports Nov 29, 2021 N/A N/A - :pypi:`pytest-embedded-serial-esp` pytest embedded plugin for testing espressif boards via serial ports Nov 29, 2021 N/A N/A - :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) - :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Oct 10, 2021 4 - Beta pytest (==6.0.1) - :pypi:`pytest-enabler` Enable installed pytest plugins Nov 08, 2021 5 - Production/Stable pytest (>=6) ; extra == 'testing' - :pypi:`pytest-encode` set your encoding and logger Nov 06, 2021 N/A N/A - :pypi:`pytest-encode-kane` set your encoding and logger Nov 16, 2021 N/A pytest - :pypi:`pytest-enhancements` Improvements for pytest (rejected upstream) Oct 30, 2019 4 - Beta N/A - :pypi:`pytest-env` py.test plugin that allows you to add environment variables. Jun 16, 2017 4 - Beta N/A - :pypi:`pytest-envfiles` A py.test plugin that parses environment files before running tests Oct 08, 2015 3 - Alpha N/A - :pypi:`pytest-env-info` Push information about the running pytest into envvars Nov 25, 2017 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-envraw` py.test plugin that allows you to add environment variables. Aug 27, 2020 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-envvars` Pytest plugin to validate use of envvars on your tests Jun 13, 2020 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-env-yaml` Apr 02, 2019 N/A N/A - :pypi:`pytest-eradicate` pytest plugin to check for commented out code Sep 08, 2020 N/A pytest (>=2.4.2) - :pypi:`pytest-error-for-skips` Pytest plugin to treat skipped tests a test failure Dec 19, 2019 4 - Beta pytest (>=4.6) - :pypi:`pytest-eth` PyTest plugin for testing Smart Contracts for Ethereum Virtual Machine (EVM). Aug 14, 2020 1 - Planning N/A - :pypi:`pytest-ethereum` pytest-ethereum: Pytest library for ethereum projects. Jun 24, 2019 3 - Alpha pytest (==3.3.2); extra == 'dev' - :pypi:`pytest-eucalyptus` Pytest Plugin for BDD Aug 13, 2019 N/A pytest (>=4.2.0) - :pypi:`pytest-eventlet` Applies eventlet monkey-patch as a pytest plugin. Oct 04, 2021 N/A pytest ; extra == 'dev' - :pypi:`pytest-excel` pytest plugin for generating excel reports Oct 06, 2020 5 - Production/Stable N/A - :pypi:`pytest-exceptional` Better exceptions Mar 16, 2017 4 - Beta N/A - :pypi:`pytest-exception-script` Walk your code through exception script to check it's resiliency to failures. Aug 04, 2020 3 - Alpha pytest - :pypi:`pytest-executable` pytest plugin for testing executables Nov 10, 2021 4 - Beta pytest (<6.3,>=4.3) - :pypi:`pytest-expect` py.test plugin to store test expectations and mark tests based on them Apr 21, 2016 4 - Beta N/A - :pypi:`pytest-expecter` Better testing with expecter and pytest. Jul 08, 2020 5 - Production/Stable N/A - :pypi:`pytest-expectr` This plugin is used to expect multiple assert using pytest framework. Oct 05, 2018 N/A pytest (>=2.4.2) - :pypi:`pytest-explicit` A Pytest plugin to ignore certain marked tests by default Jun 15, 2021 5 - Production/Stable pytest - :pypi:`pytest-exploratory` Interactive console for pytest. Aug 03, 2021 N/A pytest (>=5.3) - :pypi:`pytest-external-blockers` a special outcome for tests that are blocked for external reasons Oct 05, 2021 N/A pytest - :pypi:`pytest-extra-durations` A pytest plugin to get durations on a per-function basis and per module basis. Apr 21, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-fabric` Provides test utilities to run fabric task tests by using docker containers Sep 12, 2018 5 - Production/Stable N/A - :pypi:`pytest-factory` Use factories for test setup with py.test Sep 06, 2020 3 - Alpha pytest (>4.3) - :pypi:`pytest-factoryboy` Factory Boy support for pytest. Dec 30, 2020 6 - Mature pytest (>=4.6) - :pypi:`pytest-factoryboy-fixtures` Generates pytest fixtures that allow the use of type hinting Jun 25, 2020 N/A N/A - :pypi:`pytest-factoryboy-state` Simple factoryboy random state management Dec 11, 2020 4 - Beta pytest (>=5.0) - :pypi:`pytest-failed-screenshot` Test case fails,take a screenshot,save it,attach it to the allure Apr 21, 2021 N/A N/A - :pypi:`pytest-failed-to-verify` A pytest plugin that helps better distinguishing real test failures from setup flakiness. Aug 08, 2019 5 - Production/Stable pytest (>=4.1.0) - :pypi:`pytest-faker` Faker integration with the pytest framework. Dec 19, 2016 6 - Mature N/A - :pypi:`pytest-falcon` Pytest helpers for Falcon. Sep 07, 2016 4 - Beta N/A - :pypi:`pytest-falcon-client` Pytest \`client\` fixture for the Falcon Framework Mar 19, 2019 N/A N/A - :pypi:`pytest-fantasy` Pytest plugin for Flask Fantasy Framework Mar 14, 2019 N/A N/A - :pypi:`pytest-fastapi` Dec 27, 2020 N/A N/A - :pypi:`pytest-fastest` Use SCM and coverage to run only needed tests Mar 05, 2020 N/A N/A - :pypi:`pytest-fast-first` Pytest plugin that runs fast tests first Apr 02, 2021 3 - Alpha pytest - :pypi:`pytest-faulthandler` py.test plugin that activates the fault handler module for tests (dummy package) Jul 04, 2019 6 - Mature pytest (>=5.0) - :pypi:`pytest-fauxfactory` Integration of fauxfactory into pytest. Dec 06, 2017 5 - Production/Stable pytest (>=3.2) - :pypi:`pytest-figleaf` py.test figleaf coverage plugin Jan 18, 2010 5 - Production/Stable N/A - :pypi:`pytest-filecov` A pytest plugin to detect unused files Jun 27, 2021 4 - Beta pytest - :pypi:`pytest-filedata` easily load data from files Jan 17, 2019 4 - Beta N/A - :pypi:`pytest-filemarker` A pytest plugin that runs marked tests when files change. Dec 01, 2020 N/A pytest - :pypi:`pytest-filter-case` run test cases filter by mark Nov 05, 2020 N/A N/A - :pypi:`pytest-filter-subpackage` Pytest plugin for filtering based on sub-packages Jan 09, 2020 3 - Alpha pytest (>=3.0) - :pypi:`pytest-find-dependencies` A pytest plugin to find dependencies between tests Apr 21, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-finer-verdicts` A pytest plugin to treat non-assertion failures as test errors. Jun 18, 2020 N/A pytest (>=5.4.3) - :pypi:`pytest-firefox` pytest plugin to manipulate firefox Aug 08, 2017 3 - Alpha pytest (>=3.0.2) - :pypi:`pytest-fixture-config` Fixture configuration utils for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-fixture-maker` Pytest plugin to load fixtures from YAML files Sep 21, 2021 N/A N/A - :pypi:`pytest-fixture-marker` A pytest plugin to add markers based on fixtures used. Oct 11, 2020 5 - Production/Stable N/A - :pypi:`pytest-fixture-order` pytest plugin to control fixture evaluation order Aug 25, 2020 N/A pytest (>=3.0) - :pypi:`pytest-fixtures` Common fixtures for pytest May 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-fixture-tools` Plugin for pytest which provides tools for fixtures Aug 18, 2020 6 - Mature pytest - :pypi:`pytest-fixture-typecheck` A pytest plugin to assert type annotations at runtime. Aug 24, 2021 N/A pytest - :pypi:`pytest-flake8` pytest plugin to check FLAKE8 requirements Dec 16, 2020 4 - Beta pytest (>=3.5) - :pypi:`pytest-flake8-path` A pytest fixture for testing flake8 plugins. Aug 11, 2021 5 - Production/Stable pytest - :pypi:`pytest-flakefinder` Runs tests multiple times to expose flakiness. Jul 28, 2020 4 - Beta pytest (>=2.7.1) - :pypi:`pytest-flakes` pytest plugin to check source code with pyflakes Dec 02, 2021 5 - Production/Stable pytest (>=5) - :pypi:`pytest-flaptastic` Flaptastic py.test plugin Mar 17, 2019 N/A N/A - :pypi:`pytest-flask` A set of py.test fixtures to test Flask applications. Feb 27, 2021 5 - Production/Stable pytest (>=5.2) - :pypi:`pytest-flask-sqlalchemy` A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions. Apr 04, 2019 4 - Beta pytest (>=3.2.1) - :pypi:`pytest-flask-sqlalchemy-transactions` Run tests in transactions using pytest, Flask, and SQLalchemy. Aug 02, 2018 4 - Beta pytest (>=3.2.1) - :pypi:`pytest-flyte` Pytest fixtures for simplifying Flyte integration testing May 03, 2021 N/A pytest - :pypi:`pytest-focus` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest - :pypi:`pytest-forcefail` py.test plugin to make the test failing regardless of pytest.mark.xfail May 15, 2018 4 - Beta N/A - :pypi:`pytest-forward-compatability` A name to avoid typosquating pytest-foward-compatibility Sep 06, 2020 N/A N/A - :pypi:`pytest-forward-compatibility` A pytest plugin to shim pytest commandline options for fowards compatibility Sep 29, 2020 N/A N/A - :pypi:`pytest-freezegun` Wrap tests with fixtures in freeze_time Jul 19, 2020 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-freeze-reqs` Check if requirement files are frozen Apr 29, 2021 N/A N/A - :pypi:`pytest-frozen-uuids` Deterministically frozen UUID's for your tests Oct 19, 2021 N/A pytest (>=3.0) - :pypi:`pytest-func-cov` Pytest plugin for measuring function coverage Apr 15, 2021 3 - Alpha pytest (>=5) - :pypi:`pytest-funparam` An alternative way to parametrize test cases. Dec 02, 2021 4 - Beta pytest >=4.6.0 - :pypi:`pytest-fxa` pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A - :pypi:`pytest-fxtest` Oct 27, 2020 N/A N/A - :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A - :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A - :pypi:`pytest-gevent` Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest - :pypi:`pytest-gherkin` A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) - :pypi:`pytest-ghostinspector` For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A - :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Nov 30, 2021 N/A N/A - :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-git-fixtures` Pytest fixtures for testing with git. Mar 11, 2021 4 - Beta pytest - :pypi:`pytest-github` Plugin for py.test that associates tests with github issues using a marker. Mar 07, 2019 5 - Production/Stable N/A - :pypi:`pytest-github-actions-annotate-failures` pytest plugin to annotate failed tests with a workflow command for GitHub Actions Oct 24, 2021 N/A pytest (>=4.0.0) - :pypi:`pytest-gitignore` py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A - :pypi:`pytest-glamor-allure` Extends allure-pytest functionality Nov 26, 2021 4 - Beta pytest - :pypi:`pytest-gnupg-fixtures` Pytest fixtures for testing with gnupg. Mar 04, 2021 4 - Beta pytest - :pypi:`pytest-golden` Plugin for pytest that offloads expected outputs to data files Nov 23, 2020 N/A pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-graphql-schema` Get graphql schema as fixture for pytest Oct 18, 2019 N/A N/A - :pypi:`pytest-greendots` Green progress dots Feb 08, 2014 3 - Alpha N/A - :pypi:`pytest-growl` Growl notifications for pytest results. Jan 13, 2014 5 - Production/Stable N/A - :pypi:`pytest-grpc` pytest plugin for grpc May 01, 2020 N/A pytest (>=3.6.0) - :pypi:`pytest-hammertime` Display "🔨 " instead of "." for passed pytest tests. Jul 28, 2018 N/A pytest - :pypi:`pytest-harvest` Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes. Apr 01, 2021 5 - Production/Stable N/A - :pypi:`pytest-helm-chart` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Jun 15, 2020 4 - Beta pytest (>=5.4.2,<6.0.0) - :pypi:`pytest-helm-charts` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Oct 26, 2021 4 - Beta pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-helper` Functions to help in using the pytest testing framework May 31, 2019 5 - Production/Stable N/A - :pypi:`pytest-helpers` pytest helpers May 17, 2020 N/A pytest - :pypi:`pytest-helpers-namespace` Pytest Helpers Namespace Plugin Apr 29, 2021 5 - Production/Stable pytest (>=6.0.0) - :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) - :pypi:`pytest-historic` Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest - :pypi:`pytest-historic-hook` Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest - :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A - :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Nov 20, 2021 3 - Alpha pytest (==6.2.5) - :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A - :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Jul 12, 2021 N/A pytest (>=5.0) - :pypi:`pytest-hoverfly-wrapper` Integrates the Hoverfly HTTP proxy into Pytest Aug 29, 2021 4 - Beta N/A - :pypi:`pytest-hpfeeds` Helpers for testing hpfeeds in your python project Aug 27, 2021 4 - Beta pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-html` pytest plugin for generating HTML reports Dec 13, 2020 5 - Production/Stable pytest (!=6.0.0,>=5.0) - :pypi:`pytest-html-lee` optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-html-profiling` Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-html-reporter` Generates a static html report based on pytest framework Apr 25, 2021 N/A N/A - :pypi:`pytest-html-thread` pytest plugin for generating HTML reports Dec 29, 2020 5 - Production/Stable N/A - :pypi:`pytest-http` Fixture "http" for http requests Dec 05, 2019 N/A N/A - :pypi:`pytest-httpbin` Easily test your HTTP library against a local copy of httpbin Feb 11, 2019 5 - Production/Stable N/A - :pypi:`pytest-http-mocker` Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A - :pypi:`pytest-httpretty` A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A - :pypi:`pytest-httpserver` pytest-httpserver is a httpserver for pytest Oct 18, 2021 3 - Alpha pytest ; extra == 'dev' - :pypi:`pytest-httpx` Send responses to httpx. Nov 16, 2021 5 - Production/Stable pytest (==6.*) - :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Nov 16, 2021 N/A pytest (>=6.2.5) - :pypi:`pytest-hue` Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A - :pypi:`pytest-hylang` Pytest plugin to allow running tests written in hylang Mar 28, 2021 N/A pytest - :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A - :pypi:`pytest-ibutsu` A plugin to sent pytest results to an Ibutsu server Jun 16, 2021 4 - Beta pytest - :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Apr 08, 2020 4 - Beta N/A - :pypi:`pytest-idapro` A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A - :pypi:`pytest-idempotent` Pytest plugin for testing function idempotence. Nov 26, 2021 N/A N/A - :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Apr 23, 2021 5 - Production/Stable N/A - :pypi:`pytest-image-diff` Jul 28, 2021 3 - Alpha pytest - :pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A - :pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A - :pypi:`pytest-info-collector` pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A - :pypi:`pytest-informative-node` display more node ininformation. Apr 25, 2019 4 - Beta N/A - :pypi:`pytest-infrastructure` pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A - :pypi:`pytest-ini` Reuse pytest.ini to store env variables Sep 30, 2021 N/A N/A - :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Aug 17, 2021 5 - Production/Stable N/A - :pypi:`pytest-inmanta-extensions` Inmanta tests package May 27, 2021 5 - Production/Stable N/A - :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A - :pypi:`pytest-insta` A practical snapshot testing plugin for pytest Apr 07, 2021 N/A pytest (>=6.0.2,<7.0.0) - :pypi:`pytest-instafail` pytest plugin to show failures instantly Jun 14, 2020 4 - Beta pytest (>=2.9) - :pypi:`pytest-instrument` pytest plugin to instrument tests Apr 05, 2020 5 - Production/Stable pytest (>=5.1.0) - :pypi:`pytest-integration` Organizing pytests by integration or not Apr 16, 2020 N/A N/A - :pypi:`pytest-integration-mark` Automatic integration test marking and excluding plugin for pytest Jul 19, 2021 N/A pytest (>=5.2,<7.0) - :pypi:`pytest-interactive` A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A - :pypi:`pytest-intercept-remote` Pytest plugin for intercepting outgoing connection requests during pytest run. May 24, 2021 4 - Beta pytest (>=4.6) - :pypi:`pytest-invenio` Pytest fixtures for Invenio. May 11, 2021 5 - Production/Stable pytest (<7,>=6) - :pypi:`pytest-involve` Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-ipdb` A py.test plug-in to enable drop to ipdb debugger on test failure. Sep 02, 2014 2 - Pre-Alpha N/A - :pypi:`pytest-ipynb` THIS PROJECT IS ABANDONED Jan 29, 2019 3 - Alpha N/A - :pypi:`pytest-isort` py.test plugin to check import ordering using isort Apr 27, 2021 5 - Production/Stable N/A - :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 22, 2020 4 - Beta N/A - :pypi:`pytest-iterassert` Nicer list and iterable assertion messages for pytest May 11, 2020 3 - Alpha N/A - :pypi:`pytest-jasmine` Run jasmine tests from your pytest test suite Nov 04, 2017 1 - Planning N/A - :pypi:`pytest-jest` A custom jest-pytest oriented Pytest reporter May 22, 2018 4 - Beta pytest (>=3.3.2) - :pypi:`pytest-jira` py.test JIRA integration plugin, using markers Dec 02, 2021 3 - Alpha N/A - :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Nov 28, 2021 3 - Alpha pytest - :pypi:`pytest-jobserver` Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest - :pypi:`pytest-joke` Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1) - :pypi:`pytest-json` Generate JSON test reports Jan 18, 2016 4 - Beta N/A - :pypi:`pytest-jsonlint` UNKNOWN Aug 04, 2016 N/A N/A - :pypi:`pytest-json-report` A pytest plugin to report test results as JSON files Sep 24, 2021 4 - Beta pytest (>=3.8.0) - :pypi:`pytest-kafka` Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest Aug 24, 2021 N/A pytest - :pypi:`pytest-kafkavents` A plugin to send pytest events to Kafka Sep 08, 2021 4 - Beta pytest - :pypi:`pytest-kind` Kubernetes test support with KIND for pytest Jan 24, 2021 5 - Production/Stable N/A - :pypi:`pytest-kivy` Kivy GUI tests fixtures using pytest Jul 06, 2021 4 - Beta pytest (>=3.6) - :pypi:`pytest-knows` A pytest plugin that can automaticly skip test case based on dependence info calculated by trace Aug 22, 2014 N/A N/A - :pypi:`pytest-konira` Run Konira DSL tests with py.test Oct 09, 2011 N/A N/A - :pypi:`pytest-krtech-common` pytest krtech common library Nov 28, 2016 4 - Beta N/A - :pypi:`pytest-kwparametrize` Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks Jan 22, 2021 N/A pytest (>=6) - :pypi:`pytest-lambda` Define pytest fixtures with lambda functions. Aug 23, 2021 3 - Alpha pytest (>=3.6,<7) - :pypi:`pytest-lamp` Jan 06, 2017 3 - Alpha N/A - :pypi:`pytest-layab` Pytest fixtures for layab. Oct 05, 2020 5 - Production/Stable N/A - :pypi:`pytest-lazy-fixture` It helps to use fixtures in pytest.mark.parametrize Feb 01, 2020 4 - Beta pytest (>=3.2.5) - :pypi:`pytest-ldap` python-ldap fixtures for pytest Aug 18, 2020 N/A pytest - :pypi:`pytest-leaks` A pytest plugin to trace resource leaks. Nov 27, 2019 1 - Planning N/A - :pypi:`pytest-level` Select tests of a given level or lower Oct 21, 2019 N/A pytest - :pypi:`pytest-libfaketime` A python-libfaketime plugin for pytest. Dec 22, 2018 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Oct 29, 2021 4 - Beta N/A - :pypi:`pytest-libnotify` Pytest plugin that shows notifications about the test run Apr 02, 2021 3 - Alpha pytest - :pypi:`pytest-ligo` Jan 16, 2020 4 - Beta N/A - :pypi:`pytest-lineno` A pytest plugin to show the line numbers of test functions Dec 04, 2020 N/A pytest - :pypi:`pytest-line-profiler` Profile code executed by pytest May 03, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-lisa` Pytest plugin for organizing tests. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-listener` A simple network listener May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-litf` A pytest plugin that stream output in LITF format Jan 18, 2021 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-live` Live results for pytest Mar 08, 2020 N/A pytest - :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests Aug 25, 2021 5 - Production/Stable pytest - :pypi:`pytest-localserver` py.test plugin to test server connections locally. Nov 19, 2021 4 - Beta N/A - :pypi:`pytest-localstack` Pytest plugin for AWS integration tests Aug 22, 2019 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-lockable` lockable resource plugin for pytest Nov 09, 2021 5 - Production/Stable pytest - :pypi:`pytest-locker` Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed Oct 29, 2021 N/A pytest (>=5.4) - :pypi:`pytest-log` print log Aug 15, 2021 N/A pytest (>=3.8) - :pypi:`pytest-logbook` py.test plugin to capture logbook log messages Nov 23, 2015 5 - Production/Stable pytest (>=2.8) - :pypi:`pytest-logdog` Pytest plugin to test logging Jun 15, 2021 1 - Planning pytest (>=6.2.0) - :pypi:`pytest-logfest` Pytest plugin providing three logger fixtures with basic or full writing to log files Jul 21, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-logger` Plugin configuring handlers for loggers from Python logging module. Jul 25, 2019 4 - Beta pytest (>=3.2) - :pypi:`pytest-logging` Configures logging and allows tweaking the log level with a py.test flag Nov 04, 2015 4 - Beta N/A - :pypi:`pytest-log-report` Package for creating a pytest test run reprot Dec 26, 2019 N/A N/A - :pypi:`pytest-manual-marker` pytest marker for marking manual tests Oct 11, 2021 3 - Alpha pytest (>=6) - :pypi:`pytest-markdown` Test your markdown docs with pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) - :pypi:`pytest-marker-bugzilla` py.test bugzilla integration plugin, using markers Jan 09, 2020 N/A N/A - :pypi:`pytest-markers-presence` A simple plugin to detect missed pytest tags and markers" Feb 04, 2021 4 - Beta pytest (>=6.0) - :pypi:`pytest-markfiltration` UNKNOWN Nov 08, 2011 3 - Alpha N/A - :pypi:`pytest-mark-no-py3` pytest plugin and bowler codemod to help migrate tests to Python 3 May 17, 2019 N/A pytest - :pypi:`pytest-marks` UNKNOWN Nov 23, 2012 3 - Alpha N/A - :pypi:`pytest-matcher` Match test output against patterns stored in files Apr 23, 2020 5 - Production/Stable pytest (>=3.4) - :pypi:`pytest-match-skip` Skip matching marks. Matches partial marks using wildcards. May 15, 2019 4 - Beta pytest (>=4.4.1) - :pypi:`pytest-mat-report` this is report Jan 20, 2021 N/A N/A - :pypi:`pytest-matrix` Provide tools for generating tests from combinations of fixtures. Jun 24, 2020 5 - Production/Stable pytest (>=5.4.3,<6.0.0) - :pypi:`pytest-mccabe` pytest plugin to run the mccabe code complexity checker. Jul 22, 2020 3 - Alpha pytest (>=5.4.0) - :pypi:`pytest-md` Plugin for generating Markdown reports for pytest results Jul 11, 2019 3 - Alpha pytest (>=4.2.1) - :pypi:`pytest-md-report` A pytest plugin to make a test results report with Markdown table format. May 04, 2021 4 - Beta pytest (!=6.0.0,<7,>=3.3.2) - :pypi:`pytest-memprof` Estimates memory consumption of test functions Mar 29, 2019 4 - Beta N/A - :pypi:`pytest-menu` A pytest plugin for console based interactive test selection just after the collection phase Oct 04, 2017 3 - Alpha pytest (>=2.4.2) - :pypi:`pytest-mercurial` pytest plugin to write integration tests for projects using Mercurial Python internals Nov 21, 2020 1 - Planning N/A - :pypi:`pytest-message` Pytest plugin for sending report message of marked tests execution Nov 04, 2021 N/A pytest (>=6.2.5) - :pypi:`pytest-messenger` Pytest to Slack reporting plugin Dec 16, 2020 5 - Production/Stable N/A - :pypi:`pytest-metadata` pytest plugin for test session metadata Nov 27, 2020 5 - Production/Stable pytest (>=2.9.0) - :pypi:`pytest-metrics` Custom metrics report for pytest Apr 04, 2020 N/A pytest - :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) - :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Sep 26, 2020 N/A pytest (>=6.0.1,<7.0.0) - :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A - :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) - :pypi:`pytest-mock` Thin-wrapper around the mock package for easier use with pytest May 06, 2021 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-mock-api` A mock API server with configurable routes and responses available as a fixture. Feb 13, 2019 1 - Planning pytest (>=4.0.0) - :pypi:`pytest-mock-generator` A pytest fixture wrapper for https://pypi.org/project/mock-generator Aug 10, 2021 5 - Production/Stable N/A - :pypi:`pytest-mock-helper` Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest - :pypi:`pytest-mockito` Base fixtures for mockito Jul 11, 2018 4 - Beta N/A - :pypi:`pytest-mockredis` An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A - :pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. Dec 03, 2021 N/A pytest (>=1.0) - :pypi:`pytest-mock-server` Mock server plugin for pytest Apr 06, 2020 4 - Beta N/A - :pypi:`pytest-mockservers` A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0) - :pypi:`pytest-modifyjunit` Utility for adding additional properties to junit xml for IDM QE Jan 10, 2019 N/A N/A - :pypi:`pytest-modifyscope` pytest plugin to modify fixture scope Apr 12, 2020 N/A pytest - :pypi:`pytest-molecule` PyTest Molecule Plugin :: discover and run molecule tests Oct 06, 2021 5 - Production/Stable N/A - :pypi:`pytest-mongo` MongoDB process and client fixtures plugin for Pytest. Jun 07, 2021 5 - Production/Stable pytest - :pypi:`pytest-mongodb` pytest plugin for MongoDB fixtures Dec 07, 2019 5 - Production/Stable pytest (>=2.5.2) - :pypi:`pytest-monitor` Pytest plugin for analyzing resource usage. Aug 24, 2021 5 - Production/Stable pytest - :pypi:`pytest-monkeyplus` pytest's monkeypatch subclass with extra functionalities Sep 18, 2012 5 - Production/Stable N/A - :pypi:`pytest-monkeytype` pytest-monkeytype: Generate Monkeytype annotations from your pytest tests. Jul 29, 2020 4 - Beta N/A - :pypi:`pytest-moto` Fixtures for integration tests of AWS services,uses moto mocking library. Aug 28, 2015 1 - Planning N/A - :pypi:`pytest-motor` A pytest plugin for motor, the non-blocking MongoDB driver. Jul 21, 2021 3 - Alpha pytest - :pypi:`pytest-mp` A test batcher for multiprocessed Pytest runs May 23, 2018 4 - Beta pytest - :pypi:`pytest-mpi` pytest plugin to collect information from tests Mar 14, 2021 3 - Alpha pytest - :pypi:`pytest-mpl` pytest plugin to help with testing figures output from Matplotlib Jul 02, 2021 4 - Beta pytest - :pypi:`pytest-mproc` low-startup-overhead, scalable, distributed-testing pytest plugin Mar 07, 2021 4 - Beta pytest - :pypi:`pytest-multi-check` Pytest-плагин, реализует возможность мульти проверок и мягких проверок Jun 03, 2021 N/A pytest - :pypi:`pytest-multihost` Utility for writing multi-host tests for pytest Apr 07, 2020 4 - Beta N/A - :pypi:`pytest-multilog` Multi-process logs handling and other helpers for pytest Jun 10, 2021 N/A N/A - :pypi:`pytest-multithreading` a pytest plugin for th and concurrent testing Aug 12, 2021 N/A pytest (>=3.6) - :pypi:`pytest-mutagen` Add the mutation testing feature to pytest Jul 24, 2020 N/A pytest (>=5.4) - :pypi:`pytest-mypy` Mypy static type checker plugin for Pytest Mar 21, 2021 4 - Beta pytest (>=3.5) - :pypi:`pytest-mypyd` Mypy static type checker plugin for Pytest Aug 20, 2019 4 - Beta pytest (<4.7,>=2.8) ; python_version < "3.5" - :pypi:`pytest-mypy-plugins` pytest plugin for writing tests for mypy plugins Oct 19, 2021 3 - Alpha pytest (>=6.0.0) - :pypi:`pytest-mypy-plugins-shim` Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. Apr 12, 2021 N/A N/A - :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. Jun 13, 2021 N/A pytest - :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Nov 22, 2021 5 - Production/Stable pytest - :pypi:`pytest-needle` pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0) - :pypi:`pytest-neo` pytest-neo is a plugin for pytest that shows tests like screen of Matrix. Apr 23, 2019 3 - Alpha pytest (>=3.7.2) - :pypi:`pytest-network` A simple plugin to disable network on socket level. May 07, 2020 N/A N/A - :pypi:`pytest-never-sleep` pytest plugin helps to avoid adding tests without mock \`time.sleep\` May 05, 2021 3 - Alpha pytest (>=3.5.1) - :pypi:`pytest-nginx` nginx fixture for pytest Aug 12, 2017 5 - Production/Stable N/A - :pypi:`pytest-nginx-iplweb` nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-ngrok` Jan 22, 2020 3 - Alpha N/A - :pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0) - :pypi:`pytest-nice` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest - :pypi:`pytest-nice-parametrize` A small snippet for nicer PyTest's Parametrize Apr 17, 2021 5 - Production/Stable N/A - :pypi:`pytest-nlcov` Pytest plugin to get the coverage of the new lines (based on git diff) only Jul 07, 2021 N/A N/A - :pypi:`pytest-nocustom` Run all tests without custom markers Jul 07, 2021 5 - Production/Stable N/A - :pypi:`pytest-nodev` Test-driven source code search for Python. Jul 21, 2016 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-nogarbage` Ensure a test produces no garbage Aug 29, 2021 5 - Production/Stable pytest (>=4.6.0) - :pypi:`pytest-notebook` A pytest plugin for testing Jupyter Notebooks Sep 16, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-notice` Send pytest execution result email Nov 05, 2020 N/A N/A - :pypi:`pytest-notification` A pytest plugin for sending a desktop notification and playing a sound upon completion of tests Jun 19, 2020 N/A pytest (>=4) - :pypi:`pytest-notifier` A pytest plugin to notify test result Jun 12, 2020 3 - Alpha pytest - :pypi:`pytest-notimplemented` Pytest markers for not implemented features and tests. Aug 27, 2019 N/A pytest (>=5.1,<6.0) - :pypi:`pytest-notion` A PyTest Reporter to send test runs to Notion.so Aug 07, 2019 N/A N/A - :pypi:`pytest-nunit` A pytest plugin for generating NUnit3 test result XML output Aug 04, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-ochrus` pytest results data-base and HTML reporter Feb 21, 2018 4 - Beta N/A - :pypi:`pytest-odoo` py.test plugin to run Odoo tests Nov 04, 2021 4 - Beta pytest (>=2.9) - :pypi:`pytest-odoo-fixtures` Project description Jun 25, 2019 N/A N/A - :pypi:`pytest-oerp` pytest plugin to test OpenERP modules Feb 28, 2012 3 - Alpha N/A - :pypi:`pytest-ok` The ultimate pytest output plugin Apr 01, 2019 4 - Beta N/A - :pypi:`pytest-only` Use @pytest.mark.only to run a single test Jan 19, 2020 N/A N/A - :pypi:`pytest-oot` Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A - :pypi:`pytest-openfiles` Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) - :pypi:`pytest-opentmi` pytest plugin for publish results to opentmi Nov 04, 2021 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-operator` Fixtures for Operators Oct 26, 2021 N/A N/A - :pypi:`pytest-optional` include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A - :pypi:`pytest-optional-tests` Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) - :pypi:`pytest-orchestration` A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A - :pypi:`pytest-order` pytest plugin to run your tests in a specific order May 30, 2021 4 - Beta pytest (>=5.0) - :pypi:`pytest-ordering` pytest plugin to run your tests in a specific order Nov 14, 2018 4 - Beta pytest - :pypi:`pytest-osxnotify` OS X notifications for py.test results. May 15, 2015 N/A N/A - :pypi:`pytest-otel` pytest-otel report OpenTelemetry traces about test executed Dec 03, 2021 N/A N/A - :pypi:`pytest-pact` A simple plugin to use with pytest Jan 07, 2019 4 - Beta N/A - :pypi:`pytest-pahrametahrize` Parametrize your tests with a Boston accent. Nov 24, 2021 4 - Beta pytest (>=6.0,<7.0) - :pypi:`pytest-parallel` a pytest plugin for parallel and concurrent testing Oct 10, 2021 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-parallel-39` a pytest plugin for parallel and concurrent testing Jul 12, 2021 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-param` pytest plugin to test all, first, last or random params Sep 11, 2016 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-paramark` Configure pytest fixtures using a combination of"parametrize" and markers Jan 10, 2020 4 - Beta pytest (>=4.5.0) - :pypi:`pytest-parametrization` Simpler PyTest parametrization Nov 30, 2021 5 - Production/Stable pytest - :pypi:`pytest-parametrize-cases` A more user-friendly way to write parametrized tests. Dec 12, 2020 N/A pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-parametrized` Pytest plugin for parametrizing tests with default iterables. Oct 19, 2020 5 - Production/Stable pytest - :pypi:`pytest-parawtf` Finally spell paramete?ri[sz]e correctly Dec 03, 2018 4 - Beta pytest (>=3.6.0) - :pypi:`pytest-pass` Check out https://github.com/elilutsky/pytest-pass Dec 04, 2019 N/A N/A - :pypi:`pytest-passrunner` Pytest plugin providing the 'run_on_pass' marker Feb 10, 2021 5 - Production/Stable pytest (>=4.6.0) - :pypi:`pytest-paste-config` Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A - :pypi:`pytest-patches` A contextmanager pytest fixture for handling multiple mock patches Aug 30, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-pdb` pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A - :pypi:`pytest-peach` pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7) - :pypi:`pytest-pep257` py.test plugin for pep257 Jul 09, 2016 N/A N/A - :pypi:`pytest-pep8` pytest plugin to check PEP8 requirements Apr 27, 2014 N/A N/A - :pypi:`pytest-percent` Change the exit code of pytest test sessions when a required percent of tests pass. May 21, 2020 N/A pytest (>=5.2.0) - :pypi:`pytest-perf` pytest-perf Jun 27, 2021 5 - Production/Stable pytest (>=4.6) ; extra == 'testing' - :pypi:`pytest-performance` A simple plugin to ensure the execution of critical sections of code has not been impacted Sep 11, 2020 5 - Production/Stable pytest (>=3.7.0) - :pypi:`pytest-persistence` Pytest tool for persistent objects Nov 06, 2021 N/A N/A - :pypi:`pytest-pgsql` Pytest plugins and helpers for tests using a Postgres database. May 13, 2020 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-phmdoctest` pytest plugin to test Python examples in Markdown using phmdoctest. Nov 10, 2021 4 - Beta pytest (>=6.2) ; extra == 'test' - :pypi:`pytest-picked` Run the tests related to the changed files Dec 23, 2020 N/A pytest (>=3.5.0) - :pypi:`pytest-pigeonhole` Jun 25, 2018 5 - Production/Stable pytest (>=3.4) - :pypi:`pytest-pikachu` Show surprise when tests are passing Aug 05, 2021 5 - Production/Stable pytest - :pypi:`pytest-pilot` Slice in your test base thanks to powerful markers. Oct 09, 2020 5 - Production/Stable N/A - :pypi:`pytest-pings` 🦊 The pytest plugin for Firefox Telemetry 📊 Jun 29, 2019 3 - Alpha pytest (>=5.0.0) - :pypi:`pytest-pinned` A simple pytest plugin for pinning tests Sep 17, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-pinpoint` A pytest plugin which runs SBFL algorithms to detect faults. Sep 25, 2020 N/A pytest (>=4.4.0) - :pypi:`pytest-pipeline` Pytest plugin for functional testing of data analysispipelines Jan 24, 2017 3 - Alpha N/A - :pypi:`pytest-platform-markers` Markers for pytest to skip tests on specific platforms Sep 09, 2019 4 - Beta pytest (>=3.6.0) - :pypi:`pytest-play` pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files Jun 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-playbook` Pytest plugin for reading playbooks. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-playwright` A pytest wrapper with fixtures for Playwright to automate web browsers Oct 28, 2021 N/A pytest - :pypi:`pytest-playwrights` A pytest wrapper with fixtures for Playwright to automate web browsers Dec 02, 2021 N/A N/A - :pypi:`pytest-playwright-snapshot` A pytest wrapper for snapshot testing with playwright Aug 19, 2021 N/A N/A - :pypi:`pytest-plt` Fixtures for quickly making Matplotlib plots in tests Aug 17, 2020 5 - Production/Stable pytest - :pypi:`pytest-plugin-helpers` A plugin to help developing and testing other plugins Nov 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-plus` PyTest Plus Plugin :: extends pytest functionality Mar 19, 2020 5 - Production/Stable pytest (>=3.50) - :pypi:`pytest-pmisc` Mar 21, 2019 5 - Production/Stable N/A - :pypi:`pytest-pointers` Pytest plugin to define functions you test with special marks for better navigation and reports Oct 14, 2021 N/A N/A - :pypi:`pytest-polarion-cfme` pytest plugin for collecting test cases and recording test results Nov 13, 2017 3 - Alpha N/A - :pypi:`pytest-polarion-collect` pytest plugin for collecting polarion test cases data Jun 18, 2020 3 - Alpha pytest - :pypi:`pytest-polecat` Provides Polecat pytest fixtures Aug 12, 2019 4 - Beta N/A - :pypi:`pytest-ponyorm` PonyORM in Pytest Oct 31, 2018 N/A pytest (>=3.1.1) - :pypi:`pytest-poo` Visualize your crappy tests Mar 25, 2021 5 - Production/Stable pytest (>=2.3.4) - :pypi:`pytest-poo-fail` Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A - :pypi:`pytest-pop` A pytest plugin to help with testing pop projects Aug 19, 2021 5 - Production/Stable pytest - :pypi:`pytest-portion` Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-postgres` Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest - :pypi:`pytest-postgresql` Postgresql fixtures and fixture factories for Pytest. Nov 05, 2021 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-power` pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) - :pypi:`pytest-pretty-terminal` pytest plugin for generating prettier terminal output Nov 24, 2021 N/A pytest (>=3.4.1) - :pypi:`pytest-pride` Minitest-style test colors Apr 02, 2016 3 - Alpha N/A - :pypi:`pytest-print` pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Jun 17, 2021 5 - Production/Stable pytest (>=6) - :pypi:`pytest-profiling` Profiling plugin for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-progress` pytest plugin for instant test progress status Nov 09, 2021 5 - Production/Stable pytest (>=2.7) - :pypi:`pytest-prometheus` Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A - :pypi:`pytest-prosper` Test helpers for Prosper projects Sep 24, 2018 N/A N/A - :pypi:`pytest-pspec` A rspec format reporter for Python ptest Jun 02, 2020 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-psqlgraph` pytest plugin for testing applications that use psqlgraph Oct 19, 2021 4 - Beta pytest (>=6.0) - :pypi:`pytest-ptera` Use ptera probes in tests Oct 20, 2021 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-pudb` Pytest PuDB debugger integration Oct 25, 2018 3 - Alpha pytest (>=2.0) - :pypi:`pytest-purkinje` py.test plugin for purkinje test runner Oct 28, 2017 2 - Pre-Alpha N/A - :pypi:`pytest-pycharm` Plugin for py.test to enter PyCharm debugger on uncaught exceptions Aug 13, 2020 5 - Production/Stable pytest (>=2.3) - :pypi:`pytest-pycodestyle` pytest plugin to run pycodestyle Aug 10, 2020 3 - Alpha N/A - :pypi:`pytest-pydev` py.test plugin to connect to a remote debug server with PyDev or PyCharm. Nov 15, 2017 3 - Alpha N/A - :pypi:`pytest-pydocstyle` pytest plugin to run pydocstyle Aug 10, 2020 3 - Alpha N/A - :pypi:`pytest-pylint` pytest plugin to check source code with pylint Nov 09, 2020 5 - Production/Stable pytest (>=5.4) - :pypi:`pytest-pypi` Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A - :pypi:`pytest-pypom-navigation` Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) - :pypi:`pytest-pyppeteer` A plugin to run pyppeteer in pytest. Feb 16, 2021 4 - Beta pytest (>=6.0.2) - :pypi:`pytest-pyq` Pytest fixture "q" for pyq Mar 10, 2020 5 - Production/Stable N/A - :pypi:`pytest-pyramid` pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite Oct 15, 2021 5 - Production/Stable pytest - :pypi:`pytest-pyramid-server` Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-pyright` Pytest plugin for type checking code with Pyright Aug 16, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-pytestrail` Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) - :pypi:`pytest-pythonpath` pytest plugin for adding to the PYTHONPATH from command line or configs. Aug 22, 2018 5 - Production/Stable N/A - :pypi:`pytest-pytorch` pytest plugin for a better developer experience when working with the PyTorch test suite May 25, 2021 4 - Beta pytest - :pypi:`pytest-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0) - :pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Jun 26, 2021 4 - Beta pytest (>=6.2.0) - :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Nov 25, 2021 5 - Production/Stable pytest (>=6.2.3) - :pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A - :pypi:`pytest-qt` pytest support for PyQt and PySide applications Jun 13, 2021 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-qt-app` QT app fixture for py.test Dec 23, 2015 5 - Production/Stable N/A - :pypi:`pytest-quarantine` A plugin for pytest to manage expected test failures Nov 24, 2019 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-quickcheck` pytest plugin to generate random data inspired by QuickCheck Nov 15, 2020 4 - Beta pytest (<6.0.0,>=4.0) - :pypi:`pytest-rabbitmq` RabbitMQ process and client fixtures for pytest Jun 02, 2021 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-race` Race conditions tester for pytest Nov 21, 2016 4 - Beta N/A - :pypi:`pytest-rage` pytest plugin to implement PEP712 Oct 21, 2011 3 - Alpha N/A - :pypi:`pytest-railflow-testrail-reporter` Generate json reports along with specified metadata defined in test markers. Dec 02, 2021 5 - Production/Stable pytest - :pypi:`pytest-raises` An implementation of pytest.raises as a pytest.mark fixture Apr 23, 2020 N/A pytest (>=3.2.2) - :pypi:`pytest-raisesregexp` Simple pytest plugin to look for regex in Exceptions Dec 18, 2015 N/A N/A - :pypi:`pytest-raisin` Plugin enabling the use of exception instances with pytest.raises Jun 25, 2020 N/A pytest - :pypi:`pytest-random` py.test plugin to randomize tests Apr 28, 2013 3 - Alpha N/A - :pypi:`pytest-randomly` Pytest plugin to randomly order tests and control random.seed. Nov 30, 2021 5 - Production/Stable pytest - :pypi:`pytest-randomness` Pytest plugin about random seed management May 30, 2019 3 - Alpha N/A - :pypi:`pytest-random-num` Randomise the order in which pytest tests are run with some control over the randomness Oct 19, 2020 5 - Production/Stable N/A - :pypi:`pytest-random-order` Randomise the order in which pytest tests are run with some control over the randomness Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-readme` Test your README.md file Dec 28, 2014 5 - Production/Stable N/A - :pypi:`pytest-reana` Pytest fixtures for REANA. Nov 22, 2021 3 - Alpha N/A - :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Jul 08, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A - :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Nov 03, 2021 5 - Production/Stable pytest - :pypi:`pytest-redislite` Pytest plugin for testing code using Redis Sep 19, 2021 4 - Beta pytest - :pypi:`pytest-redmine` Pytest plugin for redmine Mar 19, 2018 1 - Planning N/A - :pypi:`pytest-ref` A plugin to store reference files to ease regression testing Nov 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-reference-formatter` Conveniently run pytest with a dot-formatted test reference. Oct 01, 2019 4 - Beta N/A - :pypi:`pytest-regressions` Easy to use fixtures to write regression tests. Jan 27, 2021 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-regtest` pytest plugin for regression tests Jun 03, 2021 N/A N/A - :pypi:`pytest-relative-order` a pytest plugin that sorts tests using "before" and "after" markers May 17, 2021 4 - Beta N/A - :pypi:`pytest-relaxed` Relaxed test discovery/organization for pytest Jun 14, 2019 5 - Production/Stable pytest (<5,>=3) - :pypi:`pytest-remfiles` Pytest plugin to create a temporary directory with remote files Jul 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-remotedata` Pytest plugin for controlling remote data access. Jul 20, 2019 3 - Alpha pytest (>=3.1) - :pypi:`pytest-remote-response` Pytest plugin for capturing and mocking connection requests. Jun 30, 2021 4 - Beta pytest (>=4.6) - :pypi:`pytest-remove-stale-bytecode` py.test plugin to remove stale byte code files. Mar 04, 2020 4 - Beta pytest - :pypi:`pytest-reorder` Reorder tests depending on their paths and names. May 31, 2018 4 - Beta pytest - :pypi:`pytest-repeat` pytest plugin for repeating tests Oct 31, 2020 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-replay` Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests Jun 09, 2021 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-repo-health` A pytest plugin to report on repository standards conformance Nov 23, 2021 3 - Alpha pytest - :pypi:`pytest-report` Creates json report that is compatible with atom.io's linter message format May 11, 2016 4 - Beta N/A - :pypi:`pytest-reporter` Generate Pytest reports with templates Jul 22, 2021 4 - Beta pytest - :pypi:`pytest-reporter-html1` A basic HTML report template for Pytest Jun 08, 2021 4 - Beta N/A - :pypi:`pytest-reportinfra` Pytest plugin for reportinfra Aug 11, 2019 3 - Alpha N/A - :pypi:`pytest-reporting` A plugin to report summarized results in a table format Oct 25, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-reportlog` Replacement for the --resultlog option, focused in simplicity and extensibility Dec 11, 2020 3 - Alpha pytest (>=5.2) - :pypi:`pytest-report-me` A pytest plugin to generate report. Dec 31, 2020 N/A pytest - :pypi:`pytest-report-parameters` pytest plugin for adding tests' parameters to junit report Jun 18, 2020 3 - Alpha pytest (>=2.4.2) - :pypi:`pytest-reportportal` Agent for Reporting results of tests to the Report Portal Jun 18, 2021 N/A pytest (>=3.8.0) - :pypi:`pytest-reqs` pytest plugin to check pinned requirements May 12, 2019 N/A pytest (>=2.4.2) - :pypi:`pytest-requests` A simple plugin to use with pytest Jun 24, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-reraise` Make multi-threaded pytest test cases fail when they should Jun 17, 2021 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-rerun` Re-run only changed files in specified branch Jul 08, 2019 N/A pytest (>=3.6) - :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Sep 17, 2021 5 - Production/Stable pytest (>=5.3) - :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest. Nov 15, 2021 N/A N/A - :pypi:`pytest-resource` Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A - :pypi:`pytest-resource-path` Provides path for uniform access to test resources in isolated directory May 01, 2021 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-responsemock` Simplified requests calls mocking for pytest Oct 10, 2020 5 - Production/Stable N/A - :pypi:`pytest-responses` py.test integration for responses Apr 26, 2021 N/A pytest (>=2.5) - :pypi:`pytest-restrict` Pytest plugin to restrict the test types allowed Aug 12, 2021 5 - Production/Stable pytest - :pypi:`pytest-rethinkdb` A RethinkDB plugin for pytest. Jul 24, 2016 4 - Beta N/A - :pypi:`pytest-reverse` Pytest plugin to reverse test order. Aug 12, 2021 5 - Production/Stable pytest - :pypi:`pytest-ringo` pytest plugin to test webapplications using the Ringo webframework Sep 27, 2017 3 - Alpha N/A - :pypi:`pytest-rng` Fixtures for seeding tests and making randomness reproducible Aug 08, 2019 5 - Production/Stable pytest - :pypi:`pytest-roast` pytest plugin for ROAST configuration override and fixtures Jul 29, 2021 5 - Production/Stable pytest - :pypi:`pytest-rocketchat` Pytest to Rocket.Chat reporting plugin Apr 18, 2021 5 - Production/Stable N/A - :pypi:`pytest-rotest` Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) - :pypi:`pytest-rpc` Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) - :pypi:`pytest-rst` Test code from RST documents with pytest Sep 21, 2021 N/A pytest - :pypi:`pytest-rt` pytest data collector plugin for Testgr Sep 04, 2021 N/A N/A - :pypi:`pytest-rts` Coverage-based regression test selection (RTS) plugin for pytest May 17, 2021 N/A pytest - :pypi:`pytest-run-changed` Pytest plugin that runs changed tests only Apr 02, 2021 3 - Alpha pytest - :pypi:`pytest-runfailed` implement a --failed option for pytest Mar 24, 2016 N/A N/A - :pypi:`pytest-runner` Invoke py.test as distutils command with dependency resolution May 19, 2021 5 - Production/Stable pytest (>=4.6) ; extra == 'testing' - :pypi:`pytest-runtime-xfail` Call runtime_xfail() to mark running test as xfail. Aug 26, 2021 N/A N/A - :pypi:`pytest-salt` Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A - :pypi:`pytest-salt-containers` A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A - :pypi:`pytest-salt-factories` Pytest Salt Plugin Sep 16, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-salt-from-filenames` Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) - :pypi:`pytest-salt-runtests-bridge` Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) - :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) - :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A - :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Dec 03, 2021 5 - Production/Stable N/A - :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A - :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Aug 31, 2020 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-securestore` An encrypted password store for use within pytest cases Nov 08, 2021 4 - Beta N/A - :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) - :pypi:`pytest-selenium` pytest plugin for Selenium Sep 19, 2020 5 - Production/Stable pytest (>=5.0.0) - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Dec 03, 2021 5 - Production/Stable N/A - :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Nov 26, 2020 5 - Production/Stable N/A - :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A - :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A - :pypi:`pytest-sentry` A pytest plugin to send testrun information to Sentry.io Apr 21, 2021 N/A pytest - :pypi:`pytest-server-fixtures` Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. Nov 27, 2021 4 - Beta N/A - :pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A - :pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest - :pypi:`pytest-session-fixture-globalize` py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A - :pypi:`pytest-session_to_file` pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. Oct 01, 2015 3 - Alpha N/A - :pypi:`pytest-sftpserver` py.test plugin to locally test sftp server connections. Sep 16, 2019 4 - Beta N/A - :pypi:`pytest-shard` Dec 11, 2020 4 - Beta pytest - :pypi:`pytest-shell` A pytest plugin to help with testing shell scripts / black box commands Nov 07, 2021 N/A N/A - :pypi:`pytest-sheraf` Versatile ZODB abstraction layer - pytest fixtures Feb 11, 2020 N/A pytest - :pypi:`pytest-sherlock` pytest plugin help to find coupled tests Nov 18, 2021 5 - Production/Stable pytest (>=3.5.1) - :pypi:`pytest-shortcuts` Expand command-line shortcuts listed in pytest configuration Oct 29, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-shutil` A goodie-bag of unix shell and environment tools for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-simplehttpserver` Simple pytest fixture to spin up an HTTP server Jun 24, 2021 4 - Beta N/A - :pypi:`pytest-simple-plugin` Simple pytest plugin Nov 27, 2019 N/A N/A - :pypi:`pytest-simple-settings` simple-settings plugin for pytest Nov 17, 2020 4 - Beta pytest - :pypi:`pytest-single-file-logging` Allow for multiple processes to log to a single file May 05, 2016 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-skip-markers` Pytest Salt Plugin Oct 04, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-skipper` A plugin that selects only tests with changes in execution path Mar 26, 2017 3 - Alpha pytest (>=3.0.6) - :pypi:`pytest-skippy` Automatically skip tests that don't need to run! Jan 27, 2018 3 - Alpha pytest (>=2.3.4) - :pypi:`pytest-skip-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Sep 28, 2021 N/A N/A - :pypi:`pytest-slack` Pytest to Slack reporting plugin Dec 15, 2020 5 - Production/Stable N/A - :pypi:`pytest-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Sep 28, 2021 N/A N/A - :pypi:`pytest-smartcollect` A plugin for collecting tests that touch changed code Oct 04, 2018 N/A pytest (>=3.5.0) - :pypi:`pytest-smartcov` Smart coverage plugin for pytest. Sep 30, 2017 3 - Alpha N/A - :pypi:`pytest-smtp` Send email with pytest execution result Feb 20, 2021 N/A pytest - :pypi:`pytest-snail` Plugin for adding a marker to slow running tests. 🐌 Nov 04, 2019 3 - Alpha pytest (>=5.0.1) - :pypi:`pytest-snapci` py.test plugin for Snap-CI Nov 12, 2015 N/A N/A - :pypi:`pytest-snapshot` A plugin for snapshot testing with pytest. Dec 02, 2021 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-snmpserver` May 12, 2021 N/A N/A - :pypi:`pytest-socket` Pytest Plugin to disable socket calls during tests Aug 28, 2021 4 - Beta pytest (>=3.6.3) - :pypi:`pytest-soft-assertions` May 05, 2020 3 - Alpha pytest - :pypi:`pytest-solr` Solr process and client fixtures for py.test. May 11, 2020 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-sorter` A simple plugin to first execute tests that historically failed more Apr 20, 2021 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-sourceorder` Test-ordering plugin for pytest Sep 01, 2021 4 - Beta pytest - :pypi:`pytest-spark` pytest plugin to run the tests with support of pyspark. Feb 23, 2020 4 - Beta pytest - :pypi:`pytest-spawner` py.test plugin to spawn process and communicate with them. Jul 31, 2015 4 - Beta N/A - :pypi:`pytest-spec` Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. May 04, 2021 N/A N/A - :pypi:`pytest-sphinx` Doctest plugin for pytest with support for Sphinx-specific doctest-directives Aug 05, 2020 4 - Beta N/A - :pypi:`pytest-spiratest` Exports unit tests as test runs in SpiraTest/Team/Plan Oct 13, 2021 N/A N/A - :pypi:`pytest-splinter` Splinter plugin for pytest testing framework Dec 25, 2020 6 - Mature N/A - :pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Nov 09, 2021 4 - Beta pytest (>=5,<7) - :pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) - :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) - :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A - :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Nov 29, 2021 N/A pytest (>5.4.0,<6.3) - :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Oct 07, 2021 N/A N/A - :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) - :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A - :pypi:`pytest-sqlalchemy` pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A - :pypi:`pytest-sql-bigquery` Yet another SQL-testing framework for BigQuery provided by pytest plugin Dec 19, 2019 N/A pytest - :pypi:`pytest-srcpaths` Add paths to sys.path Oct 15, 2021 N/A N/A - :pypi:`pytest-ssh` pytest plugin for ssh command run May 27, 2019 N/A pytest - :pypi:`pytest-start-from` Start pytest run from a given point Apr 11, 2016 N/A N/A - :pypi:`pytest-statsd` pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-stepfunctions` A small description May 08, 2021 4 - Beta pytest - :pypi:`pytest-steps` Create step-wise / incremental tests in pytest. Sep 23, 2021 5 - Production/Stable N/A - :pypi:`pytest-stepwise` Run a test suite one failing test at a time. Dec 01, 2015 4 - Beta N/A - :pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A - :pypi:`pytest-stress` A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0) - :pypi:`pytest-structlog` Structured logging assertions Sep 21, 2021 N/A pytest - :pypi:`pytest-structmpd` provide structured temporary directory Oct 17, 2018 N/A N/A - :pypi:`pytest-stub` Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A - :pypi:`pytest-stubprocess` Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0) - :pypi:`pytest-study` A pytest plugin to organize long run tests (named studies) without interfering the regular tests Sep 26, 2017 3 - Alpha pytest (>=2.0) - :pypi:`pytest-subprocess` A plugin to fake subprocess for pytest Nov 07, 2021 5 - Production/Stable pytest (>=4.0.0) - :pypi:`pytest-subtesthack` A hack to explicitly set up and tear down fixtures. Mar 02, 2021 N/A N/A - :pypi:`pytest-subtests` unittest subTest() support and subtests fixture May 29, 2021 4 - Beta pytest (>=5.3.0) - :pypi:`pytest-subunit` pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. Aug 29, 2017 N/A N/A - :pypi:`pytest-sugar` pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Jul 06, 2020 3 - Alpha N/A - :pypi:`pytest-sugar-bugfix159` Workaround for https://github.com/Frozenball/pytest-sugar/issues/159 Nov 07, 2018 5 - Production/Stable pytest (!=3.7.3,>=3.5); extra == 'testing' - :pypi:`pytest-super-check` Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc. Aug 12, 2021 5 - Production/Stable pytest - :pypi:`pytest-svn` SVN repository fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-symbols` pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. Nov 20, 2017 3 - Alpha N/A - :pypi:`pytest-takeltest` Fixtures for ansible, testinfra and molecule Oct 13, 2021 N/A N/A - :pypi:`pytest-talisker` Nov 28, 2021 N/A N/A - :pypi:`pytest-tap` Test Anything Protocol (TAP) reporting plugin for pytest Oct 27, 2021 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-tape` easy assertion with expected results saved to yaml files Mar 17, 2021 4 - Beta N/A - :pypi:`pytest-target` Pytest plugin for remote target orchestration. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-tblineinfo` tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used Dec 01, 2015 3 - Alpha pytest (>=2.0) - :pypi:`pytest-teamcity-logblock` py.test plugin to introduce block structure in teamcity build log, if output is not captured May 15, 2018 4 - Beta N/A - :pypi:`pytest-telegram` Pytest to Telegram reporting plugin Dec 10, 2020 5 - Production/Stable N/A - :pypi:`pytest-tempdir` Predictable and repeatable tempdir support. Oct 11, 2019 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-terraform` A pytest plugin for using terraform fixtures Nov 10, 2021 N/A pytest (>=6.0) - :pypi:`pytest-terraform-fixture` generate terraform resources to use with pytest Nov 14, 2018 4 - Beta N/A - :pypi:`pytest-testbook` A plugin to run tests written in Jupyter notebook Dec 11, 2016 3 - Alpha N/A - :pypi:`pytest-testconfig` Test configuration plugin for pytest. Jan 11, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-testdirectory` A py.test plugin providing temporary directories in unit tests. Nov 06, 2018 5 - Production/Stable pytest - :pypi:`pytest-testdox` A testdox format reporter for pytest Oct 13, 2020 5 - Production/Stable pytest (>=3.7.0) - :pypi:`pytest-test-groups` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Oct 25, 2016 5 - Production/Stable N/A - :pypi:`pytest-testinfra` Test infrastructures Jun 20, 2021 5 - Production/Stable pytest (!=3.0.2) - :pypi:`pytest-testlink-adaptor` pytest reporting plugin for testlink Dec 20, 2018 4 - Beta pytest (>=2.6) - :pypi:`pytest-testmon` selects tests affected by changed files and methods Oct 22, 2021 4 - Beta N/A - :pypi:`pytest-testobject` Plugin to use TestObject Suites with Pytest Sep 24, 2019 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-testrail` pytest plugin for creating TestRail runs and adding results Aug 27, 2020 N/A pytest (>=3.6) - :pypi:`pytest-testrail2` A small example package Nov 17, 2020 N/A pytest (>=5) - :pypi:`pytest-testrail-api` Плагин Pytest, для интеграции с TestRail Nov 30, 2021 N/A pytest (>=5.5) - :pypi:`pytest-testrail-api-client` TestRail Api Python Client Dec 03, 2021 N/A pytest - :pypi:`pytest-testrail-appetize` pytest plugin for creating TestRail runs and adding results Sep 29, 2021 N/A N/A - :pypi:`pytest-testrail-client` pytest plugin for Testrail Sep 29, 2020 5 - Production/Stable N/A - :pypi:`pytest-testrail-e2e` pytest plugin for creating TestRail runs and adding results Oct 11, 2021 N/A pytest (>=3.6) - :pypi:`pytest-testrail-ns` pytest plugin for creating TestRail runs and adding results Oct 08, 2021 N/A pytest (>=3.6) - :pypi:`pytest-testrail-plugin` PyTest plugin for TestRail Apr 21, 2020 3 - Alpha pytest - :pypi:`pytest-testrail-reporter` Sep 10, 2018 N/A N/A - :pypi:`pytest-testreport` Nov 12, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-testslide` TestSlide fixture for pytest Jan 07, 2021 5 - Production/Stable pytest (~=6.2) - :pypi:`pytest-test-this` Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply Sep 15, 2019 2 - Pre-Alpha pytest (>=2.3) - :pypi:`pytest-test-utils` Nov 30, 2021 N/A pytest (>=5) - :pypi:`pytest-tesults` Tesults plugin for pytest Jul 31, 2021 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-tezos` pytest-ligo Jan 16, 2020 4 - Beta N/A - :pypi:`pytest-thawgun` Pytest plugin for time travel May 26, 2020 3 - Alpha N/A - :pypi:`pytest-threadleak` Detects thread leaks Sep 08, 2017 4 - Beta N/A - :pypi:`pytest-tick` Ticking on tests Aug 31, 2021 5 - Production/Stable pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-timeit` A pytest plugin to time test function runs Oct 13, 2016 4 - Beta N/A - :pypi:`pytest-timeout` pytest plugin to abort hanging tests Oct 11, 2021 5 - Production/Stable pytest (>=5.0.0) - :pypi:`pytest-timeouts` Linux-only Pytest plugin to control durations of various test case execution phases Sep 21, 2019 5 - Production/Stable N/A - :pypi:`pytest-timer` A timer plugin for pytest Jun 02, 2021 N/A N/A - :pypi:`pytest-timestamper` Pytest plugin to add a timestamp prefix to the pytest output Jun 06, 2021 N/A N/A - :pypi:`pytest-tipsi-django` Nov 17, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-tipsi-testing` Better fixtures management. Various helpers Nov 04, 2020 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-tldr` A pytest plugin that limits the output to just the things you need. Mar 12, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-tm4j-reporter` Cloud Jira Test Management (TM4J) PyTest reporter plugin Sep 01, 2020 N/A pytest - :pypi:`pytest-tmreport` this is a vue-element ui report for pytest Nov 17, 2021 N/A N/A - :pypi:`pytest-todo` A small plugin for the pytest testing framework, marking TODO comments as failure May 23, 2019 4 - Beta pytest - :pypi:`pytest-tomato` Mar 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-toolbelt` This is just a collection of utilities for pytest, but don't really belong in pytest proper. Aug 12, 2019 3 - Alpha N/A - :pypi:`pytest-toolbox` Numerous useful plugins for pytest. Apr 07, 2018 N/A pytest (>=3.5.0) - :pypi:`pytest-tornado` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Jun 17, 2020 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-tornado5` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Nov 16, 2018 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-tornado-yen3` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Oct 15, 2018 5 - Production/Stable N/A - :pypi:`pytest-tornasync` py.test plugin for testing Python 3.5+ Tornado code Jul 15, 2019 3 - Alpha pytest (>=3.0) - :pypi:`pytest-track` Feb 26, 2021 3 - Alpha pytest (>=3.0) - :pypi:`pytest-translations` Test your translation files. Nov 05, 2021 5 - Production/Stable N/A - :pypi:`pytest-travis-fold` Folds captured output sections in Travis CI build log Nov 29, 2017 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-trello` Plugin for py.test that integrates trello using markers Nov 20, 2015 5 - Production/Stable N/A - :pypi:`pytest-trepan` Pytest plugin for trepan debugger. Jul 28, 2018 5 - Production/Stable N/A - :pypi:`pytest-trialtemp` py.test plugin for using the same _trial_temp working directory as trial Jun 08, 2015 N/A N/A - :pypi:`pytest-trio` Pytest plugin for trio Oct 16, 2020 N/A N/A - :pypi:`pytest-tspwplib` A simple plugin to use with tspwplib Jan 08, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-tstcls` Test Class Base Mar 23, 2020 5 - Production/Stable N/A - :pypi:`pytest-twisted` A twisted plugin for pytest. Aug 30, 2021 5 - Production/Stable pytest (>=2.3) - :pypi:`pytest-typhoon-xray` Typhoon HIL plugin for pytest Nov 03, 2021 4 - Beta N/A - :pypi:`pytest-tytest` Typhoon HIL plugin for pytest May 25, 2020 4 - Beta pytest (>=5.4.2) - :pypi:`pytest-ubersmith` Easily mock calls to ubersmith at the \`requests\` level. Apr 13, 2015 N/A N/A - :pypi:`pytest-ui` Text User Interface for running python tests Jul 05, 2021 4 - Beta pytest - :pypi:`pytest-unhandled-exception-exit-code` Plugin for py.test set a different exit code on uncaught exceptions Jun 22, 2020 5 - Production/Stable pytest (>=2.3) - :pypi:`pytest-unittest-filter` A pytest plugin for filtering unittest-based test classes Jan 12, 2019 4 - Beta pytest (>=3.1.0) - :pypi:`pytest-unmarked` Run only unmarked tests Aug 27, 2019 5 - Production/Stable N/A - :pypi:`pytest-unordered` Test equality of unordered collections in pytest Mar 28, 2021 4 - Beta N/A - :pypi:`pytest-upload-report` pytest-upload-report is a plugin for pytest that upload your test report for test results. Jun 18, 2021 5 - Production/Stable N/A - :pypi:`pytest-utils` Some helpers for pytest. Dec 04, 2021 4 - Beta pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-vagrant` A py.test plugin providing access to vagrant. Sep 07, 2021 5 - Production/Stable pytest - :pypi:`pytest-valgrind` May 19, 2021 N/A N/A - :pypi:`pytest-variables` pytest plugin for providing variables to tests/fixtures Oct 23, 2019 5 - Production/Stable pytest (>=2.4.2) - :pypi:`pytest-variant` Variant support for Pytest Jun 20, 2021 N/A N/A - :pypi:`pytest-vcr` Plugin for managing VCR.py cassettes Apr 26, 2019 5 - Production/Stable pytest (>=3.6.0) - :pypi:`pytest-vcr-delete-on-fail` A pytest plugin that automates vcrpy cassettes deletion on test failure. Aug 13, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) - :pypi:`pytest-vcrpandas` Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest - :pypi:`pytest-venv` py.test fixture for creating a virtual environment Aug 04, 2020 4 - Beta pytest - :pypi:`pytest-ver` Pytest module with Verification Report Aug 30, 2021 2 - Pre-Alpha N/A - :pypi:`pytest-verbose-parametrize` More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-vimqf` A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window. Feb 08, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) - :pypi:`pytest-virtualenv` Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-voluptuous` Pytest plugin for asserting data against voluptuous schema. Jun 09, 2020 N/A pytest - :pypi:`pytest-vscodedebug` A pytest plugin to easily enable debugging tests within Visual Studio Code Dec 04, 2020 4 - Beta N/A - :pypi:`pytest-vts` pytest plugin for automatic recording of http stubbed tests Jun 05, 2019 N/A pytest (>=2.3) - :pypi:`pytest-vw` pytest-vw makes your failing test cases succeed under CI tools scrutiny Oct 07, 2015 4 - Beta N/A - :pypi:`pytest-vyper` Plugin for the vyper smart contract language. May 28, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-wa-e2e-plugin` Pytest plugin for testing whatsapp bots with end to end tests Feb 18, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-watch` Local continuous test runner with pytest and watchdog. May 20, 2018 N/A N/A - :pypi:`pytest-watcher` Continiously runs pytest on changes in \*.py files Sep 18, 2021 3 - Alpha N/A - :pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A - :pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-wetest` Welian API Automation test framework pytest plugin Nov 10, 2018 4 - Beta N/A - :pypi:`pytest-whirlwind` Testing Tornado. Jun 12, 2020 N/A N/A - :pypi:`pytest-wholenodeid` pytest addon for displaying the whole node id for failures Aug 26, 2015 4 - Beta pytest (>=2.0) - :pypi:`pytest-win32consoletitle` Pytest progress in console title (Win32 only) Aug 08, 2021 N/A N/A - :pypi:`pytest-winnotify` Windows tray notifications for py.test results. Apr 22, 2016 N/A N/A - :pypi:`pytest-with-docker` pytest with docker helpers. Nov 09, 2021 N/A pytest - :pypi:`pytest-workflow` A pytest plugin for configuring workflow/pipeline tests using YAML files Dec 03, 2021 5 - Production/Stable pytest (>=5.4.0) - :pypi:`pytest-xdist` pytest xdist plugin for distributed testing and loop-on-failing modes Sep 21, 2021 5 - Production/Stable pytest (>=6.0.0) - :pypi:`pytest-xdist-debug-for-graingert` pytest xdist plugin for distributed testing and loop-on-failing modes Jul 24, 2019 5 - Production/Stable pytest (>=4.4.0) - :pypi:`pytest-xdist-forked` forked from pytest-xdist Feb 10, 2020 5 - Production/Stable pytest (>=4.4.0) - :pypi:`pytest-xdist-tracker` pytest plugin helps to reproduce failures for particular xdist node Nov 18, 2021 3 - Alpha pytest (>=3.5.1) - :pypi:`pytest-xfaillist` Maintain a xfaillist in an additional file to avoid merge-conflicts. Sep 17, 2021 N/A pytest (>=6.2.2,<7.0.0) - :pypi:`pytest-xfiles` Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A - :pypi:`pytest-xlog` Extended logging for test and decorators May 31, 2020 4 - Beta N/A - :pypi:`pytest-xpara` An extended parametrizing plugin of pytest. Oct 30, 2017 3 - Alpha pytest - :pypi:`pytest-xprocess` A pytest plugin for managing processes across test runs. Jul 28, 2021 4 - Beta pytest (>=2.8) - :pypi:`pytest-xray` May 30, 2019 3 - Alpha N/A - :pypi:`pytest-xrayjira` Mar 17, 2020 3 - Alpha pytest (==4.3.1) - :pypi:`pytest-xray-server` Oct 27, 2021 3 - Alpha pytest (>=5.3.1) - :pypi:`pytest-xvfb` A pytest plugin to run Xvfb for tests. Jun 09, 2020 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-yaml` This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest - :pypi:`pytest-yamltree` Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-yamlwsgi` Run tests against wsgi apps defined in yaml May 11, 2010 N/A N/A - :pypi:`pytest-yapf` Run yapf Jul 06, 2017 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-yapf3` Validate your Python file format with yapf Aug 03, 2020 5 - Production/Stable pytest (>=5.4) - :pypi:`pytest-yield` PyTest plugin to run tests concurrently, each \`yield\` switch context to other one Jan 23, 2019 N/A N/A - :pypi:`pytest-yuk` Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. Mar 26, 2021 N/A N/A - :pypi:`pytest-zafira` A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1) - :pypi:`pytest-zap` OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A - :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Dec 02, 2021 5 - Production/Stable pytest (>=4.5.0) - :pypi:`pytest-zigzag` Extend py.test for RPC OpenStack testing. Feb 27, 2019 4 - Beta pytest (~=3.6) - =============================================== ======================================================================================================================================================================== ============== ===================== ================================================ + =============================================== ============================================================================================================================================================================ ============== ===================== ================================================ + name summary last release status requires + =============================================== ============================================================================================================================================================================ ============== ===================== ================================================ + :pypi:`pytest-abstracts` A contextmanager pytest fixture for handling multiple mock abstracts May 25, 2022 N/A N/A + :pypi:`pytest-accept` A pytest-plugin for updating doctest outputs Jan 07, 2022 N/A pytest (>=6,<8) + :pypi:`pytest-adaptavist` pytest plugin for generating test execution results within Jira Test Management (tm4j) Jun 07, 2022 N/A pytest (>=5.4.0) + :pypi:`pytest-addons-test` 用于测试pytest的插件 Aug 02, 2021 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-adf` Pytest plugin for writing Azure Data Factory integration tests May 10, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-adf-azure-identity` Pytest plugin for writing Azure Data Factory integration tests Mar 06, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-agent` Service that exposes a REST API that can be used to interract remotely with Pytest. It is shipped with a dashboard that enables running tests in a more convenient way. Nov 25, 2021 N/A N/A + :pypi:`pytest-aggreport` pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. Mar 07, 2021 4 - Beta pytest (>=6.2.2) + :pypi:`pytest-aio` Pytest plugin for testing async python code Oct 20, 2021 4 - Beta pytest + :pypi:`pytest-aiofiles` pytest fixtures for writing aiofiles tests with pyfakefs May 14, 2017 5 - Production/Stable N/A + :pypi:`pytest-aiohttp` Pytest plugin for aiohttp support Feb 12, 2022 4 - Beta pytest (>=6.1.0) + :pypi:`pytest-aiohttp-client` Pytest \`client\` fixture for the Aiohttp Nov 01, 2020 N/A pytest (>=6) + :pypi:`pytest-aiomoto` pytest-aiomoto Jul 10, 2022 N/A pytest (>=7.0,<8.0) + :pypi:`pytest-aioresponses` py.test integration for aioresponses Jul 29, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-aioworkers` A plugin to test aioworkers project with pytest Dec 04, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-airflow` pytest support for airflow. Apr 03, 2019 3 - Alpha pytest (>=4.4.0) + :pypi:`pytest-airflow-utils` Nov 15, 2021 N/A N/A + :pypi:`pytest-alembic` A pytest plugin for verifying alembic migrations. Aug 03, 2022 N/A pytest (>=1.0) + :pypi:`pytest-allclose` Pytest fixture extending Numpy's allclose function Jul 30, 2019 5 - Production/Stable pytest + :pypi:`pytest-allure-adaptor` Plugin for py.test to generate allure xml reports Jan 10, 2018 N/A pytest (>=2.7.3) + :pypi:`pytest-allure-adaptor2` Plugin for py.test to generate allure xml reports Oct 14, 2020 N/A pytest (>=2.7.3) + :pypi:`pytest-allure-collection` pytest plugin to collect allure markers without running any tests Sep 08, 2022 N/A pytest + :pypi:`pytest-allure-dsl` pytest plugin to test case doc string dls instructions Oct 25, 2020 4 - Beta pytest + :pypi:`pytest-allure-spec-coverage` The pytest plugin aimed to display test coverage of the specs(requirements) in Allure Oct 26, 2021 N/A pytest + :pypi:`pytest-alphamoon` Static code checks used at Alphamoon Dec 30, 2021 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-android` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Feb 21, 2019 3 - Alpha pytest + :pypi:`pytest-anki` A pytest plugin for testing Anki add-ons Jul 31, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-annotate` pytest-annotate: Generate PyAnnotate annotations from your pytest tests. Jun 07, 2022 3 - Alpha pytest (<8.0.0,>=3.2.0) + :pypi:`pytest-ansible` Plugin for py.test to simplify calling ansible modules from tests or fixtures May 25, 2021 5 - Production/Stable N/A + :pypi:`pytest-ansible-playbook` Pytest fixture which runs given ansible playbook file. Mar 08, 2019 4 - Beta N/A + :pypi:`pytest-ansible-playbook-runner` Pytest fixture which runs given ansible playbook file. Dec 02, 2020 4 - Beta pytest (>=3.1.0) + :pypi:`pytest-ansible-units` A pytest plugin for running unit tests within an ansible collection Apr 14, 2022 N/A N/A + :pypi:`pytest-antilru` Bust functools.lru_cache when running pytest to avoid test pollution Jul 05, 2022 5 - Production/Stable pytest + :pypi:`pytest-anyio` The pytest anyio plugin is built into anyio. You don't need this package. Jun 29, 2021 N/A pytest + :pypi:`pytest-anything` Pytest fixtures to assert anything and something Feb 18, 2021 N/A N/A + :pypi:`pytest-aoc` Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Nov 23, 2021 N/A pytest ; extra == 'test' + :pypi:`pytest-aoreporter` pytest report Jun 27, 2022 N/A N/A + :pypi:`pytest-api` An ASGI middleware to populate OpenAPI Specification examples from pytest functions May 12, 2022 N/A pytest (>=7.1.1,<8.0.0) + :pypi:`pytest-api-soup` Validate multiple endpoints with unit testing using a single source of truth. Aug 27, 2022 N/A N/A + :pypi:`pytest-apistellar` apistellar plugin for pytest. Jun 18, 2019 N/A N/A + :pypi:`pytest-appengine` AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A + :pypi:`pytest-appium` Pytest plugin for appium Dec 05, 2019 N/A N/A + :pypi:`pytest-approvaltests` A plugin to use approvaltests with pytest May 08, 2022 4 - Beta pytest (>=7.0.1) + :pypi:`pytest-argus` pyest results colection plugin Jun 24, 2021 5 - Production/Stable pytest (>=6.2.4) + :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Jan 13, 2022 4 - Beta pytest (>=4.6) + :pypi:`pytest-asgi-server` Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) + :pypi:`pytest-asptest` test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A + :pypi:`pytest-assertcount` Plugin to count actual number of asserts in pytest May 22, 2022 N/A N/A + :pypi:`pytest-assertions` Pytest Assertions Apr 27, 2022 N/A N/A + :pypi:`pytest-assertutil` pytest-assertutil May 10, 2019 N/A N/A + :pypi:`pytest-assert-utils` Useful assertion utilities for use with pytest Apr 14, 2022 3 - Alpha N/A + :pypi:`pytest-assume` A pytest plugin that allows multiple failures per test Jun 24, 2021 N/A pytest (>=2.7) + :pypi:`pytest-assurka` A pytest plugin for Assurka Studio Aug 04, 2022 N/A N/A + :pypi:`pytest-ast-back-to-python` A plugin for pytest devs to view how assertion rewriting recodes the AST Sep 29, 2019 4 - Beta N/A + :pypi:`pytest-asteroid` PyTest plugin for docker-based testing on database images Aug 15, 2022 N/A pytest (>=6.2.5,<8.0.0) + :pypi:`pytest-astropy` Meta-package containing dependencies for testing Apr 12, 2022 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Sep 06, 2022 3 - Alpha pytest (>=4.6) + :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest + :pypi:`pytest-asyncio` Pytest support for asyncio Jul 15, 2022 4 - Beta pytest (>=6.1.0) + :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Jul 11, 2022 N/A N/A + :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) + :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) + :pypi:`pytest-async-sqlalchemy` Database testing fixtures using the SQLAlchemy asyncio API Oct 07, 2021 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-atomic` Skip rest of tests if previous test failed. Nov 24, 2018 4 - Beta N/A + :pypi:`pytest-attrib` pytest plugin to select tests based on attributes similar to the nose-attrib plugin May 24, 2016 4 - Beta N/A + :pypi:`pytest-austin` Austin plugin for pytest Oct 11, 2020 4 - Beta N/A + :pypi:`pytest-autocap` automatically capture test & fixture stdout/stderr to files May 15, 2022 N/A pytest (<7.2,>=7.1.2) + :pypi:`pytest-autochecklog` automatically check condition and log all the checks Apr 25, 2015 4 - Beta N/A + :pypi:`pytest-automation` pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. May 20, 2022 N/A pytest (>=7.0.0) + :pypi:`pytest-automock` Pytest plugin for automatical mocks creation Aug 04, 2022 N/A pytest ; extra == 'dev' + :pypi:`pytest-auto-parametrize` pytest plugin: avoid repeating arguments in parametrize Oct 02, 2016 3 - Alpha N/A + :pypi:`pytest-autotest` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Aug 25, 2021 N/A pytest + :pypi:`pytest-avoidance` Makes pytest skip tests that don not need rerunning May 23, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A + :pypi:`pytest-aws-config` Protect your AWS credentials in unit tests May 28, 2021 N/A N/A + :pypi:`pytest-axe` pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) + :pypi:`pytest-azure-devops` Simplifies using azure devops parallel strategy (https://docs.microsoft.com/en-us/azure/devops/pipelines/test/parallel-testing-any-test-runner) with pytest. Jun 20, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-azurepipelines` Formatting PyTest output for Azure Pipelines UI Mar 16, 2022 4 - Beta pytest (>=5.0.0) + :pypi:`pytest-bandit` A bandit plugin for pytest Feb 23, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-base-url` pytest plugin for URL based testing Mar 27, 2022 5 - Production/Stable pytest (>=3.0.0,<8.0.0) + :pypi:`pytest-bdd` BDD for pytest Jul 07, 2022 6 - Mature pytest (>=5.0) + :pypi:`pytest-bdd-html` pytest plugin to display BDD info in HTML test report Jul 25, 2022 3 - Alpha pytest (!=6.0.0,>=5.0) + :pypi:`pytest-bdd-ng` BDD for pytest Jul 24, 2022 4 - Beta pytest (>=5.0) + :pypi:`pytest-bdd-splinter` Common steps for pytest bdd and splinter integration Aug 12, 2019 5 - Production/Stable pytest (>=4.0.0) + :pypi:`pytest-bdd-web` A simple plugin to use with pytest Jan 02, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-bdd-wrappers` Feb 11, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-beakerlib` A pytest plugin that reports test results to the BeakerLib framework Mar 17, 2017 5 - Production/Stable pytest + :pypi:`pytest-beds` Fixtures for testing Google Appengine (GAE) apps Jun 07, 2016 4 - Beta N/A + :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A + :pypi:`pytest-benchmark` A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. Apr 17, 2021 5 - Production/Stable pytest (>=3.8) + :pypi:`pytest-bg-process` Pytest plugin to initialize background process Jan 24, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-bigchaindb` A BigchainDB plugin for pytest. Jan 24, 2022 4 - Beta N/A + :pypi:`pytest-bigquery-mock` Provides a mock fixture for python bigquery client Aug 05, 2021 N/A pytest (>=5.0) + :pypi:`pytest-black` A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A + :pypi:`pytest-black-multipy` Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' + :pypi:`pytest-blame` A pytest plugin helps developers to debug by providing useful commits history. May 04, 2019 N/A pytest (>=4.4.0) + :pypi:`pytest-blender` Blender Pytest plugin. Jun 16, 2022 N/A pytest ; extra == 'dev' + :pypi:`pytest-blink1` Pytest plugin to emit notifications via the Blink(1) RGB LED Jan 07, 2018 4 - Beta N/A + :pypi:`pytest-blockage` Disable network requests during a test run. Dec 21, 2021 N/A pytest + :pypi:`pytest-blocker` pytest plugin to mark a test as blocker and skip all other tests Sep 07, 2015 4 - Beta N/A + :pypi:`pytest-blue` A pytest plugin that adds a \`blue\` fixture for printing stuff in blue. Sep 05, 2022 N/A N/A + :pypi:`pytest-board` Local continuous test runner with pytest and watchdog. Jan 20, 2019 N/A N/A + :pypi:`pytest-bootstrap` Mar 04, 2022 N/A N/A + :pypi:`pytest-bpdb` A py.test plug-in to enable drop to bpdb debugger on test failure. Jan 19, 2015 2 - Pre-Alpha N/A + :pypi:`pytest-bravado` Pytest-bravado automatically generates from OpenAPI specification client fixtures. Feb 15, 2022 N/A N/A + :pypi:`pytest-breakword` Use breakword with pytest Aug 04, 2021 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-breed-adapter` A simple plugin to connect with breed-server Nov 07, 2018 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-briefcase` A pytest plugin for running tests on a Briefcase project. Jun 14, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-browser` A pytest plugin for console based browser test selection just after the collection phase Dec 10, 2016 3 - Alpha N/A + :pypi:`pytest-browsermob-proxy` BrowserMob proxy plugin for py.test. Jun 11, 2013 4 - Beta N/A + :pypi:`pytest-browserstack-local` \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. Feb 09, 2018 N/A N/A + :pypi:`pytest-bug` Pytest plugin for marking tests as a bug Apr 13, 2022 5 - Production/Stable pytest (>=6.2.0) + :pypi:`pytest-bugtong-tag` pytest-bugtong-tag is a plugin for pytest Jan 16, 2022 N/A N/A + :pypi:`pytest-bugzilla` py.test bugzilla integration plugin May 05, 2010 4 - Beta N/A + :pypi:`pytest-bugzilla-notifier` A plugin that allows you to execute create, update, and read information from BugZilla bugs Jun 15, 2018 4 - Beta pytest (>=2.9.2) + :pypi:`pytest-buildkite` Plugin for pytest that automatically publishes coverage and pytest report annotations to Buildkite. Jul 13, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-builtin-types` Nov 17, 2021 N/A pytest + :pypi:`pytest-bwrap` Run your tests in Bubblewrap sandboxes Oct 26, 2018 3 - Alpha N/A + :pypi:`pytest-cache` pytest plugin with mechanisms for caching across test runs Jun 04, 2013 3 - Alpha N/A + :pypi:`pytest-cache-assert` Cache assertion data to simplify regression testing of complex serializable data May 02, 2022 4 - Beta pytest (>=5.0.0) + :pypi:`pytest-cagoule` Pytest plugin to only run tests affected by changes Jan 01, 2020 3 - Alpha N/A + :pypi:`pytest-cairo` Pytest support for cairo-lang and starknet Apr 17, 2022 N/A pytest + :pypi:`pytest-camel-collect` Enable CamelCase-aware pytest class collection Aug 02, 2020 N/A pytest (>=2.9) + :pypi:`pytest-canonical-data` A plugin which allows to compare results with canonical results, based on previous runs May 08, 2020 2 - Pre-Alpha pytest (>=3.5.0) + :pypi:`pytest-caprng` A plugin that replays pRNG state on failure. May 02, 2018 4 - Beta N/A + :pypi:`pytest-capture-deprecatedwarnings` pytest plugin to capture all deprecatedwarnings and put them in one file Apr 30, 2019 N/A N/A + :pypi:`pytest-capture-warnings` pytest plugin to capture all warnings and put them in one file of your choice May 03, 2022 N/A pytest + :pypi:`pytest-cases` Separate test code from test cases in pytest. May 20, 2022 5 - Production/Stable N/A + :pypi:`pytest-cassandra` Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A + :pypi:`pytest-catchlog` py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) + :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A + :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest May 06, 2021 N/A N/A + :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A + :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A + :pypi:`pytest-change-demo` turn . into √,turn F into x Mar 02, 2022 N/A pytest + :pypi:`pytest-change-report` turn . into √,turn F into x Sep 14, 2020 N/A pytest + :pypi:`pytest-change-xds` turn . into √,turn F into x Apr 16, 2022 N/A pytest + :pypi:`pytest-chdir` A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) + :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Aug 25, 2022 5 - Production/Stable N/A + :pypi:`pytest-checkdocs` check the README when running tests Jul 31, 2021 5 - Production/Stable pytest (>=4.6) ; extra == 'testing' + :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Jul 22, 2020 5 - Production/Stable pytest (>=2.9.2) + :pypi:`pytest-check-library` check your missing library Jul 17, 2022 N/A N/A + :pypi:`pytest-check-libs` check your missing library Jul 17, 2022 N/A N/A + :pypi:`pytest-check-links` Check links in files Jul 29, 2020 N/A pytest (>=7.0) + :pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest + :pypi:`pytest-chunks` Run only a chunk of your test suite Jul 05, 2022 N/A pytest (>=6.0.0) + :pypi:`pytest-circleci` py.test plugin for CircleCI May 03, 2019 N/A N/A + :pypi:`pytest-circleci-parallelized` Parallelize pytest across CircleCI workers. Mar 26, 2019 N/A N/A + :pypi:`pytest-circleci-parallelized-rjp` Parallelize pytest across CircleCI workers. Jun 21, 2022 N/A pytest + :pypi:`pytest-ckan` Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8 Apr 28, 2020 4 - Beta pytest + :pypi:`pytest-clarity` A plugin providing an alternative, colourful diff output for failing assertions. Jun 11, 2021 N/A N/A + :pypi:`pytest-cldf` Easy quality control for CLDF datasets using pytest May 06, 2019 N/A N/A + :pypi:`pytest-click` Pytest plugin for Click Feb 11, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-cli-fixtures` Automatically register fixtures for custom CLI arguments Jul 28, 2022 N/A pytest (~=7.0) + :pypi:`pytest-clld` Jul 06, 2022 N/A pytest (>=3.6) + :pypi:`pytest-cloud` Distributed tests planner plugin for pytest testing framework. Oct 05, 2020 6 - Mature N/A + :pypi:`pytest-cloudflare-worker` pytest plugin for testing cloudflare workers Mar 30, 2021 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-cloudist` Distribute tests to cloud machines without fuss Sep 02, 2022 4 - Beta pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-cobra` PyTest plugin for testing Smart Contracts for Ethereum blockchain. Jun 29, 2019 3 - Alpha pytest (<4.0.0,>=3.7.1) + :pypi:`pytest-codecarbon` Pytest plugin for measuring carbon emissions Jun 15, 2022 N/A pytest + :pypi:`pytest-codecheckers` pytest plugin to add source code sanity checks (pep8 and friends) Feb 13, 2010 N/A N/A + :pypi:`pytest-codecov` Pytest plugin for uploading pytest-cov results to codecov.io Apr 12, 2022 4 - Beta pytest (>=4.6.0) + :pypi:`pytest-codegen` Automatically create pytest test signatures Aug 23, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-codeowners` Pytest plugin for selecting tests by GitHub CODEOWNERS. Mar 30, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-codestyle` pytest plugin to run pycodestyle Mar 23, 2020 3 - Alpha N/A + :pypi:`pytest-collect-formatter` Formatter for pytest collect output Mar 29, 2021 5 - Production/Stable N/A + :pypi:`pytest-collect-formatter2` Formatter for pytest collect output May 31, 2021 5 - Production/Stable N/A + :pypi:`pytest-collector` Python package for collecting pytest. Aug 02, 2022 N/A pytest (>=7.0,<8.0) + :pypi:`pytest-colordots` Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A + :pypi:`pytest-commander` An interactive GUI test runner for PyTest Aug 17, 2021 N/A pytest (<7.0.0,>=6.2.4) + :pypi:`pytest-common-subject` pytest framework for testing different aspects of a common method May 15, 2022 N/A pytest (>=3.6,<8) + :pypi:`pytest-concurrent` Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-config` Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A + :pypi:`pytest-confluence-report` Package stands for pytest plugin to upload results into Confluence page. Apr 17, 2022 N/A N/A + :pypi:`pytest-console-scripts` Pytest plugin for testing console scripts Mar 18, 2022 4 - Beta N/A + :pypi:`pytest-consul` pytest plugin with fixtures for testing consul aware apps Nov 24, 2018 3 - Alpha pytest + :pypi:`pytest-container` Pytest fixtures for writing container based tests Feb 01, 2022 3 - Alpha pytest (>=3.10) + :pypi:`pytest-contextfixture` Define pytest fixtures as context managers. Mar 12, 2013 4 - Beta N/A + :pypi:`pytest-contexts` A plugin to run tests written with the Contexts framework using pytest May 19, 2021 4 - Beta N/A + :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 May 24, 2021 5 - Production/Stable pytest (>=3.3.0) + :pypi:`pytest-couchdbkit` py.test extension for per-test couchdb databases using couchdbkit Apr 17, 2012 N/A N/A + :pypi:`pytest-count` count erros and send email Jan 12, 2018 4 - Beta N/A + :pypi:`pytest-cov` Pytest plugin for measuring coverage. Oct 04, 2021 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-cover` Pytest plugin for measuring coverage. Forked from \`pytest-cov\`. Aug 01, 2015 5 - Production/Stable N/A + :pypi:`pytest-coverage` Jun 17, 2015 N/A N/A + :pypi:`pytest-coverage-context` Coverage dynamic context support for PyTest, including sub-processes Jan 04, 2021 4 - Beta pytest (>=6.1.0) + :pypi:`pytest-cov-exclude` Pytest plugin for excluding tests based on coverage data Apr 29, 2016 4 - Beta pytest (>=2.8.0,<2.9.0); extra == 'dev' + :pypi:`pytest-cpp` Use pytest's runner to discover and execute C++ tests Aug 22, 2022 5 - Production/Stable pytest (>=7.0) + :pypi:`pytest-cppython` A pytest plugin that imports CPPython testing types Sep 10, 2022 N/A N/A + :pypi:`pytest-cqase` Custom qase pytest plugin Aug 22, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-cram` Run cram tests with pytest. Aug 08, 2020 N/A N/A + :pypi:`pytest-crate` Manages CrateDB instances during your integration tests May 28, 2019 3 - Alpha pytest (>=4.0) + :pypi:`pytest-cricri` A Cricri plugin for pytest. Jan 27, 2018 N/A pytest + :pypi:`pytest-crontab` add crontab task in crontab Dec 09, 2019 N/A N/A + :pypi:`pytest-csv` CSV output for pytest. Apr 22, 2021 N/A pytest (>=6.0) + :pypi:`pytest-csv-params` Pytest plugin for Test Case Parametrization with CSV files Aug 28, 2022 5 - Production/Stable pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-curio` Pytest support for curio. Oct 07, 2020 N/A N/A + :pypi:`pytest-curl-report` pytest plugin to generate curl command line report Dec 11, 2016 4 - Beta N/A + :pypi:`pytest-custom-concurrency` Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A + :pypi:`pytest-custom-exit-code` Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) + :pypi:`pytest-custom-nodeid` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 07, 2021 N/A N/A + :pypi:`pytest-custom-report` Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest + :pypi:`pytest-custom-scheduling` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 01, 2021 N/A N/A + :pypi:`pytest-cython` A plugin for testing Cython extension modules Mar 26, 2022 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-cython-collect` Jun 17, 2022 N/A pytest + :pypi:`pytest-darker` A pytest plugin for checking of modified code using Darker Aug 16, 2020 N/A pytest (>=6.0.1) ; extra == 'test' + :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A + :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A + :pypi:`pytest-databricks` Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest + :pypi:`pytest-datadir` pytest plugin for test data directories and files Oct 22, 2019 5 - Production/Stable pytest (>=2.7.0) + :pypi:`pytest-datadir-mgr` Manager for test data: downloads, artifact caching, and a tmpdir context. Aug 16, 2022 5 - Production/Stable pytest (>=7.1) + :pypi:`pytest-datadir-ng` Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Dec 25, 2019 5 - Production/Stable pytest + :pypi:`pytest-data-extractor` A pytest plugin to extract relevant metadata about tests into an external file (currently only json support) Jul 19, 2022 N/A pytest (>=7.0.1) + :pypi:`pytest-data-file` Fixture "data" and "case_data" for test from yaml file Dec 04, 2019 N/A N/A + :pypi:`pytest-datafiles` py.test plugin to create a 'tmpdir' containing predefined files/directories. May 01, 2022 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-datafixtures` Data fixtures for pytest made simple Dec 05, 2020 5 - Production/Stable N/A + :pypi:`pytest-data-from-files` pytest plugin to provide data from files loaded automatically Oct 13, 2021 4 - Beta pytest + :pypi:`pytest-dataplugin` A pytest plugin for managing an archive of test data. Sep 16, 2017 1 - Planning N/A + :pypi:`pytest-datarecorder` A py.test plugin recording and comparing test output. Apr 20, 2020 5 - Production/Stable pytest + :pypi:`pytest-dataset` Plugin for loading different datasets for pytest by prefix from json or yaml files Sep 05, 2022 5 - Production/Stable N/A + :pypi:`pytest-data-suites` Class-based pytest parametrization Jul 24, 2022 N/A pytest (>=6.0,<8.0) + :pypi:`pytest-datatest` A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration). Oct 15, 2020 4 - Beta pytest (>=3.3) + :pypi:`pytest-db` Session scope fixture "db" for mysql query or change Dec 04, 2019 N/A N/A + :pypi:`pytest-dbfixtures` Databases fixtures plugin for py.test. Dec 07, 2016 4 - Beta N/A + :pypi:`pytest-db-plugin` Nov 27, 2021 N/A pytest (>=5.0) + :pypi:`pytest-dbt-adapter` A pytest plugin for testing dbt adapter plugins Nov 24, 2021 N/A pytest (<7,>=6) + :pypi:`pytest-dbt-conventions` A pytest plugin for linting a dbt project's conventions Mar 02, 2022 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-dbt-core` Pytest extension for dbt. Jul 22, 2022 N/A pytest (>=6.2.5) ; extra == 'test' + :pypi:`pytest-dbus-notification` D-BUS notifications for pytest results. Mar 05, 2014 5 - Production/Stable N/A + :pypi:`pytest-deadfixtures` A simple plugin to list unused fixtures in pytest Jul 23, 2020 5 - Production/Stable N/A + :pypi:`pytest-deepcov` deepcov Mar 30, 2021 N/A N/A + :pypi:`pytest-defer` Aug 24, 2021 N/A N/A + :pypi:`pytest-demo-plugin` pytest示例插件 May 15, 2021 N/A N/A + :pypi:`pytest-dependency` Manage dependencies of tests Feb 14, 2020 4 - Beta N/A + :pypi:`pytest-depends` Tests that depend on other tests Apr 05, 2020 5 - Production/Stable pytest (>=3) + :pypi:`pytest-deprecate` Mark tests as testing a deprecated feature with a warning note. Jul 01, 2019 N/A N/A + :pypi:`pytest-describe` Describe-style plugin for pytest Nov 13, 2021 4 - Beta pytest (>=4.0.0) + :pypi:`pytest-describe-it` plugin for rich text descriptions Jul 19, 2019 4 - Beta pytest + :pypi:`pytest-devpi-server` DevPI server fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-dhos` Common fixtures for pytest in DHOS services and libraries Sep 07, 2022 N/A N/A + :pypi:`pytest-diamond` pytest plugin for diamond Aug 31, 2015 4 - Beta N/A + :pypi:`pytest-dicom` pytest plugin to provide DICOM fixtures Dec 19, 2018 3 - Alpha pytest + :pypi:`pytest-dictsdiff` Jul 26, 2019 N/A N/A + :pypi:`pytest-diff` A simple plugin to use with pytest Mar 30, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-diff-selector` Get tests affected by code changes (using git) Feb 24, 2022 4 - Beta pytest (>=6.2.2) ; extra == 'all' + :pypi:`pytest-disable` pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A + :pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. Mar 27, 2022 4 - Beta pytest (!=6.0.0,<8,>=3.3.2) + :pypi:`pytest-django` A Django plugin for pytest. Dec 07, 2021 5 - Production/Stable pytest (>=5.4.0) + :pypi:`pytest-django-ahead` A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9) + :pypi:`pytest-djangoapp` Nice pytest plugin to help you with Django pluggable application testing. Aug 04, 2021 4 - Beta N/A + :pypi:`pytest-django-cache-xdist` A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A + :pypi:`pytest-django-casperjs` Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A + :pypi:`pytest-django-dotenv` Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-django-factories` Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A + :pypi:`pytest-django-filefield` Replaces FileField.storage with something you can patch globally. May 09, 2022 5 - Production/Stable pytest >= 5.2 + :pypi:`pytest-django-gcir` A Django plugin for pytest. Mar 06, 2018 5 - Production/Stable N/A + :pypi:`pytest-django-haystack` Cleanup your Haystack indexes between tests Sep 03, 2017 5 - Production/Stable pytest (>=2.3.4) + :pypi:`pytest-django-ifactory` A model instance factory for pytest-django Feb 09, 2022 3 - Alpha N/A + :pypi:`pytest-django-lite` The bare minimum to integrate py.test with Django. Jan 30, 2014 N/A N/A + :pypi:`pytest-django-liveserver-ssl` Jan 20, 2022 3 - Alpha N/A + :pypi:`pytest-django-model` A Simple Way to Test your Django Models Feb 14, 2019 4 - Beta N/A + :pypi:`pytest-django-ordering` A pytest plugin for preserving the order in which Django runs tests. Jul 25, 2019 5 - Production/Stable pytest (>=2.3.0) + :pypi:`pytest-django-queries` Generate performance reports from your django database performance tests. Mar 01, 2021 N/A N/A + :pypi:`pytest-djangorestframework` A djangorestframework plugin for pytest Aug 11, 2019 4 - Beta N/A + :pypi:`pytest-django-rq` A pytest plugin to help writing unit test for django-rq Apr 13, 2020 4 - Beta N/A + :pypi:`pytest-django-sqlcounts` py.test plugin for reporting the number of SQLs executed per django testcase. Jun 16, 2015 4 - Beta N/A + :pypi:`pytest-django-testing-postgresql` Use a temporary PostgreSQL database with pytest-django Jan 31, 2022 4 - Beta N/A + :pypi:`pytest-doc` A documentation plugin for py.test. Jun 28, 2015 5 - Production/Stable N/A + :pypi:`pytest-docfiles` pytest plugin to test codeblocks in your documentation. Dec 22, 2021 4 - Beta pytest (>=3.7.0) + :pypi:`pytest-docgen` An RST Documentation Generator for pytest-based test suites Apr 17, 2020 N/A N/A + :pypi:`pytest-docker` Simple pytest fixtures for Docker and docker-compose based tests Jul 27, 2022 N/A pytest (<8.0,>=4.0) + :pypi:`pytest-docker-apache-fixtures` Pytest fixtures for testing with apache2 (httpd). Feb 16, 2022 4 - Beta pytest + :pypi:`pytest-docker-butla` Jun 16, 2019 3 - Alpha N/A + :pypi:`pytest-dockerc` Run, manage and stop Docker Compose project from Docker API Oct 09, 2020 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-docker-compose` Manages Docker containers during your integration tests Jan 26, 2021 5 - Production/Stable pytest (>=3.3) + :pypi:`pytest-docker-db` A plugin to use docker databases for pytests Mar 20, 2021 5 - Production/Stable pytest (>=3.1.1) + :pypi:`pytest-docker-fixtures` pytest docker fixtures Jul 06, 2022 3 - Alpha N/A + :pypi:`pytest-docker-git-fixtures` Pytest fixtures for testing with git scm. Feb 09, 2022 4 - Beta pytest + :pypi:`pytest-docker-haproxy-fixtures` Pytest fixtures for testing with haproxy. Feb 09, 2022 4 - Beta pytest + :pypi:`pytest-docker-pexpect` pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest + :pypi:`pytest-docker-postgresql` A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-docker-py` Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) + :pypi:`pytest-docker-registry-fixtures` Pytest fixtures for testing with docker registries. Apr 08, 2022 4 - Beta pytest + :pypi:`pytest-docker-service` pytest plugin to start docker container Mar 21, 2022 3 - Alpha pytest + :pypi:`pytest-docker-squid-fixtures` Pytest fixtures for testing with squid. Feb 09, 2022 4 - Beta pytest + :pypi:`pytest-docker-tools` Docker integration tests for pytest Feb 17, 2022 4 - Beta pytest (>=6.0.1) + :pypi:`pytest-docs` Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-docstyle` pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A + :pypi:`pytest-doctest-custom` A py.test plugin for customizing string representations of doctest results. Jul 25, 2016 4 - Beta N/A + :pypi:`pytest-doctest-ellipsis-markers` Setup additional values for ELLIPSIS_MARKER for doctests Jan 12, 2018 4 - Beta N/A + :pypi:`pytest-doctest-import` A simple pytest plugin to import names and add them to the doctest namespace. Nov 13, 2018 4 - Beta pytest (>=3.3.0) + :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Feb 25, 2022 3 - Alpha pytest (>=4.6) + :pypi:`pytest-dolphin` Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) + :pypi:`pytest-doorstop` A pytest plugin for adding test results into doorstop items. Jun 09, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-dotenv` A py.test plugin that parses environment files before running tests Jun 16, 2020 4 - Beta pytest (>=5.0.0) + :pypi:`pytest-drf` A Django REST framework plugin for pytest. Jul 12, 2022 5 - Production/Stable pytest (>=3.7) + :pypi:`pytest-drivings` Tool to allow webdriver automation to be ran locally or remotely Jan 13, 2021 N/A N/A + :pypi:`pytest-drop-dup-tests` A Pytest plugin to drop duplicated tests during collection May 23, 2020 4 - Beta pytest (>=2.7) + :pypi:`pytest-dummynet` A py.test plugin providing access to a dummynet. Dec 15, 2021 5 - Production/Stable pytest + :pypi:`pytest-dump2json` A pytest plugin for dumping test results to json. Jun 29, 2015 N/A N/A + :pypi:`pytest-duration-insights` Jun 25, 2021 N/A N/A + :pypi:`pytest-durations` Pytest plugin reporting fixtures and test functions execution time. Apr 22, 2022 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-dynamicrerun` A pytest plugin to rerun tests dynamically based off of test outcome and output. Aug 15, 2020 4 - Beta N/A + :pypi:`pytest-dynamodb` DynamoDB fixtures for pytest Jun 03, 2021 5 - Production/Stable pytest + :pypi:`pytest-easy-addoption` pytest-easy-addoption: Easy way to work with pytest addoption Jan 22, 2020 N/A N/A + :pypi:`pytest-easy-api` Simple API testing with pytest Mar 26, 2018 N/A N/A + :pypi:`pytest-easyMPI` Package that supports mpi tests in pytest Oct 21, 2020 N/A N/A + :pypi:`pytest-easyread` pytest plugin that makes terminal printouts of the reports easier to read Nov 17, 2017 N/A N/A + :pypi:`pytest-easy-server` Pytest plugin for easy testing against servers May 01, 2021 4 - Beta pytest (<5.0.0,>=4.3.1) ; python_version < "3.5" + :pypi:`pytest-ebics-sandbox` A pytest plugin for testing against an EBICS sandbox server. Requires docker. Aug 15, 2022 N/A N/A + :pypi:`pytest-ec2` Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A + :pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Jan 08, 2020 5 - Production/Stable N/A + :pypi:`pytest-elasticsearch` Elasticsearch fixtures and fixture factories for Pytest. Mar 01, 2022 5 - Production/Stable pytest (>=6.2.0) + :pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) + :pypi:`pytest-eliot` An eliot plugin for pytest. Aug 31, 2022 1 - Planning pytest (>=5.4.0) + :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest + :pypi:`pytest-embedded` pytest embedded plugin Aug 23, 2022 N/A pytest (>=7.0) + :pypi:`pytest-embedded-arduino` pytest embedded plugin for Arduino projects Aug 23, 2022 N/A N/A + :pypi:`pytest-embedded-idf` pytest embedded plugin for esp-idf project Aug 23, 2022 N/A N/A + :pypi:`pytest-embedded-jtag` pytest embedded plugin for testing with jtag Aug 23, 2022 N/A N/A + :pypi:`pytest-embedded-qemu` pytest embedded plugin for qemu, not target chip Aug 23, 2022 N/A N/A + :pypi:`pytest-embedded-serial` pytest embedded plugin for testing serial ports Aug 23, 2022 N/A N/A + :pypi:`pytest-embedded-serial-esp` pytest embedded plugin for testing espressif boards via serial ports Aug 23, 2022 N/A N/A + :pypi:`pytest-embrace` 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. Aug 27, 2022 N/A pytest (>=7.0,<8.0) + :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) + :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Apr 12, 2022 4 - Beta pytest (==7.0.1) + :pypi:`pytest-enabler` Enable installed pytest plugins Jun 22, 2022 5 - Production/Stable pytest (>=6) ; extra == 'testing' + :pypi:`pytest-encode` set your encoding and logger Nov 06, 2021 N/A N/A + :pypi:`pytest-encode-kane` set your encoding and logger Nov 16, 2021 N/A pytest + :pypi:`pytest-enhancements` Improvements for pytest (rejected upstream) Oct 30, 2019 4 - Beta N/A + :pypi:`pytest-env` py.test plugin that allows you to add environment variables. Jun 16, 2017 4 - Beta N/A + :pypi:`pytest-envfiles` A py.test plugin that parses environment files before running tests Oct 08, 2015 3 - Alpha N/A + :pypi:`pytest-env-info` Push information about the running pytest into envvars Nov 25, 2017 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-envraw` py.test plugin that allows you to add environment variables. Aug 27, 2020 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-envvars` Pytest plugin to validate use of envvars on your tests Jun 13, 2020 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-env-yaml` Apr 02, 2019 N/A N/A + :pypi:`pytest-eradicate` pytest plugin to check for commented out code Sep 08, 2020 N/A pytest (>=2.4.2) + :pypi:`pytest-error-for-skips` Pytest plugin to treat skipped tests a test failure Dec 19, 2019 4 - Beta pytest (>=4.6) + :pypi:`pytest-eth` PyTest plugin for testing Smart Contracts for Ethereum Virtual Machine (EVM). Aug 14, 2020 1 - Planning N/A + :pypi:`pytest-ethereum` pytest-ethereum: Pytest library for ethereum projects. Jun 24, 2019 3 - Alpha pytest (==3.3.2); extra == 'dev' + :pypi:`pytest-eucalyptus` Pytest Plugin for BDD Jun 28, 2022 N/A pytest (>=4.2.0) + :pypi:`pytest-eventlet` Applies eventlet monkey-patch as a pytest plugin. Oct 04, 2021 N/A pytest ; extra == 'dev' + :pypi:`pytest-excel` pytest plugin for generating excel reports Jan 31, 2022 5 - Production/Stable N/A + :pypi:`pytest-exceptional` Better exceptions Mar 16, 2017 4 - Beta N/A + :pypi:`pytest-exception-script` Walk your code through exception script to check it's resiliency to failures. Aug 04, 2020 3 - Alpha pytest + :pypi:`pytest-executable` pytest plugin for testing executables Nov 10, 2021 4 - Beta pytest (<6.3,>=4.3) + :pypi:`pytest-execution-timer` A timer for the phases of Pytest's execution. Dec 24, 2021 4 - Beta N/A + :pypi:`pytest-expect` py.test plugin to store test expectations and mark tests based on them Apr 21, 2016 4 - Beta N/A + :pypi:`pytest-expecter` Better testing with expecter and pytest. Jan 10, 2022 5 - Production/Stable N/A + :pypi:`pytest-expectr` This plugin is used to expect multiple assert using pytest framework. Oct 05, 2018 N/A pytest (>=2.4.2) + :pypi:`pytest-experiments` A pytest plugin to help developers of research-oriented software projects keep track of the results of their numerical experiments. Dec 13, 2021 4 - Beta pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-explicit` A Pytest plugin to ignore certain marked tests by default Jun 15, 2021 5 - Production/Stable pytest + :pypi:`pytest-exploratory` Interactive console for pytest. Feb 21, 2022 N/A pytest (>=6.2) + :pypi:`pytest-extensions` A collection of helpers for pytest to ease testing Aug 17, 2022 4 - Beta pytest ; extra == 'testing' + :pypi:`pytest-external-blockers` a special outcome for tests that are blocked for external reasons Oct 05, 2021 N/A pytest + :pypi:`pytest-extra-durations` A pytest plugin to get durations on a per-function basis and per module basis. Apr 21, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-fabric` Provides test utilities to run fabric task tests by using docker containers Sep 12, 2018 5 - Production/Stable N/A + :pypi:`pytest-factory` Use factories for test setup with py.test Sep 06, 2020 3 - Alpha pytest (>4.3) + :pypi:`pytest-factoryboy` Factory Boy support for pytest. Jun 14, 2022 6 - Mature pytest (>=5.0.0) + :pypi:`pytest-factoryboy-fixtures` Generates pytest fixtures that allow the use of type hinting Jun 25, 2020 N/A N/A + :pypi:`pytest-factoryboy-state` Simple factoryboy random state management Mar 22, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-failed-screenshot` Test case fails,take a screenshot,save it,attach it to the allure Apr 21, 2021 N/A N/A + :pypi:`pytest-failed-to-verify` A pytest plugin that helps better distinguishing real test failures from setup flakiness. Aug 08, 2019 5 - Production/Stable pytest (>=4.1.0) + :pypi:`pytest-fail-slow` Fail tests that take too long to run Aug 13, 2022 4 - Beta pytest (>=6.0) + :pypi:`pytest-faker` Faker integration with the pytest framework. Dec 19, 2016 6 - Mature N/A + :pypi:`pytest-falcon` Pytest helpers for Falcon. Sep 07, 2016 4 - Beta N/A + :pypi:`pytest-falcon-client` Pytest \`client\` fixture for the Falcon Framework Mar 19, 2019 N/A N/A + :pypi:`pytest-fantasy` Pytest plugin for Flask Fantasy Framework Mar 14, 2019 N/A N/A + :pypi:`pytest-fastapi` Dec 27, 2020 N/A N/A + :pypi:`pytest-fastapi-deps` A fixture which allows easy replacement of fastapi dependencies for testing Jul 20, 2022 5 - Production/Stable pytest + :pypi:`pytest-fastest` Use SCM and coverage to run only needed tests Mar 05, 2020 N/A N/A + :pypi:`pytest-fast-first` Pytest plugin that runs fast tests first Apr 02, 2021 3 - Alpha pytest + :pypi:`pytest-faulthandler` py.test plugin that activates the fault handler module for tests (dummy package) Jul 04, 2019 6 - Mature pytest (>=5.0) + :pypi:`pytest-fauxfactory` Integration of fauxfactory into pytest. Dec 06, 2017 5 - Production/Stable pytest (>=3.2) + :pypi:`pytest-figleaf` py.test figleaf coverage plugin Jan 18, 2010 5 - Production/Stable N/A + :pypi:`pytest-filecov` A pytest plugin to detect unused files Jun 27, 2021 4 - Beta pytest + :pypi:`pytest-filedata` easily load data from files Jan 17, 2019 4 - Beta N/A + :pypi:`pytest-filemarker` A pytest plugin that runs marked tests when files change. Dec 01, 2020 N/A pytest + :pypi:`pytest-filter-case` run test cases filter by mark Nov 05, 2020 N/A N/A + :pypi:`pytest-filter-subpackage` Pytest plugin for filtering based on sub-packages Jan 09, 2020 3 - Alpha pytest (>=3.0) + :pypi:`pytest-find-dependencies` A pytest plugin to find dependencies between tests Apr 09, 2022 4 - Beta pytest (>=4.3.0) + :pypi:`pytest-finer-verdicts` A pytest plugin to treat non-assertion failures as test errors. Jun 18, 2020 N/A pytest (>=5.4.3) + :pypi:`pytest-firefox` pytest plugin to manipulate firefox Aug 08, 2017 3 - Alpha pytest (>=3.0.2) + :pypi:`pytest-fixture-config` Fixture configuration utils for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-fixture-maker` Pytest plugin to load fixtures from YAML files Sep 21, 2021 N/A N/A + :pypi:`pytest-fixture-marker` A pytest plugin to add markers based on fixtures used. Oct 11, 2020 5 - Production/Stable N/A + :pypi:`pytest-fixture-order` pytest plugin to control fixture evaluation order May 16, 2022 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-fixture-rtttg` Warn or fail on fixture name clash Feb 23, 2022 N/A pytest (>=7.0.1,<8.0.0) + :pypi:`pytest-fixtures` Common fixtures for pytest May 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-fixture-tools` Plugin for pytest which provides tools for fixtures Aug 18, 2020 6 - Mature pytest + :pypi:`pytest-fixture-typecheck` A pytest plugin to assert type annotations at runtime. Aug 24, 2021 N/A pytest + :pypi:`pytest-flake8` pytest plugin to check FLAKE8 requirements Mar 18, 2022 4 - Beta pytest (>=7.0) + :pypi:`pytest-flake8-path` A pytest fixture for testing flake8 plugins. May 11, 2022 5 - Production/Stable pytest + :pypi:`pytest-flake8-v2` pytest plugin to check FLAKE8 requirements Mar 01, 2022 5 - Production/Stable pytest (>=7.0) + :pypi:`pytest-flakefinder` Runs tests multiple times to expose flakiness. Jul 28, 2020 4 - Beta pytest (>=2.7.1) + :pypi:`pytest-flakes` pytest plugin to check source code with pyflakes Dec 02, 2021 5 - Production/Stable pytest (>=5) + :pypi:`pytest-flaptastic` Flaptastic py.test plugin Mar 17, 2019 N/A N/A + :pypi:`pytest-flask` A set of py.test fixtures to test Flask applications. Feb 27, 2021 5 - Production/Stable pytest (>=5.2) + :pypi:`pytest-flask-ligand` Pytest fixtures and helper functions to use for testing flask-ligand microservices. Aug 18, 2022 4 - Beta pytest (~=7.1) + :pypi:`pytest-flask-sqlalchemy` A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions. Apr 30, 2022 4 - Beta pytest (>=3.2.1) + :pypi:`pytest-flask-sqlalchemy-transactions` Run tests in transactions using pytest, Flask, and SQLalchemy. Aug 02, 2018 4 - Beta pytest (>=3.2.1) + :pypi:`pytest-fluent` A pytest plugin in order to provide logs via fluentd Jul 12, 2022 4 - Beta pytest + :pypi:`pytest-flyte` Pytest fixtures for simplifying Flyte integration testing May 03, 2021 N/A pytest + :pypi:`pytest-focus` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest + :pypi:`pytest-forcefail` py.test plugin to make the test failing regardless of pytest.mark.xfail May 15, 2018 4 - Beta N/A + :pypi:`pytest-forward-compatability` A name to avoid typosquating pytest-foward-compatibility Sep 06, 2020 N/A N/A + :pypi:`pytest-forward-compatibility` A pytest plugin to shim pytest commandline options for fowards compatibility Sep 29, 2020 N/A N/A + :pypi:`pytest-freezegun` Wrap tests with fixtures in freeze_time Jul 19, 2020 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-freeze-reqs` Check if requirement files are frozen Apr 29, 2021 N/A N/A + :pypi:`pytest-frozen-uuids` Deterministically frozen UUID's for your tests Apr 17, 2022 N/A pytest (>=3.0) + :pypi:`pytest-func-cov` Pytest plugin for measuring function coverage Apr 15, 2021 3 - Alpha pytest (>=5) + :pypi:`pytest-funparam` An alternative way to parametrize test cases. Dec 02, 2021 4 - Beta pytest >=4.6.0 + :pypi:`pytest-fxa` pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A + :pypi:`pytest-fxtest` Oct 27, 2020 N/A N/A + :pypi:`pytest-fzf` fzf-based test selector for pytest Aug 17, 2022 1 - Planning pytest (>=7.1.2) + :pypi:`pytest-gather-fixtures` set up asynchronous pytest fixtures concurrently Apr 12, 2022 N/A pytest (>=6.0.0) + :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A + :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A + :pypi:`pytest-gevent` Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest + :pypi:`pytest-gherkin` A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) + :pypi:`pytest-gh-log-group` pytest plugin for gh actions Jan 11, 2022 3 - Alpha pytest + :pypi:`pytest-ghostinspector` For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A + :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Aug 23, 2022 N/A N/A + :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-git-fixtures` Pytest fixtures for testing with git. Mar 11, 2021 4 - Beta pytest + :pypi:`pytest-github` Plugin for py.test that associates tests with github issues using a marker. Mar 07, 2019 5 - Production/Stable N/A + :pypi:`pytest-github-actions-annotate-failures` pytest plugin to annotate failed tests with a workflow command for GitHub Actions Jul 02, 2022 N/A pytest (>=4.0.0) + :pypi:`pytest-github-report` Generate a GitHub report using pytest in GitHub Workflows Jun 03, 2022 4 - Beta N/A + :pypi:`pytest-gitignore` py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A + :pypi:`pytest-glamor-allure` Extends allure-pytest functionality Jul 22, 2022 4 - Beta pytest + :pypi:`pytest-gnupg-fixtures` Pytest fixtures for testing with gnupg. Mar 04, 2021 4 - Beta pytest + :pypi:`pytest-golden` Plugin for pytest that offloads expected outputs to data files Jul 18, 2022 N/A pytest (>=6.1.2) + :pypi:`pytest-google-chat` Notify google chat channel for test results Mar 27, 2022 4 - Beta pytest + :pypi:`pytest-graphql-schema` Get graphql schema as fixture for pytest Oct 18, 2019 N/A N/A + :pypi:`pytest-greendots` Green progress dots Feb 08, 2014 3 - Alpha N/A + :pypi:`pytest-growl` Growl notifications for pytest results. Jan 13, 2014 5 - Production/Stable N/A + :pypi:`pytest-grpc` pytest plugin for grpc May 01, 2020 N/A pytest (>=3.6.0) + :pypi:`pytest-hammertime` Display "🔨 " instead of "." for passed pytest tests. Jul 28, 2018 N/A pytest + :pypi:`pytest-harvest` Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes. Jun 10, 2022 5 - Production/Stable N/A + :pypi:`pytest-helm-chart` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Jun 15, 2020 4 - Beta pytest (>=5.4.2,<6.0.0) + :pypi:`pytest-helm-charts` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Aug 03, 2022 4 - Beta pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-helper` Functions to help in using the pytest testing framework May 31, 2019 5 - Production/Stable N/A + :pypi:`pytest-helpers` pytest helpers May 17, 2020 N/A pytest + :pypi:`pytest-helpers-namespace` Pytest Helpers Namespace Plugin Dec 29, 2021 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) + :pypi:`pytest-historic` Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest + :pypi:`pytest-historic-hook` Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest + :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Sep 09, 2022 3 - Alpha pytest (==7.1.2) + :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A + :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Mar 28, 2022 N/A pytest (>=5.0) + :pypi:`pytest-hoverfly-wrapper` Integrates the Hoverfly HTTP proxy into Pytest Feb 22, 2022 5 - Production/Stable N/A + :pypi:`pytest-hpfeeds` Helpers for testing hpfeeds in your python project Aug 27, 2021 4 - Beta pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-html` pytest plugin for generating HTML reports Dec 13, 2020 5 - Production/Stable pytest (!=6.0.0,>=5.0) + :pypi:`pytest-html-lee` optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-html-merger` Pytest HTML reports merging utility Apr 03, 2022 N/A N/A + :pypi:`pytest-html-object-storage` Pytest report plugin for send HTML report on object-storage Mar 04, 2022 5 - Production/Stable N/A + :pypi:`pytest-html-profiling` Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-html-reporter` Generates a static html report based on pytest framework Feb 13, 2022 N/A N/A + :pypi:`pytest-html-report-merger` Aug 31, 2022 N/A N/A + :pypi:`pytest-html-thread` pytest plugin for generating HTML reports Dec 29, 2020 5 - Production/Stable N/A + :pypi:`pytest-http` Fixture "http" for http requests Dec 05, 2019 N/A N/A + :pypi:`pytest-httpbin` Easily test your HTTP library against a local copy of httpbin Mar 16, 2022 5 - Production/Stable pytest ; extra == 'test' + :pypi:`pytest-http-mocker` Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A + :pypi:`pytest-httpretty` A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A + :pypi:`pytest-httpserver` pytest-httpserver is a httpserver for pytest Jul 29, 2022 3 - Alpha N/A + :pypi:`pytest-httpx` Send responses to httpx. May 24, 2022 5 - Production/Stable pytest (<8.*,>=6.*) + :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Nov 16, 2021 N/A pytest (>=6.2.5) + :pypi:`pytest-hue` Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A + :pypi:`pytest-hylang` Pytest plugin to allow running tests written in hylang Mar 28, 2021 N/A pytest + :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A + :pypi:`pytest-ibutsu` A plugin to sent pytest results to an Ibutsu server Aug 05, 2022 4 - Beta N/A + :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Aug 09, 2022 4 - Beta N/A + :pypi:`pytest-idapro` A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A + :pypi:`pytest-idem` A pytest plugin to help with testing idem projects Sep 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-idempotent` Pytest plugin for testing function idempotence. Jul 25, 2022 N/A N/A + :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Apr 23, 2021 5 - Production/Stable N/A + :pypi:`pytest-image-diff` Jun 08, 2022 3 - Alpha pytest + :pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A + :pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A + :pypi:`pytest-info-collector` pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A + :pypi:`pytest-informative-node` display more node ininformation. Apr 25, 2019 4 - Beta N/A + :pypi:`pytest-infrastructure` pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A + :pypi:`pytest-ini` Reuse pytest.ini to store env variables Apr 26, 2022 N/A N/A + :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Sep 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-inmanta-extensions` Inmanta tests package Aug 10, 2022 5 - Production/Stable N/A + :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules Sep 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Jun 16, 2022 4 - Beta N/A + :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A + :pypi:`pytest-insta` A practical snapshot testing plugin for pytest Feb 28, 2022 N/A pytest (>=6.0.2) + :pypi:`pytest-instafail` pytest plugin to show failures instantly Jun 14, 2020 4 - Beta pytest (>=2.9) + :pypi:`pytest-instrument` pytest plugin to instrument tests Apr 05, 2020 5 - Production/Stable pytest (>=5.1.0) + :pypi:`pytest-integration` Organizing pytests by integration or not Apr 16, 2020 N/A N/A + :pypi:`pytest-integration-mark` Automatic integration test marking and excluding plugin for pytest Jul 19, 2021 N/A pytest (>=5.2,<7.0) + :pypi:`pytest-interactive` A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A + :pypi:`pytest-intercept-remote` Pytest plugin for intercepting outgoing connection requests during pytest run. May 24, 2021 4 - Beta pytest (>=4.6) + :pypi:`pytest-invenio` "Pytest fixtures for Invenio." Aug 09, 2022 5 - Production/Stable pytest (<7,>=6) + :pypi:`pytest-involve` Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-ipdb` A py.test plug-in to enable drop to ipdb debugger on test failure. Sep 02, 2014 2 - Pre-Alpha N/A + :pypi:`pytest-ipynb` THIS PROJECT IS ABANDONED Jan 29, 2019 3 - Alpha N/A + :pypi:`pytest-isort` py.test plugin to check import ordering using isort Feb 08, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-is-running` pytest plugin providing a function to check if pytest is running. Aug 19, 2022 5 - Production/Stable N/A + :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 22, 2020 4 - Beta N/A + :pypi:`pytest-iterassert` Nicer list and iterable assertion messages for pytest May 11, 2020 3 - Alpha N/A + :pypi:`pytest-iters` A contextmanager pytest fixture for handling multiple mock iters May 24, 2022 N/A N/A + :pypi:`pytest-jasmine` Run jasmine tests from your pytest test suite Nov 04, 2017 1 - Planning N/A + :pypi:`pytest-jest` A custom jest-pytest oriented Pytest reporter May 22, 2018 4 - Beta pytest (>=3.3.2) + :pypi:`pytest-jira` py.test JIRA integration plugin, using markers Apr 07, 2022 3 - Alpha N/A + :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Aug 25, 2022 4 - Beta pytest + :pypi:`pytest-jobserver` Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest + :pypi:`pytest-joke` Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1) + :pypi:`pytest-json` Generate JSON test reports Jan 18, 2016 4 - Beta N/A + :pypi:`pytest-json-fixtures` JSON output for the --fixtures flag Aug 09, 2022 4 - Beta pytest (>=7.1.0) + :pypi:`pytest-jsonlint` UNKNOWN Aug 04, 2016 N/A N/A + :pypi:`pytest-json-report` A pytest plugin to report test results as JSON files Mar 15, 2022 4 - Beta pytest (>=3.8.0) + :pypi:`pytest-kafka` Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest Aug 24, 2021 N/A pytest + :pypi:`pytest-kafkavents` A plugin to send pytest events to Kafka Sep 08, 2021 4 - Beta pytest + :pypi:`pytest-kexi` Apr 29, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-kind` Kubernetes test support with KIND for pytest Sep 08, 2022 5 - Production/Stable N/A + :pypi:`pytest-kivy` Kivy GUI tests fixtures using pytest Jul 06, 2021 4 - Beta pytest (>=3.6) + :pypi:`pytest-knows` A pytest plugin that can automaticly skip test case based on dependence info calculated by trace Aug 22, 2014 N/A N/A + :pypi:`pytest-konira` Run Konira DSL tests with py.test Oct 09, 2011 N/A N/A + :pypi:`pytest-krtech-common` pytest krtech common library Nov 28, 2016 4 - Beta N/A + :pypi:`pytest-kwparametrize` Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks Jan 22, 2021 N/A pytest (>=6) + :pypi:`pytest-lambda` Define pytest fixtures with lambda functions. Aug 20, 2022 3 - Alpha pytest (>=3.6,<8) + :pypi:`pytest-lamp` Jan 06, 2017 3 - Alpha N/A + :pypi:`pytest-launchable` Launchable Pytest Plugin Jun 14, 2022 N/A pytest (>=4.2.0) + :pypi:`pytest-layab` Pytest fixtures for layab. Oct 05, 2020 5 - Production/Stable N/A + :pypi:`pytest-lazy-fixture` It helps to use fixtures in pytest.mark.parametrize Feb 01, 2020 4 - Beta pytest (>=3.2.5) + :pypi:`pytest-ldap` python-ldap fixtures for pytest Aug 18, 2020 N/A pytest + :pypi:`pytest-leak-finder` Find the previous test that makes another to fail Apr 18, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-leaks` A pytest plugin to trace resource leaks. Nov 27, 2019 1 - Planning N/A + :pypi:`pytest-level` Select tests of a given level or lower Oct 21, 2019 N/A pytest + :pypi:`pytest-libfaketime` A python-libfaketime plugin for pytest. Dec 22, 2018 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Jul 11, 2022 4 - Beta N/A + :pypi:`pytest-libnotify` Pytest plugin that shows notifications about the test run Apr 02, 2021 3 - Alpha pytest + :pypi:`pytest-ligo` Jan 16, 2020 4 - Beta N/A + :pypi:`pytest-lineno` A pytest plugin to show the line numbers of test functions Dec 04, 2020 N/A pytest + :pypi:`pytest-line-profiler` Profile code executed by pytest May 03, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-line-profiler-apn` Profile code executed by pytest Aug 14, 2022 4 - Beta N/A + :pypi:`pytest-lisa` Pytest plugin for organizing tests. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) + :pypi:`pytest-listener` A simple network listener May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-litf` A pytest plugin that stream output in LITF format Jan 18, 2021 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-live` Live results for pytest Mar 08, 2020 N/A pytest + :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests Aug 25, 2021 5 - Production/Stable pytest + :pypi:`pytest-localserver` pytest plugin to test server connections locally. Aug 30, 2022 4 - Beta N/A + :pypi:`pytest-localstack` Pytest plugin for AWS integration tests Aug 22, 2019 4 - Beta pytest (>=3.3.0) + :pypi:`pytest-lockable` lockable resource plugin for pytest Jul 20, 2022 5 - Production/Stable pytest + :pypi:`pytest-locker` Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed Oct 29, 2021 N/A pytest (>=5.4) + :pypi:`pytest-log` print log Aug 15, 2021 N/A pytest (>=3.8) + :pypi:`pytest-logbook` py.test plugin to capture logbook log messages Nov 23, 2015 5 - Production/Stable pytest (>=2.8) + :pypi:`pytest-logdog` Pytest plugin to test logging Jun 15, 2021 1 - Planning pytest (>=6.2.0) + :pypi:`pytest-logfest` Pytest plugin providing three logger fixtures with basic or full writing to log files Jul 21, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-logger` Plugin configuring handlers for loggers from Python logging module. Jul 25, 2019 4 - Beta pytest (>=3.2) + :pypi:`pytest-logging` Configures logging and allows tweaking the log level with a py.test flag Nov 04, 2015 4 - Beta N/A + :pypi:`pytest-log-report` Package for creating a pytest test run reprot Dec 26, 2019 N/A N/A + :pypi:`pytest-loguru` Pytest Loguru Apr 12, 2022 5 - Production/Stable N/A + :pypi:`pytest-loop` pytest plugin for looping tests Jul 22, 2022 5 - Production/Stable pytest (>=6) + :pypi:`pytest-lsp` Pytest plugin for end-to-end testing of language servers Jul 18, 2022 3 - Alpha pytest + :pypi:`pytest-manual-marker` pytest marker for marking manual tests Aug 04, 2022 3 - Alpha N/A + :pypi:`pytest-markdoctest` A pytest plugin to doctest your markdown files Jul 22, 2022 4 - Beta pytest (>=6) + :pypi:`pytest-markdown` Test your markdown docs with pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) + :pypi:`pytest-markdown-docs` Run markdown code fences through pytest Sep 02, 2022 N/A N/A + :pypi:`pytest-marker-bugzilla` py.test bugzilla integration plugin, using markers Jan 09, 2020 N/A N/A + :pypi:`pytest-markers-presence` A simple plugin to detect missed pytest tags and markers" Feb 04, 2021 4 - Beta pytest (>=6.0) + :pypi:`pytest-markfiltration` UNKNOWN Nov 08, 2011 3 - Alpha N/A + :pypi:`pytest-mark-no-py3` pytest plugin and bowler codemod to help migrate tests to Python 3 May 17, 2019 N/A pytest + :pypi:`pytest-marks` UNKNOWN Nov 23, 2012 3 - Alpha N/A + :pypi:`pytest-matcher` Match test output against patterns stored in files Dec 10, 2021 5 - Production/Stable N/A + :pypi:`pytest-match-skip` Skip matching marks. Matches partial marks using wildcards. May 15, 2019 4 - Beta pytest (>=4.4.1) + :pypi:`pytest-mat-report` this is report Jan 20, 2021 N/A N/A + :pypi:`pytest-matrix` Provide tools for generating tests from combinations of fixtures. Jun 24, 2020 5 - Production/Stable pytest (>=5.4.3,<6.0.0) + :pypi:`pytest-maybe-raises` Pytest fixture for optional exception testing. May 27, 2022 N/A pytest ; extra == 'dev' + :pypi:`pytest-mccabe` pytest plugin to run the mccabe code complexity checker. Jul 22, 2020 3 - Alpha pytest (>=5.4.0) + :pypi:`pytest-md` Plugin for generating Markdown reports for pytest results Jul 11, 2019 3 - Alpha pytest (>=4.2.1) + :pypi:`pytest-md-report` A pytest plugin to make a test results report with Markdown table format. Aug 06, 2022 4 - Beta pytest (!=6.0.0,<8,>=3.3.2) + :pypi:`pytest-memprof` Estimates memory consumption of test functions Mar 29, 2019 4 - Beta N/A + :pypi:`pytest-memray` A simple plugin to use with pytest Aug 21, 2022 N/A N/A + :pypi:`pytest-menu` A pytest plugin for console based interactive test selection just after the collection phase Oct 04, 2017 3 - Alpha pytest (>=2.4.2) + :pypi:`pytest-mercurial` pytest plugin to write integration tests for projects using Mercurial Python internals Nov 21, 2020 1 - Planning N/A + :pypi:`pytest-mesh` pytest_mesh插件 Aug 05, 2022 N/A pytest (==7.1.2) + :pypi:`pytest-message` Pytest plugin for sending report message of marked tests execution Aug 04, 2022 N/A pytest (>=6.2.5) + :pypi:`pytest-messenger` Pytest to Slack reporting plugin Feb 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-metadata` pytest plugin for test session metadata Jul 15, 2022 5 - Production/Stable pytest (>=3.0.0,<8.0.0) + :pypi:`pytest-metrics` Custom metrics report for pytest Apr 04, 2020 N/A pytest + :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) + :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1) + :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A + :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) + :pypi:`pytest-mock` Thin-wrapper around the mock package for easier use with pytest Jul 05, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-mock-api` A mock API server with configurable routes and responses available as a fixture. Feb 13, 2019 1 - Planning pytest (>=4.0.0) + :pypi:`pytest-mock-generator` A pytest fixture wrapper for https://pypi.org/project/mock-generator May 16, 2022 5 - Production/Stable N/A + :pypi:`pytest-mock-helper` Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest + :pypi:`pytest-mockito` Base fixtures for mockito Jul 11, 2018 4 - Beta N/A + :pypi:`pytest-mockredis` An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A + :pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. Aug 24, 2022 N/A pytest (>=1.0) + :pypi:`pytest-mock-server` Mock server plugin for pytest Jan 09, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-mockservers` A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0) + :pypi:`pytest-modified-env` Pytest plugin to fail a test if it leaves modified \`os.environ\` afterwards. Jan 29, 2022 4 - Beta N/A + :pypi:`pytest-modifyjunit` Utility for adding additional properties to junit xml for IDM QE Jan 10, 2019 N/A N/A + :pypi:`pytest-modifyscope` pytest plugin to modify fixture scope Apr 12, 2020 N/A pytest + :pypi:`pytest-molecule` PyTest Molecule Plugin :: discover and run molecule tests Mar 29, 2022 5 - Production/Stable pytest (>=7.0.0) + :pypi:`pytest-mongo` MongoDB process and client fixtures plugin for Pytest. Jun 07, 2021 5 - Production/Stable pytest + :pypi:`pytest-mongodb` pytest plugin for MongoDB fixtures Dec 07, 2019 5 - Production/Stable pytest (>=2.5.2) + :pypi:`pytest-monitor` Pytest plugin for analyzing resource usage. May 18, 2022 5 - Production/Stable pytest + :pypi:`pytest-monkeyplus` pytest's monkeypatch subclass with extra functionalities Sep 18, 2012 5 - Production/Stable N/A + :pypi:`pytest-monkeytype` pytest-monkeytype: Generate Monkeytype annotations from your pytest tests. Jul 29, 2020 4 - Beta N/A + :pypi:`pytest-moto` Fixtures for integration tests of AWS services,uses moto mocking library. Aug 28, 2015 1 - Planning N/A + :pypi:`pytest-motor` A pytest plugin for motor, the non-blocking MongoDB driver. Jul 21, 2021 3 - Alpha pytest + :pypi:`pytest-mp` A test batcher for multiprocessed Pytest runs May 23, 2018 4 - Beta pytest + :pypi:`pytest-mpi` pytest plugin to collect information from tests Jan 08, 2022 3 - Alpha pytest + :pypi:`pytest-mpl` pytest plugin to help with testing figures output from Matplotlib Jul 23, 2022 4 - Beta pytest + :pypi:`pytest-mproc` low-startup-overhead, scalable, distributed-testing pytest plugin Sep 04, 2022 4 - Beta pytest (>=6) + :pypi:`pytest-multi-check` Pytest-плагин, реализует возможность мульти проверок и мягких проверок Jul 12, 2022 N/A pytest + :pypi:`pytest-multihost` Utility for writing multi-host tests for pytest Apr 07, 2020 4 - Beta N/A + :pypi:`pytest-multilog` Multi-process logs handling and other helpers for pytest Jun 10, 2021 N/A N/A + :pypi:`pytest-multithreading` a pytest plugin for th and concurrent testing Aug 12, 2021 N/A pytest (>=3.6) + :pypi:`pytest-multithreading-allure` pytest_multithreading_allure Mar 22, 2022 N/A N/A + :pypi:`pytest-mutagen` Add the mutation testing feature to pytest Jul 24, 2020 N/A pytest (>=5.4) + :pypi:`pytest-mypy` Mypy static type checker plugin for Pytest Feb 07, 2022 4 - Beta pytest (>=6.2) ; python_version >= "3.10" + :pypi:`pytest-mypyd` Mypy static type checker plugin for Pytest Aug 20, 2019 4 - Beta pytest (<4.7,>=2.8) ; python_version < "3.5" + :pypi:`pytest-mypy-plugins` pytest plugin for writing tests for mypy plugins Aug 15, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-mypy-plugins-shim` Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. Apr 12, 2021 N/A N/A + :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. May 30, 2022 N/A N/A + :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Feb 15, 2022 5 - Production/Stable pytest (>=6.2) + :pypi:`pytest-needle` pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0) + :pypi:`pytest-neo` pytest-neo is a plugin for pytest that shows tests like screen of Matrix. Jan 08, 2022 3 - Alpha pytest (>=6.2.0) + :pypi:`pytest-network` A simple plugin to disable network on socket level. May 07, 2020 N/A N/A + :pypi:`pytest-network-endpoints` Network endpoints plugin for pytest Mar 06, 2022 N/A pytest + :pypi:`pytest-never-sleep` pytest plugin helps to avoid adding tests without mock \`time.sleep\` May 05, 2021 3 - Alpha pytest (>=3.5.1) + :pypi:`pytest-nginx` nginx fixture for pytest Aug 12, 2017 5 - Production/Stable N/A + :pypi:`pytest-nginx-iplweb` nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-ngrok` Jan 20, 2022 3 - Alpha pytest + :pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0) + :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Aug 23, 2022 N/A pytest (==6.2.5) + :pypi:`pytest-nice` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest + :pypi:`pytest-nice-parametrize` A small snippet for nicer PyTest's Parametrize Apr 17, 2021 5 - Production/Stable N/A + :pypi:`pytest-nlcov` Pytest plugin to get the coverage of the new lines (based on git diff) only Jul 07, 2021 N/A N/A + :pypi:`pytest-nocustom` Run all tests without custom markers Jul 07, 2021 5 - Production/Stable N/A + :pypi:`pytest-nodev` Test-driven source code search for Python. Jul 21, 2016 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-nogarbage` Ensure a test produces no garbage Aug 29, 2021 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-notice` Send pytest execution result email Nov 05, 2020 N/A N/A + :pypi:`pytest-notification` A pytest plugin for sending a desktop notification and playing a sound upon completion of tests Jun 19, 2020 N/A pytest (>=4) + :pypi:`pytest-notifier` A pytest plugin to notify test result Jun 12, 2020 3 - Alpha pytest + :pypi:`pytest-notimplemented` Pytest markers for not implemented features and tests. Aug 27, 2019 N/A pytest (>=5.1,<6.0) + :pypi:`pytest-notion` A PyTest Reporter to send test runs to Notion.so Aug 07, 2019 N/A N/A + :pypi:`pytest-nunit` A pytest plugin for generating NUnit3 test result XML output Jul 26, 2022 4 - Beta N/A + :pypi:`pytest-object-getter` Import any object from a 3rd party module while mocking its namespace on demand. Jul 31, 2022 5 - Production/Stable pytest + :pypi:`pytest-ochrus` pytest results data-base and HTML reporter Feb 21, 2018 4 - Beta N/A + :pypi:`pytest-odoo` py.test plugin to run Odoo tests Feb 08, 2022 4 - Beta N/A + :pypi:`pytest-odoo-fixtures` Project description Jun 25, 2019 N/A N/A + :pypi:`pytest-oerp` pytest plugin to test OpenERP modules Feb 28, 2012 3 - Alpha N/A + :pypi:`pytest-ok` The ultimate pytest output plugin Apr 01, 2019 4 - Beta N/A + :pypi:`pytest-only` Use @pytest.mark.only to run a single test Jun 14, 2022 5 - Production/Stable pytest (<7.1); python_version <= "3.6" + :pypi:`pytest-oot` Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A + :pypi:`pytest-openfiles` Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) + :pypi:`pytest-opentelemetry` A pytest plugin for instrumenting test runs via OpenTelemetry Sep 07, 2022 N/A pytest + :pypi:`pytest-opentmi` pytest plugin for publish results to opentmi Jun 02, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-operator` Fixtures for Operators Aug 17, 2022 N/A pytest + :pypi:`pytest-optional` include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A + :pypi:`pytest-optional-tests` Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) + :pypi:`pytest-orchestration` A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A + :pypi:`pytest-order` pytest plugin to run your tests in a specific order Jan 09, 2022 4 - Beta pytest (>=5.0) ; python_version < "3.10" + :pypi:`pytest-ordering` pytest plugin to run your tests in a specific order Nov 14, 2018 4 - Beta pytest + :pypi:`pytest-osxnotify` OS X notifications for py.test results. May 15, 2015 N/A N/A + :pypi:`pytest-otel` pytest-otel report OpenTelemetry traces about test executed May 26, 2022 N/A N/A + :pypi:`pytest-owner` Add owner mark for tests Apr 25, 2022 N/A N/A + :pypi:`pytest-pact` A simple plugin to use with pytest Jan 07, 2019 4 - Beta N/A + :pypi:`pytest-pahrametahrize` Parametrize your tests with a Boston accent. Nov 24, 2021 4 - Beta pytest (>=6.0,<7.0) + :pypi:`pytest-parallel` a pytest plugin for parallel and concurrent testing Oct 10, 2021 3 - Alpha pytest (>=3.0.0) + :pypi:`pytest-parallel-39` a pytest plugin for parallel and concurrent testing Jul 12, 2021 3 - Alpha pytest (>=3.0.0) + :pypi:`pytest-param` pytest plugin to test all, first, last or random params Sep 11, 2016 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-paramark` Configure pytest fixtures using a combination of"parametrize" and markers Jan 10, 2020 4 - Beta pytest (>=4.5.0) + :pypi:`pytest-parametrization` Simpler PyTest parametrization May 22, 2022 5 - Production/Stable N/A + :pypi:`pytest-parametrize-cases` A more user-friendly way to write parametrized tests. Mar 13, 2022 N/A pytest (>=6.1.2) + :pypi:`pytest-parametrized` Pytest plugin for parametrizing tests with default iterables. Oct 19, 2020 5 - Production/Stable pytest + :pypi:`pytest-parawtf` Finally spell paramete?ri[sz]e correctly Dec 03, 2018 4 - Beta pytest (>=3.6.0) + :pypi:`pytest-pass` Check out https://github.com/elilutsky/pytest-pass Dec 04, 2019 N/A N/A + :pypi:`pytest-passrunner` Pytest plugin providing the 'run_on_pass' marker Feb 10, 2021 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-paste-config` Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A + :pypi:`pytest-patches` A contextmanager pytest fixture for handling multiple mock patches Aug 30, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-pdb` pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A + :pypi:`pytest-peach` pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7) + :pypi:`pytest-pep257` py.test plugin for pep257 Jul 09, 2016 N/A N/A + :pypi:`pytest-pep8` pytest plugin to check PEP8 requirements Apr 27, 2014 N/A N/A + :pypi:`pytest-percent` Change the exit code of pytest test sessions when a required percent of tests pass. May 21, 2020 N/A pytest (>=5.2.0) + :pypi:`pytest-perf` pytest-perf Jun 23, 2022 5 - Production/Stable pytest (>=6) ; extra == 'testing' + :pypi:`pytest-performance` A simple plugin to ensure the execution of critical sections of code has not been impacted Sep 11, 2020 5 - Production/Stable pytest (>=3.7.0) + :pypi:`pytest-persistence` Pytest tool for persistent objects Jun 27, 2022 N/A N/A + :pypi:`pytest-pg` A tiny plugin for pytest which runs PostgreSQL in Docker Jun 07, 2022 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-pgsql` Pytest plugins and helpers for tests using a Postgres database. May 13, 2020 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-phmdoctest` pytest plugin to test Python examples in Markdown using phmdoctest. Apr 15, 2022 4 - Beta pytest (>=5.4.3) + :pypi:`pytest-picked` Run the tests related to the changed files Dec 23, 2020 N/A pytest (>=3.5.0) + :pypi:`pytest-pigeonhole` Jun 25, 2018 5 - Production/Stable pytest (>=3.4) + :pypi:`pytest-pikachu` Show surprise when tests are passing Aug 05, 2021 5 - Production/Stable pytest + :pypi:`pytest-pilot` Slice in your test base thanks to powerful markers. Oct 09, 2020 5 - Production/Stable N/A + :pypi:`pytest-pings` 🦊 The pytest plugin for Firefox Telemetry 📊 Jun 29, 2019 3 - Alpha pytest (>=5.0.0) + :pypi:`pytest-pinned` A simple pytest plugin for pinning tests Sep 17, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-pinpoint` A pytest plugin which runs SBFL algorithms to detect faults. Sep 25, 2020 N/A pytest (>=4.4.0) + :pypi:`pytest-pipeline` Pytest plugin for functional testing of data analysispipelines Jan 24, 2017 3 - Alpha N/A + :pypi:`pytest-platform-markers` Markers for pytest to skip tests on specific platforms Sep 09, 2019 4 - Beta pytest (>=3.6.0) + :pypi:`pytest-play` pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files Jun 12, 2019 5 - Production/Stable N/A + :pypi:`pytest-playbook` Pytest plugin for reading playbooks. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) + :pypi:`pytest-playwright` A pytest wrapper with fixtures for Playwright to automate web browsers Mar 16, 2022 N/A pytest + :pypi:`pytest-playwrights` A pytest wrapper with fixtures for Playwright to automate web browsers Dec 02, 2021 N/A N/A + :pypi:`pytest-playwright-snapshot` A pytest wrapper for snapshot testing with playwright Aug 19, 2021 N/A N/A + :pypi:`pytest-playwright-visual` A pytest fixture for visual testing with Playwright Apr 28, 2022 N/A N/A + :pypi:`pytest-plt` Fixtures for quickly making Matplotlib plots in tests Aug 17, 2020 5 - Production/Stable pytest + :pypi:`pytest-plugin-helpers` A plugin to help developing and testing other plugins Nov 23, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-plus` PyTest Plus Plugin :: extends pytest functionality Mar 19, 2020 5 - Production/Stable pytest (>=3.50) + :pypi:`pytest-pmisc` Mar 21, 2019 5 - Production/Stable N/A + :pypi:`pytest-pointers` Pytest plugin to define functions you test with special marks for better navigation and reports Oct 14, 2021 N/A N/A + :pypi:`pytest-polarion-cfme` pytest plugin for collecting test cases and recording test results Nov 13, 2017 3 - Alpha N/A + :pypi:`pytest-polarion-collect` pytest plugin for collecting polarion test cases data Jun 18, 2020 3 - Alpha pytest + :pypi:`pytest-polecat` Provides Polecat pytest fixtures Aug 12, 2019 4 - Beta N/A + :pypi:`pytest-ponyorm` PonyORM in Pytest Oct 31, 2018 N/A pytest (>=3.1.1) + :pypi:`pytest-poo` Visualize your crappy tests Mar 25, 2021 5 - Production/Stable pytest (>=2.3.4) + :pypi:`pytest-poo-fail` Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A + :pypi:`pytest-pop` A pytest plugin to help with testing pop projects Aug 18, 2022 5 - Production/Stable pytest + :pypi:`pytest-portion` Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-postgres` Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest + :pypi:`pytest-postgresql` Postgresql fixtures and fixture factories for Pytest. Mar 11, 2022 5 - Production/Stable pytest (>=6.2.0) + :pypi:`pytest-power` pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) + :pypi:`pytest-prefer-nested-dup-tests` A Pytest plugin to drop duplicated tests during collection, but will prefer keeping nested packages. Apr 27, 2022 4 - Beta pytest (>=7.1.1,<8.0.0) + :pypi:`pytest-pretty-terminal` pytest plugin for generating prettier terminal output Jan 31, 2022 N/A pytest (>=3.4.1) + :pypi:`pytest-pride` Minitest-style test colors Apr 02, 2016 3 - Alpha N/A + :pypi:`pytest-print` pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Dec 28, 2021 5 - Production/Stable pytest (>=6) + :pypi:`pytest-profiles` pytest plugin for configuration profiles Dec 09, 2021 4 - Beta pytest (>=3.7.0) + :pypi:`pytest-profiling` Profiling plugin for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-progress` pytest plugin for instant test progress status Jan 31, 2022 5 - Production/Stable N/A + :pypi:`pytest-prometheus` Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A + :pypi:`pytest-prometheus-pushgateway` Pytest report plugin for Zulip Sep 01, 2022 5 - Production/Stable pytest + :pypi:`pytest-prosper` Test helpers for Prosper projects Sep 24, 2018 N/A N/A + :pypi:`pytest-pspec` A rspec format reporter for Python ptest Jun 02, 2020 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-psqlgraph` pytest plugin for testing applications that use psqlgraph Oct 19, 2021 4 - Beta pytest (>=6.0) + :pypi:`pytest-ptera` Use ptera probes in tests Mar 01, 2022 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-pudb` Pytest PuDB debugger integration Oct 25, 2018 3 - Alpha pytest (>=2.0) + :pypi:`pytest-purkinje` py.test plugin for purkinje test runner Oct 28, 2017 2 - Pre-Alpha N/A + :pypi:`pytest-pycharm` Plugin for py.test to enter PyCharm debugger on uncaught exceptions Aug 13, 2020 5 - Production/Stable pytest (>=2.3) + :pypi:`pytest-pycodestyle` pytest plugin to run pycodestyle Mar 13, 2022 3 - Alpha N/A + :pypi:`pytest-pydev` py.test plugin to connect to a remote debug server with PyDev or PyCharm. Nov 15, 2017 3 - Alpha N/A + :pypi:`pytest-pydocstyle` pytest plugin to run pydocstyle Mar 13, 2022 3 - Alpha N/A + :pypi:`pytest-pylint` pytest plugin to check source code with pylint Sep 10, 2022 5 - Production/Stable pytest (>=5.4) + :pypi:`pytest-pymysql-autorecord` Record PyMySQL queries and mock with the stored data. Sep 02, 2022 N/A N/A + :pypi:`pytest-pyodide` "Pytest plugin for testing applications that use Pyodide" Sep 08, 2022 N/A pytest + :pypi:`pytest-pypi` Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A + :pypi:`pytest-pypom-navigation` Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) + :pypi:`pytest-pyppeteer` A plugin to run pyppeteer in pytest Apr 28, 2022 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-pyq` Pytest fixture "q" for pyq Mar 10, 2020 5 - Production/Stable N/A + :pypi:`pytest-pyramid` pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite Oct 15, 2021 5 - Production/Stable pytest + :pypi:`pytest-pyramid-server` Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-pyright` Pytest plugin for type checking code with Pyright Aug 16, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-pytestrail` Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) + :pypi:`pytest-pythonpath` pytest plugin for adding to the PYTHONPATH from command line or configs. Feb 10, 2022 5 - Production/Stable pytest (<7,>=2.5.2) + :pypi:`pytest-pytorch` pytest plugin for a better developer experience when working with the PyTorch test suite May 25, 2021 4 - Beta pytest + :pypi:`pytest-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0) + :pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Jun 26, 2021 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Jun 26, 2022 5 - Production/Stable pytest (>=6.2.3) + :pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A + :pypi:`pytest-qt` pytest support for PyQt and PySide applications Jun 23, 2022 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-qt-app` QT app fixture for py.test Dec 23, 2015 5 - Production/Stable N/A + :pypi:`pytest-quarantine` A plugin for pytest to manage expected test failures Nov 24, 2019 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-quickcheck` pytest plugin to generate random data inspired by QuickCheck Nov 15, 2020 4 - Beta pytest (<6.0.0,>=4.0) + :pypi:`pytest-rabbitmq` RabbitMQ process and client fixtures for pytest Feb 11, 2022 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-race` Race conditions tester for pytest Jun 07, 2022 4 - Beta N/A + :pypi:`pytest-rage` pytest plugin to implement PEP712 Oct 21, 2011 3 - Alpha N/A + :pypi:`pytest-rail` pytest plugin for creating TestRail runs and adding results May 02, 2022 N/A pytest (>=3.6) + :pypi:`pytest-railflow-testrail-reporter` Generate json reports along with specified metadata defined in test markers. Jun 29, 2022 5 - Production/Stable pytest + :pypi:`pytest-raises` An implementation of pytest.raises as a pytest.mark fixture Apr 23, 2020 N/A pytest (>=3.2.2) + :pypi:`pytest-raisesregexp` Simple pytest plugin to look for regex in Exceptions Dec 18, 2015 N/A N/A + :pypi:`pytest-raisin` Plugin enabling the use of exception instances with pytest.raises Feb 06, 2022 N/A pytest + :pypi:`pytest-random` py.test plugin to randomize tests Apr 28, 2013 3 - Alpha N/A + :pypi:`pytest-randomly` Pytest plugin to randomly order tests and control random.seed. May 11, 2022 5 - Production/Stable pytest + :pypi:`pytest-randomness` Pytest plugin about random seed management May 30, 2019 3 - Alpha N/A + :pypi:`pytest-random-num` Randomise the order in which pytest tests are run with some control over the randomness Oct 19, 2020 5 - Production/Stable N/A + :pypi:`pytest-random-order` Randomise the order in which pytest tests are run with some control over the randomness Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A + :pypi:`pytest-reana` Pytest fixtures for REANA. May 16, 2022 3 - Alpha N/A + :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Jun 20, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A + :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Feb 10, 2022 5 - Production/Stable pytest (>=6.2.0) + :pypi:`pytest-redislite` Pytest plugin for testing code using Redis Apr 05, 2022 4 - Beta pytest + :pypi:`pytest-redmine` Pytest plugin for redmine Mar 19, 2018 1 - Planning N/A + :pypi:`pytest-ref` A plugin to store reference files to ease regression testing Nov 23, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-reference-formatter` Conveniently run pytest with a dot-formatted test reference. Oct 01, 2019 4 - Beta N/A + :pypi:`pytest-regex-dependency` Management of Pytest dependencies via regex patterns Jun 12, 2022 N/A pytest + :pypi:`pytest-regressions` Easy to use fixtures to write regression tests. Jan 19, 2022 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-regtest` pytest plugin for regression tests Jul 08, 2022 N/A N/A + :pypi:`pytest-relative-order` a pytest plugin that sorts tests using "before" and "after" markers May 17, 2021 4 - Beta N/A + :pypi:`pytest-relaxed` Relaxed test discovery/organization for pytest Jun 14, 2019 5 - Production/Stable pytest (<5,>=3) + :pypi:`pytest-remfiles` Pytest plugin to create a temporary directory with remote files Jul 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-remotedata` Pytest plugin for controlling remote data access. Dec 21, 2021 3 - Alpha pytest (>=4.6) + :pypi:`pytest-remote-response` Pytest plugin for capturing and mocking connection requests. Jun 05, 2022 4 - Beta pytest (>=4.6) + :pypi:`pytest-remove-stale-bytecode` py.test plugin to remove stale byte code files. Mar 04, 2020 4 - Beta pytest + :pypi:`pytest-reorder` Reorder tests depending on their paths and names. May 31, 2018 4 - Beta pytest + :pypi:`pytest-repeat` pytest plugin for repeating tests Oct 31, 2020 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-replay` Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests Jun 09, 2021 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-repo-health` A pytest plugin to report on repository standards conformance Dec 16, 2021 3 - Alpha pytest + :pypi:`pytest-report` Creates json report that is compatible with atom.io's linter message format May 11, 2016 4 - Beta N/A + :pypi:`pytest-reporter` Generate Pytest reports with templates Jul 22, 2021 4 - Beta pytest + :pypi:`pytest-reporter-html1` A basic HTML report template for Pytest Jun 08, 2021 4 - Beta N/A + :pypi:`pytest-reportinfra` Pytest plugin for reportinfra Aug 11, 2019 3 - Alpha N/A + :pypi:`pytest-reporting` A plugin to report summarized results in a table format Oct 25, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-reportlog` Replacement for the --resultlog option, focused in simplicity and extensibility Dec 11, 2020 3 - Alpha pytest (>=5.2) + :pypi:`pytest-report-me` A pytest plugin to generate report. Dec 31, 2020 N/A pytest + :pypi:`pytest-report-parameters` pytest plugin for adding tests' parameters to junit report Jun 18, 2020 3 - Alpha pytest (>=2.4.2) + :pypi:`pytest-reportportal` Agent for Reporting results of tests to the Report Portal Jun 24, 2022 N/A pytest (>=3.8.0) + :pypi:`pytest-reqs` pytest plugin to check pinned requirements May 12, 2019 N/A pytest (>=2.4.2) + :pypi:`pytest-requests` A simple plugin to use with pytest Jun 24, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-requestselapsed` collect and show http requests elapsed time Aug 14, 2022 N/A N/A + :pypi:`pytest-requests-futures` Pytest Plugin to Mock Requests Futures Jul 06, 2022 5 - Production/Stable pytest + :pypi:`pytest-requires` A pytest plugin to elegantly skip tests with optional requirements Dec 21, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-reraise` Make multi-threaded pytest test cases fail when they should Jun 17, 2021 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-rerun` Re-run only changed files in specified branch Jul 08, 2019 N/A pytest (>=3.6) + :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Sep 17, 2021 5 - Production/Stable pytest (>=5.3) + :pypi:`pytest-rerunfailures-all-logs` pytest plugin to re-run tests to eliminate flaky failures Mar 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest. Aug 12, 2022 N/A N/A + :pypi:`pytest-resource` Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A + :pypi:`pytest-resource-path` Provides path for uniform access to test resources in isolated directory May 01, 2021 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-responsemock` Simplified requests calls mocking for pytest Mar 10, 2022 5 - Production/Stable N/A + :pypi:`pytest-responses` py.test integration for responses Apr 26, 2021 N/A pytest (>=2.5) + :pypi:`pytest-rest-api` Aug 08, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-restrict` Pytest plugin to restrict the test types allowed May 11, 2022 5 - Production/Stable pytest + :pypi:`pytest-rethinkdb` A RethinkDB plugin for pytest. Jul 24, 2016 4 - Beta N/A + :pypi:`pytest-retry` Adds the ability to retry flaky tests in CI environments Aug 16, 2022 N/A pytest (>=7.0.0) + :pypi:`pytest-reverse` Pytest plugin to reverse test order. May 11, 2022 5 - Production/Stable pytest + :pypi:`pytest-rich` Leverage rich for richer test session output Mar 03, 2022 4 - Beta pytest (>=7.0) + :pypi:`pytest-rich-reporter` A pytest plugin using Rich for beautiful test result formatting. Feb 17, 2022 1 - Planning pytest (>=5.0.0) + :pypi:`pytest-ringo` pytest plugin to test webapplications using the Ringo webframework Sep 27, 2017 3 - Alpha N/A + :pypi:`pytest-rmsis` Sycronise pytest results to Jira RMsis Aug 10, 2022 N/A pytest (>=5.3.5) + :pypi:`pytest-rng` Fixtures for seeding tests and making randomness reproducible Aug 08, 2019 5 - Production/Stable pytest + :pypi:`pytest-roast` pytest plugin for ROAST configuration override and fixtures Jul 29, 2021 5 - Production/Stable pytest + :pypi:`pytest-rocketchat` Pytest to Rocket.Chat reporting plugin Apr 18, 2021 5 - Production/Stable N/A + :pypi:`pytest-rotest` Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) + :pypi:`pytest-rpc` Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) + :pypi:`pytest-rst` Test code from RST documents with pytest Aug 02, 2022 N/A N/A + :pypi:`pytest-rt` pytest data collector plugin for Testgr May 05, 2022 N/A N/A + :pypi:`pytest-rts` Coverage-based regression test selection (RTS) plugin for pytest May 17, 2021 N/A pytest + :pypi:`pytest-run-changed` Pytest plugin that runs changed tests only Apr 02, 2021 3 - Alpha pytest + :pypi:`pytest-runfailed` implement a --failed option for pytest Mar 24, 2016 N/A N/A + :pypi:`pytest-runner` Invoke py.test as distutils command with dependency resolution Feb 25, 2022 5 - Production/Stable pytest (>=6) ; extra == 'testing' + :pypi:`pytest-run-subprocess` Pytest Plugin for running and testing subprocesses. Jul 03, 2022 5 - Production/Stable pytest + :pypi:`pytest-runtime-xfail` Call runtime_xfail() to mark running test as xfail. Aug 26, 2021 N/A N/A + :pypi:`pytest-saccharin` pytest-saccharin is a updated fork of pytest-sugar, a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). May 10, 2022 3 - Alpha N/A + :pypi:`pytest-salt` Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A + :pypi:`pytest-salt-containers` A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A + :pypi:`pytest-salt-factories` Pytest Salt Plugin Aug 25, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-salt-from-filenames` Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) + :pypi:`pytest-salt-runtests-bridge` Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) + :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) + :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A + :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Sep 09, 2022 5 - Production/Stable N/A + :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A + :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Mar 14, 2022 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-securestore` An encrypted password store for use within pytest cases Nov 08, 2021 4 - Beta N/A + :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) + :pypi:`pytest-selenium` pytest plugin for Selenium Mar 28, 2022 5 - Production/Stable pytest (>=6.0.0,<7.0.0) + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Sep 09, 2022 5 - Production/Stable N/A + :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Apr 29, 2022 5 - Production/Stable N/A + :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A + :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A + :pypi:`pytest-sentry` A pytest plugin to send testrun information to Sentry.io Apr 21, 2021 N/A pytest + :pypi:`pytest-server-fixtures` Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. May 09, 2022 4 - Beta N/A + :pypi:`pytest-servers` pytest servers Aug 22, 2022 3 - Alpha pytest (==7.1.2) + :pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A + :pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest + :pypi:`pytest-session-fixture-globalize` py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A + :pypi:`pytest-session_to_file` pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. Oct 01, 2015 3 - Alpha N/A + :pypi:`pytest-sftpserver` py.test plugin to locally test sftp server connections. Sep 16, 2019 4 - Beta N/A + :pypi:`pytest-shard` Dec 11, 2020 4 - Beta pytest + :pypi:`pytest-sharkreport` this is pytest report plugin. Jul 11, 2022 N/A pytest (>=3.5) + :pypi:`pytest-shell` A pytest plugin to help with testing shell scripts / black box commands Mar 27, 2022 N/A N/A + :pypi:`pytest-shell-utilities` Pytest plugin to simplify running shell commands against the system Jul 28, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-sheraf` Versatile ZODB abstraction layer - pytest fixtures Feb 11, 2020 N/A pytest + :pypi:`pytest-sherlock` pytest plugin help to find coupled tests Nov 18, 2021 5 - Production/Stable pytest (>=3.5.1) + :pypi:`pytest-shortcuts` Expand command-line shortcuts listed in pytest configuration Oct 29, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-shutil` A goodie-bag of unix shell and environment tools for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-simplehttpserver` Simple pytest fixture to spin up an HTTP server Jun 24, 2021 4 - Beta N/A + :pypi:`pytest-simple-plugin` Simple pytest plugin Nov 27, 2019 N/A N/A + :pypi:`pytest-simple-settings` simple-settings plugin for pytest Nov 17, 2020 4 - Beta pytest + :pypi:`pytest-single-file-logging` Allow for multiple processes to log to a single file May 05, 2016 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-skip-markers` Pytest Salt Plugin May 09, 2022 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-skipper` A plugin that selects only tests with changes in execution path Mar 26, 2017 3 - Alpha pytest (>=3.0.6) + :pypi:`pytest-skippy` Automatically skip tests that don't need to run! Jan 27, 2018 3 - Alpha pytest (>=2.3.4) + :pypi:`pytest-skip-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Apr 26, 2022 N/A N/A + :pypi:`pytest-slack` Pytest to Slack reporting plugin Dec 15, 2020 5 - Production/Stable N/A + :pypi:`pytest-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Sep 28, 2021 N/A N/A + :pypi:`pytest-smartcollect` A plugin for collecting tests that touch changed code Oct 04, 2018 N/A pytest (>=3.5.0) + :pypi:`pytest-smartcov` Smart coverage plugin for pytest. Sep 30, 2017 3 - Alpha N/A + :pypi:`pytest-smell` Automated bad smell detection tool for Pytest Jun 26, 2022 N/A N/A + :pypi:`pytest-smtp` Send email with pytest execution result Feb 20, 2021 N/A pytest + :pypi:`pytest-snail` Plugin for adding a marker to slow running tests. 🐌 Nov 04, 2019 3 - Alpha pytest (>=5.0.1) + :pypi:`pytest-snapci` py.test plugin for Snap-CI Nov 12, 2015 N/A N/A + :pypi:`pytest-snapshot` A plugin for snapshot testing with pytest. Apr 23, 2022 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-snmpserver` May 12, 2021 N/A N/A + :pypi:`pytest-snowflake-bdd` Setup test data and run tests on snowflake in BDD style! Jan 05, 2022 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-socket` Pytest Plugin to disable socket calls during tests Jan 23, 2022 4 - Beta pytest (>=3.6.3) + :pypi:`pytest-sofaepione` Test the installation of SOFA and the SofaEpione plugin. Aug 17, 2022 N/A N/A + :pypi:`pytest-soft-assertions` May 05, 2020 3 - Alpha pytest + :pypi:`pytest-solidity` A PyTest library plugin for Solidity language. Jan 15, 2022 1 - Planning pytest (<7,>=6.0.1) ; extra == 'tests' + :pypi:`pytest-solr` Solr process and client fixtures for py.test. May 11, 2020 3 - Alpha pytest (>=3.0.0) + :pypi:`pytest-sorter` A simple plugin to first execute tests that historically failed more Apr 20, 2021 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-sosu` Unofficial PyTest plugin for Sauce Labs Apr 11, 2022 2 - Pre-Alpha pytest + :pypi:`pytest-sourceorder` Test-ordering plugin for pytest Sep 01, 2021 4 - Beta pytest + :pypi:`pytest-spark` pytest plugin to run the tests with support of pyspark. Feb 23, 2020 4 - Beta pytest + :pypi:`pytest-spawner` py.test plugin to spawn process and communicate with them. Jul 31, 2015 4 - Beta N/A + :pypi:`pytest-spec` Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. May 04, 2021 N/A N/A + :pypi:`pytest-spec2md` Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest. Jun 26, 2022 N/A pytest (>7.0) + :pypi:`pytest-speed` Modern benchmarking library for python with pytest integration. Jul 06, 2022 3 - Alpha pytest (>=7) + :pypi:`pytest-sphinx` Doctest plugin for pytest with support for Sphinx-specific doctest-directives Sep 06, 2022 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-spiratest` Exports unit tests as test runs in SpiraTest/Team/Plan Feb 08, 2022 N/A N/A + :pypi:`pytest-splinter` Splinter plugin for pytest testing framework Sep 09, 2022 6 - Mature pytest (>=3.0.0) + :pypi:`pytest-splinter4` Pytest plugin for the splinter automation library Jun 11, 2022 6 - Mature pytest (<8.0,>=7.1.2) + :pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Apr 22, 2022 4 - Beta pytest (>=5,<8) + :pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) + :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) + :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Sep 08, 2022 N/A pytest (>5.4.0,<6.3) + :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Mar 16, 2022 N/A N/A + :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) + :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A + :pypi:`pytest-sqlalchemy` pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A + :pypi:`pytest-sqlalchemy-mock` pytest sqlalchemy plugin for mock Aug 10, 2022 3 - Alpha pytest (>=2.0) + :pypi:`pytest-sql-bigquery` Yet another SQL-testing framework for BigQuery provided by pytest plugin Dec 19, 2019 N/A pytest + :pypi:`pytest-squadcast` Pytest report plugin for Squadcast Feb 22, 2022 5 - Production/Stable pytest + :pypi:`pytest-srcpaths` Add paths to sys.path Oct 15, 2021 N/A N/A + :pypi:`pytest-ssh` pytest plugin for ssh command run May 27, 2019 N/A pytest + :pypi:`pytest-start-from` Start pytest run from a given point Apr 11, 2016 N/A N/A + :pypi:`pytest-statsd` pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-stepfunctions` A small description May 08, 2021 4 - Beta pytest + :pypi:`pytest-steps` Create step-wise / incremental tests in pytest. Sep 23, 2021 5 - Production/Stable N/A + :pypi:`pytest-stepwise` Run a test suite one failing test at a time. Dec 01, 2015 4 - Beta N/A + :pypi:`pytest-stf` pytest plugin for openSTF Sep 09, 2022 N/A pytest (>=5.0) + :pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A + :pypi:`pytest-stress` A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0) + :pypi:`pytest-structlog` Structured logging assertions Sep 21, 2021 N/A pytest + :pypi:`pytest-structmpd` provide structured temporary directory Oct 17, 2018 N/A N/A + :pypi:`pytest-stub` Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A + :pypi:`pytest-stubprocess` Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0) + :pypi:`pytest-study` A pytest plugin to organize long run tests (named studies) without interfering the regular tests Sep 26, 2017 3 - Alpha pytest (>=2.0) + :pypi:`pytest-subprocess` A plugin to fake subprocess for pytest Feb 09, 2022 5 - Production/Stable pytest (>=4.0.0) + :pypi:`pytest-subtesthack` A hack to explicitly set up and tear down fixtures. Jul 16, 2022 N/A N/A + :pypi:`pytest-subtests` unittest subTest() support and subtests fixture May 26, 2022 4 - Beta pytest (>=7.0) + :pypi:`pytest-subunit` pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. Aug 29, 2017 N/A N/A + :pypi:`pytest-sugar` pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Jul 10, 2022 3 - Alpha pytest (>=2.9) + :pypi:`pytest-sugar-bugfix159` Workaround for https://github.com/Frozenball/pytest-sugar/issues/159 Nov 07, 2018 5 - Production/Stable pytest (!=3.7.3,>=3.5); extra == 'testing' + :pypi:`pytest-super-check` Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc. May 11, 2022 5 - Production/Stable pytest + :pypi:`pytest-svn` SVN repository fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-symbols` pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. Nov 20, 2017 3 - Alpha N/A + :pypi:`pytest-system-statistics` Pytest plugin to track and report system usage statistics Feb 16, 2022 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-system-test-plugin` Pyst - Pytest System-Test Plugin Feb 03, 2022 N/A N/A + :pypi:`pytest-takeltest` Fixtures for ansible, testinfra and molecule Jan 04, 2022 N/A N/A + :pypi:`pytest-talisker` Nov 28, 2021 N/A N/A + :pypi:`pytest-tap` Test Anything Protocol (TAP) reporting plugin for pytest Oct 27, 2021 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-tape` easy assertion with expected results saved to yaml files Mar 17, 2021 4 - Beta N/A + :pypi:`pytest-target` Pytest plugin for remote target orchestration. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) + :pypi:`pytest-tblineinfo` tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used Dec 01, 2015 3 - Alpha pytest (>=2.0) + :pypi:`pytest-teamcity-logblock` py.test plugin to introduce block structure in teamcity build log, if output is not captured May 15, 2018 4 - Beta N/A + :pypi:`pytest-telegram` Pytest to Telegram reporting plugin Dec 10, 2020 5 - Production/Stable N/A + :pypi:`pytest-tempdir` Predictable and repeatable tempdir support. Oct 11, 2019 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-terra-fixt` Terraform and Terragrunt fixtures for pytest Sep 09, 2022 N/A pytest (==6.2.5) + :pypi:`pytest-terraform` A pytest plugin for using terraform fixtures Sep 01, 2022 N/A pytest (>=6.0) + :pypi:`pytest-terraform-fixture` generate terraform resources to use with pytest Nov 14, 2018 4 - Beta N/A + :pypi:`pytest-testbook` A plugin to run tests written in Jupyter notebook Dec 11, 2016 3 - Alpha N/A + :pypi:`pytest-testconfig` Test configuration plugin for pytest. Jan 11, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-testdirectory` A py.test plugin providing temporary directories in unit tests. Feb 21, 2022 5 - Production/Stable pytest + :pypi:`pytest-testdox` A testdox format reporter for pytest Apr 19, 2022 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-test-grouping` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Jun 17, 2022 5 - Production/Stable pytest (>=2.5) + :pypi:`pytest-test-groups` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Oct 25, 2016 5 - Production/Stable N/A + :pypi:`pytest-testinfra` Test infrastructures Jun 19, 2022 5 - Production/Stable pytest (!=3.0.2) + :pypi:`pytest-testlink-adaptor` pytest reporting plugin for testlink Dec 20, 2018 4 - Beta pytest (>=2.6) + :pypi:`pytest-testmon` selects tests affected by changed files and methods Sep 09, 2022 4 - Beta N/A + :pypi:`pytest-testmon-oc` nOly selects tests affected by changed files and methods Jun 01, 2022 4 - Beta pytest (<8,>=5) + :pypi:`pytest-testmon-skip-libraries` selects tests affected by changed files and methods Jun 13, 2022 4 - Beta N/A + :pypi:`pytest-testobject` Plugin to use TestObject Suites with Pytest Sep 24, 2019 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-testpluggy` set your encoding Jan 07, 2022 N/A pytest + :pypi:`pytest-testrail` pytest plugin for creating TestRail runs and adding results Aug 27, 2020 N/A pytest (>=3.6) + :pypi:`pytest-testrail2` A small example package Nov 17, 2020 N/A pytest (>=5) + :pypi:`pytest-testrail-api` Плагин Pytest, для интеграции с TestRail Aug 29, 2022 N/A pytest (>=5.5) + :pypi:`pytest-testrail-api-client` TestRail Api Python Client Dec 14, 2021 N/A pytest + :pypi:`pytest-testrail-appetize` pytest plugin for creating TestRail runs and adding results Sep 29, 2021 N/A N/A + :pypi:`pytest-testrail-client` pytest plugin for Testrail Sep 29, 2020 5 - Production/Stable N/A + :pypi:`pytest-testrail-e2e` pytest plugin for creating TestRail runs and adding results Oct 11, 2021 N/A pytest (>=3.6) + :pypi:`pytest-testrail-integrator` Pytest plugin for sending report to testrail system. Aug 01, 2022 N/A pytest (>=6.2.5) + :pypi:`pytest-testrail-ns` pytest plugin for creating TestRail runs and adding results Aug 12, 2022 N/A N/A + :pypi:`pytest-testrail-plugin` PyTest plugin for TestRail Apr 21, 2020 3 - Alpha pytest + :pypi:`pytest-testrail-reporter` Sep 10, 2018 N/A N/A + :pypi:`pytest-testreport` May 23, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-testreport-new` Aug 15, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-testslide` TestSlide fixture for pytest Jan 07, 2021 5 - Production/Stable pytest (~=6.2) + :pypi:`pytest-test-this` Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply Sep 15, 2019 2 - Pre-Alpha pytest (>=2.3) + :pypi:`pytest-test-utils` Jul 14, 2022 N/A pytest (>=5) + :pypi:`pytest-tesults` Tesults plugin for pytest Dec 31, 2021 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-tezos` pytest-ligo Jan 16, 2020 4 - Beta N/A + :pypi:`pytest-th2-bdd` pytest_th2_bdd May 13, 2022 N/A N/A + :pypi:`pytest-thawgun` Pytest plugin for time travel May 26, 2020 3 - Alpha N/A + :pypi:`pytest-threadleak` Detects thread leaks Jul 03, 2022 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-tick` Ticking on tests Aug 31, 2021 5 - Production/Stable pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-timeit` A pytest plugin to time test function runs Oct 13, 2016 4 - Beta N/A + :pypi:`pytest-timeout` pytest plugin to abort hanging tests Jan 18, 2022 5 - Production/Stable pytest (>=5.0.0) + :pypi:`pytest-timeouts` Linux-only Pytest plugin to control durations of various test case execution phases Sep 21, 2019 5 - Production/Stable N/A + :pypi:`pytest-timer` A timer plugin for pytest Jun 02, 2021 N/A N/A + :pypi:`pytest-timestamper` Pytest plugin to add a timestamp prefix to the pytest output Jun 06, 2021 N/A N/A + :pypi:`pytest-timestamps` A simple plugin to view timestamps for each test Jan 16, 2022 N/A pytest (>=5.2) + :pypi:`pytest-tipsi-django` Nov 17, 2021 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-tipsi-testing` Better fixtures management. Various helpers Nov 04, 2020 4 - Beta pytest (>=3.3.0) + :pypi:`pytest-tldr` A pytest plugin that limits the output to just the things you need. Mar 12, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-tm4j-reporter` Cloud Jira Test Management (TM4J) PyTest reporter plugin Sep 01, 2020 N/A pytest + :pypi:`pytest-tmnet` A small example package Mar 01, 2022 N/A N/A + :pypi:`pytest-tmp-files` Utilities to create temporary file hierarchies in pytest. Apr 03, 2022 N/A pytest + :pypi:`pytest-tmpfs` A pytest plugin that helps you on using a temporary filesystem for testing. Aug 29, 2022 N/A pytest + :pypi:`pytest-tmreport` this is a vue-element ui report for pytest Aug 12, 2022 N/A N/A + :pypi:`pytest-todo` A small plugin for the pytest testing framework, marking TODO comments as failure May 23, 2019 4 - Beta pytest + :pypi:`pytest-tomato` Mar 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-toolbelt` This is just a collection of utilities for pytest, but don't really belong in pytest proper. Aug 12, 2019 3 - Alpha N/A + :pypi:`pytest-toolbox` Numerous useful plugins for pytest. Apr 07, 2018 N/A pytest (>=3.5.0) + :pypi:`pytest-tools` Pytest tools Jul 04, 2022 4 - Beta N/A + :pypi:`pytest-tornado` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Jun 17, 2020 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-tornado5` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Nov 16, 2018 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-tornado-yen3` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Oct 15, 2018 5 - Production/Stable N/A + :pypi:`pytest-tornasync` py.test plugin for testing Python 3.5+ Tornado code Jul 15, 2019 3 - Alpha pytest (>=3.0) + :pypi:`pytest-trace` Save OpenTelemetry spans generated during testing Jun 19, 2022 N/A pytest (>=4.6) + :pypi:`pytest-track` Feb 26, 2021 3 - Alpha pytest (>=3.0) + :pypi:`pytest-translations` Test your translation files. Nov 05, 2021 5 - Production/Stable N/A + :pypi:`pytest-travis-fold` Folds captured output sections in Travis CI build log Nov 29, 2017 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-trello` Plugin for py.test that integrates trello using markers Nov 20, 2015 5 - Production/Stable N/A + :pypi:`pytest-trepan` Pytest plugin for trepan debugger. Jul 28, 2018 5 - Production/Stable N/A + :pypi:`pytest-trialtemp` py.test plugin for using the same _trial_temp working directory as trial Jun 08, 2015 N/A N/A + :pypi:`pytest-trio` Pytest plugin for trio Oct 16, 2020 N/A N/A + :pypi:`pytest-trytond` Pytest plugin for the Tryton server framework Feb 02, 2022 3 - Alpha pytest (>=5) + :pypi:`pytest-tspwplib` A simple plugin to use with tspwplib Jan 08, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-tst` Customize pytest options, output and exit code to make it compatible with tst Apr 27, 2022 N/A pytest (>=5.0.0) + :pypi:`pytest-tstcls` Test Class Base Mar 23, 2020 5 - Production/Stable N/A + :pypi:`pytest-tui` Text User Interface (TUI) for Pytest, with optional auto-launch and HTML export Sep 07, 2022 4 - Beta pytest (>=6.2.5) + :pypi:`pytest-twilio-conversations-client-mock` Aug 02, 2022 N/A N/A + :pypi:`pytest-twisted` A twisted plugin for pytest. Aug 30, 2021 5 - Production/Stable pytest (>=2.3) + :pypi:`pytest-typechecker` Run type checkers on specified test files Feb 04, 2022 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-typhoon-config` A Typhoon HIL plugin that facilitates test parameter configuration at runtime Apr 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-typhoon-xray` Typhoon HIL plugin for pytest Mar 07, 2022 4 - Beta N/A + :pypi:`pytest-tytest` Typhoon HIL plugin for pytest May 25, 2020 4 - Beta pytest (>=5.4.2) + :pypi:`pytest-ubersmith` Easily mock calls to ubersmith at the \`requests\` level. Apr 13, 2015 N/A N/A + :pypi:`pytest-ui` Text User Interface for running python tests Jul 05, 2021 4 - Beta pytest + :pypi:`pytest-unflakable` Unflakable plugin for PyTest Jun 14, 2022 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-unhandled-exception-exit-code` Plugin for py.test set a different exit code on uncaught exceptions Jun 22, 2020 5 - Production/Stable pytest (>=2.3) + :pypi:`pytest-unittest-filter` A pytest plugin for filtering unittest-based test classes Jan 12, 2019 4 - Beta pytest (>=3.1.0) + :pypi:`pytest-unmarked` Run only unmarked tests Aug 27, 2019 5 - Production/Stable N/A + :pypi:`pytest-unordered` Test equality of unordered collections in pytest Jul 08, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-unstable` Set a test as unstable to return 0 even if it failed Jun 10, 2022 4 - Beta N/A + :pypi:`pytest-upload-report` pytest-upload-report is a plugin for pytest that upload your test report for test results. Jun 18, 2021 5 - Production/Stable N/A + :pypi:`pytest-utils` Some helpers for pytest. Dec 04, 2021 4 - Beta pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-vagrant` A py.test plugin providing access to vagrant. Sep 07, 2021 5 - Production/Stable pytest + :pypi:`pytest-valgrind` May 19, 2021 N/A N/A + :pypi:`pytest-variables` pytest plugin for providing variables to tests/fixtures Mar 27, 2022 5 - Production/Stable pytest (>=3.0.0,<8.0.0) + :pypi:`pytest-variant` Variant support for Pytest Jun 06, 2022 N/A N/A + :pypi:`pytest-vcr` Plugin for managing VCR.py cassettes Apr 26, 2019 5 - Production/Stable pytest (>=3.6.0) + :pypi:`pytest-vcr-delete-on-fail` A pytest plugin that automates vcrpy cassettes deletion on test failure. Jun 20, 2022 5 - Production/Stable pytest (>=6.2.2) + :pypi:`pytest-vcrpandas` Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest + :pypi:`pytest-venv` py.test fixture for creating a virtual environment Aug 04, 2020 4 - Beta pytest + :pypi:`pytest-ver` Pytest module with Verification Report Aug 21, 2022 2 - Pre-Alpha N/A + :pypi:`pytest-verbose-parametrize` More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-vimqf` A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window. Feb 08, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) + :pypi:`pytest-virtualenv` Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-vnc` VNC client for Pytest Jun 03, 2022 N/A pytest + :pypi:`pytest-voluptuous` Pytest plugin for asserting data against voluptuous schema. Jun 09, 2020 N/A pytest + :pypi:`pytest-vscodedebug` A pytest plugin to easily enable debugging tests within Visual Studio Code Dec 04, 2020 4 - Beta N/A + :pypi:`pytest-vts` pytest plugin for automatic recording of http stubbed tests Jun 05, 2019 N/A pytest (>=2.3) + :pypi:`pytest-vw` pytest-vw makes your failing test cases succeed under CI tools scrutiny Oct 07, 2015 4 - Beta N/A + :pypi:`pytest-vyper` Plugin for the vyper smart contract language. May 28, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-wa-e2e-plugin` Pytest plugin for testing whatsapp bots with end to end tests Feb 18, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-wake` Aug 30, 2022 N/A pytest + :pypi:`pytest-watch` Local continuous test runner with pytest and watchdog. May 20, 2018 N/A N/A + :pypi:`pytest-watcher` Continiously runs pytest on changes in \*.py files Dec 30, 2021 3 - Alpha N/A + :pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A + :pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-wetest` Welian API Automation test framework pytest plugin Nov 10, 2018 4 - Beta N/A + :pypi:`pytest-whirlwind` Testing Tornado. Jun 12, 2020 N/A N/A + :pypi:`pytest-wholenodeid` pytest addon for displaying the whole node id for failures Aug 26, 2015 4 - Beta pytest (>=2.0) + :pypi:`pytest-win32consoletitle` Pytest progress in console title (Win32 only) Aug 08, 2021 N/A N/A + :pypi:`pytest-winnotify` Windows tray notifications for py.test results. Apr 22, 2016 N/A N/A + :pypi:`pytest-wiremock` A pytest plugin for programmatically using wiremock in integration tests Mar 27, 2022 N/A pytest (>=7.1.1,<8.0.0) + :pypi:`pytest-with-docker` pytest with docker helpers. Nov 09, 2021 N/A pytest + :pypi:`pytest-workflow` A pytest plugin for configuring workflow/pipeline tests using YAML files Dec 03, 2021 5 - Production/Stable pytest (>=5.4.0) + :pypi:`pytest-xdist` pytest xdist plugin for distributed testing and loop-on-failing modes Dec 10, 2021 5 - Production/Stable pytest (>=6.2.0) + :pypi:`pytest-xdist-debug-for-graingert` pytest xdist plugin for distributed testing and loop-on-failing modes Jul 24, 2019 5 - Production/Stable pytest (>=4.4.0) + :pypi:`pytest-xdist-forked` forked from pytest-xdist Feb 10, 2020 5 - Production/Stable pytest (>=4.4.0) + :pypi:`pytest-xdist-tracker` pytest plugin helps to reproduce failures for particular xdist node Nov 18, 2021 3 - Alpha pytest (>=3.5.1) + :pypi:`pytest-xfaillist` Maintain a xfaillist in an additional file to avoid merge-conflicts. Sep 17, 2021 N/A pytest (>=6.2.2,<7.0.0) + :pypi:`pytest-xfiles` Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A + :pypi:`pytest-xlog` Extended logging for test and decorators May 31, 2020 4 - Beta N/A + :pypi:`pytest-xlsx` pytest plugin for generating test cases by xlsx(excel) Aug 04, 2022 N/A N/A + :pypi:`pytest-xpara` An extended parametrizing plugin of pytest. Oct 30, 2017 3 - Alpha pytest + :pypi:`pytest-xprocess` A pytest plugin for managing processes across test runs. Aug 29, 2022 4 - Beta pytest (>=2.8) + :pypi:`pytest-xray` May 30, 2019 3 - Alpha N/A + :pypi:`pytest-xrayjira` Mar 17, 2020 3 - Alpha pytest (==4.3.1) + :pypi:`pytest-xray-server` May 03, 2022 3 - Alpha pytest (>=5.3.1) + :pypi:`pytest-xreport` May 17, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-xvfb` A pytest plugin to run Xvfb for tests. Jun 09, 2020 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-yaml` This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest + :pypi:`pytest-yamltree` Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-yamlwsgi` Run tests against wsgi apps defined in yaml May 11, 2010 N/A N/A + :pypi:`pytest-yapf` Run yapf Jul 06, 2017 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-yapf3` Validate your Python file format with yapf Aug 03, 2020 5 - Production/Stable pytest (>=5.4) + :pypi:`pytest-yield` PyTest plugin to run tests concurrently, each \`yield\` switch context to other one Jan 23, 2019 N/A N/A + :pypi:`pytest-yls` Pytest plugin to test the YLS as a whole. Aug 08, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-yuk` Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. Mar 26, 2021 N/A N/A + :pypi:`pytest-zafira` A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1) + :pypi:`pytest-zap` OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A + :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Jun 02, 2022 5 - Production/Stable pytest (>=4.5.0) + :pypi:`pytest-zigzag` Extend py.test for RPC OpenStack testing. Feb 27, 2019 4 - Beta pytest (~=3.6) + :pypi:`pytest-zulip` Pytest report plugin for Zulip May 07, 2022 5 - Production/Stable pytest + =============================================== ============================================================================================================================================================================ ============== ===================== ================================================ .. only:: latex + :pypi:`pytest-abstracts` + *last release*: May 25, 2022, + *status*: N/A, + *requires*: N/A + + A contextmanager pytest fixture for handling multiple mock abstracts + :pypi:`pytest-accept` - *last release*: Nov 22, 2021, + *last release*: Jan 07, 2022, *status*: N/A, - *requires*: pytest (>=6,<7) + *requires*: pytest (>=6,<8) A pytest-plugin for updating doctest outputs :pypi:`pytest-adaptavist` - *last release*: Nov 30, 2021, + *last release*: Jun 07, 2022, *status*: N/A, *requires*: pytest (>=5.4.0) @@ -1050,11 +1212,11 @@ This list contains 963 plugins. pytest fixtures for writing aiofiles tests with pyfakefs :pypi:`pytest-aiohttp` - *last release*: Dec 05, 2017, - *status*: N/A, - *requires*: pytest + *last release*: Feb 12, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.1.0) - pytest plugin for aiohttp support + Pytest plugin for aiohttp support :pypi:`pytest-aiohttp-client` *last release*: Nov 01, 2020, @@ -1063,6 +1225,13 @@ This list contains 963 plugins. Pytest \`client\` fixture for the Aiohttp + :pypi:`pytest-aiomoto` + *last release*: Jul 10, 2022, + *status*: N/A, + *requires*: pytest (>=7.0,<8.0) + + pytest-aiomoto + :pypi:`pytest-aioresponses` *last release*: Jul 29, 2021, *status*: 4 - Beta, @@ -1092,7 +1261,7 @@ This list contains 963 plugins. :pypi:`pytest-alembic` - *last release*: Dec 02, 2021, + *last release*: Aug 03, 2022, *status*: N/A, *requires*: pytest (>=1.0) @@ -1119,6 +1288,13 @@ This list contains 963 plugins. Plugin for py.test to generate allure xml reports + :pypi:`pytest-allure-collection` + *last release*: Sep 08, 2022, + *status*: N/A, + *requires*: pytest + + pytest plugin to collect allure markers without running any tests + :pypi:`pytest-allure-dsl` *last release*: Oct 25, 2020, *status*: 4 - Beta, @@ -1134,8 +1310,8 @@ This list contains 963 plugins. The pytest plugin aimed to display test coverage of the specs(requirements) in Allure :pypi:`pytest-alphamoon` - *last release*: Oct 21, 2021, - *status*: 4 - Beta, + *last release*: Dec 30, 2021, + *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.0) Static code checks used at Alphamoon @@ -1148,16 +1324,16 @@ This list contains 963 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. :pypi:`pytest-anki` - *last release*: Oct 14, 2021, + *last release*: Jul 31, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) A pytest plugin for testing Anki add-ons :pypi:`pytest-annotate` - *last release*: Nov 29, 2021, + *last release*: Jun 07, 2022, *status*: 3 - Alpha, - *requires*: pytest (<7.0.0,>=3.2.0) + *requires*: pytest (<8.0.0,>=3.2.0) pytest-annotate: Generate PyAnnotate annotations from your pytest tests. @@ -1182,8 +1358,15 @@ This list contains 963 plugins. Pytest fixture which runs given ansible playbook file. + :pypi:`pytest-ansible-units` + *last release*: Apr 14, 2022, + *status*: N/A, + *requires*: N/A + + A pytest plugin for running unit tests within an ansible collection + :pypi:`pytest-antilru` - *last release*: Apr 11, 2019, + *last release*: Jul 05, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -1210,12 +1393,26 @@ This list contains 963 plugins. Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures + :pypi:`pytest-aoreporter` + *last release*: Jun 27, 2022, + *status*: N/A, + *requires*: N/A + + pytest report + :pypi:`pytest-api` - *last release*: May 04, 2021, + *last release*: May 12, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.1,<8.0.0) + + An ASGI middleware to populate OpenAPI Specification examples from pytest functions + + :pypi:`pytest-api-soup` + *last release*: Aug 27, 2022, *status*: N/A, *requires*: N/A - PyTest-API Python Web Framework built for testing purposes. + Validate multiple endpoints with unit testing using a single source of truth. :pypi:`pytest-apistellar` *last release*: Jun 18, 2019, @@ -1239,9 +1436,9 @@ This list contains 963 plugins. Pytest plugin for appium :pypi:`pytest-approvaltests` - *last release*: Feb 07, 2021, + *last release*: May 08, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest (>=7.0.1) A plugin to use approvaltests with pytest @@ -1253,9 +1450,9 @@ This list contains 963 plugins. pyest results colection plugin :pypi:`pytest-arraydiff` - *last release*: Dec 06, 2018, + *last release*: Jan 13, 2022, *status*: 4 - Beta, - *requires*: pytest + *requires*: pytest (>=4.6) pytest plugin to help with comparing array output from tests @@ -1273,6 +1470,20 @@ This list contains 963 plugins. test Answer Set Programming programs + :pypi:`pytest-assertcount` + *last release*: May 22, 2022, + *status*: N/A, + *requires*: N/A + + Plugin to count actual number of asserts in pytest + + :pypi:`pytest-assertions` + *last release*: Apr 27, 2022, + *status*: N/A, + *requires*: N/A + + Pytest Assertions + :pypi:`pytest-assertutil` *last release*: May 10, 2019, *status*: N/A, @@ -1281,7 +1492,7 @@ This list contains 963 plugins. pytest-assertutil :pypi:`pytest-assert-utils` - *last release*: Sep 21, 2021, + *last release*: Apr 14, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -1294,6 +1505,13 @@ This list contains 963 plugins. A pytest plugin that allows multiple failures per test + :pypi:`pytest-assurka` + *last release*: Aug 04, 2022, + *status*: N/A, + *requires*: N/A + + A pytest plugin for Assurka Studio + :pypi:`pytest-ast-back-to-python` *last release*: Sep 29, 2019, *status*: 4 - Beta, @@ -1301,17 +1519,24 @@ This list contains 963 plugins. A plugin for pytest devs to view how assertion rewriting recodes the AST + :pypi:`pytest-asteroid` + *last release*: Aug 15, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5,<8.0.0) + + PyTest plugin for docker-based testing on database images + :pypi:`pytest-astropy` - *last release*: Sep 21, 2021, + *last release*: Apr 12, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=4.6) Meta-package containing dependencies for testing :pypi:`pytest-astropy-header` - *last release*: Dec 18, 2019, + *last release*: Sep 06, 2022, *status*: 3 - Alpha, - *requires*: pytest (>=2.8) + *requires*: pytest (>=4.6) pytest plugin to add diagnostic information to the header of the test output @@ -1323,15 +1548,15 @@ This list contains 963 plugins. :pypi:`pytest-asyncio` - *last release*: Oct 15, 2021, + *last release*: Jul 15, 2022, *status*: 4 - Beta, - *requires*: pytest (>=5.4.0) + *requires*: pytest (>=6.1.0) - Pytest support for asyncio. + Pytest support for asyncio :pypi:`pytest-asyncio-cooperative` - *last release*: Oct 12, 2021, - *status*: 4 - Beta, + *last release*: Jul 11, 2022, + *status*: N/A, *requires*: N/A Run all your asynchronous tests cooperatively. @@ -1378,6 +1603,13 @@ This list contains 963 plugins. Austin plugin for pytest + :pypi:`pytest-autocap` + *last release*: May 15, 2022, + *status*: N/A, + *requires*: pytest (<7.2,>=7.1.2) + + automatically capture test & fixture stdout/stderr to files + :pypi:`pytest-autochecklog` *last release*: Apr 25, 2015, *status*: 4 - Beta, @@ -1386,14 +1618,14 @@ This list contains 963 plugins. automatically check condition and log all the checks :pypi:`pytest-automation` - *last release*: Oct 01, 2021, + *last release*: May 20, 2022, *status*: N/A, - *requires*: pytest + *requires*: pytest (>=7.0.0) pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. :pypi:`pytest-automock` - *last release*: Apr 22, 2020, + *last release*: Aug 04, 2022, *status*: N/A, *requires*: pytest ; extra == 'dev' @@ -1441,11 +1673,18 @@ This list contains 963 plugins. pytest plugin for axe-selenium-python - :pypi:`pytest-azurepipelines` - *last release*: Jul 23, 2020, + :pypi:`pytest-azure-devops` + *last release*: Jun 20, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) + Simplifies using azure devops parallel strategy (https://docs.microsoft.com/en-us/azure/devops/pipelines/test/parallel-testing-any-test-runner) with pytest. + + :pypi:`pytest-azurepipelines` + *last release*: Mar 16, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=5.0.0) + Formatting PyTest output for Azure Pipelines UI :pypi:`pytest-bandit` @@ -1456,16 +1695,30 @@ This list contains 963 plugins. A bandit plugin for pytest :pypi:`pytest-base-url` - *last release*: Jun 19, 2020, + *last release*: Mar 27, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.7.3) + *requires*: pytest (>=3.0.0,<8.0.0) pytest plugin for URL based testing :pypi:`pytest-bdd` - *last release*: Oct 25, 2021, + *last release*: Jul 07, 2022, *status*: 6 - Mature, - *requires*: pytest (>=4.3) + *requires*: pytest (>=5.0) + + BDD for pytest + + :pypi:`pytest-bdd-html` + *last release*: Jul 25, 2022, + *status*: 3 - Alpha, + *requires*: pytest (!=6.0.0,>=5.0) + + pytest plugin to display BDD info in HTML test report + + :pypi:`pytest-bdd-ng` + *last release*: Jul 24, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=5.0) BDD for pytest @@ -1519,14 +1772,14 @@ This list contains 963 plugins. A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. :pypi:`pytest-bg-process` - *last release*: Aug 17, 2021, + *last release*: Jan 24, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) Pytest plugin to initialize background process :pypi:`pytest-bigchaindb` - *last release*: Aug 17, 2021, + *last release*: Jan 24, 2022, *status*: 4 - Beta, *requires*: N/A @@ -1561,9 +1814,9 @@ This list contains 963 plugins. A pytest plugin helps developers to debug by providing useful commits history. :pypi:`pytest-blender` - *last release*: Oct 29, 2021, + *last release*: Jun 16, 2022, *status*: N/A, - *requires*: pytest (==6.2.5) ; extra == 'dev' + *requires*: pytest ; extra == 'dev' Blender Pytest plugin. @@ -1575,7 +1828,7 @@ This list contains 963 plugins. Pytest plugin to emit notifications via the Blink(1) RGB LED :pypi:`pytest-blockage` - *last release*: Feb 13, 2019, + *last release*: Dec 21, 2021, *status*: N/A, *requires*: pytest @@ -1588,6 +1841,13 @@ This list contains 963 plugins. pytest plugin to mark a test as blocker and skip all other tests + :pypi:`pytest-blue` + *last release*: Sep 05, 2022, + *status*: N/A, + *requires*: N/A + + A pytest plugin that adds a \`blue\` fixture for printing stuff in blue. + :pypi:`pytest-board` *last release*: Jan 20, 2019, *status*: N/A, @@ -1595,6 +1855,13 @@ This list contains 963 plugins. Local continuous test runner with pytest and watchdog. + :pypi:`pytest-bootstrap` + *last release*: Mar 04, 2022, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-bpdb` *last release*: Jan 19, 2015, *status*: 2 - Pre-Alpha, @@ -1603,7 +1870,7 @@ This list contains 963 plugins. A py.test plug-in to enable drop to bpdb debugger on test failure. :pypi:`pytest-bravado` - *last release*: Jul 19, 2021, + *last release*: Feb 15, 2022, *status*: N/A, *requires*: N/A @@ -1652,14 +1919,14 @@ This list contains 963 plugins. \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. :pypi:`pytest-bug` - *last release*: Jun 02, 2020, + *last release*: Apr 13, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.6.0) + *requires*: pytest (>=6.2.0) Pytest plugin for marking tests as a bug :pypi:`pytest-bugtong-tag` - *last release*: Apr 23, 2021, + *last release*: Jan 16, 2022, *status*: N/A, *requires*: N/A @@ -1708,9 +1975,9 @@ This list contains 963 plugins. pytest plugin with mechanisms for caching across test runs :pypi:`pytest-cache-assert` - *last release*: Nov 03, 2021, + *last release*: May 02, 2022, *status*: 4 - Beta, - *requires*: pytest (>=5) + *requires*: pytest (>=5.0.0) Cache assertion data to simplify regression testing of complex serializable data @@ -1721,6 +1988,13 @@ This list contains 963 plugins. Pytest plugin to only run tests affected by changes + :pypi:`pytest-cairo` + *last release*: Apr 17, 2022, + *status*: N/A, + *requires*: pytest + + Pytest support for cairo-lang and starknet + :pypi:`pytest-camel-collect` *last release*: Aug 02, 2020, *status*: N/A, @@ -1749,15 +2023,15 @@ This list contains 963 plugins. pytest plugin to capture all deprecatedwarnings and put them in one file - :pypi:`pytest-capturelogs` - *last release*: Sep 11, 2021, - *status*: 3 - Alpha, - *requires*: N/A + :pypi:`pytest-capture-warnings` + *last release*: May 03, 2022, + *status*: N/A, + *requires*: pytest - A sample Python project + pytest plugin to capture all warnings and put them in one file of your choice :pypi:`pytest-cases` - *last release*: Nov 08, 2021, + *last release*: May 20, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -1805,6 +2079,13 @@ This list contains 963 plugins. A set of py.test fixtures for AWS Chalice + :pypi:`pytest-change-demo` + *last release*: Mar 02, 2022, + *status*: N/A, + *requires*: pytest + + turn . into √,turn F into x + :pypi:`pytest-change-report` *last release*: Sep 14, 2020, *status*: N/A, @@ -1812,6 +2093,13 @@ This list contains 963 plugins. turn . into √,turn F into x + :pypi:`pytest-change-xds` + *last release*: Apr 16, 2022, + *status*: N/A, + *requires*: pytest + + turn . into √,turn F into x + :pypi:`pytest-chdir` *last release*: Jan 28, 2020, *status*: N/A, @@ -1819,6 +2107,13 @@ This list contains 963 plugins. A pytest fixture for changing current working directory + :pypi:`pytest-check` + *last release*: Aug 25, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + A pytest plugin that allows multiple failures per test. + :pypi:`pytest-checkdocs` *last release*: Jul 31, 2021, *status*: 5 - Production/Stable, @@ -1833,10 +2128,24 @@ This list contains 963 plugins. plugin to check if there are ipdb debugs left + :pypi:`pytest-check-library` + *last release*: Jul 17, 2022, + *status*: N/A, + *requires*: N/A + + check your missing library + + :pypi:`pytest-check-libs` + *last release*: Jul 17, 2022, + *status*: N/A, + *requires*: N/A + + check your missing library + :pypi:`pytest-check-links` *last release*: Jul 29, 2020, *status*: N/A, - *requires*: pytest (>=4.6) + *requires*: pytest (>=7.0) Check links in files @@ -1847,6 +2156,13 @@ This list contains 963 plugins. pytest plugin to test Check_MK checks + :pypi:`pytest-chunks` + *last release*: Jul 05, 2022, + *status*: N/A, + *requires*: pytest (>=6.0.0) + + Run only a chunk of your test suite + :pypi:`pytest-circleci` *last release*: May 03, 2019, *status*: N/A, @@ -1861,6 +2177,13 @@ This list contains 963 plugins. Parallelize pytest across CircleCI workers. + :pypi:`pytest-circleci-parallelized-rjp` + *last release*: Jun 21, 2022, + *status*: N/A, + *requires*: pytest + + Parallelize pytest across CircleCI workers. + :pypi:`pytest-ckan` *last release*: Apr 28, 2020, *status*: 4 - Beta, @@ -1883,14 +2206,21 @@ This list contains 963 plugins. Easy quality control for CLDF datasets using pytest :pypi:`pytest-click` - *last release*: Aug 29, 2020, + *last release*: Feb 11, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) - Py.test plugin for Click + Pytest plugin for Click + + :pypi:`pytest-cli-fixtures` + *last release*: Jul 28, 2022, + *status*: N/A, + *requires*: pytest (~=7.0) + + Automatically register fixtures for custom CLI arguments :pypi:`pytest-clld` - *last release*: Nov 29, 2021, + *last release*: Jul 06, 2022, *status*: N/A, *requires*: pytest (>=3.6) @@ -1910,6 +2240,13 @@ This list contains 963 plugins. pytest plugin for testing cloudflare workers + :pypi:`pytest-cloudist` + *last release*: Sep 02, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.2,<8.0.0) + + Distribute tests to cloud machines without fuss + :pypi:`pytest-cobra` *last release*: Jun 29, 2019, *status*: 3 - Alpha, @@ -1917,12 +2254,12 @@ This list contains 963 plugins. PyTest plugin for testing Smart Contracts for Ethereum blockchain. - :pypi:`pytest-codeblocks` - *last release*: Oct 13, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=6) + :pypi:`pytest-codecarbon` + *last release*: Jun 15, 2022, + *status*: N/A, + *requires*: pytest - Test code blocks in your READMEs + Pytest plugin for measuring carbon emissions :pypi:`pytest-codecheckers` *last release*: Feb 13, 2010, @@ -1932,7 +2269,7 @@ This list contains 963 plugins. pytest plugin to add source code sanity checks (pep8 and friends) :pypi:`pytest-codecov` - *last release*: Oct 27, 2021, + *last release*: Apr 12, 2022, *status*: 4 - Beta, *requires*: pytest (>=4.6.0) @@ -1945,6 +2282,13 @@ This list contains 963 plugins. Automatically create pytest test signatures + :pypi:`pytest-codeowners` + *last release*: Mar 30, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.0.0) + + Pytest plugin for selecting tests by GitHub CODEOWNERS. + :pypi:`pytest-codestyle` *last release*: Mar 23, 2020, *status*: 3 - Alpha, @@ -1966,6 +2310,13 @@ This list contains 963 plugins. Formatter for pytest collect output + :pypi:`pytest-collector` + *last release*: Aug 02, 2022, + *status*: N/A, + *requires*: pytest (>=7.0,<8.0) + + Python package for collecting pytest. + :pypi:`pytest-colordots` *last release*: Oct 06, 2017, *status*: 5 - Production/Stable, @@ -1981,9 +2332,9 @@ This list contains 963 plugins. An interactive GUI test runner for PyTest :pypi:`pytest-common-subject` - *last release*: Nov 12, 2020, + *last release*: May 15, 2022, *status*: N/A, - *requires*: pytest (>=3.6,<7) + *requires*: pytest (>=3.6,<8) pytest framework for testing different aspects of a common method @@ -2002,14 +2353,14 @@ This list contains 963 plugins. Base configurations and utilities for developing your Python project test suite with pytest. :pypi:`pytest-confluence-report` - *last release*: Nov 06, 2020, + *last release*: Apr 17, 2022, *status*: N/A, *requires*: N/A Package stands for pytest plugin to upload results into Confluence page. :pypi:`pytest-console-scripts` - *last release*: Sep 28, 2021, + *last release*: Mar 18, 2022, *status*: 4 - Beta, *requires*: N/A @@ -2023,7 +2374,7 @@ This list contains 963 plugins. pytest plugin with fixtures for testing consul aware apps :pypi:`pytest-container` - *last release*: Nov 19, 2021, + *last release*: Feb 01, 2022, *status*: 3 - Alpha, *requires*: pytest (>=3.10) @@ -2100,12 +2451,26 @@ This list contains 963 plugins. Pytest plugin for excluding tests based on coverage data :pypi:`pytest-cpp` - *last release*: Dec 03, 2021, + *last release*: Aug 22, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (!=5.4.0,!=5.4.1) + *requires*: pytest (>=7.0) Use pytest's runner to discover and execute C++ tests + :pypi:`pytest-cppython` + *last release*: Sep 10, 2022, + *status*: N/A, + *requires*: N/A + + A pytest plugin that imports CPPython testing types + + :pypi:`pytest-cqase` + *last release*: Aug 22, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + Custom qase pytest plugin + :pypi:`pytest-cram` *last release*: Aug 08, 2020, *status*: N/A, @@ -2141,6 +2506,13 @@ This list contains 963 plugins. CSV output for pytest. + :pypi:`pytest-csv-params` + *last release*: Aug 28, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.1.2,<8.0.0) + + Pytest plugin for Test Case Parametrization with CSV files + :pypi:`pytest-curio` *last release*: Oct 07, 2020, *status*: N/A, @@ -2191,12 +2563,19 @@ This list contains 963 plugins. Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report :pypi:`pytest-cython` - *last release*: Jan 26, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=2.7.3) + *last release*: Mar 26, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=4.6.0) A plugin for testing Cython extension modules + :pypi:`pytest-cython-collect` + *last release*: Jun 17, 2022, + *status*: N/A, + *requires*: pytest + + + :pypi:`pytest-darker` *last release*: Aug 16, 2020, *status*: N/A, @@ -2233,11 +2612,11 @@ This list contains 963 plugins. pytest plugin for test data directories and files :pypi:`pytest-datadir-mgr` - *last release*: Aug 16, 2021, + *last release*: Aug 16, 2022, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: pytest (>=7.1) - Manager for test data providing downloads, caching of generated files, and a context for temp directories. + Manager for test data: downloads, artifact caching, and a tmpdir context. :pypi:`pytest-datadir-ng` *last release*: Dec 25, 2019, @@ -2246,6 +2625,13 @@ This list contains 963 plugins. Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. + :pypi:`pytest-data-extractor` + *last release*: Jul 19, 2022, + *status*: N/A, + *requires*: pytest (>=7.0.1) + + A pytest plugin to extract relevant metadata about tests into an external file (currently only json support) + :pypi:`pytest-data-file` *last release*: Dec 04, 2019, *status*: N/A, @@ -2254,7 +2640,7 @@ This list contains 963 plugins. Fixture "data" and "case_data" for test from yaml file :pypi:`pytest-datafiles` - *last release*: Oct 07, 2018, + *last release*: May 01, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.6) @@ -2288,6 +2674,20 @@ This list contains 963 plugins. A py.test plugin recording and comparing test output. + :pypi:`pytest-dataset` + *last release*: Sep 05, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + Plugin for loading different datasets for pytest by prefix from json or yaml files + + :pypi:`pytest-data-suites` + *last release*: Jul 24, 2022, + *status*: N/A, + *requires*: pytest (>=6.0,<8.0) + + Class-based pytest parametrization + :pypi:`pytest-datatest` *last release*: Oct 15, 2020, *status*: 4 - Beta, @@ -2323,6 +2723,20 @@ This list contains 963 plugins. A pytest plugin for testing dbt adapter plugins + :pypi:`pytest-dbt-conventions` + *last release*: Mar 02, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5,<7.0.0) + + A pytest plugin for linting a dbt project's conventions + + :pypi:`pytest-dbt-core` + *last release*: Jul 22, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5) ; extra == 'test' + + Pytest extension for dbt. + :pypi:`pytest-dbus-notification` *last release*: Mar 05, 2014, *status*: 5 - Production/Stable, @@ -2400,6 +2814,13 @@ This list contains 963 plugins. DevPI server fixture for py.test + :pypi:`pytest-dhos` + *last release*: Sep 07, 2022, + *status*: N/A, + *requires*: N/A + + Common fixtures for pytest in DHOS services and libraries + :pypi:`pytest-diamond` *last release*: Aug 31, 2015, *status*: 4 - Beta, @@ -2428,6 +2849,13 @@ This list contains 963 plugins. A simple plugin to use with pytest + :pypi:`pytest-diff-selector` + *last release*: Feb 24, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.2) ; extra == 'all' + + Get tests affected by code changes (using git) + :pypi:`pytest-disable` *last release*: Sep 10, 2015, *status*: 4 - Beta, @@ -2443,14 +2871,14 @@ This list contains 963 plugins. Disable plugins per test :pypi:`pytest-discord` - *last release*: Mar 20, 2021, - *status*: 3 - Alpha, - *requires*: pytest (!=6.0.0,<7,>=3.3.2) + *last release*: Mar 27, 2022, + *status*: 4 - Beta, + *requires*: pytest (!=6.0.0,<8,>=3.3.2) A pytest plugin to notify test results to a Discord channel. :pypi:`pytest-django` - *last release*: Dec 02, 2021, + *last release*: Dec 07, 2021, *status*: 5 - Production/Stable, *requires*: pytest (>=5.4.0) @@ -2498,6 +2926,13 @@ This list contains 963 plugins. Factories for your Django models that can be used as Pytest fixtures. + :pypi:`pytest-django-filefield` + *last release*: May 09, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest >= 5.2 + + Replaces FileField.storage with something you can patch globally. + :pypi:`pytest-django-gcir` *last release*: Mar 06, 2018, *status*: 5 - Production/Stable, @@ -2513,7 +2948,7 @@ This list contains 963 plugins. Cleanup your Haystack indexes between tests :pypi:`pytest-django-ifactory` - *last release*: Jan 13, 2021, + *last release*: Feb 09, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -2527,7 +2962,7 @@ This list contains 963 plugins. The bare minimum to integrate py.test with Django. :pypi:`pytest-django-liveserver-ssl` - *last release*: Jul 30, 2021, + *last release*: Jan 20, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -2576,8 +3011,8 @@ This list contains 963 plugins. py.test plugin for reporting the number of SQLs executed per django testcase. :pypi:`pytest-django-testing-postgresql` - *last release*: Dec 05, 2019, - *status*: 3 - Alpha, + *last release*: Jan 31, 2022, + *status*: 4 - Beta, *requires*: N/A Use a temporary PostgreSQL database with pytest-django @@ -2589,6 +3024,13 @@ This list contains 963 plugins. A documentation plugin for py.test. + :pypi:`pytest-docfiles` + *last release*: Dec 22, 2021, + *status*: 4 - Beta, + *requires*: pytest (>=3.7.0) + + pytest plugin to test codeblocks in your documentation. + :pypi:`pytest-docgen` *last release*: Apr 17, 2020, *status*: N/A, @@ -2597,12 +3039,19 @@ This list contains 963 plugins. An RST Documentation Generator for pytest-based test suites :pypi:`pytest-docker` - *last release*: Jun 14, 2021, + *last release*: Jul 27, 2022, *status*: N/A, - *requires*: pytest (<7.0,>=4.0) + *requires*: pytest (<8.0,>=4.0) Simple pytest fixtures for Docker and docker-compose based tests + :pypi:`pytest-docker-apache-fixtures` + *last release*: Feb 16, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Pytest fixtures for testing with apache2 (httpd). + :pypi:`pytest-docker-butla` *last release*: Jun 16, 2019, *status*: 3 - Alpha, @@ -2632,19 +3081,26 @@ This list contains 963 plugins. A plugin to use docker databases for pytests :pypi:`pytest-docker-fixtures` - *last release*: Nov 23, 2021, + *last release*: Jul 06, 2022, *status*: 3 - Alpha, *requires*: N/A pytest docker fixtures :pypi:`pytest-docker-git-fixtures` - *last release*: Mar 11, 2021, + *last release*: Feb 09, 2022, *status*: 4 - Beta, *requires*: pytest Pytest fixtures for testing with git scm. + :pypi:`pytest-docker-haproxy-fixtures` + *last release*: Feb 09, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Pytest fixtures for testing with haproxy. + :pypi:`pytest-docker-pexpect` *last release*: Jan 14, 2019, *status*: N/A, @@ -2667,16 +3123,30 @@ This list contains 963 plugins. Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. :pypi:`pytest-docker-registry-fixtures` - *last release*: Mar 04, 2021, + *last release*: Apr 08, 2022, *status*: 4 - Beta, *requires*: pytest Pytest fixtures for testing with docker registries. + :pypi:`pytest-docker-service` + *last release*: Mar 21, 2022, + *status*: 3 - Alpha, + *requires*: pytest + + pytest plugin to start docker container + + :pypi:`pytest-docker-squid-fixtures` + *last release*: Feb 09, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Pytest fixtures for testing with squid. + :pypi:`pytest-docker-tools` - *last release*: Jul 23, 2021, + *last release*: Feb 17, 2022, *status*: 4 - Beta, - *requires*: pytest (>=6.0.1,<7.0.0) + *requires*: pytest (>=6.0.1) Docker integration tests for pytest @@ -2716,19 +3186,12 @@ This list contains 963 plugins. A simple pytest plugin to import names and add them to the doctest namespace. :pypi:`pytest-doctestplus` - *last release*: Nov 16, 2021, + *last release*: Feb 25, 2022, *status*: 3 - Alpha, *requires*: pytest (>=4.6) Pytest plugin with advanced doctest features. - :pypi:`pytest-doctest-ufunc` - *last release*: Aug 02, 2020, - *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) - - A plugin to run doctests in docstrings of Numpy ufuncs - :pypi:`pytest-dolphin` *last release*: Nov 30, 2016, *status*: 4 - Beta, @@ -2751,9 +3214,9 @@ This list contains 963 plugins. A py.test plugin that parses environment files before running tests :pypi:`pytest-drf` - *last release*: Nov 12, 2020, + *last release*: Jul 12, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.6) + *requires*: pytest (>=3.7) A Django REST framework plugin for pytest. @@ -2772,7 +3235,7 @@ This list contains 963 plugins. A Pytest plugin to drop duplicated tests during collection :pypi:`pytest-dummynet` - *last release*: Oct 13, 2021, + *last release*: Dec 15, 2021, *status*: 5 - Production/Stable, *requires*: pytest @@ -2792,6 +3255,13 @@ This list contains 963 plugins. + :pypi:`pytest-durations` + *last release*: Apr 22, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=4.6) + + Pytest plugin reporting fixtures and test functions execution time. + :pypi:`pytest-dynamicrerun` *last release*: Aug 15, 2020, *status*: 4 - Beta, @@ -2841,6 +3311,13 @@ This list contains 963 plugins. Pytest plugin for easy testing against servers + :pypi:`pytest-ebics-sandbox` + *last release*: Aug 15, 2022, + *status*: N/A, + *requires*: N/A + + A pytest plugin for testing against an EBICS sandbox server. Requires docker. + :pypi:`pytest-ec2` *last release*: Oct 22, 2019, *status*: 3 - Alpha, @@ -2856,9 +3333,9 @@ This list contains 963 plugins. pytest plugin with mechanisms for echoing environment variables, package version and generic attributes :pypi:`pytest-elasticsearch` - *last release*: May 12, 2021, + *last release*: Mar 01, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0) + *requires*: pytest (>=6.2.0) Elasticsearch fixtures and fixture factories for Pytest. @@ -2869,6 +3346,13 @@ This list contains 963 plugins. Tool to help automate user interfaces + :pypi:`pytest-eliot` + *last release*: Aug 31, 2022, + *status*: 1 - Planning, + *requires*: pytest (>=5.4.0) + + An eliot plugin for pytest. + :pypi:`pytest-elk-reporter` *last release*: Jan 24, 2021, *status*: 4 - Beta, @@ -2884,54 +3368,61 @@ This list contains 963 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Nov 29, 2021, + *last release*: Aug 23, 2022, *status*: N/A, - *requires*: pytest (>=6.2.0) + *requires*: pytest (>=7.0) pytest embedded plugin + :pypi:`pytest-embedded-arduino` + *last release*: Aug 23, 2022, + *status*: N/A, + *requires*: N/A + + pytest embedded plugin for Arduino projects + :pypi:`pytest-embedded-idf` - *last release*: Nov 29, 2021, + *last release*: Aug 23, 2022, *status*: N/A, *requires*: N/A pytest embedded plugin for esp-idf project :pypi:`pytest-embedded-jtag` - *last release*: Nov 29, 2021, + *last release*: Aug 23, 2022, *status*: N/A, *requires*: N/A pytest embedded plugin for testing with jtag :pypi:`pytest-embedded-qemu` - *last release*: Nov 29, 2021, + *last release*: Aug 23, 2022, *status*: N/A, *requires*: N/A pytest embedded plugin for qemu, not target chip - :pypi:`pytest-embedded-qemu-idf` - *last release*: Jun 29, 2021, - *status*: N/A, - *requires*: N/A - - pytest embedded plugin for esp-idf project by qemu, not target chip - :pypi:`pytest-embedded-serial` - *last release*: Nov 29, 2021, + *last release*: Aug 23, 2022, *status*: N/A, *requires*: N/A pytest embedded plugin for testing serial ports :pypi:`pytest-embedded-serial-esp` - *last release*: Nov 29, 2021, + *last release*: Aug 23, 2022, *status*: N/A, *requires*: N/A pytest embedded plugin for testing espressif boards via serial ports + :pypi:`pytest-embrace` + *last release*: Aug 27, 2022, + *status*: N/A, + *requires*: pytest (>=7.0,<8.0) + + 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. + :pypi:`pytest-emoji` *last release*: Feb 19, 2019, *status*: 4 - Beta, @@ -2940,14 +3431,14 @@ This list contains 963 plugins. A pytest plugin that adds emojis to your test result report :pypi:`pytest-emoji-output` - *last release*: Oct 10, 2021, + *last release*: Apr 12, 2022, *status*: 4 - Beta, - *requires*: pytest (==6.0.1) + *requires*: pytest (==7.0.1) Pytest plugin to represent test output with emoji support :pypi:`pytest-enabler` - *last release*: Nov 08, 2021, + *last release*: Jun 22, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=6) ; extra == 'testing' @@ -3045,7 +3536,7 @@ This list contains 963 plugins. pytest-ethereum: Pytest library for ethereum projects. :pypi:`pytest-eucalyptus` - *last release*: Aug 13, 2019, + *last release*: Jun 28, 2022, *status*: N/A, *requires*: pytest (>=4.2.0) @@ -3059,7 +3550,7 @@ This list contains 963 plugins. Applies eventlet monkey-patch as a pytest plugin. :pypi:`pytest-excel` - *last release*: Oct 06, 2020, + *last release*: Jan 31, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -3086,6 +3577,13 @@ This list contains 963 plugins. pytest plugin for testing executables + :pypi:`pytest-execution-timer` + *last release*: Dec 24, 2021, + *status*: 4 - Beta, + *requires*: N/A + + A timer for the phases of Pytest's execution. + :pypi:`pytest-expect` *last release*: Apr 21, 2016, *status*: 4 - Beta, @@ -3094,7 +3592,7 @@ This list contains 963 plugins. py.test plugin to store test expectations and mark tests based on them :pypi:`pytest-expecter` - *last release*: Jul 08, 2020, + *last release*: Jan 10, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -3107,6 +3605,13 @@ This list contains 963 plugins. This plugin is used to expect multiple assert using pytest framework. + :pypi:`pytest-experiments` + *last release*: Dec 13, 2021, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.5,<7.0.0) + + A pytest plugin to help developers of research-oriented software projects keep track of the results of their numerical experiments. + :pypi:`pytest-explicit` *last release*: Jun 15, 2021, *status*: 5 - Production/Stable, @@ -3115,12 +3620,19 @@ This list contains 963 plugins. A Pytest plugin to ignore certain marked tests by default :pypi:`pytest-exploratory` - *last release*: Aug 03, 2021, + *last release*: Feb 21, 2022, *status*: N/A, - *requires*: pytest (>=5.3) + *requires*: pytest (>=6.2) Interactive console for pytest. + :pypi:`pytest-extensions` + *last release*: Aug 17, 2022, + *status*: 4 - Beta, + *requires*: pytest ; extra == 'testing' + + A collection of helpers for pytest to ease testing + :pypi:`pytest-external-blockers` *last release*: Oct 05, 2021, *status*: N/A, @@ -3150,9 +3662,9 @@ This list contains 963 plugins. Use factories for test setup with py.test :pypi:`pytest-factoryboy` - *last release*: Dec 30, 2020, + *last release*: Jun 14, 2022, *status*: 6 - Mature, - *requires*: pytest (>=4.6) + *requires*: pytest (>=5.0.0) Factory Boy support for pytest. @@ -3164,8 +3676,8 @@ This list contains 963 plugins. Generates pytest fixtures that allow the use of type hinting :pypi:`pytest-factoryboy-state` - *last release*: Dec 11, 2020, - *status*: 4 - Beta, + *last release*: Mar 22, 2022, + *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) Simple factoryboy random state management @@ -3184,6 +3696,13 @@ This list contains 963 plugins. A pytest plugin that helps better distinguishing real test failures from setup flakiness. + :pypi:`pytest-fail-slow` + *last release*: Aug 13, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.0) + + Fail tests that take too long to run + :pypi:`pytest-faker` *last release*: Dec 19, 2016, *status*: 6 - Mature, @@ -3219,6 +3738,13 @@ This list contains 963 plugins. + :pypi:`pytest-fastapi-deps` + *last release*: Jul 20, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + A fixture which allows easy replacement of fastapi dependencies for testing + :pypi:`pytest-fastest` *last release*: Mar 05, 2020, *status*: N/A, @@ -3290,9 +3816,9 @@ This list contains 963 plugins. Pytest plugin for filtering based on sub-packages :pypi:`pytest-find-dependencies` - *last release*: Apr 21, 2021, + *last release*: Apr 09, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest (>=4.3.0) A pytest plugin to find dependencies between tests @@ -3332,12 +3858,19 @@ This list contains 963 plugins. A pytest plugin to add markers based on fixtures used. :pypi:`pytest-fixture-order` - *last release*: Aug 25, 2020, - *status*: N/A, + *last release*: May 16, 2022, + *status*: 5 - Production/Stable, *requires*: pytest (>=3.0) pytest plugin to control fixture evaluation order + :pypi:`pytest-fixture-rtttg` + *last release*: Feb 23, 2022, + *status*: N/A, + *requires*: pytest (>=7.0.1,<8.0.0) + + Warn or fail on fixture name clash + :pypi:`pytest-fixtures` *last release*: May 01, 2019, *status*: 5 - Production/Stable, @@ -3360,19 +3893,26 @@ This list contains 963 plugins. A pytest plugin to assert type annotations at runtime. :pypi:`pytest-flake8` - *last release*: Dec 16, 2020, + *last release*: Mar 18, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5) + *requires*: pytest (>=7.0) pytest plugin to check FLAKE8 requirements :pypi:`pytest-flake8-path` - *last release*: Aug 11, 2021, + *last release*: May 11, 2022, *status*: 5 - Production/Stable, *requires*: pytest A pytest fixture for testing flake8 plugins. + :pypi:`pytest-flake8-v2` + *last release*: Mar 01, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.0) + + pytest plugin to check FLAKE8 requirements + :pypi:`pytest-flakefinder` *last release*: Jul 28, 2020, *status*: 4 - Beta, @@ -3401,8 +3941,15 @@ This list contains 963 plugins. A set of py.test fixtures to test Flask applications. + :pypi:`pytest-flask-ligand` + *last release*: Aug 18, 2022, + *status*: 4 - Beta, + *requires*: pytest (~=7.1) + + Pytest fixtures and helper functions to use for testing flask-ligand microservices. + :pypi:`pytest-flask-sqlalchemy` - *last release*: Apr 04, 2019, + *last release*: Apr 30, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.2.1) @@ -3415,6 +3962,13 @@ This list contains 963 plugins. Run tests in transactions using pytest, Flask, and SQLalchemy. + :pypi:`pytest-fluent` + *last release*: Jul 12, 2022, + *status*: 4 - Beta, + *requires*: pytest + + A pytest plugin in order to provide logs via fluentd + :pypi:`pytest-flyte` *last release*: May 03, 2021, *status*: N/A, @@ -3465,7 +4019,7 @@ This list contains 963 plugins. Check if requirement files are frozen :pypi:`pytest-frozen-uuids` - *last release*: Oct 19, 2021, + *last release*: Apr 17, 2022, *status*: N/A, *requires*: pytest (>=3.0) @@ -3499,6 +4053,20 @@ This list contains 963 plugins. + :pypi:`pytest-fzf` + *last release*: Aug 17, 2022, + *status*: 1 - Planning, + *requires*: pytest (>=7.1.2) + + fzf-based test selector for pytest + + :pypi:`pytest-gather-fixtures` + *last release*: Apr 12, 2022, + *status*: N/A, + *requires*: pytest (>=6.0.0) + + set up asynchronous pytest fixtures concurrently + :pypi:`pytest-gc` *last release*: Feb 01, 2018, *status*: N/A, @@ -3527,6 +4095,13 @@ This list contains 963 plugins. A flexible framework for executing BDD gherkin tests + :pypi:`pytest-gh-log-group` + *last release*: Jan 11, 2022, + *status*: 3 - Alpha, + *requires*: pytest + + pytest plugin for gh actions + :pypi:`pytest-ghostinspector` *last release*: May 17, 2016, *status*: 3 - Alpha, @@ -3535,7 +4110,7 @@ This list contains 963 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Nov 30, 2021, + *last release*: Aug 23, 2022, *status*: N/A, *requires*: N/A @@ -3570,12 +4145,19 @@ This list contains 963 plugins. Plugin for py.test that associates tests with github issues using a marker. :pypi:`pytest-github-actions-annotate-failures` - *last release*: Oct 24, 2021, + *last release*: Jul 02, 2022, *status*: N/A, *requires*: pytest (>=4.0.0) pytest plugin to annotate failed tests with a workflow command for GitHub Actions + :pypi:`pytest-github-report` + *last release*: Jun 03, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Generate a GitHub report using pytest in GitHub Workflows + :pypi:`pytest-gitignore` *last release*: Jul 17, 2015, *status*: 4 - Beta, @@ -3584,7 +4166,7 @@ This list contains 963 plugins. py.test plugin to ignore the same files as git :pypi:`pytest-glamor-allure` - *last release*: Nov 26, 2021, + *last release*: Jul 22, 2022, *status*: 4 - Beta, *requires*: pytest @@ -3598,12 +4180,19 @@ This list contains 963 plugins. Pytest fixtures for testing with gnupg. :pypi:`pytest-golden` - *last release*: Nov 23, 2020, + *last release*: Jul 18, 2022, *status*: N/A, - *requires*: pytest (>=6.1.2,<7.0.0) + *requires*: pytest (>=6.1.2) Plugin for pytest that offloads expected outputs to data files + :pypi:`pytest-google-chat` + *last release*: Mar 27, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Notify google chat channel for test results + :pypi:`pytest-graphql-schema` *last release*: Oct 18, 2019, *status*: N/A, @@ -3640,7 +4229,7 @@ This list contains 963 plugins. Display "🔨 " instead of "." for passed pytest tests. :pypi:`pytest-harvest` - *last release*: Apr 01, 2021, + *last release*: Jun 10, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -3654,9 +4243,9 @@ This list contains 963 plugins. A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. :pypi:`pytest-helm-charts` - *last release*: Oct 26, 2021, + *last release*: Aug 03, 2022, *status*: 4 - Beta, - *requires*: pytest (>=6.1.2,<7.0.0) + *requires*: pytest (>=7.1.2,<8.0.0) A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. @@ -3675,7 +4264,7 @@ This list contains 963 plugins. pytest helpers :pypi:`pytest-helpers-namespace` - *last release*: Apr 29, 2021, + *last release*: Dec 29, 2021, *status*: 5 - Production/Stable, *requires*: pytest (>=6.0.0) @@ -3710,12 +4299,19 @@ This list contains 963 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Nov 20, 2021, + *last release*: Sep 09, 2022, *status*: 3 - Alpha, - *requires*: pytest (==6.2.5) + *requires*: pytest (==7.1.2) Experimental package to automatically extract test plugins for Home Assistant custom components + :pypi:`pytest-honey` + *last release*: Jan 07, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A simple plugin to use with pytest + :pypi:`pytest-honors` *last release*: Mar 06, 2020, *status*: 4 - Beta, @@ -3724,15 +4320,15 @@ This list contains 963 plugins. Report on tests that honor constraints, and guard against regressions :pypi:`pytest-hoverfly` - *last release*: Jul 12, 2021, + *last release*: Mar 28, 2022, *status*: N/A, *requires*: pytest (>=5.0) Simplify working with Hoverfly from pytest :pypi:`pytest-hoverfly-wrapper` - *last release*: Aug 29, 2021, - *status*: 4 - Beta, + *last release*: Feb 22, 2022, + *status*: 5 - Production/Stable, *requires*: N/A Integrates the Hoverfly HTTP proxy into Pytest @@ -3758,6 +4354,20 @@ This list contains 963 plugins. optimized pytest plugin for generating HTML reports + :pypi:`pytest-html-merger` + *last release*: Apr 03, 2022, + *status*: N/A, + *requires*: N/A + + Pytest HTML reports merging utility + + :pypi:`pytest-html-object-storage` + *last release*: Mar 04, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + Pytest report plugin for send HTML report on object-storage + :pypi:`pytest-html-profiling` *last release*: Feb 11, 2020, *status*: 5 - Production/Stable, @@ -3766,12 +4376,19 @@ This list contains 963 plugins. Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. :pypi:`pytest-html-reporter` - *last release*: Apr 25, 2021, + *last release*: Feb 13, 2022, *status*: N/A, *requires*: N/A Generates a static html report based on pytest framework + :pypi:`pytest-html-report-merger` + *last release*: Aug 31, 2022, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-html-thread` *last release*: Dec 29, 2020, *status*: 5 - Production/Stable, @@ -3787,9 +4404,9 @@ This list contains 963 plugins. Fixture "http" for http requests :pypi:`pytest-httpbin` - *last release*: Feb 11, 2019, + *last release*: Mar 16, 2022, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest ; extra == 'test' Easily test your HTTP library against a local copy of httpbin @@ -3808,16 +4425,16 @@ This list contains 963 plugins. A thin wrapper of HTTPretty for pytest :pypi:`pytest-httpserver` - *last release*: Oct 18, 2021, + *last release*: Jul 29, 2022, *status*: 3 - Alpha, - *requires*: pytest ; extra == 'dev' + *requires*: N/A pytest-httpserver is a httpserver for pytest :pypi:`pytest-httpx` - *last release*: Nov 16, 2021, + *last release*: May 24, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (==6.*) + *requires*: pytest (<8.*,>=6.*) Send responses to httpx. @@ -3850,14 +4467,14 @@ This list contains 963 plugins. help hypo module for pytest :pypi:`pytest-ibutsu` - *last release*: Jun 16, 2021, + *last release*: Aug 05, 2022, *status*: 4 - Beta, - *requires*: pytest + *requires*: N/A A plugin to sent pytest results to an Ibutsu server :pypi:`pytest-icdiff` - *last release*: Apr 08, 2020, + *last release*: Aug 09, 2022, *status*: 4 - Beta, *requires*: N/A @@ -3870,8 +4487,15 @@ This list contains 963 plugins. A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api + :pypi:`pytest-idem` + *last release*: Sep 07, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + A pytest plugin to help with testing idem projects + :pypi:`pytest-idempotent` - *last release*: Nov 26, 2021, + *last release*: Jul 25, 2022, *status*: N/A, *requires*: N/A @@ -3885,7 +4509,7 @@ This list contains 963 plugins. ignore failures from flaky tests (pytest plugin) :pypi:`pytest-image-diff` - *last release*: Jul 28, 2021, + *last release*: Jun 08, 2022, *status*: 3 - Alpha, *requires*: pytest @@ -3927,26 +4551,40 @@ This list contains 963 plugins. pytest stack validation prior to testing executing :pypi:`pytest-ini` - *last release*: Sep 30, 2021, + *last release*: Apr 26, 2022, *status*: N/A, *requires*: N/A Reuse pytest.ini to store env variables :pypi:`pytest-inmanta` - *last release*: Aug 17, 2021, + *last release*: Sep 07, 2022, *status*: 5 - Production/Stable, *requires*: N/A A py.test plugin providing fixtures to simplify inmanta modules testing. :pypi:`pytest-inmanta-extensions` - *last release*: May 27, 2021, + *last release*: Aug 10, 2022, *status*: 5 - Production/Stable, *requires*: N/A Inmanta tests package + :pypi:`pytest-inmanta-lsm` + *last release*: Sep 07, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + Common fixtures for inmanta LSM related modules + + :pypi:`pytest-inmanta-yang` + *last release*: Jun 16, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Common fixtures used in inmanta yang related modules + :pypi:`pytest-Inomaly` *last release*: Feb 13, 2018, *status*: 4 - Beta, @@ -3955,9 +4593,9 @@ This list contains 963 plugins. A simple image diff plugin for pytest :pypi:`pytest-insta` - *last release*: Apr 07, 2021, + *last release*: Feb 28, 2022, *status*: N/A, - *requires*: pytest (>=6.0.2,<7.0.0) + *requires*: pytest (>=6.0.2) A practical snapshot testing plugin for pytest @@ -4004,11 +4642,11 @@ This list contains 963 plugins. Pytest plugin for intercepting outgoing connection requests during pytest run. :pypi:`pytest-invenio` - *last release*: May 11, 2021, + *last release*: Aug 09, 2022, *status*: 5 - Production/Stable, *requires*: pytest (<7,>=6) - Pytest fixtures for Invenio. + "Pytest fixtures for Invenio." :pypi:`pytest-involve` *last release*: Feb 02, 2020, @@ -4032,12 +4670,19 @@ This list contains 963 plugins. THIS PROJECT IS ABANDONED :pypi:`pytest-isort` - *last release*: Apr 27, 2021, + *last release*: Feb 08, 2022, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest (>=5.0) py.test plugin to check import ordering using isort + :pypi:`pytest-is-running` + *last release*: Aug 19, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + pytest plugin providing a function to check if pytest is running. + :pypi:`pytest-it` *last release*: Jan 22, 2020, *status*: 4 - Beta, @@ -4052,6 +4697,13 @@ This list contains 963 plugins. Nicer list and iterable assertion messages for pytest + :pypi:`pytest-iters` + *last release*: May 24, 2022, + *status*: N/A, + *requires*: N/A + + A contextmanager pytest fixture for handling multiple mock iters + :pypi:`pytest-jasmine` *last release*: Nov 04, 2017, *status*: 1 - Planning, @@ -4067,15 +4719,15 @@ This list contains 963 plugins. A custom jest-pytest oriented Pytest reporter :pypi:`pytest-jira` - *last release*: Dec 02, 2021, + *last release*: Apr 07, 2022, *status*: 3 - Alpha, *requires*: N/A py.test JIRA integration plugin, using markers :pypi:`pytest-jira-xray` - *last release*: Nov 28, 2021, - *status*: 3 - Alpha, + *last release*: Aug 25, 2022, + *status*: 4 - Beta, *requires*: pytest pytest plugin to integrate tests with JIRA XRAY @@ -4101,6 +4753,13 @@ This list contains 963 plugins. Generate JSON test reports + :pypi:`pytest-json-fixtures` + *last release*: Aug 09, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.0) + + JSON output for the --fixtures flag + :pypi:`pytest-jsonlint` *last release*: Aug 04, 2016, *status*: N/A, @@ -4109,7 +4768,7 @@ This list contains 963 plugins. UNKNOWN :pypi:`pytest-json-report` - *last release*: Sep 24, 2021, + *last release*: Mar 15, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.8.0) @@ -4129,8 +4788,15 @@ This list contains 963 plugins. A plugin to send pytest events to Kafka + :pypi:`pytest-kexi` + *last release*: Apr 29, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + + :pypi:`pytest-kind` - *last release*: Jan 24, 2021, + *last release*: Sep 08, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -4172,9 +4838,9 @@ This list contains 963 plugins. Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks :pypi:`pytest-lambda` - *last release*: Aug 23, 2021, + *last release*: Aug 20, 2022, *status*: 3 - Alpha, - *requires*: pytest (>=3.6,<7) + *requires*: pytest (>=3.6,<8) Define pytest fixtures with lambda functions. @@ -4185,6 +4851,13 @@ This list contains 963 plugins. + :pypi:`pytest-launchable` + *last release*: Jun 14, 2022, + *status*: N/A, + *requires*: pytest (>=4.2.0) + + Launchable Pytest Plugin + :pypi:`pytest-layab` *last release*: Oct 05, 2020, *status*: 5 - Production/Stable, @@ -4206,6 +4879,13 @@ This list contains 963 plugins. python-ldap fixtures for pytest + :pypi:`pytest-leak-finder` + *last release*: Apr 18, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Find the previous test that makes another to fail + :pypi:`pytest-leaks` *last release*: Nov 27, 2019, *status*: 1 - Planning, @@ -4228,7 +4908,7 @@ This list contains 963 plugins. A python-libfaketime plugin for pytest. :pypi:`pytest-libiio` - *last release*: Oct 29, 2021, + *last release*: Jul 11, 2022, *status*: 4 - Beta, *requires*: N/A @@ -4262,6 +4942,13 @@ This list contains 963 plugins. Profile code executed by pytest + :pypi:`pytest-line-profiler-apn` + *last release*: Aug 14, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Profile code executed by pytest + :pypi:`pytest-lisa` *last release*: Jan 21, 2021, *status*: 3 - Alpha, @@ -4298,11 +4985,11 @@ This list contains 963 plugins. A PyTest plugin which provides an FTP fixture for your tests :pypi:`pytest-localserver` - *last release*: Nov 19, 2021, + *last release*: Aug 30, 2022, *status*: 4 - Beta, *requires*: N/A - py.test plugin to test server connections locally. + pytest plugin to test server connections locally. :pypi:`pytest-localstack` *last release*: Aug 22, 2019, @@ -4312,7 +4999,7 @@ This list contains 963 plugins. Pytest plugin for AWS integration tests :pypi:`pytest-lockable` - *last release*: Nov 09, 2021, + *last release*: Jul 20, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -4374,13 +5061,41 @@ This list contains 963 plugins. Package for creating a pytest test run reprot + :pypi:`pytest-loguru` + *last release*: Apr 12, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + Pytest Loguru + + :pypi:`pytest-loop` + *last release*: Jul 22, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=6) + + pytest plugin for looping tests + + :pypi:`pytest-lsp` + *last release*: Jul 18, 2022, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest plugin for end-to-end testing of language servers + :pypi:`pytest-manual-marker` - *last release*: Oct 11, 2021, + *last release*: Aug 04, 2022, *status*: 3 - Alpha, - *requires*: pytest (>=6) + *requires*: N/A pytest marker for marking manual tests + :pypi:`pytest-markdoctest` + *last release*: Jul 22, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6) + + A pytest plugin to doctest your markdown files + :pypi:`pytest-markdown` *last release*: Jan 15, 2021, *status*: 4 - Beta, @@ -4388,6 +5103,13 @@ This list contains 963 plugins. Test your markdown docs with pytest + :pypi:`pytest-markdown-docs` + *last release*: Sep 02, 2022, + *status*: N/A, + *requires*: N/A + + Run markdown code fences through pytest + :pypi:`pytest-marker-bugzilla` *last release*: Jan 09, 2020, *status*: N/A, @@ -4424,9 +5146,9 @@ This list contains 963 plugins. UNKNOWN :pypi:`pytest-matcher` - *last release*: Apr 23, 2020, + *last release*: Dec 10, 2021, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.4) + *requires*: N/A Match test output against patterns stored in files @@ -4451,6 +5173,13 @@ This list contains 963 plugins. Provide tools for generating tests from combinations of fixtures. + :pypi:`pytest-maybe-raises` + *last release*: May 27, 2022, + *status*: N/A, + *requires*: pytest ; extra == 'dev' + + Pytest fixture for optional exception testing. + :pypi:`pytest-mccabe` *last release*: Jul 22, 2020, *status*: 3 - Alpha, @@ -4466,9 +5195,9 @@ This list contains 963 plugins. Plugin for generating Markdown reports for pytest results :pypi:`pytest-md-report` - *last release*: May 04, 2021, + *last release*: Aug 06, 2022, *status*: 4 - Beta, - *requires*: pytest (!=6.0.0,<7,>=3.3.2) + *requires*: pytest (!=6.0.0,<8,>=3.3.2) A pytest plugin to make a test results report with Markdown table format. @@ -4479,6 +5208,13 @@ This list contains 963 plugins. Estimates memory consumption of test functions + :pypi:`pytest-memray` + *last release*: Aug 21, 2022, + *status*: N/A, + *requires*: N/A + + A simple plugin to use with pytest + :pypi:`pytest-menu` *last release*: Oct 04, 2017, *status*: 3 - Alpha, @@ -4493,24 +5229,31 @@ This list contains 963 plugins. pytest plugin to write integration tests for projects using Mercurial Python internals + :pypi:`pytest-mesh` + *last release*: Aug 05, 2022, + *status*: N/A, + *requires*: pytest (==7.1.2) + + pytest_mesh插件 + :pypi:`pytest-message` - *last release*: Nov 04, 2021, + *last release*: Aug 04, 2022, *status*: N/A, *requires*: pytest (>=6.2.5) Pytest plugin for sending report message of marked tests execution :pypi:`pytest-messenger` - *last release*: Dec 16, 2020, + *last release*: Feb 07, 2022, *status*: 5 - Production/Stable, *requires*: N/A Pytest to Slack reporting plugin :pypi:`pytest-metadata` - *last release*: Nov 27, 2020, + *last release*: Jul 15, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.9.0) + *requires*: pytest (>=3.0.0,<8.0.0) pytest plugin for test session metadata @@ -4529,9 +5272,9 @@ This list contains 963 plugins. Mimesis integration with the pytest test runner :pypi:`pytest-minecraft` - *last release*: Sep 26, 2020, + *last release*: Apr 06, 2022, *status*: N/A, - *requires*: pytest (>=6.0.1,<7.0.0) + *requires*: pytest (>=6.0.1) A pytest plugin for running tests against Minecraft releases @@ -4557,7 +5300,7 @@ This list contains 963 plugins. pytest plugin to display test execution output like a mochajs :pypi:`pytest-mock` - *last release*: May 06, 2021, + *last release*: Jul 05, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) @@ -4571,7 +5314,7 @@ This list contains 963 plugins. A mock API server with configurable routes and responses available as a fixture. :pypi:`pytest-mock-generator` - *last release*: Aug 10, 2021, + *last release*: May 16, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -4599,16 +5342,16 @@ This list contains 963 plugins. An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. :pypi:`pytest-mock-resources` - *last release*: Dec 03, 2021, + *last release*: Aug 24, 2022, *status*: N/A, *requires*: pytest (>=1.0) A pytest plugin for easily instantiating reproducible mock resources. :pypi:`pytest-mock-server` - *last release*: Apr 06, 2020, + *last release*: Jan 09, 2022, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=3.5.0) Mock server plugin for pytest @@ -4619,6 +5362,13 @@ This list contains 963 plugins. A set of fixtures to test your requests to HTTP/UDP servers + :pypi:`pytest-modified-env` + *last release*: Jan 29, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Pytest plugin to fail a test if it leaves modified \`os.environ\` afterwards. + :pypi:`pytest-modifyjunit` *last release*: Jan 10, 2019, *status*: N/A, @@ -4634,9 +5384,9 @@ This list contains 963 plugins. pytest plugin to modify fixture scope :pypi:`pytest-molecule` - *last release*: Oct 06, 2021, + *last release*: Mar 29, 2022, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest (>=7.0.0) PyTest Molecule Plugin :: discover and run molecule tests @@ -4655,7 +5405,7 @@ This list contains 963 plugins. pytest plugin for MongoDB fixtures :pypi:`pytest-monitor` - *last release*: Aug 24, 2021, + *last release*: May 18, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -4697,28 +5447,28 @@ This list contains 963 plugins. A test batcher for multiprocessed Pytest runs :pypi:`pytest-mpi` - *last release*: Mar 14, 2021, + *last release*: Jan 08, 2022, *status*: 3 - Alpha, *requires*: pytest pytest plugin to collect information from tests :pypi:`pytest-mpl` - *last release*: Jul 02, 2021, + *last release*: Jul 23, 2022, *status*: 4 - Beta, *requires*: pytest pytest plugin to help with testing figures output from Matplotlib :pypi:`pytest-mproc` - *last release*: Mar 07, 2021, + *last release*: Sep 04, 2022, *status*: 4 - Beta, - *requires*: pytest + *requires*: pytest (>=6) low-startup-overhead, scalable, distributed-testing pytest plugin :pypi:`pytest-multi-check` - *last release*: Jun 03, 2021, + *last release*: Jul 12, 2022, *status*: N/A, *requires*: pytest @@ -4745,6 +5495,13 @@ This list contains 963 plugins. a pytest plugin for th and concurrent testing + :pypi:`pytest-multithreading-allure` + *last release*: Mar 22, 2022, + *status*: N/A, + *requires*: N/A + + pytest_multithreading_allure + :pypi:`pytest-mutagen` *last release*: Jul 24, 2020, *status*: N/A, @@ -4753,9 +5510,9 @@ This list contains 963 plugins. Add the mutation testing feature to pytest :pypi:`pytest-mypy` - *last release*: Mar 21, 2021, + *last release*: Feb 07, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5) + *requires*: pytest (>=6.2) ; python_version >= "3.10" Mypy static type checker plugin for Pytest @@ -4767,8 +5524,8 @@ This list contains 963 plugins. Mypy static type checker plugin for Pytest :pypi:`pytest-mypy-plugins` - *last release*: Oct 19, 2021, - *status*: 3 - Alpha, + *last release*: Aug 15, 2022, + *status*: 4 - Beta, *requires*: pytest (>=6.0.0) pytest plugin for writing tests for mypy plugins @@ -4781,16 +5538,16 @@ This list contains 963 plugins. Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. :pypi:`pytest-mypy-testing` - *last release*: Jun 13, 2021, + *last release*: May 30, 2022, *status*: N/A, - *requires*: pytest + *requires*: N/A Pytest plugin to check mypy output. :pypi:`pytest-mysql` - *last release*: Nov 22, 2021, + *last release*: Feb 15, 2022, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: pytest (>=6.2) MySQL process and client fixtures for pytest @@ -4802,9 +5559,9 @@ This list contains 963 plugins. pytest plugin for visual testing websites using selenium :pypi:`pytest-neo` - *last release*: Apr 23, 2019, + *last release*: Jan 08, 2022, *status*: 3 - Alpha, - *requires*: pytest (>=3.7.2) + *requires*: pytest (>=6.2.0) pytest-neo is a plugin for pytest that shows tests like screen of Matrix. @@ -4815,6 +5572,13 @@ This list contains 963 plugins. A simple plugin to disable network on socket level. + :pypi:`pytest-network-endpoints` + *last release*: Mar 06, 2022, + *status*: N/A, + *requires*: pytest + + Network endpoints plugin for pytest + :pypi:`pytest-never-sleep` *last release*: May 05, 2021, *status*: 3 - Alpha, @@ -4837,9 +5601,9 @@ This list contains 963 plugins. nginx fixture for pytest - iplweb temporary fork :pypi:`pytest-ngrok` - *last release*: Jan 22, 2020, + *last release*: Jan 20, 2022, *status*: 3 - Alpha, - *requires*: N/A + *requires*: pytest @@ -4850,6 +5614,13 @@ This list contains 963 plugins. pytest ngs fixtures + :pypi:`pytest-nhsd-apim` + *last release*: Aug 23, 2022, + *status*: N/A, + *requires*: pytest (==6.2.5) + + Pytest plugin accessing NHSDigital's APIM proxies + :pypi:`pytest-nice` *last release*: May 04, 2019, *status*: 4 - Beta, @@ -4892,13 +5663,6 @@ This list contains 963 plugins. Ensure a test produces no garbage - :pypi:`pytest-notebook` - *last release*: Sep 16, 2020, - *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) - - A pytest plugin for testing Jupyter Notebooks - :pypi:`pytest-notice` *last release*: Nov 05, 2020, *status*: N/A, @@ -4935,12 +5699,19 @@ This list contains 963 plugins. A PyTest Reporter to send test runs to Notion.so :pypi:`pytest-nunit` - *last release*: Aug 04, 2020, + *last release*: Jul 26, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: N/A A pytest plugin for generating NUnit3 test result XML output + :pypi:`pytest-object-getter` + *last release*: Jul 31, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Import any object from a 3rd party module while mocking its namespace on demand. + :pypi:`pytest-ochrus` *last release*: Feb 21, 2018, *status*: 4 - Beta, @@ -4949,9 +5720,9 @@ This list contains 963 plugins. pytest results data-base and HTML reporter :pypi:`pytest-odoo` - *last release*: Nov 04, 2021, + *last release*: Feb 08, 2022, *status*: 4 - Beta, - *requires*: pytest (>=2.9) + *requires*: N/A py.test plugin to run Odoo tests @@ -4977,9 +5748,9 @@ This list contains 963 plugins. The ultimate pytest output plugin :pypi:`pytest-only` - *last release*: Jan 19, 2020, - *status*: N/A, - *requires*: N/A + *last release*: Jun 14, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (<7.1); python_version <= "3.6" Use @pytest.mark.only to run a single test @@ -4997,17 +5768,24 @@ This list contains 963 plugins. Pytest plugin for detecting inadvertent open file handles + :pypi:`pytest-opentelemetry` + *last release*: Sep 07, 2022, + *status*: N/A, + *requires*: pytest + + A pytest plugin for instrumenting test runs via OpenTelemetry + :pypi:`pytest-opentmi` - *last release*: Nov 04, 2021, + *last release*: Jun 02, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) pytest plugin for publish results to opentmi :pypi:`pytest-operator` - *last release*: Oct 26, 2021, + *last release*: Aug 17, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest Fixtures for Operators @@ -5033,9 +5811,9 @@ This list contains 963 plugins. A pytest plugin for orchestrating tests :pypi:`pytest-order` - *last release*: May 30, 2021, + *last release*: Jan 09, 2022, *status*: 4 - Beta, - *requires*: pytest (>=5.0) + *requires*: pytest (>=5.0) ; python_version < "3.10" pytest plugin to run your tests in a specific order @@ -5054,12 +5832,19 @@ This list contains 963 plugins. OS X notifications for py.test results. :pypi:`pytest-otel` - *last release*: Dec 03, 2021, + *last release*: May 26, 2022, *status*: N/A, *requires*: N/A pytest-otel report OpenTelemetry traces about test executed + :pypi:`pytest-owner` + *last release*: Apr 25, 2022, + *status*: N/A, + *requires*: N/A + + Add owner mark for tests + :pypi:`pytest-pact` *last release*: Jan 07, 2019, *status*: 4 - Beta, @@ -5103,16 +5888,16 @@ This list contains 963 plugins. Configure pytest fixtures using a combination of"parametrize" and markers :pypi:`pytest-parametrization` - *last release*: Nov 30, 2021, + *last release*: May 22, 2022, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: N/A Simpler PyTest parametrization :pypi:`pytest-parametrize-cases` - *last release*: Dec 12, 2020, + *last release*: Mar 13, 2022, *status*: N/A, - *requires*: pytest (>=6.1.2,<7.0.0) + *requires*: pytest (>=6.1.2) A more user-friendly way to write parametrized tests. @@ -5194,9 +5979,9 @@ This list contains 963 plugins. Change the exit code of pytest test sessions when a required percent of tests pass. :pypi:`pytest-perf` - *last release*: Jun 27, 2021, + *last release*: Jun 23, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.6) ; extra == 'testing' + *requires*: pytest (>=6) ; extra == 'testing' pytest-perf @@ -5208,12 +5993,19 @@ This list contains 963 plugins. A simple plugin to ensure the execution of critical sections of code has not been impacted :pypi:`pytest-persistence` - *last release*: Nov 06, 2021, + *last release*: Jun 27, 2022, *status*: N/A, *requires*: N/A Pytest tool for persistent objects + :pypi:`pytest-pg` + *last release*: Jun 07, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=6.0.0) + + A tiny plugin for pytest which runs PostgreSQL in Docker + :pypi:`pytest-pgsql` *last release*: May 13, 2020, *status*: 5 - Production/Stable, @@ -5222,9 +6014,9 @@ This list contains 963 plugins. Pytest plugins and helpers for tests using a Postgres database. :pypi:`pytest-phmdoctest` - *last release*: Nov 10, 2021, + *last release*: Apr 15, 2022, *status*: 4 - Beta, - *requires*: pytest (>=6.2) ; extra == 'test' + *requires*: pytest (>=5.4.3) pytest plugin to test Python examples in Markdown using phmdoctest. @@ -5306,7 +6098,7 @@ This list contains 963 plugins. Pytest plugin for reading playbooks. :pypi:`pytest-playwright` - *last release*: Oct 28, 2021, + *last release*: Mar 16, 2022, *status*: N/A, *requires*: pytest @@ -5326,6 +6118,13 @@ This list contains 963 plugins. A pytest wrapper for snapshot testing with playwright + :pypi:`pytest-playwright-visual` + *last release*: Apr 28, 2022, + *status*: N/A, + *requires*: N/A + + A pytest fixture for visual testing with Playwright + :pypi:`pytest-plt` *last release*: Aug 17, 2020, *status*: 5 - Production/Stable, @@ -5404,7 +6203,7 @@ This list contains 963 plugins. Visualize your failed tests with poo :pypi:`pytest-pop` - *last release*: Aug 19, 2021, + *last release*: Aug 18, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -5425,9 +6224,9 @@ This list contains 963 plugins. Run PostgreSQL in Docker container in Pytest. :pypi:`pytest-postgresql` - *last release*: Nov 05, 2021, + *last release*: Mar 11, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0) + *requires*: pytest (>=6.2.0) Postgresql fixtures and fixture factories for Pytest. @@ -5438,8 +6237,15 @@ This list contains 963 plugins. pytest plugin with powerful fixtures + :pypi:`pytest-prefer-nested-dup-tests` + *last release*: Apr 27, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.1,<8.0.0) + + A Pytest plugin to drop duplicated tests during collection, but will prefer keeping nested packages. + :pypi:`pytest-pretty-terminal` - *last release*: Nov 24, 2021, + *last release*: Jan 31, 2022, *status*: N/A, *requires*: pytest (>=3.4.1) @@ -5453,12 +6259,19 @@ This list contains 963 plugins. Minitest-style test colors :pypi:`pytest-print` - *last release*: Jun 17, 2021, + *last release*: Dec 28, 2021, *status*: 5 - Production/Stable, *requires*: pytest (>=6) pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) + :pypi:`pytest-profiles` + *last release*: Dec 09, 2021, + *status*: 4 - Beta, + *requires*: pytest (>=3.7.0) + + pytest plugin for configuration profiles + :pypi:`pytest-profiling` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -5467,9 +6280,9 @@ This list contains 963 plugins. Profiling plugin for py.test :pypi:`pytest-progress` - *last release*: Nov 09, 2021, + *last release*: Jan 31, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.7) + *requires*: N/A pytest plugin for instant test progress status @@ -5480,6 +6293,13 @@ This list contains 963 plugins. Report test pass / failures to a Prometheus PushGateway + :pypi:`pytest-prometheus-pushgateway` + *last release*: Sep 01, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest report plugin for Zulip + :pypi:`pytest-prosper` *last release*: Sep 24, 2018, *status*: N/A, @@ -5502,7 +6322,7 @@ This list contains 963 plugins. pytest plugin for testing applications that use psqlgraph :pypi:`pytest-ptera` - *last release*: Oct 20, 2021, + *last release*: Mar 01, 2022, *status*: N/A, *requires*: pytest (>=6.2.4,<7.0.0) @@ -5530,7 +6350,7 @@ This list contains 963 plugins. Plugin for py.test to enter PyCharm debugger on uncaught exceptions :pypi:`pytest-pycodestyle` - *last release*: Aug 10, 2020, + *last release*: Mar 13, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -5544,19 +6364,33 @@ This list contains 963 plugins. py.test plugin to connect to a remote debug server with PyDev or PyCharm. :pypi:`pytest-pydocstyle` - *last release*: Aug 10, 2020, + *last release*: Mar 13, 2022, *status*: 3 - Alpha, *requires*: N/A pytest plugin to run pydocstyle :pypi:`pytest-pylint` - *last release*: Nov 09, 2020, + *last release*: Sep 10, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=5.4) pytest plugin to check source code with pylint + :pypi:`pytest-pymysql-autorecord` + *last release*: Sep 02, 2022, + *status*: N/A, + *requires*: N/A + + Record PyMySQL queries and mock with the stored data. + + :pypi:`pytest-pyodide` + *last release*: Sep 08, 2022, + *status*: N/A, + *requires*: pytest + + "Pytest plugin for testing applications that use Pyodide" + :pypi:`pytest-pypi` *last release*: Mar 04, 2018, *status*: 3 - Alpha, @@ -5572,11 +6406,11 @@ This list contains 963 plugins. Core engine for cookiecutter-qa and pytest-play packages :pypi:`pytest-pyppeteer` - *last release*: Feb 16, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=6.0.2) + *last release*: Apr 28, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5,<7.0.0) - A plugin to run pyppeteer in pytest. + A plugin to run pyppeteer in pytest :pypi:`pytest-pyq` *last release*: Mar 10, 2020, @@ -5614,9 +6448,9 @@ This list contains 963 plugins. Pytest plugin for interaction with TestRail :pypi:`pytest-pythonpath` - *last release*: Aug 22, 2018, + *last release*: Feb 10, 2022, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest (<7,>=2.5.2) pytest plugin for adding to the PYTHONPATH from command line or configs. @@ -5642,7 +6476,7 @@ This list contains 963 plugins. Pytest plugin for uploading test results to your QA Touch Testrun. :pypi:`pytest-qgis` - *last release*: Nov 25, 2021, + *last release*: Jun 26, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=6.2.3) @@ -5663,7 +6497,7 @@ This list contains 963 plugins. pytest plugin to generate test result QR codes :pypi:`pytest-qt` - *last release*: Jun 13, 2021, + *last release*: Jun 23, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.0.0) @@ -5691,14 +6525,14 @@ This list contains 963 plugins. pytest plugin to generate random data inspired by QuickCheck :pypi:`pytest-rabbitmq` - *last release*: Jun 02, 2021, + *last release*: Feb 11, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.0.0) RabbitMQ process and client fixtures for pytest :pypi:`pytest-race` - *last release*: Nov 21, 2016, + *last release*: Jun 07, 2022, *status*: 4 - Beta, *requires*: N/A @@ -5711,8 +6545,15 @@ This list contains 963 plugins. pytest plugin to implement PEP712 + :pypi:`pytest-rail` + *last release*: May 02, 2022, + *status*: N/A, + *requires*: pytest (>=3.6) + + pytest plugin for creating TestRail runs and adding results + :pypi:`pytest-railflow-testrail-reporter` - *last release*: Dec 02, 2021, + *last release*: Jun 29, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -5733,7 +6574,7 @@ This list contains 963 plugins. Simple pytest plugin to look for regex in Exceptions :pypi:`pytest-raisin` - *last release*: Jun 25, 2020, + *last release*: Feb 06, 2022, *status*: N/A, *requires*: pytest @@ -5747,7 +6588,7 @@ This list contains 963 plugins. py.test plugin to randomize tests :pypi:`pytest-randomly` - *last release*: Nov 30, 2021, + *last release*: May 11, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -5775,21 +6616,21 @@ This list contains 963 plugins. Randomise the order in which pytest tests are run with some control over the randomness :pypi:`pytest-readme` - *last release*: Dec 28, 2014, + *last release*: Sep 02, 2022, *status*: 5 - Production/Stable, *requires*: N/A Test your README.md file :pypi:`pytest-reana` - *last release*: Nov 22, 2021, + *last release*: May 16, 2022, *status*: 3 - Alpha, *requires*: N/A Pytest fixtures for REANA. :pypi:`pytest-recording` - *last release*: Jul 08, 2021, + *last release*: Jun 20, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) @@ -5803,14 +6644,14 @@ This list contains 963 plugins. Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal :pypi:`pytest-redis` - *last release*: Nov 03, 2021, + *last release*: Feb 10, 2022, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: pytest (>=6.2.0) Redis fixtures and fixture factories for Pytest. :pypi:`pytest-redislite` - *last release*: Sep 19, 2021, + *last release*: Apr 05, 2022, *status*: 4 - Beta, *requires*: pytest @@ -5837,15 +6678,22 @@ This list contains 963 plugins. Conveniently run pytest with a dot-formatted test reference. + :pypi:`pytest-regex-dependency` + *last release*: Jun 12, 2022, + *status*: N/A, + *requires*: pytest + + Management of Pytest dependencies via regex patterns + :pypi:`pytest-regressions` - *last release*: Jan 27, 2021, + *last release*: Jan 19, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.0) Easy to use fixtures to write regression tests. :pypi:`pytest-regtest` - *last release*: Jun 03, 2021, + *last release*: Jul 08, 2022, *status*: N/A, *requires*: N/A @@ -5873,14 +6721,14 @@ This list contains 963 plugins. Pytest plugin to create a temporary directory with remote files :pypi:`pytest-remotedata` - *last release*: Jul 20, 2019, + *last release*: Dec 21, 2021, *status*: 3 - Alpha, - *requires*: pytest (>=3.1) + *requires*: pytest (>=4.6) Pytest plugin for controlling remote data access. :pypi:`pytest-remote-response` - *last release*: Jun 30, 2021, + *last release*: Jun 05, 2022, *status*: 4 - Beta, *requires*: pytest (>=4.6) @@ -5915,7 +6763,7 @@ This list contains 963 plugins. Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests :pypi:`pytest-repo-health` - *last release*: Nov 23, 2021, + *last release*: Dec 16, 2021, *status*: 3 - Alpha, *requires*: pytest @@ -5978,7 +6826,7 @@ This list contains 963 plugins. pytest plugin for adding tests' parameters to junit report :pypi:`pytest-reportportal` - *last release*: Jun 18, 2021, + *last release*: Jun 24, 2022, *status*: N/A, *requires*: pytest (>=3.8.0) @@ -5998,6 +6846,27 @@ This list contains 963 plugins. A simple plugin to use with pytest + :pypi:`pytest-requestselapsed` + *last release*: Aug 14, 2022, + *status*: N/A, + *requires*: N/A + + collect and show http requests elapsed time + + :pypi:`pytest-requests-futures` + *last release*: Jul 06, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest Plugin to Mock Requests Futures + + :pypi:`pytest-requires` + *last release*: Dec 21, 2021, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A pytest plugin to elegantly skip tests with optional requirements + :pypi:`pytest-reraise` *last release*: Jun 17, 2021, *status*: 5 - Production/Stable, @@ -6019,8 +6888,15 @@ This list contains 963 plugins. pytest plugin to re-run tests to eliminate flaky failures + :pypi:`pytest-rerunfailures-all-logs` + *last release*: Mar 07, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + pytest plugin to re-run tests to eliminate flaky failures + :pypi:`pytest-resilient-circuits` - *last release*: Nov 15, 2021, + *last release*: Aug 12, 2022, *status*: N/A, *requires*: N/A @@ -6041,7 +6917,7 @@ This list contains 963 plugins. Provides path for uniform access to test resources in isolated directory :pypi:`pytest-responsemock` - *last release*: Oct 10, 2020, + *last release*: Mar 10, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -6054,8 +6930,15 @@ This list contains 963 plugins. py.test integration for responses + :pypi:`pytest-rest-api` + *last release*: Aug 08, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + + :pypi:`pytest-restrict` - *last release*: Aug 12, 2021, + *last release*: May 11, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -6068,13 +6951,34 @@ This list contains 963 plugins. A RethinkDB plugin for pytest. + :pypi:`pytest-retry` + *last release*: Aug 16, 2022, + *status*: N/A, + *requires*: pytest (>=7.0.0) + + Adds the ability to retry flaky tests in CI environments + :pypi:`pytest-reverse` - *last release*: Aug 12, 2021, + *last release*: May 11, 2022, *status*: 5 - Production/Stable, *requires*: pytest Pytest plugin to reverse test order. + :pypi:`pytest-rich` + *last release*: Mar 03, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.0) + + Leverage rich for richer test session output + + :pypi:`pytest-rich-reporter` + *last release*: Feb 17, 2022, + *status*: 1 - Planning, + *requires*: pytest (>=5.0.0) + + A pytest plugin using Rich for beautiful test result formatting. + :pypi:`pytest-ringo` *last release*: Sep 27, 2017, *status*: 3 - Alpha, @@ -6082,6 +6986,13 @@ This list contains 963 plugins. pytest plugin to test webapplications using the Ringo webframework + :pypi:`pytest-rmsis` + *last release*: Aug 10, 2022, + *status*: N/A, + *requires*: pytest (>=5.3.5) + + Sycronise pytest results to Jira RMsis + :pypi:`pytest-rng` *last release*: Aug 08, 2019, *status*: 5 - Production/Stable, @@ -6118,14 +7029,14 @@ This list contains 963 plugins. Extend py.test for RPC OpenStack testing. :pypi:`pytest-rst` - *last release*: Sep 21, 2021, + *last release*: Aug 02, 2022, *status*: N/A, - *requires*: pytest + *requires*: N/A Test code from RST documents with pytest :pypi:`pytest-rt` - *last release*: Sep 04, 2021, + *last release*: May 05, 2022, *status*: N/A, *requires*: N/A @@ -6153,12 +7064,19 @@ This list contains 963 plugins. implement a --failed option for pytest :pypi:`pytest-runner` - *last release*: May 19, 2021, + *last release*: Feb 25, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.6) ; extra == 'testing' + *requires*: pytest (>=6) ; extra == 'testing' Invoke py.test as distutils command with dependency resolution + :pypi:`pytest-run-subprocess` + *last release*: Jul 03, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest Plugin for running and testing subprocesses. + :pypi:`pytest-runtime-xfail` *last release*: Aug 26, 2021, *status*: N/A, @@ -6166,6 +7084,13 @@ This list contains 963 plugins. Call runtime_xfail() to mark running test as xfail. + :pypi:`pytest-saccharin` + *last release*: May 10, 2022, + *status*: 3 - Alpha, + *requires*: N/A + + pytest-saccharin is a updated fork of pytest-sugar, a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). + :pypi:`pytest-salt` *last release*: Jan 27, 2020, *status*: 4 - Beta, @@ -6181,7 +7106,7 @@ This list contains 963 plugins. A Pytest plugin that builds and creates docker containers :pypi:`pytest-salt-factories` - *last release*: Sep 16, 2021, + *last release*: Aug 25, 2022, *status*: 4 - Beta, *requires*: pytest (>=6.0.0) @@ -6223,7 +7148,7 @@ This list contains 963 plugins. :pypi:`pytest-sbase` - *last release*: Dec 03, 2021, + *last release*: Sep 09, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -6237,7 +7162,7 @@ This list contains 963 plugins. pytest plugin for test scenarios :pypi:`pytest-schema` - *last release*: Aug 31, 2020, + *last release*: Mar 14, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.0) @@ -6258,21 +7183,21 @@ This list contains 963 plugins. A pytest plugin which allows to (de-)select tests from a file. :pypi:`pytest-selenium` - *last release*: Sep 19, 2020, + *last release*: Mar 28, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.0.0) + *requires*: pytest (>=6.0.0,<7.0.0) pytest plugin for Selenium :pypi:`pytest-seleniumbase` - *last release*: Dec 03, 2021, + *last release*: Sep 09, 2022, *status*: 5 - Production/Stable, *requires*: N/A A complete web automation framework for end-to-end testing. :pypi:`pytest-selenium-enhancer` - *last release*: Nov 26, 2020, + *last release*: Apr 29, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -6307,12 +7232,19 @@ This list contains 963 plugins. Extensible server fixures for py.test :pypi:`pytest-serverless` - *last release*: Nov 27, 2021, + *last release*: May 09, 2022, *status*: 4 - Beta, *requires*: N/A Automatically mocks resources from serverless.yml in pytest using moto. + :pypi:`pytest-servers` + *last release*: Aug 22, 2022, + *status*: 3 - Alpha, + *requires*: pytest (==7.1.2) + + pytest servers + :pypi:`pytest-services` *last release*: Oct 30, 2020, *status*: 6 - Mature, @@ -6355,13 +7287,27 @@ This list contains 963 plugins. + :pypi:`pytest-sharkreport` + *last release*: Jul 11, 2022, + *status*: N/A, + *requires*: pytest (>=3.5) + + this is pytest report plugin. + :pypi:`pytest-shell` - *last release*: Nov 07, 2021, + *last release*: Mar 27, 2022, *status*: N/A, *requires*: N/A A pytest plugin to help with testing shell scripts / black box commands + :pypi:`pytest-shell-utilities` + *last release*: Jul 28, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.0.0) + + Pytest plugin to simplify running shell commands against the system + :pypi:`pytest-sheraf` *last release*: Feb 11, 2020, *status*: N/A, @@ -6419,8 +7365,8 @@ This list contains 963 plugins. Allow for multiple processes to log to a single file :pypi:`pytest-skip-markers` - *last release*: Oct 04, 2021, - *status*: 4 - Beta, + *last release*: May 09, 2022, + *status*: 5 - Production/Stable, *requires*: pytest (>=6.0.0) Pytest Salt Plugin @@ -6440,7 +7386,7 @@ This list contains 963 plugins. Automatically skip tests that don't need to run! :pypi:`pytest-skip-slow` - *last release*: Sep 28, 2021, + *last release*: Apr 26, 2022, *status*: N/A, *requires*: N/A @@ -6474,6 +7420,13 @@ This list contains 963 plugins. Smart coverage plugin for pytest. + :pypi:`pytest-smell` + *last release*: Jun 26, 2022, + *status*: N/A, + *requires*: N/A + + Automated bad smell detection tool for Pytest + :pypi:`pytest-smtp` *last release*: Feb 20, 2021, *status*: N/A, @@ -6496,7 +7449,7 @@ This list contains 963 plugins. py.test plugin for Snap-CI :pypi:`pytest-snapshot` - *last release*: Dec 02, 2021, + *last release*: Apr 23, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.0.0) @@ -6509,13 +7462,27 @@ This list contains 963 plugins. + :pypi:`pytest-snowflake-bdd` + *last release*: Jan 05, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.0) + + Setup test data and run tests on snowflake in BDD style! + :pypi:`pytest-socket` - *last release*: Aug 28, 2021, + *last release*: Jan 23, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.6.3) Pytest Plugin to disable socket calls during tests + :pypi:`pytest-sofaepione` + *last release*: Aug 17, 2022, + *status*: N/A, + *requires*: N/A + + Test the installation of SOFA and the SofaEpione plugin. + :pypi:`pytest-soft-assertions` *last release*: May 05, 2020, *status*: 3 - Alpha, @@ -6523,6 +7490,13 @@ This list contains 963 plugins. + :pypi:`pytest-solidity` + *last release*: Jan 15, 2022, + *status*: 1 - Planning, + *requires*: pytest (<7,>=6.0.1) ; extra == 'tests' + + A PyTest library plugin for Solidity language. + :pypi:`pytest-solr` *last release*: May 11, 2020, *status*: 3 - Alpha, @@ -6537,6 +7511,13 @@ This list contains 963 plugins. A simple plugin to first execute tests that historically failed more + :pypi:`pytest-sosu` + *last release*: Apr 11, 2022, + *status*: 2 - Pre-Alpha, + *requires*: pytest + + Unofficial PyTest plugin for Sauce Labs + :pypi:`pytest-sourceorder` *last release*: Sep 01, 2021, *status*: 4 - Beta, @@ -6565,31 +7546,52 @@ This list contains 963 plugins. Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. + :pypi:`pytest-spec2md` + *last release*: Jun 26, 2022, + *status*: N/A, + *requires*: pytest (>7.0) + + Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest. + + :pypi:`pytest-speed` + *last release*: Jul 06, 2022, + *status*: 3 - Alpha, + *requires*: pytest (>=7) + + Modern benchmarking library for python with pytest integration. + :pypi:`pytest-sphinx` - *last release*: Aug 05, 2020, + *last release*: Sep 06, 2022, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=7.0.0) Doctest plugin for pytest with support for Sphinx-specific doctest-directives :pypi:`pytest-spiratest` - *last release*: Oct 13, 2021, + *last release*: Feb 08, 2022, *status*: N/A, *requires*: N/A Exports unit tests as test runs in SpiraTest/Team/Plan :pypi:`pytest-splinter` - *last release*: Dec 25, 2020, + *last release*: Sep 09, 2022, *status*: 6 - Mature, - *requires*: N/A + *requires*: pytest (>=3.0.0) Splinter plugin for pytest testing framework + :pypi:`pytest-splinter4` + *last release*: Jun 11, 2022, + *status*: 6 - Mature, + *requires*: pytest (<8.0,>=7.1.2) + + Pytest plugin for the splinter automation library + :pypi:`pytest-split` - *last release*: Nov 09, 2021, + *last release*: Apr 22, 2022, *status*: 4 - Beta, - *requires*: pytest (>=5,<7) + *requires*: pytest (>=5,<8) Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. @@ -6615,14 +7617,14 @@ This list contains 963 plugins. :pypi:`pytest-splunk-addon` - *last release*: Nov 29, 2021, + *last release*: Sep 08, 2022, *status*: N/A, *requires*: pytest (>5.4.0,<6.3) A Dynamic test tool for Splunk Apps and Add-ons :pypi:`pytest-splunk-addon-ui-smartx` - *last release*: Oct 07, 2021, + *last release*: Mar 16, 2022, *status*: N/A, *requires*: N/A @@ -6649,6 +7651,13 @@ This list contains 963 plugins. pytest plugin with sqlalchemy related fixtures + :pypi:`pytest-sqlalchemy-mock` + *last release*: Aug 10, 2022, + *status*: 3 - Alpha, + *requires*: pytest (>=2.0) + + pytest sqlalchemy plugin for mock + :pypi:`pytest-sql-bigquery` *last release*: Dec 19, 2019, *status*: N/A, @@ -6656,6 +7665,13 @@ This list contains 963 plugins. Yet another SQL-testing framework for BigQuery provided by pytest plugin + :pypi:`pytest-squadcast` + *last release*: Feb 22, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest report plugin for Squadcast + :pypi:`pytest-srcpaths` *last release*: Oct 15, 2021, *status*: N/A, @@ -6705,6 +7721,13 @@ This list contains 963 plugins. Run a test suite one failing test at a time. + :pypi:`pytest-stf` + *last release*: Sep 09, 2022, + *status*: N/A, + *requires*: pytest (>=5.0) + + pytest plugin for openSTF + :pypi:`pytest-stoq` *last release*: Feb 09, 2021, *status*: 4 - Beta, @@ -6755,23 +7778,23 @@ This list contains 963 plugins. A pytest plugin to organize long run tests (named studies) without interfering the regular tests :pypi:`pytest-subprocess` - *last release*: Nov 07, 2021, + *last release*: Feb 09, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=4.0.0) A plugin to fake subprocess for pytest :pypi:`pytest-subtesthack` - *last release*: Mar 02, 2021, + *last release*: Jul 16, 2022, *status*: N/A, *requires*: N/A A hack to explicitly set up and tear down fixtures. :pypi:`pytest-subtests` - *last release*: May 29, 2021, + *last release*: May 26, 2022, *status*: 4 - Beta, - *requires*: pytest (>=5.3.0) + *requires*: pytest (>=7.0) unittest subTest() support and subtests fixture @@ -6783,9 +7806,9 @@ This list contains 963 plugins. pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. :pypi:`pytest-sugar` - *last release*: Jul 06, 2020, + *last release*: Jul 10, 2022, *status*: 3 - Alpha, - *requires*: N/A + *requires*: pytest (>=2.9) pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). @@ -6797,7 +7820,7 @@ This list contains 963 plugins. Workaround for https://github.com/Frozenball/pytest-sugar/issues/159 :pypi:`pytest-super-check` - *last release*: Aug 12, 2021, + *last release*: May 11, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -6817,8 +7840,22 @@ This list contains 963 plugins. pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. + :pypi:`pytest-system-statistics` + *last release*: Feb 16, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=6.0.0) + + Pytest plugin to track and report system usage statistics + + :pypi:`pytest-system-test-plugin` + *last release*: Feb 03, 2022, + *status*: N/A, + *requires*: N/A + + Pyst - Pytest System-Test Plugin + :pypi:`pytest-takeltest` - *last release*: Oct 13, 2021, + *last release*: Jan 04, 2022, *status*: N/A, *requires*: N/A @@ -6880,8 +7917,15 @@ This list contains 963 plugins. Predictable and repeatable tempdir support. + :pypi:`pytest-terra-fixt` + *last release*: Sep 09, 2022, + *status*: N/A, + *requires*: pytest (==6.2.5) + + Terraform and Terragrunt fixtures for pytest + :pypi:`pytest-terraform` - *last release*: Nov 10, 2021, + *last release*: Sep 01, 2022, *status*: N/A, *requires*: pytest (>=6.0) @@ -6909,19 +7953,26 @@ This list contains 963 plugins. Test configuration plugin for pytest. :pypi:`pytest-testdirectory` - *last release*: Nov 06, 2018, + *last release*: Feb 21, 2022, *status*: 5 - Production/Stable, *requires*: pytest A py.test plugin providing temporary directories in unit tests. :pypi:`pytest-testdox` - *last release*: Oct 13, 2020, + *last release*: Apr 19, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.7.0) + *requires*: pytest (>=4.6.0) A testdox format reporter for pytest + :pypi:`pytest-test-grouping` + *last release*: Jun 17, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=2.5) + + A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. + :pypi:`pytest-test-groups` *last release*: Oct 25, 2016, *status*: 5 - Production/Stable, @@ -6930,7 +7981,7 @@ This list contains 963 plugins. A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. :pypi:`pytest-testinfra` - *last release*: Jun 20, 2021, + *last release*: Jun 19, 2022, *status*: 5 - Production/Stable, *requires*: pytest (!=3.0.2) @@ -6944,7 +7995,21 @@ This list contains 963 plugins. pytest reporting plugin for testlink :pypi:`pytest-testmon` - *last release*: Oct 22, 2021, + *last release*: Sep 09, 2022, + *status*: 4 - Beta, + *requires*: N/A + + selects tests affected by changed files and methods + + :pypi:`pytest-testmon-oc` + *last release*: Jun 01, 2022, + *status*: 4 - Beta, + *requires*: pytest (<8,>=5) + + nOly selects tests affected by changed files and methods + + :pypi:`pytest-testmon-skip-libraries` + *last release*: Jun 13, 2022, *status*: 4 - Beta, *requires*: N/A @@ -6957,6 +8022,13 @@ This list contains 963 plugins. Plugin to use TestObject Suites with Pytest + :pypi:`pytest-testpluggy` + *last release*: Jan 07, 2022, + *status*: N/A, + *requires*: pytest + + set your encoding + :pypi:`pytest-testrail` *last release*: Aug 27, 2020, *status*: N/A, @@ -6972,14 +8044,14 @@ This list contains 963 plugins. A small example package :pypi:`pytest-testrail-api` - *last release*: Nov 30, 2021, + *last release*: Aug 29, 2022, *status*: N/A, *requires*: pytest (>=5.5) Плагин Pytest, для интеграции с TestRail :pypi:`pytest-testrail-api-client` - *last release*: Dec 03, 2021, + *last release*: Dec 14, 2021, *status*: N/A, *requires*: pytest @@ -7006,10 +8078,17 @@ This list contains 963 plugins. pytest plugin for creating TestRail runs and adding results + :pypi:`pytest-testrail-integrator` + *last release*: Aug 01, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5) + + Pytest plugin for sending report to testrail system. + :pypi:`pytest-testrail-ns` - *last release*: Oct 08, 2021, + *last release*: Aug 12, 2022, *status*: N/A, - *requires*: pytest (>=3.6) + *requires*: N/A pytest plugin for creating TestRail runs and adding results @@ -7028,7 +8107,14 @@ This list contains 963 plugins. :pypi:`pytest-testreport` - *last release*: Nov 12, 2021, + *last release*: May 23, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + + + :pypi:`pytest-testreport-new` + *last release*: Aug 15, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) @@ -7049,14 +8135,14 @@ This list contains 963 plugins. Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply :pypi:`pytest-test-utils` - *last release*: Nov 30, 2021, + *last release*: Jul 14, 2022, *status*: N/A, *requires*: pytest (>=5) :pypi:`pytest-tesults` - *last release*: Jul 31, 2021, + *last release*: Dec 31, 2021, *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.0) @@ -7069,6 +8155,13 @@ This list contains 963 plugins. pytest-ligo + :pypi:`pytest-th2-bdd` + *last release*: May 13, 2022, + *status*: N/A, + *requires*: N/A + + pytest_th2_bdd + :pypi:`pytest-thawgun` *last release*: May 26, 2020, *status*: 3 - Alpha, @@ -7077,9 +8170,9 @@ This list contains 963 plugins. Pytest plugin for time travel :pypi:`pytest-threadleak` - *last release*: Sep 08, 2017, + *last release*: Jul 03, 2022, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=3.1.1) Detects thread leaks @@ -7098,7 +8191,7 @@ This list contains 963 plugins. A pytest plugin to time test function runs :pypi:`pytest-timeout` - *last release*: Oct 11, 2021, + *last release*: Jan 18, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=5.0.0) @@ -7125,6 +8218,13 @@ This list contains 963 plugins. Pytest plugin to add a timestamp prefix to the pytest output + :pypi:`pytest-timestamps` + *last release*: Jan 16, 2022, + *status*: N/A, + *requires*: pytest (>=5.2) + + A simple plugin to view timestamps for each test + :pypi:`pytest-tipsi-django` *last release*: Nov 17, 2021, *status*: 4 - Beta, @@ -7153,8 +8253,29 @@ This list contains 963 plugins. Cloud Jira Test Management (TM4J) PyTest reporter plugin + :pypi:`pytest-tmnet` + *last release*: Mar 01, 2022, + *status*: N/A, + *requires*: N/A + + A small example package + + :pypi:`pytest-tmp-files` + *last release*: Apr 03, 2022, + *status*: N/A, + *requires*: pytest + + Utilities to create temporary file hierarchies in pytest. + + :pypi:`pytest-tmpfs` + *last release*: Aug 29, 2022, + *status*: N/A, + *requires*: pytest + + A pytest plugin that helps you on using a temporary filesystem for testing. + :pypi:`pytest-tmreport` - *last release*: Nov 17, 2021, + *last release*: Aug 12, 2022, *status*: N/A, *requires*: N/A @@ -7188,6 +8309,13 @@ This list contains 963 plugins. Numerous useful plugins for pytest. + :pypi:`pytest-tools` + *last release*: Jul 04, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Pytest tools + :pypi:`pytest-tornado` *last release*: Jun 17, 2020, *status*: 5 - Production/Stable, @@ -7216,6 +8344,13 @@ This list contains 963 plugins. py.test plugin for testing Python 3.5+ Tornado code + :pypi:`pytest-trace` + *last release*: Jun 19, 2022, + *status*: N/A, + *requires*: pytest (>=4.6) + + Save OpenTelemetry spans generated during testing + :pypi:`pytest-track` *last release*: Feb 26, 2021, *status*: 3 - Alpha, @@ -7265,6 +8400,13 @@ This list contains 963 plugins. Pytest plugin for trio + :pypi:`pytest-trytond` + *last release*: Feb 02, 2022, + *status*: 3 - Alpha, + *requires*: pytest (>=5) + + Pytest plugin for the Tryton server framework + :pypi:`pytest-tspwplib` *last release*: Jan 08, 2021, *status*: 4 - Beta, @@ -7272,6 +8414,13 @@ This list contains 963 plugins. A simple plugin to use with tspwplib + :pypi:`pytest-tst` + *last release*: Apr 27, 2022, + *status*: N/A, + *requires*: pytest (>=5.0.0) + + Customize pytest options, output and exit code to make it compatible with tst + :pypi:`pytest-tstcls` *last release*: Mar 23, 2020, *status*: 5 - Production/Stable, @@ -7279,6 +8428,20 @@ This list contains 963 plugins. Test Class Base + :pypi:`pytest-tui` + *last release*: Sep 07, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.5) + + Text User Interface (TUI) for Pytest, with optional auto-launch and HTML export + + :pypi:`pytest-twilio-conversations-client-mock` + *last release*: Aug 02, 2022, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-twisted` *last release*: Aug 30, 2021, *status*: 5 - Production/Stable, @@ -7286,8 +8449,22 @@ This list contains 963 plugins. A twisted plugin for pytest. + :pypi:`pytest-typechecker` + *last release*: Feb 04, 2022, + *status*: N/A, + *requires*: pytest (>=6.2.5,<7.0.0) + + Run type checkers on specified test files + + :pypi:`pytest-typhoon-config` + *last release*: Apr 07, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + A Typhoon HIL plugin that facilitates test parameter configuration at runtime + :pypi:`pytest-typhoon-xray` - *last release*: Nov 03, 2021, + *last release*: Mar 07, 2022, *status*: 4 - Beta, *requires*: N/A @@ -7314,6 +8491,13 @@ This list contains 963 plugins. Text User Interface for running python tests + :pypi:`pytest-unflakable` + *last release*: Jun 14, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.0) + + Unflakable plugin for PyTest + :pypi:`pytest-unhandled-exception-exit-code` *last release*: Jun 22, 2020, *status*: 5 - Production/Stable, @@ -7336,12 +8520,19 @@ This list contains 963 plugins. Run only unmarked tests :pypi:`pytest-unordered` - *last release*: Mar 28, 2021, + *last release*: Jul 08, 2022, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=6.0.0) Test equality of unordered collections in pytest + :pypi:`pytest-unstable` + *last release*: Jun 10, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Set a test as unstable to return 0 even if it failed + :pypi:`pytest-upload-report` *last release*: Jun 18, 2021, *status*: 5 - Production/Stable, @@ -7371,14 +8562,14 @@ This list contains 963 plugins. :pypi:`pytest-variables` - *last release*: Oct 23, 2019, + *last release*: Mar 27, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.4.2) + *requires*: pytest (>=3.0.0,<8.0.0) pytest plugin for providing variables to tests/fixtures :pypi:`pytest-variant` - *last release*: Jun 20, 2021, + *last release*: Jun 06, 2022, *status*: N/A, *requires*: N/A @@ -7392,9 +8583,9 @@ This list contains 963 plugins. Plugin for managing VCR.py cassettes :pypi:`pytest-vcr-delete-on-fail` - *last release*: Aug 13, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=6.2.2,<7.0.0) + *last release*: Jun 20, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=6.2.2) A pytest plugin that automates vcrpy cassettes deletion on test failure. @@ -7413,7 +8604,7 @@ This list contains 963 plugins. py.test fixture for creating a virtual environment :pypi:`pytest-ver` - *last release*: Aug 30, 2021, + *last release*: Aug 21, 2022, *status*: 2 - Pre-Alpha, *requires*: N/A @@ -7440,6 +8631,13 @@ This list contains 963 plugins. Virtualenv fixture for py.test + :pypi:`pytest-vnc` + *last release*: Jun 03, 2022, + *status*: N/A, + *requires*: pytest + + VNC client for Pytest + :pypi:`pytest-voluptuous` *last release*: Jun 09, 2020, *status*: N/A, @@ -7482,6 +8680,13 @@ This list contains 963 plugins. Pytest plugin for testing whatsapp bots with end to end tests + :pypi:`pytest-wake` + *last release*: Aug 30, 2022, + *status*: N/A, + *requires*: pytest + + + :pypi:`pytest-watch` *last release*: May 20, 2018, *status*: N/A, @@ -7490,7 +8695,7 @@ This list contains 963 plugins. Local continuous test runner with pytest and watchdog. :pypi:`pytest-watcher` - *last release*: Sep 18, 2021, + *last release*: Dec 30, 2021, *status*: 3 - Alpha, *requires*: N/A @@ -7545,6 +8750,13 @@ This list contains 963 plugins. Windows tray notifications for py.test results. + :pypi:`pytest-wiremock` + *last release*: Mar 27, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.1,<8.0.0) + + A pytest plugin for programmatically using wiremock in integration tests + :pypi:`pytest-with-docker` *last release*: Nov 09, 2021, *status*: N/A, @@ -7560,9 +8772,9 @@ This list contains 963 plugins. A pytest plugin for configuring workflow/pipeline tests using YAML files :pypi:`pytest-xdist` - *last release*: Sep 21, 2021, + *last release*: Dec 10, 2021, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.0.0) + *requires*: pytest (>=6.2.0) pytest xdist plugin for distributed testing and loop-on-failing modes @@ -7608,6 +8820,13 @@ This list contains 963 plugins. Extended logging for test and decorators + :pypi:`pytest-xlsx` + *last release*: Aug 04, 2022, + *status*: N/A, + *requires*: N/A + + pytest plugin for generating test cases by xlsx(excel) + :pypi:`pytest-xpara` *last release*: Oct 30, 2017, *status*: 3 - Alpha, @@ -7616,7 +8835,7 @@ This list contains 963 plugins. An extended parametrizing plugin of pytest. :pypi:`pytest-xprocess` - *last release*: Jul 28, 2021, + *last release*: Aug 29, 2022, *status*: 4 - Beta, *requires*: pytest (>=2.8) @@ -7637,12 +8856,19 @@ This list contains 963 plugins. :pypi:`pytest-xray-server` - *last release*: Oct 27, 2021, + *last release*: May 03, 2022, *status*: 3 - Alpha, *requires*: pytest (>=5.3.1) + :pypi:`pytest-xreport` + *last release*: May 17, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + + :pypi:`pytest-xvfb` *last release*: Jun 09, 2020, *status*: 4 - Beta, @@ -7692,6 +8918,13 @@ This list contains 963 plugins. PyTest plugin to run tests concurrently, each \`yield\` switch context to other one + :pypi:`pytest-yls` + *last release*: Aug 08, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + Pytest plugin to test the YLS as a whole. + :pypi:`pytest-yuk` *last release*: Mar 26, 2021, *status*: N/A, @@ -7714,7 +8947,7 @@ This list contains 963 plugins. OWASP ZAP plugin for py.test. :pypi:`pytest-zebrunner` - *last release*: Dec 02, 2021, + *last release*: Jun 02, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=4.5.0) @@ -7726,3 +8959,10 @@ This list contains 963 plugins. *requires*: pytest (~=3.6) Extend py.test for RPC OpenStack testing. + + :pypi:`pytest-zulip` + *last release*: May 07, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest report plugin for Zulip diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 42897e3d18f..bbad27e09cd 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -9,6 +9,39 @@ This page contains the full reference to pytest's API. :depth: 3 :local: +Constants +--------- + +pytest.__version__ +~~~~~~~~~~~~~~~~~~ + +The current pytest version, as a string:: + + >>> import pytest + >>> pytest.__version__ + '7.0.0' + + +.. _`version-tuple`: + +pytest.version_tuple +~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 7.0 + +The current pytest version, as a tuple:: + + >>> import pytest + >>> pytest.version_tuple + (7, 0, 0) + +For pre-releases, the last component will be a string with the prerelease version:: + + >>> import pytest + >>> pytest.version_tuple + (7, 0, '0rc1') + + Functions --------- @@ -59,7 +92,7 @@ pytest.param pytest.raises ~~~~~~~~~~~~~ -**Tutorial**: :ref:`assertraises`. +**Tutorial**: :ref:`assertraises` .. autofunction:: pytest.raises(expected_exception: Exception [, *, match]) :with: excinfo @@ -67,15 +100,15 @@ pytest.raises pytest.deprecated_call ~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`ensuring_function_triggers`. +**Tutorial**: :ref:`ensuring_function_triggers` -.. autofunction:: pytest.deprecated_call() +.. autofunction:: pytest.deprecated_call([match]) :with: pytest.register_assert_rewrite ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`assertion-rewriting`. +**Tutorial**: :ref:`assertion-rewriting` .. autofunction:: pytest.register_assert_rewrite @@ -90,7 +123,7 @@ pytest.warns pytest.freeze_includes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`freezing-pytest`. +**Tutorial**: :ref:`freezing-pytest` .. autofunction:: pytest.freeze_includes @@ -110,7 +143,7 @@ fixtures or plugins. pytest.mark.filterwarnings ~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`filterwarnings`. +**Tutorial**: :ref:`filterwarnings` Add warning filters to marked test items. @@ -136,7 +169,7 @@ Add warning filters to marked test items. pytest.mark.parametrize ~~~~~~~~~~~~~~~~~~~~~~~ -:ref:`parametrize`. +**Tutorial**: :ref:`parametrize` This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see there. @@ -146,7 +179,7 @@ This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see pytest.mark.skip ~~~~~~~~~~~~~~~~ -:ref:`skip`. +**Tutorial**: :ref:`skip` Unconditionally skip a test function. @@ -160,7 +193,7 @@ Unconditionally skip a test function. pytest.mark.skipif ~~~~~~~~~~~~~~~~~~ -:ref:`skipif`. +**Tutorial**: :ref:`skipif` Skip a test function if a condition is ``True``. @@ -176,7 +209,7 @@ Skip a test function if a condition is ``True``. pytest.mark.usefixtures ~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`usefixtures`. +**Tutorial**: :ref:`usefixtures` Mark a test function as using the given fixture names. @@ -198,7 +231,7 @@ Mark a test function as using the given fixture names. pytest.mark.xfail ~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`xfail`. +**Tutorial**: :ref:`xfail` Marks a test function as *expected to fail*. @@ -212,7 +245,7 @@ Marks a test function as *expected to fail*. :keyword str reason: Reason why the test function is marked as xfail. :keyword Type[Exception] raises: - Exception subclass expected to be raised by the test function; other exceptions will fail the test. + Exception subclass (or tuple of subclasses) expected to be raised by the test function; other exceptions will fail the test. :keyword bool run: If the test function should actually be executed. If ``False``, the function will always xfail and will not be executed (useful if a function is segfaulting). @@ -226,37 +259,6 @@ Marks a test function as *expected to fail*. a new release of a library fixes a known bug). -pytest.__version__ -~~~~~~~~~~~~~~~~~~ - -The current pytest version, as a string:: - - >>> import pytest - >>> pytest.__version__ - '7.0.0' - - -.. _`version-tuple`: - -pytest.version_tuple -~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 7.0 - -The current pytest version, as a tuple:: - - >>> import pytest - >>> pytest.version_tuple - (7, 0, 0) - -For pre-releases, the last component will be a string with the prerelease version:: - - >>> import pytest - >>> pytest.version_tuple - (7, 0, '0rc1') - - - Custom marks ~~~~~~~~~~~~ @@ -288,14 +290,14 @@ Example for using multiple custom markers: def test_function(): ... -When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers_with_node>` is used with multiple markers, the marker closest to the function will be iterated over first. The above example will result in ``@pytest.mark.slow`` followed by ``@pytest.mark.timeout(...)``. +When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers_with_node <_pytest.nodes.Node.iter_markers_with_node>` is used with multiple markers, the marker closest to the function will be iterated over first. The above example will result in ``@pytest.mark.slow`` followed by ``@pytest.mark.timeout(...)``. .. _`fixtures-api`: Fixtures -------- -**Tutorial**: :ref:`fixture`. +**Tutorial**: :ref:`fixture` Fixtures are requested by test functions or other fixtures by declaring them as argument names. @@ -331,192 +333,96 @@ For more details, consult the full :ref:`fixtures docs `. :decorator: -.. fixture:: cache - -config.cache -~~~~~~~~~~~~ - -**Tutorial**: :ref:`cache`. - -The ``config.cache`` object allows other plugins and fixtures -to store and retrieve values across test runs. To access it from fixtures -request ``pytestconfig`` into your fixture and get it with ``pytestconfig.cache``. - -Under the hood, the cache plugin uses the simple -``dumps``/``loads`` API of the :py:mod:`json` stdlib module. - -``config.cache`` is an instance of :class:`pytest.Cache`: - -.. autoclass:: pytest.Cache() - :members: - - -.. fixture:: capsys - -capsys -~~~~~~ - -:ref:`captures`. - -.. autofunction:: _pytest.capture.capsys() - :no-auto-options: - - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_output(capsys): - print("hello") - captured = capsys.readouterr() - assert captured.out == "hello\n" - -.. autoclass:: pytest.CaptureFixture() - :members: - - -.. fixture:: capsysbinary - -capsysbinary -~~~~~~~~~~~~ - -:ref:`captures`. - -.. autofunction:: _pytest.capture.capsysbinary() - :no-auto-options: - - Returns an instance of :class:`CaptureFixture[bytes] `. - - Example: - - .. code-block:: python - - def test_output(capsysbinary): - print("hello") - captured = capsysbinary.readouterr() - assert captured.out == b"hello\n" - - .. fixture:: capfd capfd ~~~~~~ -:ref:`captures`. +**Tutorial**: :ref:`captures` .. autofunction:: _pytest.capture.capfd() :no-auto-options: - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_system_echo(capfd): - os.system('echo "hello"') - captured = capfd.readouterr() - assert captured.out == "hello\n" - .. fixture:: capfdbinary capfdbinary ~~~~~~~~~~~~ -:ref:`captures`. +**Tutorial**: :ref:`captures` .. autofunction:: _pytest.capture.capfdbinary() :no-auto-options: - Returns an instance of :class:`CaptureFixture[bytes] `. - - Example: - - .. code-block:: python - - def test_system_echo(capfdbinary): - os.system('echo "hello"') - captured = capfdbinary.readouterr() - assert captured.out == b"hello\n" - - -.. fixture:: doctest_namespace - -doctest_namespace -~~~~~~~~~~~~~~~~~ -:ref:`doctest`. +.. fixture:: caplog -.. autofunction:: _pytest.doctest.doctest_namespace() +caplog +~~~~~~ - Usually this fixture is used in conjunction with another ``autouse`` fixture: +**Tutorial**: :ref:`logging` - .. code-block:: python +.. autofunction:: _pytest.logging.caplog() + :no-auto-options: - @pytest.fixture(autouse=True) - def add_np(doctest_namespace): - doctest_namespace["np"] = numpy + Returns a :class:`pytest.LogCaptureFixture` instance. - For more details: :ref:`doctest_namespace`. +.. autoclass:: pytest.LogCaptureFixture() + :members: -.. fixture:: request +.. fixture:: capsys -request -~~~~~~~ +capsys +~~~~~~ -:ref:`request example`. +**Tutorial**: :ref:`captures` -The ``request`` fixture is a special fixture providing information of the requesting test function. +.. autofunction:: _pytest.capture.capsys() + :no-auto-options: -.. autoclass:: pytest.FixtureRequest() +.. autoclass:: pytest.CaptureFixture() :members: +.. fixture:: capsysbinary -.. fixture:: pytestconfig - -pytestconfig +capsysbinary ~~~~~~~~~~~~ -.. autofunction:: _pytest.fixtures.pytestconfig() - - -.. fixture:: record_property - -record_property -~~~~~~~~~~~~~~~~~~~ +**Tutorial**: :ref:`captures` -**Tutorial**: :ref:`record_property example`. +.. autofunction:: _pytest.capture.capsysbinary() + :no-auto-options: -.. autofunction:: _pytest.junitxml.record_property() +.. fixture:: cache -.. fixture:: record_testsuite_property +config.cache +~~~~~~~~~~~~ -record_testsuite_property -~~~~~~~~~~~~~~~~~~~~~~~~~ +**Tutorial**: :ref:`cache` -**Tutorial**: :ref:`record_testsuite_property example`. +The ``config.cache`` object allows other plugins and fixtures +to store and retrieve values across test runs. To access it from fixtures +request ``pytestconfig`` into your fixture and get it with ``pytestconfig.cache``. -.. autofunction:: _pytest.junitxml.record_testsuite_property() +Under the hood, the cache plugin uses the simple +``dumps``/``loads`` API of the :py:mod:`json` stdlib module. +``config.cache`` is an instance of :class:`pytest.Cache`: -.. fixture:: caplog +.. autoclass:: pytest.Cache() + :members: -caplog -~~~~~~ -:ref:`logging`. +.. fixture:: doctest_namespace -.. autofunction:: _pytest.logging.caplog() - :no-auto-options: +doctest_namespace +~~~~~~~~~~~~~~~~~ - Returns a :class:`pytest.LogCaptureFixture` instance. +**Tutorial**: :ref:`doctest` -.. autoclass:: pytest.LogCaptureFixture() - :members: +.. autofunction:: _pytest.doctest.doctest_namespace() .. fixture:: monkeypatch @@ -524,7 +430,7 @@ caplog monkeypatch ~~~~~~~~~~~ -:ref:`monkeypatching`. +**Tutorial**: :ref:`monkeypatching` .. autofunction:: _pytest.monkeypatch.monkeypatch() :no-auto-options: @@ -535,6 +441,14 @@ monkeypatch :members: +.. fixture:: pytestconfig + +pytestconfig +~~~~~~~~~~~~ + +.. autofunction:: _pytest.fixtures.pytestconfig() + + .. fixture:: pytester pytester @@ -571,18 +485,25 @@ To use it, include in your topmost ``conftest.py`` file: .. autoclass:: pytest.RecordedHookCall() :members: -.. fixture:: testdir -testdir -~~~~~~~ +.. fixture:: record_property -Identical to :fixture:`pytester`, but provides an instance whose methods return -legacy ``py.path.local`` objects instead when applicable. +record_property +~~~~~~~~~~~~~~~~~~~ -New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`. +**Tutorial**: :ref:`record_property example` + +.. autofunction:: _pytest.junitxml.record_property() -.. autoclass:: pytest.Testdir() - :members: + +.. fixture:: record_testsuite_property + +record_testsuite_property +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Tutorial**: :ref:`record_testsuite_property example` + +.. autofunction:: _pytest.junitxml.record_testsuite_property() .. fixture:: recwarn @@ -598,11 +519,33 @@ recwarn .. autoclass:: pytest.WarningsRecorder() :members: -Each recorded warning is an instance of :class:`warnings.WarningMessage`. -.. note:: - ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated - differently; see :ref:`ensuring_function_triggers`. +.. fixture:: request + +request +~~~~~~~ + +**Example**: :ref:`request example` + +The ``request`` fixture is a special fixture providing information of the requesting test function. + +.. autoclass:: pytest.FixtureRequest() + :members: + + +.. fixture:: testdir + +testdir +~~~~~~~ + +Identical to :fixture:`pytester`, but provides an instance whose methods return +legacy ``py.path.local`` objects instead when applicable. + +New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`. + +.. autoclass:: pytest.Testdir() + :members: + :noindex: TimeoutExpired .. fixture:: tmp_path @@ -610,7 +553,7 @@ Each recorded warning is an instance of :class:`warnings.WarningMessage`. tmp_path ~~~~~~~~ -:ref:`tmp_path` +**Tutorial**: :ref:`tmp_path` .. autofunction:: _pytest.tmpdir.tmp_path() :no-auto-options: @@ -621,7 +564,7 @@ tmp_path tmp_path_factory ~~~~~~~~~~~~~~~~ -:ref:`tmp_path_factory example` +**Tutorial**: :ref:`tmp_path_factory example` .. _`tmp_path_factory factory api`: @@ -636,7 +579,7 @@ tmp_path_factory tmpdir ~~~~~~ -:ref:`tmpdir and tmpdir_factory` +**Tutorial**: :ref:`tmpdir and tmpdir_factory` .. autofunction:: _pytest.legacypath.LegacyTmpdirPlugin.tmpdir() :no-auto-options: @@ -647,7 +590,7 @@ tmpdir tmpdir_factory ~~~~~~~~~~~~~~ -:ref:`tmpdir and tmpdir_factory` +**Tutorial**: :ref:`tmpdir and tmpdir_factory` ``tmpdir_factory`` is an instance of :class:`~pytest.TempdirFactory`: @@ -660,7 +603,7 @@ tmpdir_factory Hooks ----- -:ref:`writing-plugins`. +**Tutorial**: :ref:`writing-plugins` .. currentmodule:: _pytest.hookspec @@ -671,9 +614,13 @@ Bootstrapping hooks Bootstrapping hooks called for plugins registered early enough (internal and setuptools plugins). +.. hook:: pytest_load_initial_conftests .. autofunction:: pytest_load_initial_conftests +.. hook:: pytest_cmdline_preparse .. autofunction:: pytest_cmdline_preparse +.. hook:: pytest_cmdline_parse .. autofunction:: pytest_cmdline_parse +.. hook:: pytest_cmdline_main .. autofunction:: pytest_cmdline_main .. _`initialization-hooks`: @@ -683,13 +630,20 @@ Initialization hooks Initialization hooks called for plugins and ``conftest.py`` files. +.. hook:: pytest_addoption .. autofunction:: pytest_addoption +.. hook:: pytest_addhooks .. autofunction:: pytest_addhooks +.. hook:: pytest_configure .. autofunction:: pytest_configure +.. hook:: pytest_unconfigure .. autofunction:: pytest_unconfigure +.. hook:: pytest_sessionstart .. autofunction:: pytest_sessionstart +.. hook:: pytest_sessionfinish .. autofunction:: pytest_sessionfinish +.. hook:: pytest_plugin_registered .. autofunction:: pytest_plugin_registered Collection hooks @@ -697,21 +651,34 @@ Collection hooks ``pytest`` calls the following hooks for collecting files and directories: +.. hook:: pytest_collection .. autofunction:: pytest_collection +.. hook:: pytest_ignore_collect .. autofunction:: pytest_ignore_collect +.. hook:: pytest_collect_file .. autofunction:: pytest_collect_file +.. hook:: pytest_pycollect_makemodule .. autofunction:: pytest_pycollect_makemodule For influencing the collection of objects in Python modules you can use the following hook: +.. hook:: pytest_pycollect_makeitem .. autofunction:: pytest_pycollect_makeitem +.. hook:: pytest_generate_tests .. autofunction:: pytest_generate_tests +.. hook:: pytest_make_parametrize_id .. autofunction:: pytest_make_parametrize_id +Hooks for influencing test skipping: + +.. hook:: pytest_markeval_namespace +.. autofunction:: pytest_markeval_namespace + After collection is complete, you can modify the order of items, delete or otherwise amend the test items: +.. hook:: pytest_collection_modifyitems .. autofunction:: pytest_collection_modifyitems .. note:: @@ -725,13 +692,21 @@ Test running (runtest) hooks All runtest related hooks receive a :py:class:`pytest.Item ` object. +.. hook:: pytest_runtestloop .. autofunction:: pytest_runtestloop +.. hook:: pytest_runtest_protocol .. autofunction:: pytest_runtest_protocol +.. hook:: pytest_runtest_logstart .. autofunction:: pytest_runtest_logstart +.. hook:: pytest_runtest_logfinish .. autofunction:: pytest_runtest_logfinish +.. hook:: pytest_runtest_setup .. autofunction:: pytest_runtest_setup +.. hook:: pytest_runtest_call .. autofunction:: pytest_runtest_call +.. hook:: pytest_runtest_teardown .. autofunction:: pytest_runtest_teardown +.. hook:: pytest_runtest_makereport .. autofunction:: pytest_runtest_makereport For deeper understanding you may look at the default implementation of @@ -740,6 +715,7 @@ in ``_pytest.pdb`` which interacts with ``_pytest.capture`` and its input/output capturing in order to immediately drop into interactive debugging when a test failure occurs. +.. hook:: pytest_pyfunc_call .. autofunction:: pytest_pyfunc_call Reporting hooks @@ -747,27 +723,45 @@ Reporting hooks Session related reporting hooks: +.. hook:: pytest_collectstart .. autofunction:: pytest_collectstart +.. hook:: pytest_make_collect_report .. autofunction:: pytest_make_collect_report +.. hook:: pytest_itemcollected .. autofunction:: pytest_itemcollected +.. hook:: pytest_collectreport .. autofunction:: pytest_collectreport +.. hook:: pytest_deselected .. autofunction:: pytest_deselected +.. hook:: pytest_report_header .. autofunction:: pytest_report_header +.. hook:: pytest_report_collectionfinish .. autofunction:: pytest_report_collectionfinish +.. hook:: pytest_report_teststatus .. autofunction:: pytest_report_teststatus +.. hook:: pytest_report_to_serializable +.. autofunction:: pytest_report_to_serializable +.. hook:: pytest_report_from_serializable +.. autofunction:: pytest_report_from_serializable +.. hook:: pytest_terminal_summary .. autofunction:: pytest_terminal_summary +.. hook:: pytest_fixture_setup .. autofunction:: pytest_fixture_setup +.. hook:: pytest_fixture_post_finalizer .. autofunction:: pytest_fixture_post_finalizer -.. autofunction:: pytest_warning_captured +.. hook:: pytest_warning_recorded .. autofunction:: pytest_warning_recorded Central hook for reporting about test execution: +.. hook:: pytest_runtest_logreport .. autofunction:: pytest_runtest_logreport Assertion related hooks: +.. hook:: pytest_assertrepr_compare .. autofunction:: pytest_assertrepr_compare +.. hook:: pytest_assertion_pass .. autofunction:: pytest_assertion_pass @@ -777,10 +771,16 @@ Debugging/Interaction hooks There are few hooks which can be used for special reporting or interaction with exceptions: +.. hook:: pytest_internalerror .. autofunction:: pytest_internalerror +.. hook:: pytest_keyboard_interrupt .. autofunction:: pytest_keyboard_interrupt +.. hook:: pytest_exception_interact .. autofunction:: pytest_exception_interact +.. hook:: pytest_enter_pdb .. autofunction:: pytest_enter_pdb +.. hook:: pytest_leave_pdb +.. autofunction:: pytest_leave_pdb Objects @@ -1047,6 +1047,14 @@ Environment Variables Environment variables that can be used to change pytest's behavior. +.. envvar:: CI + +When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to ``BUILD_NUMBER`` variable. + +.. envvar:: BUILD_NUMBER + +When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to CI variable. + .. envvar:: PYTEST_ADDOPTS This contains a command-line (parsed by the py:mod:`shlex` module) that will be **prepended** to the command line given @@ -1133,6 +1141,12 @@ Custom warnings generated in some situations such as improper usage or deprecate .. autoclass:: pytest.PytestExperimentalApiWarning :show-inheritance: +.. autoclass:: pytest.PytestReturnNotNoneWarning + :show-inheritance: + +.. autoclass:: pytest.PytestRemovedIn8Warning + :show-inheritance: + .. autoclass:: pytest.PytestUnhandledCoroutineWarning :show-inheritance: @@ -1154,9 +1168,10 @@ Consult the :ref:`internal-warnings` section in the documentation for more infor Configuration Options --------------------- -Here is a list of builtin configuration options that may be written in a ``pytest.ini``, ``pyproject.toml``, ``tox.ini`` or ``setup.cfg`` -file, usually located at the root of your repository. To see each file format in details, see -:ref:`config file formats`. +Here is a list of builtin configuration options that may be written in a ``pytest.ini`` (or ``.pytest.ini``), +``pyproject.toml``, ``tox.ini``, or ``setup.cfg`` file, usually located at the root of your repository. + +To see each file format in details, see :ref:`config file formats`. .. warning:: Usage of ``setup.cfg`` is not recommended except for very simple use cases. ``.cfg`` @@ -1455,7 +1470,7 @@ passed multiple times. The expected format is ``name=value``. For example:: - Sets a file name relative to the ``pytest.ini`` file where log messages should be written to, in addition + Sets a file name relative to the current working directory where log messages should be written to, in addition to the other logging facilities that are active. .. code-block:: ini @@ -1702,6 +1717,8 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets list of directories that should be searched for tests when no specific directories, files or test ids are given in the command line when executing pytest from the :ref:`rootdir ` directory. + File system paths may use shell-style wildcards, including the recursive + ``**`` pattern. Useful when all project tests are in a known location to speed up test collection and to avoid picking up undesired tests by accident. @@ -1754,8 +1771,8 @@ All the command-line flags can be obtained by running ``pytest --help``:: file_or_dir general: - -k EXPRESSION only run tests which match the given substring - expression. An expression is a python evaluatable + -k EXPRESSION Only run tests which match the given substring + expression. An expression is a Python evaluatable expression where all names are substring-matched against test names and their parent classes. Example: -k 'test_method or test_other' matches all @@ -1769,93 +1786,93 @@ All the command-line flags can be obtained by running ``pytest --help``:: 'extra_keyword_matches' set, as well as functions which have names assigned directly to them. The matching is case-insensitive. - -m MARKEXPR only run tests matching given mark expression. - For example: -m 'mark1 and not mark2'. + -m MARKEXPR Only run tests matching given mark expression. For + example: -m 'mark1 and not mark2'. --markers show markers (builtin, plugin and per-project ones). - -x, --exitfirst exit instantly on first error or failed test. + -x, --exitfirst Exit instantly on first error or failed test --fixtures, --funcargs - show available fixtures, sorted by plugin appearance + Show available fixtures, sorted by plugin appearance (fixtures with leading '_' are only shown with '-v') - --fixtures-per-test show fixtures per test - --pdb start the interactive Python debugger on errors or - KeyboardInterrupt. + --fixtures-per-test Show fixtures per test + --pdb Start the interactive Python debugger on errors or + KeyboardInterrupt --pdbcls=modulename:classname - specify a custom interactive Python debugger for use + Specify a custom interactive Python debugger for use with --pdb.For example: --pdbcls=IPython.terminal.debugger:TerminalPdb - --trace Immediately break when running each test. - --capture=method per-test capturing method: one of fd|sys|no|tee-sys. - -s shortcut for --capture=no. - --runxfail report the results of xfail tests as if they were + --trace Immediately break when running each test + --capture=method Per-test capturing method: one of fd|sys|no|tee-sys + -s Shortcut for --capture=no + --runxfail Report the results of xfail tests as if they were not marked - --lf, --last-failed rerun only the tests that failed at the last run (or + --lf, --last-failed Rerun only the tests that failed at the last run (or all if none failed) - --ff, --failed-first run all tests, but run the last failures first. - This may re-order tests and thus lead to repeated - fixture setup/teardown. - --nf, --new-first run tests from new files first, then the rest of the + --ff, --failed-first Run all tests, but run the last failures first. This + may re-order tests and thus lead to repeated fixture + setup/teardown. + --nf, --new-first Run tests from new files first, then the rest of the tests sorted by file mtime --cache-show=[CACHESHOW] - show cache contents, don't perform collection or + Show cache contents, don't perform collection or tests. Optional argument: glob (default: '*'). - --cache-clear remove all cache contents at start of test run. + --cache-clear Remove all cache contents at start of test run --lfnf={all,none}, --last-failed-no-failures={all,none} - which tests to run with no previously (known) - failures. - --sw, --stepwise exit on test failure and continue from last failing + Which tests to run with no previously (known) + failures + --sw, --stepwise Exit on test failure and continue from last failing test next time --sw-skip, --stepwise-skip - ignore the first failing test but stop on the next - failing test. - implicitly enables --stepwise. + Ignore the first failing test but stop on the next + failing test. Implicitly enables --stepwise. - reporting: - --durations=N show N slowest setup/test durations (N=0 for all). + Reporting: + --durations=N Show N slowest setup/test durations (N=0 for all) --durations-min=N Minimal duration in seconds for inclusion in slowest - list. Default 0.005 - -v, --verbose increase verbosity. - --no-header disable header - --no-summary disable summary - -q, --quiet decrease verbosity. - --verbosity=VERBOSE set verbosity. Default is 0. - -r chars show extra test summary info as specified by chars: + list. Default: 0.005. + -v, --verbose Increase verbosity + --no-header Disable header + --no-summary Disable summary + -q, --quiet Decrease verbosity + --verbosity=VERBOSE Set verbosity. Default: 0. + -r chars Show extra test summary info as specified by chars: (f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed, (p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. (w)arnings are enabled by default (see --disable-warnings), 'N' can be used to reset the list. (default: 'fE'). --disable-warnings, --disable-pytest-warnings - disable warnings summary - -l, --showlocals show locals in tracebacks (disabled by default). - --tb=style traceback print mode - (auto/long/short/line/native/no). + Disable warnings summary + -l, --showlocals Show locals in tracebacks (disabled by default) + --no-showlocals Hide locals in tracebacks (negate --showlocals + passed through addopts) + --tb=style Traceback print mode + (auto/long/short/line/native/no) --show-capture={no,stdout,stderr,log,all} Controls how captured stdout/stderr/log is shown on - failed tests. Default is 'all'. - --full-trace don't cut any tracebacks (default is to cut). - --color=color color terminal output (yes/no/auto). + failed tests. Default: all. + --full-trace Don't cut any tracebacks (default is to cut) + --color=color Color terminal output (yes/no/auto) --code-highlight={yes,no} Whether code should be highlighted (only if --color - is also enabled) - --pastebin=mode send failed|all info to bpaste.net pastebin service. - --junit-xml=path create junit-xml style report file at given path. - --junit-prefix=str prepend prefix to classnames in junit-xml output + is also enabled). Default: yes. + --pastebin=mode Send failed|all info to bpaste.net pastebin service + --junit-xml=path Create junit-xml style report file at given path + --junit-prefix=str Prepend prefix to classnames in junit-xml output pytest-warnings: -W PYTHONWARNINGS, --pythonwarnings=PYTHONWARNINGS - set which warnings to report, see -W option of - python itself. - --maxfail=num exit after first num failures or errors. - --strict-config any warnings encountered while parsing the `pytest` - section of the configuration file raise errors. - --strict-markers markers not registered in the `markers` section of - the configuration file raise errors. - --strict (deprecated) alias to --strict-markers. - -c file load configuration from `file` instead of trying to - locate one of the implicit configuration files. + Set which warnings to report, see -W option of + Python itself + --maxfail=num Exit after first num failures or errors + --strict-config Any warnings encountered while parsing the `pytest` + section of the configuration file raise errors + --strict-markers Markers not registered in the `markers` section of + the configuration file raise errors + --strict (Deprecated) alias to --strict-markers + -c file Load configuration from `file` instead of trying to + locate one of the implicit configuration files --continue-on-collection-errors - Force test execution even if collection errors - occur. + Force test execution even if collection errors occur --rootdir=ROOTDIR Define root directory for tests. Can be relative path: 'root_dir', './root_dir', 'root_dir/another_dir/'; absolute path: @@ -1863,123 +1880,120 @@ All the command-line flags can be obtained by running ``pytest --help``:: '$HOME/root_dir'. collection: - --collect-only, --co only collect tests, don't execute them. - --pyargs try to interpret all arguments as python packages. - --ignore=path ignore path during collection (multi-allowed). - --ignore-glob=path ignore path pattern during collection (multi- - allowed). + --collect-only, --co Only collect tests, don't execute them + --pyargs Try to interpret all arguments as Python packages + --ignore=path Ignore path during collection (multi-allowed) + --ignore-glob=path Ignore path pattern during collection (multi- + allowed) --deselect=nodeid_prefix - deselect item (via node id prefix) during collection - (multi-allowed). - --confcutdir=dir only load conftest.py's relative to specified dir. - --noconftest Don't load any conftest.py files. - --keep-duplicates Keep duplicate tests. + Deselect item (via node id prefix) during collection + (multi-allowed) + --confcutdir=dir Only load conftest.py's relative to specified dir + --noconftest Don't load any conftest.py files + --keep-duplicates Keep duplicate tests --collect-in-virtualenv Don't ignore tests in a local virtualenv directory --import-mode={prepend,append,importlib} - prepend/append to sys.path when importing test - modules and conftest files, default is to prepend. - --doctest-modules run doctests in all .py modules + Prepend/append to sys.path when importing test + modules and conftest files. Default: prepend. + --doctest-modules Run doctests in all .py modules --doctest-report={none,cdiff,ndiff,udiff,only_first_failure} - choose another output format for diffs on doctest + Choose another output format for diffs on doctest failure - --doctest-glob=pat doctests file matching pattern, default: test*.txt + --doctest-glob=pat Doctests file matching pattern, default: test*.txt --doctest-ignore-import-errors - ignore doctest ImportErrors + Ignore doctest ImportErrors --doctest-continue-on-failure - for a given doctest, continue to run after the first + For a given doctest, continue to run after the first failure test session debugging and configuration: - --basetemp=dir base temporary directory for this test run.(warning: - this directory is removed if it exists) - -V, --version display pytest version and information about + --basetemp=dir Base temporary directory for this test run. + (Warning: this directory is removed if it exists.) + -V, --version Display pytest version and information about plugins. When given twice, also display information about plugins. - -h, --help show help message and configuration info - -p name early-load given plugin module name or entry point - (multi-allowed). - To avoid loading of plugins, use the `no:` prefix, - e.g. `no:doctest`. - --trace-config trace considerations of conftest.py files. + -h, --help Show help message and configuration info + -p name Early-load given plugin module name or entry point + (multi-allowed). To avoid loading of plugins, use + the `no:` prefix, e.g. `no:doctest`. + --trace-config Trace considerations of conftest.py files --debug=[DEBUG_FILE_NAME] - store internal tracing debug information in this log - file. - This file is opened with 'w' and truncated as a - result, care advised. - Defaults to 'pytestdebug.log'. + Store internal tracing debug information in this log + file. This file is opened with 'w' and truncated as + a result, care advised. Default: pytestdebug.log. -o OVERRIDE_INI, --override-ini=OVERRIDE_INI - override ini option with "option=value" style, e.g. + Override ini option with "option=value" style, e.g. `-o xfail_strict=True -o cache_dir=cache`. --assert=MODE Control assertion debugging tools. 'plain' performs no assertion debugging. 'rewrite' (the default) rewrites assert statements in test modules on import to provide assert expression information. - --setup-only only setup fixtures, do not execute tests. - --setup-show show setup of fixtures while executing tests. - --setup-plan show what fixtures and tests would be executed but - don't execute anything. + --setup-only Only setup fixtures, do not execute tests + --setup-show Show setup of fixtures while executing tests + --setup-plan Show what fixtures and tests would be executed but + don't execute anything logging: - --log-level=LEVEL level of messages to catch/display. - Not set by default, so it depends on the root/parent - log handler's effective level, where it is "WARNING" - by default. + --log-level=LEVEL Level of messages to catch/display. Not set by + default, so it depends on the root/parent log + handler's effective level, where it is "WARNING" by + default. --log-format=LOG_FORMAT - log format as used by the logging module. + Log format used by the logging module --log-date-format=LOG_DATE_FORMAT - log date format as used by the logging module. + Log date format used by the logging module --log-cli-level=LOG_CLI_LEVEL - cli logging level. + CLI logging level --log-cli-format=LOG_CLI_FORMAT - log format as used by the logging module. + Log format used by the logging module --log-cli-date-format=LOG_CLI_DATE_FORMAT - log date format as used by the logging module. - --log-file=LOG_FILE path to a file when logging will be written to. + Log date format used by the logging module + --log-file=LOG_FILE Path to a file when logging will be written to --log-file-level=LOG_FILE_LEVEL - log file logging level. + Log file logging level --log-file-format=LOG_FILE_FORMAT - log format as used by the logging module. + Log format used by the logging module --log-file-date-format=LOG_FILE_DATE_FORMAT - log date format as used by the logging module. + Log date format used by the logging module --log-auto-indent=LOG_AUTO_INDENT Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer. [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found: - markers (linelist): markers for test functions + markers (linelist): Markers for test functions empty_parameter_set_mark (string): - default marker for empty parametersets - norecursedirs (args): directory patterns to avoid for recursion - testpaths (args): directories to search for tests when no files or - directories are given in the command line. + Default marker for empty parametersets + norecursedirs (args): Directory patterns to avoid for recursion + testpaths (args): Directories to search for tests when no files or + directories are given on the command line filterwarnings (linelist): Each line specifies a pattern for warnings.filterwarnings. Processed after -W/--pythonwarnings. - usefixtures (args): list of default fixtures to be used with this + usefixtures (args): List of default fixtures to be used with this project - python_files (args): glob-style file patterns for Python test module + python_files (args): Glob-style file patterns for Python test module discovery python_classes (args): - prefixes or glob names for Python test class + Prefixes or glob names for Python test class discovery python_functions (args): - prefixes or glob names for Python test function and + Prefixes or glob names for Python test function and method discovery disable_test_id_escaping_and_forfeit_all_rights_to_community_support (bool): - disable string escape non-ascii characters, might + Disable string escape non-ASCII characters, might cause unwanted side effects(use at your own risk) console_output_style (string): - console output: "classic", or with additional + Console output: "classic", or with additional progress information ("progress" (percentage) | - "count"). - xfail_strict (bool): default for the strict parameter of xfail markers + "count") + xfail_strict (bool): Default for the strict parameter of xfail markers when not given explicitly (default: False) enable_assertion_pass_hook (bool): - Enables the pytest_assertion_pass hook.Make sure to + Enables the pytest_assertion_pass hook. Make sure to delete any previously generated pyc cache files. junit_suite_name (string): Test suite name for JUnit report @@ -1994,44 +2008,45 @@ All the command-line flags can be obtained by running ``pytest --help``:: junit_family (string): Emit XML for schema: one of legacy|xunit1|xunit2 doctest_optionflags (args): - option flags for doctests + Option flags for doctests doctest_encoding (string): - encoding used for doctest files - cache_dir (string): cache directory path. - log_level (string): default value for --log-level - log_format (string): default value for --log-format + Encoding used for doctest files + cache_dir (string): Cache directory path + log_level (string): Default value for --log-level + log_format (string): Default value for --log-format log_date_format (string): - default value for --log-date-format - log_cli (bool): enable log display during test run (also known as - "live logging"). + Default value for --log-date-format + log_cli (bool): Enable log display during test run (also known as + "live logging") log_cli_level (string): - default value for --log-cli-level + Default value for --log-cli-level log_cli_format (string): - default value for --log-cli-format + Default value for --log-cli-format log_cli_date_format (string): - default value for --log-cli-date-format - log_file (string): default value for --log-file + Default value for --log-cli-date-format + log_file (string): Default value for --log-file log_file_level (string): - default value for --log-file-level + Default value for --log-file-level log_file_format (string): - default value for --log-file-format + Default value for --log-file-format log_file_date_format (string): - default value for --log-file-date-format + Default value for --log-file-date-format log_auto_indent (string): - default value for --log-auto-indent + Default value for --log-auto-indent + pythonpath (paths): Add paths to sys.path faulthandler_timeout (string): Dump the traceback of all threads if a test takes - more than TIMEOUT seconds to finish. - addopts (args): extra command line options - minversion (string): minimally required pytest version + more than TIMEOUT seconds to finish + addopts (args): Extra command line options + minversion (string): Minimally required pytest version required_plugins (args): - plugins that must be present for pytest to run + Plugins that must be present for pytest to run - environment variables: - PYTEST_ADDOPTS extra command line options - PYTEST_PLUGINS comma-separated plugins to load during startup - PYTEST_DISABLE_PLUGIN_AUTOLOAD set to disable plugin auto-loading - PYTEST_DEBUG set to enable debug tracing of pytest's internals + Environment variables: + PYTEST_ADDOPTS Extra command line options + PYTEST_PLUGINS Comma-separated plugins to load during startup + PYTEST_DISABLE_PLUGIN_AUTOLOAD Set to disable plugin auto-loading + PYTEST_DEBUG Set to enable debug tracing of pytest's internals to see available markers type: pytest --markers diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index 5b49cb7fccc..b6059723cd5 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -1,7 +1,11 @@ pallets-sphinx-themes pluggy>=1.0 -pygments-pytest>=2.2.0 +pygments-pytest>=2.3.0 sphinx-removed-in>=0.2.0 -sphinx>=3.1,<4 +sphinx>=5,<6 sphinxcontrib-trio sphinxcontrib-svg2pdfconverter +# Pin packaging because it no longer handles 'latest' version, which +# is the version that is assigned to the docs. +# See https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045. +packaging <22 diff --git a/doc/en/talks.rst b/doc/en/talks.rst index 6843c82bab5..b9b153a792e 100644 --- a/doc/en/talks.rst +++ b/doc/en/talks.rst @@ -11,9 +11,16 @@ Books - `Python Testing with pytest, by Brian Okken (2017) `_. +- `Python Testing with pytest, Second Edition, by Brian Okken (2022) + `_. + Talks and blog postings --------------------------------------------- +- Training: `pytest - simple, rapid and fun testing with Python `_, Florian Bruhin, PyConDE 2022 + +- `pytest: Simple, rapid and fun testing with Python, `_ (@ 4:22:32), Florian Bruhin, WeAreDevelopers World Congress 2021 + - Webinar: `pytest: Test Driven Development für Python (German) `_, Florian Bruhin, via mylearning.ch, 2020 - Webinar: `Simplify Your Tests with Fixtures `_, Oliver Bestwalter, via JetBrains, 2020 diff --git a/extra/setup-py.test/setup.py b/extra/setup-py.test/setup.py deleted file mode 100644 index d0560ce1f5f..00000000000 --- a/extra/setup-py.test/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -import sys -from distutils.core import setup - -if __name__ == "__main__": - if "sdist" not in sys.argv[1:]: - raise ValueError("please use 'pytest' pypi package instead of 'py.test'") - setup( - name="py.test", - version="0.0", - description="please use 'pytest' for installation", - ) diff --git a/pyproject.toml b/pyproject.toml index 5d32b755c74..a4139a5c051 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,6 @@ requires = [ # sync with setup.py until we discard non-pep-517/518 "setuptools>=45.0", "setuptools-scm[toml]>=6.2.3", - "wheel", ] build-backend = "setuptools.build_meta" @@ -28,8 +27,6 @@ filterwarnings = [ "default:the imp module is deprecated in favour of importlib:DeprecationWarning:nose.*", # distutils is deprecated in 3.10, scheduled for removal in 3.12 "ignore:The distutils package is deprecated:DeprecationWarning", - # produced by python3.6/site.py itself (3.6.7 on Travis, could not trigger it with 3.6.8)." - "ignore:.*U.*mode is deprecated:DeprecationWarning:(?!(pytest|_pytest))", # produced by pytest-xdist "ignore:.*type argument to addoption.*:DeprecationWarning", # produced on execnet (pytest-xdist) @@ -40,6 +37,9 @@ filterwarnings = [ # Those are caught/handled by pyupgrade, and not easy to filter with the # module being the filename (with .py removed). "default:invalid escape sequence:DeprecationWarning", + # ignore not yet fixed warnings for hook markers + "default:.*not marked using pytest.hook.*", + "ignore:.*not marked using pytest.hook.*::xdist.*", # ignore use of unregistered marks, because we use many to test the implementation "ignore::_pytest.warning_types.PytestUnknownMarkWarning", # https://github.com/benjaminp/six/issues/341 @@ -113,4 +113,9 @@ template = "changelog/_template.rst" showcontent = true [tool.black] -target-version = ['py36'] +target-version = ['py37'] + +# check-wheel-contents is executed by the build-and-inspect-python-package action. +[tool.check-wheel-contents] +# W009: Wheel contains multiple toplevel library entries +ignore = "W009" diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py index f16e7b9625c..7a80de7edaa 100644 --- a/scripts/prepare-release-pr.py +++ b/scripts/prepare-release-pr.py @@ -88,7 +88,9 @@ def prepare_release_pr( print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.") - if prerelease: + if is_major: + template_name = "release.major.rst" + elif prerelease: template_name = "release.pre.rst" elif is_feature_release: template_name = "release.minor.rst" @@ -104,6 +106,7 @@ def prepare_release_pr( "--", version, template_name, + release_branch, # doc_version "--skip-check-links", ] print("Running", " ".join(cmdline)) diff --git a/scripts/release.major.rst b/scripts/release.major.rst new file mode 100644 index 00000000000..76e447f0c6d --- /dev/null +++ b/scripts/release.major.rst @@ -0,0 +1,24 @@ +pytest-{version} +======================================= + +The pytest team is proud to announce the {version} release! + +This release contains new features, improvements, bug fixes, and breaking changes, so users +are encouraged to take a look at the CHANGELOG carefully: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all of the contributors to this release: + +{contributors} + +Happy testing, +The pytest Development Team diff --git a/scripts/release.minor.rst b/scripts/release.minor.rst index 76e447f0c6d..9a06d3d4140 100644 --- a/scripts/release.minor.rst +++ b/scripts/release.minor.rst @@ -3,8 +3,8 @@ pytest-{version} The pytest team is proud to announce the {version} release! -This release contains new features, improvements, bug fixes, and breaking changes, so users -are encouraged to take a look at the CHANGELOG carefully: +This release contains new features, improvements, and bug fixes, +the full list of changes is available in the changelog: https://docs.pytest.org/en/stable/changelog.html diff --git a/scripts/release.pre.rst b/scripts/release.pre.rst index f0b054de3fc..960fae7e4f6 100644 --- a/scripts/release.pre.rst +++ b/scripts/release.pre.rst @@ -19,7 +19,7 @@ You can upgrade from PyPI via: Users are encouraged to take a look at the CHANGELOG carefully: - https://docs.pytest.org/en/stable/changelog.html + https://docs.pytest.org/en/{doc_version}/changelog.html Thanks to all the contributors to this release: diff --git a/scripts/release.py b/scripts/release.py index 19608991b61..19fef428428 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -10,7 +10,7 @@ from colorama import init -def announce(version, template_name): +def announce(version, template_name, doc_version): """Generates a new release announcement entry in the docs.""" # Get our list of authors stdout = check_output(["git", "describe", "--abbrev=0", "--tags"]) @@ -31,7 +31,9 @@ def announce(version, template_name): ) contributors_text = "\n".join(f"* {name}" for name in sorted(contributors)) + "\n" - text = template_text.format(version=version, contributors=contributors_text) + text = template_text.format( + version=version, contributors=contributors_text, doc_version=doc_version + ) target = Path(__file__).parent.joinpath(f"../doc/en/announce/release-{version}.rst") target.write_text(text, encoding="UTF-8") @@ -82,9 +84,9 @@ def check_links(): check_call(["tox", "-e", "docs-checklinks"]) -def pre_release(version, template_name, *, skip_check_links): +def pre_release(version, template_name, doc_version, *, skip_check_links): """Generates new docs, release announcements and creates a local tag.""" - announce(version, template_name) + announce(version, template_name, doc_version) regen(version) changelog(version, write_out=True) fix_formatting() @@ -112,11 +114,15 @@ def main(): parser.add_argument( "template_name", help="Name of template file to use for release announcement" ) + parser.add_argument( + "doc_version", help="For prereleases, the version to link to in the docs" + ) parser.add_argument("--skip-check-links", action="store_true", default=False) options = parser.parse_args() pre_release( options.version, options.template_name, + options.doc_version, skip_check_links=options.skip_check_links, ) diff --git a/setup.cfg b/setup.cfg index 26a5d2e63e5..ceb02877af5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,11 +17,11 @@ classifiers = Operating System :: POSIX Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Topic :: Software Development :: Libraries Topic :: Software Development :: Testing Topic :: Utilities @@ -37,21 +37,22 @@ packages = _pytest _pytest._code _pytest._io + _pytest._py _pytest.assertion _pytest.config _pytest.mark pytest +py_modules = py install_requires = attrs>=19.2.0 iniconfig packaging pluggy>=0.12,<2.0 - py>=1.8.2 - tomli>=1.0.0 - atomicwrites>=1.0;sys_platform=="win32" colorama;sys_platform=="win32" + exceptiongroup>=1.0.0rc8;python_version<"3.11" importlib-metadata>=0.12;python_version<"3.8" -python_requires = >=3.6 + tomli>=1.0.0;python_version<"3.11" +python_requires = >=3.7 package_dir = =src setup_requires = diff --git a/src/_pytest/_argcomplete.py b/src/_pytest/_argcomplete.py index 41d9d9407c7..120f09ff68f 100644 --- a/src/_pytest/_argcomplete.py +++ b/src/_pytest/_argcomplete.py @@ -108,7 +108,6 @@ def __call__(self, prefix: str, **kwargs: Any) -> List[str]: def try_argcomplete(parser: argparse.ArgumentParser) -> None: argcomplete.autocomplete(parser, always_complete_options=False) - else: def try_argcomplete(parser: argparse.ArgumentParser) -> None: diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index b19ee7c64d9..97985def1c3 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -1,5 +1,6 @@ import ast import inspect +import os import re import sys import traceback @@ -55,6 +56,9 @@ _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] +if sys.version_info[:2] < (3, 11): + from exceptiongroup import BaseExceptionGroup + class Code: """Wrapper around Python code objects.""" @@ -343,10 +347,10 @@ def f(cur: TracebackType) -> Iterable[TracebackEntry]: def cut( self, - path: Optional[Union[Path, str]] = None, + path: Optional[Union["os.PathLike[str]", str]] = None, lineno: Optional[int] = None, firstlineno: Optional[int] = None, - excludepath: Optional[Path] = None, + excludepath: Optional["os.PathLike[str]"] = None, ) -> "Traceback": """Return a Traceback instance wrapping part of this Traceback. @@ -357,15 +361,17 @@ def cut( for formatting reasons (removing some uninteresting bits that deal with handling of the exception/traceback). """ + path_ = None if path is None else os.fspath(path) + excludepath_ = None if excludepath is None else os.fspath(excludepath) for x in self: code = x.frame.code codepath = code.path - if path is not None and codepath != path: + if path is not None and str(codepath) != path_: continue if ( excludepath is not None and isinstance(codepath, Path) - and excludepath in codepath.parents + and excludepath_ in (str(p) for p in codepath.parents) # type: ignore[operator] ): continue if lineno is not None and x.lineno != lineno: @@ -669,10 +675,11 @@ def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": If it matches `True` is returned, otherwise an `AssertionError` is raised. """ __tracebackhide__ = True - msg = "Regex pattern {!r} does not match {!r}." - if regexp == str(self.value): - msg += " Did you mean to `re.escape()` the regex?" - assert re.search(regexp, str(self.value)), msg.format(regexp, str(self.value)) + value = str(self.value) + msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" + if regexp == value: + msg += "\n Did you mean to `re.escape()` the regex?" + assert re.search(regexp, value), msg # Return True to allow for "assert excinfo.match()". return True @@ -895,7 +902,7 @@ def _truncate_recursive_traceback( max_frames=max_frames, total=len(traceback), ) - # Type ignored because adding two instaces of a List subtype + # Type ignored because adding two instances of a List subtype # currently incorrectly has type List instead of the subtype. traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore else: @@ -920,7 +927,21 @@ def repr_excinfo( while e is not None and id(e) not in seen: seen.add(id(e)) if excinfo_: - reprtraceback = self.repr_traceback(excinfo_) + # Fall back to native traceback as a temporary workaround until + # full support for exception groups added to ExceptionInfo. + # See https://github.com/pytest-dev/pytest/issues/9159 + if isinstance(e, BaseExceptionGroup): + reprtraceback: Union[ + ReprTracebackNative, ReprTraceback + ] = ReprTracebackNative( + traceback.format_exception( + type(excinfo_.value), + excinfo_.value, + excinfo_.traceback[0]._rawentry, + ) + ) + else: + reprtraceback = self.repr_traceback(excinfo_) reprcrash: Optional[ReprFileLocation] = ( excinfo_._getreprcrash() if self.style != "value" else None ) diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 6f54057c0a9..208cfb80037 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -149,6 +149,11 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i values: List[int] = [] for x in ast.walk(node): if isinstance(x, (ast.stmt, ast.ExceptHandler)): + # Before Python 3.8, the lineno of a decorated class or function pointed at the decorator. + # Since Python 3.8, the lineno points to the class/def, so need to include the decorators. + if isinstance(x, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + for d in x.decorator_list: + values.append(d.lineno - 1) values.append(x.lineno - 1) for name in ("finalbody", "orelse"): val: Optional[List[ast.stmt]] = getattr(x, name, None) diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index e7ff5cab203..c701872238c 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -41,7 +41,7 @@ class SafeRepr(reprlib.Repr): information on exceptions raised during the call. """ - def __init__(self, maxsize: Optional[int]) -> None: + def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None: """ :param maxsize: If not None, will truncate the resulting repr to that specific size, using ellipsis @@ -54,10 +54,15 @@ def __init__(self, maxsize: Optional[int]) -> None: # truncation. self.maxstring = maxsize if maxsize is not None else 1_000_000_000 self.maxsize = maxsize + self.use_ascii = use_ascii def repr(self, x: object) -> str: try: - s = super().repr(x) + if self.use_ascii: + s = ascii(x) + else: + s = super().repr(x) + except (KeyboardInterrupt, SystemExit): raise except BaseException as exc: @@ -94,7 +99,9 @@ def safeformat(obj: object) -> str: DEFAULT_REPR_MAX_SIZE = 240 -def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str: +def saferepr( + obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False +) -> str: """Return a size-limited safe repr-string for the given object. Failing __repr__ functions of user instances will be represented @@ -104,7 +111,27 @@ def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str This function is a wrapper around the Repr/reprlib functionality of the stdlib. """ - return SafeRepr(maxsize).repr(obj) + + return SafeRepr(maxsize, use_ascii).repr(obj) + + +def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str: + """Return an unlimited-size safe repr-string for the given object. + + As with saferepr, failing __repr__ functions of user instances + will be represented with a short exception info. + + This function is a wrapper around simple repr. + + Note: a cleaner solution would be to alter ``saferepr``this way + when maxsize=None, but that might affect some other code. + """ + try: + if use_ascii: + return ascii(obj) + return repr(obj) + except Exception as exc: + return _format_repr_exception(exc, obj) class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter): diff --git a/src/_pytest/_py/__init__.py b/src/_pytest/_py/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/_pytest/_py/error.py b/src/_pytest/_py/error.py new file mode 100644 index 00000000000..0b8f2d535ef --- /dev/null +++ b/src/_pytest/_py/error.py @@ -0,0 +1,109 @@ +"""create errno-specific classes for IO or os calls.""" +from __future__ import annotations + +import errno +import os +import sys +from typing import Callable +from typing import TYPE_CHECKING +from typing import TypeVar + +if TYPE_CHECKING: + from typing_extensions import ParamSpec + + P = ParamSpec("P") + +R = TypeVar("R") + + +class Error(EnvironmentError): + def __repr__(self) -> str: + return "{}.{} {!r}: {} ".format( + self.__class__.__module__, + self.__class__.__name__, + self.__class__.__doc__, + " ".join(map(str, self.args)), + # repr(self.args) + ) + + def __str__(self) -> str: + s = "[{}]: {}".format( + self.__class__.__doc__, + " ".join(map(str, self.args)), + ) + return s + + +_winerrnomap = { + 2: errno.ENOENT, + 3: errno.ENOENT, + 17: errno.EEXIST, + 18: errno.EXDEV, + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable + 22: errno.ENOTDIR, + 20: errno.ENOTDIR, + 267: errno.ENOTDIR, + 5: errno.EACCES, # anything better? +} + + +class ErrorMaker: + """lazily provides Exception classes for each possible POSIX errno + (as defined per the 'errno' module). All such instances + subclass EnvironmentError. + """ + + _errno2class: dict[int, type[Error]] = {} + + def __getattr__(self, name: str) -> type[Error]: + if name[0] == "_": + raise AttributeError(name) + eno = getattr(errno, name) + cls = self._geterrnoclass(eno) + setattr(self, name, cls) + return cls + + def _geterrnoclass(self, eno: int) -> type[Error]: + try: + return self._errno2class[eno] + except KeyError: + clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,)) + errorcls = type( + clsname, + (Error,), + {"__module__": "py.error", "__doc__": os.strerror(eno)}, + ) + self._errno2class[eno] = errorcls + return errorcls + + def checked_call( + self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs + ) -> R: + """Call a function and raise an errno-exception if applicable.""" + __tracebackhide__ = True + try: + return func(*args, **kwargs) + except Error: + raise + except OSError as value: + if not hasattr(value, "errno"): + raise + errno = value.errno + if sys.platform == "win32": + try: + cls = self._geterrnoclass(_winerrnomap[errno]) + except KeyError: + raise value + else: + # we are not on Windows, or we got a proper OSError + cls = self._geterrnoclass(errno) + + raise cls(f"{func.__name__}{args!r}") + + +_error_maker = ErrorMaker() +checked_call = _error_maker.checked_call + + +def __getattr__(attr: str) -> type[Error]: + return getattr(_error_maker, attr) # type: ignore[no-any-return] diff --git a/src/_pytest/_py/path.py b/src/_pytest/_py/path.py new file mode 100644 index 00000000000..00f1515238b --- /dev/null +++ b/src/_pytest/_py/path.py @@ -0,0 +1,1474 @@ +"""local path implementation.""" +from __future__ import annotations + +import atexit +import fnmatch +import importlib.util +import io +import os +import posixpath +import sys +import uuid +import warnings +from contextlib import contextmanager +from os.path import abspath +from os.path import dirname +from os.path import exists +from os.path import isabs +from os.path import isdir +from os.path import isfile +from os.path import islink +from os.path import normpath +from stat import S_ISDIR +from stat import S_ISLNK +from stat import S_ISREG +from typing import Any +from typing import Callable +from typing import overload +from typing import TYPE_CHECKING + +from . import error + +if TYPE_CHECKING: + from typing import Literal + +# Moved from local.py. +iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt") + + +class Checkers: + _depend_on_existence = "exists", "link", "dir", "file" + + def __init__(self, path): + self.path = path + + def dotfile(self): + return self.path.basename.startswith(".") + + def ext(self, arg): + if not arg.startswith("."): + arg = "." + arg + return self.path.ext == arg + + def basename(self, arg): + return self.path.basename == arg + + def basestarts(self, arg): + return self.path.basename.startswith(arg) + + def relto(self, arg): + return self.path.relto(arg) + + def fnmatch(self, arg): + return self.path.fnmatch(arg) + + def endswith(self, arg): + return str(self.path).endswith(arg) + + def _evaluate(self, kw): + from .._code.source import getrawcode + + for name, value in kw.items(): + invert = False + meth = None + try: + meth = getattr(self, name) + except AttributeError: + if name[:3] == "not": + invert = True + try: + meth = getattr(self, name[3:]) + except AttributeError: + pass + if meth is None: + raise TypeError(f"no {name!r} checker available for {self.path!r}") + try: + if getrawcode(meth).co_argcount > 1: + if (not meth(value)) ^ invert: + return False + else: + if bool(value) ^ bool(meth()) ^ invert: + return False + except (error.ENOENT, error.ENOTDIR, error.EBUSY): + # EBUSY feels not entirely correct, + # but its kind of necessary since ENOMEDIUM + # is not accessible in python + for name in self._depend_on_existence: + if name in kw: + if kw.get(name): + return False + name = "not" + name + if name in kw: + if not kw.get(name): + return False + return True + + _statcache: Stat + + def _stat(self) -> Stat: + try: + return self._statcache + except AttributeError: + try: + self._statcache = self.path.stat() + except error.ELOOP: + self._statcache = self.path.lstat() + return self._statcache + + def dir(self): + return S_ISDIR(self._stat().mode) + + def file(self): + return S_ISREG(self._stat().mode) + + def exists(self): + return self._stat() + + def link(self): + st = self.path.lstat() + return S_ISLNK(st.mode) + + +class NeverRaised(Exception): + pass + + +class Visitor: + def __init__(self, fil, rec, ignore, bf, sort): + if isinstance(fil, str): + fil = FNMatcher(fil) + if isinstance(rec, str): + self.rec: Callable[[LocalPath], bool] = FNMatcher(rec) + elif not hasattr(rec, "__call__") and rec: + self.rec = lambda path: True + else: + self.rec = rec + self.fil = fil + self.ignore = ignore + self.breadthfirst = bf + self.optsort = sort and sorted or (lambda x: x) + + def gen(self, path): + try: + entries = path.listdir() + except self.ignore: + return + rec = self.rec + dirs = self.optsort( + [p for p in entries if p.check(dir=1) and (rec is None or rec(p))] + ) + if not self.breadthfirst: + for subdir in dirs: + for p in self.gen(subdir): + yield p + for p in self.optsort(entries): + if self.fil is None or self.fil(p): + yield p + if self.breadthfirst: + for subdir in dirs: + for p in self.gen(subdir): + yield p + + +class FNMatcher: + def __init__(self, pattern): + self.pattern = pattern + + def __call__(self, path): + pattern = self.pattern + + if ( + pattern.find(path.sep) == -1 + and iswin32 + and pattern.find(posixpath.sep) != -1 + ): + # Running on Windows, the pattern has no Windows path separators, + # and the pattern has one or more Posix path separators. Replace + # the Posix path separators with the Windows path separator. + pattern = pattern.replace(posixpath.sep, path.sep) + + if pattern.find(path.sep) == -1: + name = path.basename + else: + name = str(path) # path.strpath # XXX svn? + if not os.path.isabs(pattern): + pattern = "*" + path.sep + pattern + return fnmatch.fnmatch(name, pattern) + + +def map_as_list(func, iter): + return list(map(func, iter)) + + +class Stat: + if TYPE_CHECKING: + + @property + def size(self) -> int: + ... + + @property + def mtime(self) -> float: + ... + + def __getattr__(self, name: str) -> Any: + return getattr(self._osstatresult, "st_" + name) + + def __init__(self, path, osstatresult): + self.path = path + self._osstatresult = osstatresult + + @property + def owner(self): + if iswin32: + raise NotImplementedError("XXX win32") + import pwd + + entry = error.checked_call(pwd.getpwuid, self.uid) + return entry[0] + + @property + def group(self): + """Return group name of file.""" + if iswin32: + raise NotImplementedError("XXX win32") + import grp + + entry = error.checked_call(grp.getgrgid, self.gid) + return entry[0] + + def isdir(self): + return S_ISDIR(self._osstatresult.st_mode) + + def isfile(self): + return S_ISREG(self._osstatresult.st_mode) + + def islink(self): + self.path.lstat() + return S_ISLNK(self._osstatresult.st_mode) + + +def getuserid(user): + import pwd + + if not isinstance(user, int): + user = pwd.getpwnam(user)[2] + return user + + +def getgroupid(group): + import grp + + if not isinstance(group, int): + group = grp.getgrnam(group)[2] + return group + + +class LocalPath: + """Object oriented interface to os.path and other local filesystem + related information. + """ + + class ImportMismatchError(ImportError): + """raised on pyimport() if there is a mismatch of __file__'s""" + + sep = os.sep + + def __init__(self, path=None, expanduser=False): + """Initialize and return a local Path instance. + + Path can be relative to the current directory. + If path is None it defaults to the current working directory. + If expanduser is True, tilde-expansion is performed. + Note that Path instances always carry an absolute path. + Note also that passing in a local path object will simply return + the exact same path object. Use new() to get a new copy. + """ + if path is None: + self.strpath = error.checked_call(os.getcwd) + else: + try: + path = os.fspath(path) + except TypeError: + raise ValueError( + "can only pass None, Path instances " + "or non-empty strings to LocalPath" + ) + if expanduser: + path = os.path.expanduser(path) + self.strpath = abspath(path) + + if sys.platform != "win32": + + def chown(self, user, group, rec=0): + """Change ownership to the given user and group. + user and group may be specified by a number or + by a name. if rec is True change ownership + recursively. + """ + uid = getuserid(user) + gid = getgroupid(group) + if rec: + for x in self.visit(rec=lambda x: x.check(link=0)): + if x.check(link=0): + error.checked_call(os.chown, str(x), uid, gid) + error.checked_call(os.chown, str(self), uid, gid) + + def readlink(self) -> str: + """Return value of a symbolic link.""" + # https://github.com/python/mypy/issues/12278 + return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value] + + def mklinkto(self, oldname): + """Posix style hard link to another name.""" + error.checked_call(os.link, str(oldname), str(self)) + + def mksymlinkto(self, value, absolute=1): + """Create a symbolic link with the given value (pointing to another name).""" + if absolute: + error.checked_call(os.symlink, str(value), self.strpath) + else: + base = self.common(value) + # with posix local paths '/' is always a common base + relsource = self.__class__(value).relto(base) + reldest = self.relto(base) + n = reldest.count(self.sep) + target = self.sep.join(("..",) * n + (relsource,)) + error.checked_call(os.symlink, target, self.strpath) + + def __div__(self, other): + return self.join(os.fspath(other)) + + __truediv__ = __div__ # py3k + + @property + def basename(self): + """Basename part of path.""" + return self._getbyspec("basename")[0] + + @property + def dirname(self): + """Dirname part of path.""" + return self._getbyspec("dirname")[0] + + @property + def purebasename(self): + """Pure base name of the path.""" + return self._getbyspec("purebasename")[0] + + @property + def ext(self): + """Extension of the path (including the '.').""" + return self._getbyspec("ext")[0] + + def read_binary(self): + """Read and return a bytestring from reading the path.""" + with self.open("rb") as f: + return f.read() + + def read_text(self, encoding): + """Read and return a Unicode string from reading the path.""" + with self.open("r", encoding=encoding) as f: + return f.read() + + def read(self, mode="r"): + """Read and return a bytestring from reading the path.""" + with self.open(mode) as f: + return f.read() + + def readlines(self, cr=1): + """Read and return a list of lines from the path. if cr is False, the + newline will be removed from the end of each line.""" + mode = "r" + + if not cr: + content = self.read(mode) + return content.split("\n") + else: + f = self.open(mode) + try: + return f.readlines() + finally: + f.close() + + def load(self): + """(deprecated) return object unpickled from self.read()""" + f = self.open("rb") + try: + import pickle + + return error.checked_call(pickle.load, f) + finally: + f.close() + + def move(self, target): + """Move this path to target.""" + if target.relto(self): + raise error.EINVAL(target, "cannot move path into a subdirectory of itself") + try: + self.rename(target) + except error.EXDEV: # invalid cross-device link + self.copy(target) + self.remove() + + def fnmatch(self, pattern): + """Return true if the basename/fullname matches the glob-'pattern'. + + valid pattern characters:: + + * matches everything + ? matches any single character + [seq] matches any character in seq + [!seq] matches any char not in seq + + If the pattern contains a path-separator then the full path + is used for pattern matching and a '*' is prepended to the + pattern. + + if the pattern doesn't contain a path-separator the pattern + is only matched against the basename. + """ + return FNMatcher(pattern)(self) + + def relto(self, relpath): + """Return a string which is the relative part of the path + to the given 'relpath'. + """ + if not isinstance(relpath, (str, LocalPath)): + raise TypeError(f"{relpath!r}: not a string or path object") + strrelpath = str(relpath) + if strrelpath and strrelpath[-1] != self.sep: + strrelpath += self.sep + # assert strrelpath[-1] == self.sep + # assert strrelpath[-2] != self.sep + strself = self.strpath + if sys.platform == "win32" or getattr(os, "_name", None) == "nt": + if os.path.normcase(strself).startswith(os.path.normcase(strrelpath)): + return strself[len(strrelpath) :] + elif strself.startswith(strrelpath): + return strself[len(strrelpath) :] + return "" + + def ensure_dir(self, *args): + """Ensure the path joined with args is a directory.""" + return self.ensure(*args, **{"dir": True}) + + def bestrelpath(self, dest): + """Return a string which is a relative path from self + (assumed to be a directory) to dest such that + self.join(bestrelpath) == dest and if not such + path can be determined return dest. + """ + try: + if self == dest: + return os.curdir + base = self.common(dest) + if not base: # can be the case on windows + return str(dest) + self2base = self.relto(base) + reldest = dest.relto(base) + if self2base: + n = self2base.count(self.sep) + 1 + else: + n = 0 + lst = [os.pardir] * n + if reldest: + lst.append(reldest) + target = dest.sep.join(lst) + return target + except AttributeError: + return str(dest) + + def exists(self): + return self.check() + + def isdir(self): + return self.check(dir=1) + + def isfile(self): + return self.check(file=1) + + def parts(self, reverse=False): + """Return a root-first list of all ancestor directories + plus the path itself. + """ + current = self + lst = [self] + while 1: + last = current + current = current.dirpath() + if last == current: + break + lst.append(current) + if not reverse: + lst.reverse() + return lst + + def common(self, other): + """Return the common part shared with the other path + or None if there is no common part. + """ + last = None + for x, y in zip(self.parts(), other.parts()): + if x != y: + return last + last = x + return last + + def __add__(self, other): + """Return new path object with 'other' added to the basename""" + return self.new(basename=self.basename + str(other)) + + def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False): + """Yields all paths below the current one + + fil is a filter (glob pattern or callable), if not matching the + path will not be yielded, defaulting to None (everything is + returned) + + rec is a filter (glob pattern or callable) that controls whether + a node is descended, defaulting to None + + ignore is an Exception class that is ignoredwhen calling dirlist() + on any of the paths (by default, all exceptions are reported) + + bf if True will cause a breadthfirst search instead of the + default depthfirst. Default: False + + sort if True will sort entries within each directory level. + """ + yield from Visitor(fil, rec, ignore, bf, sort).gen(self) + + def _sortlist(self, res, sort): + if sort: + if hasattr(sort, "__call__"): + warnings.warn( + DeprecationWarning( + "listdir(sort=callable) is deprecated and breaks on python3" + ), + stacklevel=3, + ) + res.sort(sort) + else: + res.sort() + + def __fspath__(self): + return self.strpath + + def __hash__(self): + s = self.strpath + if iswin32: + s = s.lower() + return hash(s) + + def __eq__(self, other): + s1 = os.fspath(self) + try: + s2 = os.fspath(other) + except TypeError: + return False + if iswin32: + s1 = s1.lower() + try: + s2 = s2.lower() + except AttributeError: + return False + return s1 == s2 + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return os.fspath(self) < os.fspath(other) + + def __gt__(self, other): + return os.fspath(self) > os.fspath(other) + + def samefile(self, other): + """Return True if 'other' references the same file as 'self'.""" + other = os.fspath(other) + if not isabs(other): + other = abspath(other) + if self == other: + return True + if not hasattr(os.path, "samefile"): + return False + return error.checked_call(os.path.samefile, self.strpath, other) + + def remove(self, rec=1, ignore_errors=False): + """Remove a file or directory (or a directory tree if rec=1). + if ignore_errors is True, errors while removing directories will + be ignored. + """ + if self.check(dir=1, link=0): + if rec: + # force remove of readonly files on windows + if iswin32: + self.chmod(0o700, rec=1) + import shutil + + error.checked_call( + shutil.rmtree, self.strpath, ignore_errors=ignore_errors + ) + else: + error.checked_call(os.rmdir, self.strpath) + else: + if iswin32: + self.chmod(0o700) + error.checked_call(os.remove, self.strpath) + + def computehash(self, hashtype="md5", chunksize=524288): + """Return hexdigest of hashvalue for this file.""" + try: + try: + import hashlib as mod + except ImportError: + if hashtype == "sha1": + hashtype = "sha" + mod = __import__(hashtype) + hash = getattr(mod, hashtype)() + except (AttributeError, ImportError): + raise ValueError(f"Don't know how to compute {hashtype!r} hash") + f = self.open("rb") + try: + while 1: + buf = f.read(chunksize) + if not buf: + return hash.hexdigest() + hash.update(buf) + finally: + f.close() + + def new(self, **kw): + """Create a modified version of this path. + the following keyword arguments modify various path parts:: + + a:/some/path/to/a/file.ext + xx drive + xxxxxxxxxxxxxxxxx dirname + xxxxxxxx basename + xxxx purebasename + xxx ext + """ + obj = object.__new__(self.__class__) + if not kw: + obj.strpath = self.strpath + return obj + drive, dirname, basename, purebasename, ext = self._getbyspec( + "drive,dirname,basename,purebasename,ext" + ) + if "basename" in kw: + if "purebasename" in kw or "ext" in kw: + raise ValueError("invalid specification %r" % kw) + else: + pb = kw.setdefault("purebasename", purebasename) + try: + ext = kw["ext"] + except KeyError: + pass + else: + if ext and not ext.startswith("."): + ext = "." + ext + kw["basename"] = pb + ext + + if "dirname" in kw and not kw["dirname"]: + kw["dirname"] = drive + else: + kw.setdefault("dirname", dirname) + kw.setdefault("sep", self.sep) + obj.strpath = normpath("%(dirname)s%(sep)s%(basename)s" % kw) + return obj + + def _getbyspec(self, spec: str) -> list[str]: + """See new for what 'spec' can be.""" + res = [] + parts = self.strpath.split(self.sep) + + args = filter(None, spec.split(",")) + for name in args: + if name == "drive": + res.append(parts[0]) + elif name == "dirname": + res.append(self.sep.join(parts[:-1])) + else: + basename = parts[-1] + if name == "basename": + res.append(basename) + else: + i = basename.rfind(".") + if i == -1: + purebasename, ext = basename, "" + else: + purebasename, ext = basename[:i], basename[i:] + if name == "purebasename": + res.append(purebasename) + elif name == "ext": + res.append(ext) + else: + raise ValueError("invalid part specification %r" % name) + return res + + def dirpath(self, *args, **kwargs): + """Return the directory path joined with any given path arguments.""" + if not kwargs: + path = object.__new__(self.__class__) + path.strpath = dirname(self.strpath) + if args: + path = path.join(*args) + return path + return self.new(basename="").join(*args, **kwargs) + + def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath: + """Return a new path by appending all 'args' as path + components. if abs=1 is used restart from root if any + of the args is an absolute path. + """ + sep = self.sep + strargs = [os.fspath(arg) for arg in args] + strpath = self.strpath + if abs: + newargs: list[str] = [] + for arg in reversed(strargs): + if isabs(arg): + strpath = arg + strargs = newargs + break + newargs.insert(0, arg) + # special case for when we have e.g. strpath == "/" + actual_sep = "" if strpath.endswith(sep) else sep + for arg in strargs: + arg = arg.strip(sep) + if iswin32: + # allow unix style paths even on windows. + arg = arg.strip("/") + arg = arg.replace("/", sep) + strpath = strpath + actual_sep + arg + actual_sep = sep + obj = object.__new__(self.__class__) + obj.strpath = normpath(strpath) + return obj + + def open(self, mode="r", ensure=False, encoding=None): + """Return an opened file with the given mode. + + If ensure is True, create parent directories if needed. + """ + if ensure: + self.dirpath().ensure(dir=1) + if encoding: + return error.checked_call(io.open, self.strpath, mode, encoding=encoding) + return error.checked_call(open, self.strpath, mode) + + def _fastjoin(self, name): + child = object.__new__(self.__class__) + child.strpath = self.strpath + self.sep + name + return child + + def islink(self): + return islink(self.strpath) + + def check(self, **kw): + """Check a path for existence and properties. + + Without arguments, return True if the path exists, otherwise False. + + valid checkers:: + + file=1 # is a file + file=0 # is not a file (may not even exist) + dir=1 # is a dir + link=1 # is a link + exists=1 # exists + + You can specify multiple checker definitions, for example:: + + path.check(file=1, link=1) # a link pointing to a file + """ + if not kw: + return exists(self.strpath) + if len(kw) == 1: + if "dir" in kw: + return not kw["dir"] ^ isdir(self.strpath) + if "file" in kw: + return not kw["file"] ^ isfile(self.strpath) + if not kw: + kw = {"exists": 1} + return Checkers(self)._evaluate(kw) + + _patternchars = set("*?[" + os.path.sep) + + def listdir(self, fil=None, sort=None): + """List directory contents, possibly filter by the given fil func + and possibly sorted. + """ + if fil is None and sort is None: + names = error.checked_call(os.listdir, self.strpath) + return map_as_list(self._fastjoin, names) + if isinstance(fil, str): + if not self._patternchars.intersection(fil): + child = self._fastjoin(fil) + if exists(child.strpath): + return [child] + return [] + fil = FNMatcher(fil) + names = error.checked_call(os.listdir, self.strpath) + res = [] + for name in names: + child = self._fastjoin(name) + if fil is None or fil(child): + res.append(child) + self._sortlist(res, sort) + return res + + def size(self) -> int: + """Return size of the underlying file object""" + return self.stat().size + + def mtime(self) -> float: + """Return last modification time of the path.""" + return self.stat().mtime + + def copy(self, target, mode=False, stat=False): + """Copy path to target. + + If mode is True, will copy copy permission from path to target. + If stat is True, copy permission, last modification + time, last access time, and flags from path to target. + """ + if self.check(file=1): + if target.check(dir=1): + target = target.join(self.basename) + assert self != target + copychunked(self, target) + if mode: + copymode(self.strpath, target.strpath) + if stat: + copystat(self, target) + else: + + def rec(p): + return p.check(link=0) + + for x in self.visit(rec=rec): + relpath = x.relto(self) + newx = target.join(relpath) + newx.dirpath().ensure(dir=1) + if x.check(link=1): + newx.mksymlinkto(x.readlink()) + continue + elif x.check(file=1): + copychunked(x, newx) + elif x.check(dir=1): + newx.ensure(dir=1) + if mode: + copymode(x.strpath, newx.strpath) + if stat: + copystat(x, newx) + + def rename(self, target): + """Rename this path to target.""" + target = os.fspath(target) + return error.checked_call(os.rename, self.strpath, target) + + def dump(self, obj, bin=1): + """Pickle object into path location""" + f = self.open("wb") + import pickle + + try: + error.checked_call(pickle.dump, obj, f, bin) + finally: + f.close() + + def mkdir(self, *args): + """Create & return the directory joined with args.""" + p = self.join(*args) + error.checked_call(os.mkdir, os.fspath(p)) + return p + + def write_binary(self, data, ensure=False): + """Write binary data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("wb") as f: + f.write(data) + + def write_text(self, data, encoding, ensure=False): + """Write text data into path using the specified encoding. + If ensure is True create missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("w", encoding=encoding) as f: + f.write(data) + + def write(self, data, mode="w", ensure=False): + """Write data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + if "b" in mode: + if not isinstance(data, bytes): + raise ValueError("can only process bytes") + else: + if not isinstance(data, str): + if not isinstance(data, bytes): + data = str(data) + else: + data = data.decode(sys.getdefaultencoding()) + f = self.open(mode) + try: + f.write(data) + finally: + f.close() + + def _ensuredirs(self): + parent = self.dirpath() + if parent == self: + return self + if parent.check(dir=0): + parent._ensuredirs() + if self.check(dir=0): + try: + self.mkdir() + except error.EEXIST: + # race condition: file/dir created by another thread/process. + # complain if it is not a dir + if self.check(dir=0): + raise + return self + + def ensure(self, *args, **kwargs): + """Ensure that an args-joined path exists (by default as + a file). if you specify a keyword argument 'dir=True' + then the path is forced to be a directory path. + """ + p = self.join(*args) + if kwargs.get("dir", 0): + return p._ensuredirs() + else: + p.dirpath()._ensuredirs() + if not p.check(file=1): + p.open("w").close() + return p + + @overload + def stat(self, raising: Literal[True] = ...) -> Stat: + ... + + @overload + def stat(self, raising: Literal[False]) -> Stat | None: + ... + + def stat(self, raising: bool = True) -> Stat | None: + """Return an os.stat() tuple.""" + if raising: + return Stat(self, error.checked_call(os.stat, self.strpath)) + try: + return Stat(self, os.stat(self.strpath)) + except KeyboardInterrupt: + raise + except Exception: + return None + + def lstat(self) -> Stat: + """Return an os.lstat() tuple.""" + return Stat(self, error.checked_call(os.lstat, self.strpath)) + + def setmtime(self, mtime=None): + """Set modification time for the given path. if 'mtime' is None + (the default) then the file's mtime is set to current time. + + Note that the resolution for 'mtime' is platform dependent. + """ + if mtime is None: + return error.checked_call(os.utime, self.strpath, mtime) + try: + return error.checked_call(os.utime, self.strpath, (-1, mtime)) + except error.EINVAL: + return error.checked_call(os.utime, self.strpath, (self.atime(), mtime)) + + def chdir(self): + """Change directory to self and return old current directory""" + try: + old = self.__class__() + except error.ENOENT: + old = None + error.checked_call(os.chdir, self.strpath) + return old + + @contextmanager + def as_cwd(self): + """ + Return a context manager, which changes to the path's dir during the + managed "with" context. + On __enter__ it returns the old dir, which might be ``None``. + """ + old = self.chdir() + try: + yield old + finally: + if old is not None: + old.chdir() + + def realpath(self): + """Return a new path which contains no symbolic links.""" + return self.__class__(os.path.realpath(self.strpath)) + + def atime(self): + """Return last access time of the path.""" + return self.stat().atime + + def __repr__(self): + return "local(%r)" % self.strpath + + def __str__(self): + """Return string representation of the Path.""" + return self.strpath + + def chmod(self, mode, rec=0): + """Change permissions to the given mode. If mode is an + integer it directly encodes the os-specific modes. + if rec is True perform recursively. + """ + if not isinstance(mode, int): + raise TypeError(f"mode {mode!r} must be an integer") + if rec: + for x in self.visit(rec=rec): + error.checked_call(os.chmod, str(x), mode) + error.checked_call(os.chmod, self.strpath, mode) + + def pypkgpath(self): + """Return the Python package path by looking for the last + directory upwards which still contains an __init__.py. + Return None if a pkgpath can not be determined. + """ + pkgpath = None + for parent in self.parts(reverse=True): + if parent.isdir(): + if not parent.join("__init__.py").exists(): + break + if not isimportable(parent.basename): + break + pkgpath = parent + return pkgpath + + def _ensuresyspath(self, ensuremode, path): + if ensuremode: + s = str(path) + if ensuremode == "append": + if s not in sys.path: + sys.path.append(s) + else: + if s != sys.path[0]: + sys.path.insert(0, s) + + def pyimport(self, modname=None, ensuresyspath=True): + """Return path as an imported python module. + + If modname is None, look for the containing package + and construct an according module name. + The module will be put/looked up in sys.modules. + if ensuresyspath is True then the root dir for importing + the file (taking __init__.py files into account) will + be prepended to sys.path if it isn't there already. + If ensuresyspath=="append" the root dir will be appended + if it isn't already contained in sys.path. + if ensuresyspath is False no modification of syspath happens. + + Special value of ensuresyspath=="importlib" is intended + purely for using in pytest, it is capable only of importing + separate .py files outside packages, e.g. for test suite + without any __init__.py file. It effectively allows having + same-named test modules in different places and offers + mild opt-in via this option. Note that it works only in + recent versions of python. + """ + if not self.check(): + raise error.ENOENT(self) + + if ensuresyspath == "importlib": + if modname is None: + modname = self.purebasename + spec = importlib.util.spec_from_file_location(modname, str(self)) + if spec is None or spec.loader is None: + raise ImportError( + f"Can't find module {modname} at location {str(self)}" + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + pkgpath = None + if modname is None: + pkgpath = self.pypkgpath() + if pkgpath is not None: + pkgroot = pkgpath.dirpath() + names = self.new(ext="").relto(pkgroot).split(self.sep) + if names[-1] == "__init__": + names.pop() + modname = ".".join(names) + else: + pkgroot = self.dirpath() + modname = self.purebasename + + self._ensuresyspath(ensuresyspath, pkgroot) + __import__(modname) + mod = sys.modules[modname] + if self.basename == "__init__.py": + return mod # we don't check anything as we might + # be in a namespace package ... too icky to check + modfile = mod.__file__ + assert modfile is not None + if modfile[-4:] in (".pyc", ".pyo"): + modfile = modfile[:-1] + elif modfile.endswith("$py.class"): + modfile = modfile[:-9] + ".py" + if modfile.endswith(os.path.sep + "__init__.py"): + if self.basename != "__init__.py": + modfile = modfile[:-12] + try: + issame = self.samefile(modfile) + except error.ENOENT: + issame = False + if not issame: + ignore = os.getenv("PY_IGNORE_IMPORTMISMATCH") + if ignore != "1": + raise self.ImportMismatchError(modname, modfile, self) + return mod + else: + try: + return sys.modules[modname] + except KeyError: + # we have a custom modname, do a pseudo-import + import types + + mod = types.ModuleType(modname) + mod.__file__ = str(self) + sys.modules[modname] = mod + try: + with open(str(self), "rb") as f: + exec(f.read(), mod.__dict__) + except BaseException: + del sys.modules[modname] + raise + return mod + + def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str: + """Return stdout text from executing a system child process, + where the 'self' path points to executable. + The process is directly invoked and not through a system shell. + """ + from subprocess import Popen, PIPE + + popen_opts.pop("stdout", None) + popen_opts.pop("stderr", None) + proc = Popen( + [str(self)] + [str(arg) for arg in argv], + **popen_opts, + stdout=PIPE, + stderr=PIPE, + ) + stdout: str | bytes + stdout, stderr = proc.communicate() + ret = proc.wait() + if isinstance(stdout, bytes): + stdout = stdout.decode(sys.getdefaultencoding()) + if ret != 0: + if isinstance(stderr, bytes): + stderr = stderr.decode(sys.getdefaultencoding()) + raise RuntimeError( + ret, + ret, + str(self), + stdout, + stderr, + ) + return stdout + + @classmethod + def sysfind(cls, name, checker=None, paths=None): + """Return a path object found by looking at the systems + underlying PATH specification. If the checker is not None + it will be invoked to filter matching paths. If a binary + cannot be found, None is returned + Note: This is probably not working on plain win32 systems + but may work on cygwin. + """ + if isabs(name): + p = local(name) + if p.check(file=1): + return p + else: + if paths is None: + if iswin32: + paths = os.environ["Path"].split(";") + if "" not in paths and "." not in paths: + paths.append(".") + try: + systemroot = os.environ["SYSTEMROOT"] + except KeyError: + pass + else: + paths = [ + path.replace("%SystemRoot%", systemroot) for path in paths + ] + else: + paths = os.environ["PATH"].split(":") + tryadd = [] + if iswin32: + tryadd += os.environ["PATHEXT"].split(os.pathsep) + tryadd.append("") + + for x in paths: + for addext in tryadd: + p = local(x).join(name, abs=True) + addext + try: + if p.check(file=1): + if checker: + if not checker(p): + continue + return p + except error.EACCES: + pass + return None + + @classmethod + def _gethomedir(cls): + try: + x = os.environ["HOME"] + except KeyError: + try: + x = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"] + except KeyError: + return None + return cls(x) + + # """ + # special class constructors for local filesystem paths + # """ + @classmethod + def get_temproot(cls): + """Return the system's temporary directory + (where tempfiles are usually created in) + """ + import tempfile + + return local(tempfile.gettempdir()) + + @classmethod + def mkdtemp(cls, rootdir=None): + """Return a Path object pointing to a fresh new temporary directory + (which we created ourself). + """ + import tempfile + + if rootdir is None: + rootdir = cls.get_temproot() + return cls(error.checked_call(tempfile.mkdtemp, dir=str(rootdir))) + + @classmethod + def make_numbered_dir( + cls, prefix="session-", rootdir=None, keep=3, lock_timeout=172800 + ): # two days + """Return unique directory with a number greater than the current + maximum one. The number is assumed to start directly after prefix. + if keep is true directories with a number less than (maxnum-keep) + will be removed. If .lock files are used (lock_timeout non-zero), + algorithm is multi-process safe. + """ + if rootdir is None: + rootdir = cls.get_temproot() + + nprefix = prefix.lower() + + def parse_num(path): + """Parse the number out of a path (if it matches the prefix)""" + nbasename = path.basename.lower() + if nbasename.startswith(nprefix): + try: + return int(nbasename[len(nprefix) :]) + except ValueError: + pass + + def create_lockfile(path): + """Exclusively create lockfile. Throws when failed""" + mypid = os.getpid() + lockfile = path.join(".lock") + if hasattr(lockfile, "mksymlinkto"): + lockfile.mksymlinkto(str(mypid)) + else: + fd = error.checked_call( + os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644 + ) + with os.fdopen(fd, "w") as f: + f.write(str(mypid)) + return lockfile + + def atexit_remove_lockfile(lockfile): + """Ensure lockfile is removed at process exit""" + mypid = os.getpid() + + def try_remove_lockfile(): + # in a fork() situation, only the last process should + # remove the .lock, otherwise the other processes run the + # risk of seeing their temporary dir disappear. For now + # we remove the .lock in the parent only (i.e. we assume + # that the children finish before the parent). + if os.getpid() != mypid: + return + try: + lockfile.remove() + except error.Error: + pass + + atexit.register(try_remove_lockfile) + + # compute the maximum number currently in use with the prefix + lastmax = None + while True: + maxnum = -1 + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None: + maxnum = max(maxnum, num) + + # make the new directory + try: + udir = rootdir.mkdir(prefix + str(maxnum + 1)) + if lock_timeout: + lockfile = create_lockfile(udir) + atexit_remove_lockfile(lockfile) + except (error.EEXIST, error.ENOENT, error.EBUSY): + # race condition (1): another thread/process created the dir + # in the meantime - try again + # race condition (2): another thread/process spuriously acquired + # lock treating empty directory as candidate + # for removal - try again + # race condition (3): another thread/process tried to create the lock at + # the same time (happened in Python 3.3 on Windows) + # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa + if lastmax == maxnum: + raise + lastmax = maxnum + continue + break + + def get_mtime(path): + """Read file modification time""" + try: + return path.lstat().mtime + except error.Error: + pass + + garbage_prefix = prefix + "garbage-" + + def is_garbage(path): + """Check if path denotes directory scheduled for removal""" + bn = path.basename + return bn.startswith(garbage_prefix) + + # prune old directories + udir_time = get_mtime(udir) + if keep and udir_time: + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None and num <= (maxnum - keep): + try: + # try acquiring lock to remove directory as exclusive user + if lock_timeout: + create_lockfile(path) + except (error.EEXIST, error.ENOENT, error.EBUSY): + path_time = get_mtime(path) + if not path_time: + # assume directory doesn't exist now + continue + if abs(udir_time - path_time) < lock_timeout: + # assume directory with lockfile exists + # and lock timeout hasn't expired yet + continue + + # path dir locked for exclusive use + # and scheduled for removal to avoid another thread/process + # treating it as a new directory or removal candidate + garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4())) + try: + path.rename(garbage_path) + garbage_path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + if is_garbage(path): + try: + path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + + # make link... + try: + username = os.environ["USER"] # linux, et al + except KeyError: + try: + username = os.environ["USERNAME"] # windows + except KeyError: + username = "current" + + src = str(udir) + dest = src[: src.rfind("-")] + "-" + username + try: + os.unlink(dest) + except OSError: + pass + try: + os.symlink(src, dest) + except (OSError, AttributeError, NotImplementedError): + pass + + return udir + + +def copymode(src, dest): + """Copy permission from src to dst.""" + import shutil + + shutil.copymode(src, dest) + + +def copystat(src, dest): + """Copy permission, last modification time, + last access time, and flags from src to dst.""" + import shutil + + shutil.copystat(str(src), str(dest)) + + +def copychunked(src, dest): + chunksize = 524288 # half a meg of bytes + fsrc = src.open("rb") + try: + fdest = dest.open("wb") + try: + while 1: + buf = fsrc.read(chunksize) + if not buf: + break + fdest.write(buf) + finally: + fdest.close() + finally: + fsrc.close() + + +def isimportable(name): + if name and (name[0].isalpha() or name[0] == "_"): + name = name.replace("_", "") + return not name or name.isalnum() + + +local = LocalPath diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 480a26ad867..a46e58136ba 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -39,7 +39,7 @@ def pytest_addoption(parser: Parser) -> None: "enable_assertion_pass_hook", type="bool", default=False, - help="Enables the pytest_assertion_pass hook." + help="Enables the pytest_assertion_pass hook. " "Make sure to delete any previously generated pyc cache files.", ) @@ -53,7 +53,7 @@ def register_assert_rewrite(*names: str) -> None: actually imported, usually in your __init__.py if you are a plugin using a package. - :raises TypeError: If the given module names are not strings. + :param names: The module names to register. """ for name in names: if not isinstance(name, str): diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 456681ab29e..cb64a33c078 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -100,9 +100,6 @@ def find_spec( spec is None # this is a namespace package (without `__init__.py`) # there's nothing to rewrite there - # python3.6: `namespace` - # python3.7+: `None` - or spec.origin == "namespace" or spec.origin is None # we can only rewrite source files or not isinstance(spec.loader, importlib.machinery.SourceFileLoader) @@ -193,7 +190,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: return False # For matching the name it must be as if it was a filename. - path = PurePath(os.path.sep.join(parts) + ".py") + path = PurePath(*parts).with_suffix(".py") for pat in self.fnpats: # if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based @@ -276,13 +273,22 @@ def get_data(self, pathname: Union[str, bytes]) -> bytes: with open(pathname, "rb") as f: return f.read() - if sys.version_info >= (3, 9): + if sys.version_info >= (3, 10): - def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: # type: ignore - from types import SimpleNamespace - from importlib.readers import FileReader + if sys.version_info >= (3, 12): + from importlib.resources.abc import TraversableResources + else: + from importlib.abc import TraversableResources + + def get_resource_reader(self, name: str) -> TraversableResources: # type: ignore + if sys.version_info < (3, 11): + from importlib.readers import FileReader + else: + from importlib.resources.readers import FileReader - return FileReader(SimpleNamespace(path=self._rewritten_names[name])) + return FileReader( # type:ignore[no-any-return] + types.SimpleNamespace(path=self._rewritten_names[name]) + ) def _write_pyc_fp( @@ -293,9 +299,8 @@ def _write_pyc_fp( # import. However, there's little reason to deviate. fp.write(importlib.util.MAGIC_NUMBER) # https://www.python.org/dev/peps/pep-0552/ - if sys.version_info >= (3, 7): - flags = b"\x00\x00\x00\x00" - fp.write(flags) + flags = b"\x00\x00\x00\x00" + fp.write(flags) # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903) mtime = int(source_stat.st_mtime) & 0xFFFFFFFF size = source_stat.st_size & 0xFFFFFFFF @@ -304,54 +309,29 @@ def _write_pyc_fp( fp.write(marshal.dumps(co)) -if sys.platform == "win32": - from atomicwrites import atomic_write - - def _write_pyc( - state: "AssertionState", - co: types.CodeType, - source_stat: os.stat_result, - pyc: Path, - ) -> bool: - try: - with atomic_write(os.fspath(pyc), mode="wb", overwrite=True) as fp: - _write_pyc_fp(fp, source_stat, co) - except OSError as e: - state.trace(f"error writing pyc file at {pyc}: {e}") - # we ignore any failure to write the cache file - # there are many reasons, permission-denied, pycache dir being a - # file etc. - return False - return True - - -else: - - def _write_pyc( - state: "AssertionState", - co: types.CodeType, - source_stat: os.stat_result, - pyc: Path, - ) -> bool: - proc_pyc = f"{pyc}.{os.getpid()}" - try: - fp = open(proc_pyc, "wb") - except OSError as e: - state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}") - return False - - try: +def _write_pyc( + state: "AssertionState", + co: types.CodeType, + source_stat: os.stat_result, + pyc: Path, +) -> bool: + proc_pyc = f"{pyc}.{os.getpid()}" + try: + with open(proc_pyc, "wb") as fp: _write_pyc_fp(fp, source_stat, co) - os.rename(proc_pyc, pyc) - except OSError as e: - state.trace(f"error writing pyc file at {pyc}: {e}") - # we ignore any failure to write the cache file - # there are many reasons, permission-denied, pycache dir being a - # file etc. - return False - finally: - fp.close() - return True + except OSError as e: + state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}") + return False + + try: + os.replace(proc_pyc, pyc) + except OSError as e: + state.trace(f"error writing pyc file at {pyc}: {e}") + # we ignore any failure to write the cache file + # there are many reasons, permission-denied, pycache dir being a + # file etc. + return False + return True def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]: @@ -377,31 +357,29 @@ def _read_pyc( except OSError: return None with fp: - # https://www.python.org/dev/peps/pep-0552/ - has_flags = sys.version_info >= (3, 7) try: stat_result = os.stat(source) mtime = int(stat_result.st_mtime) size = stat_result.st_size - data = fp.read(16 if has_flags else 12) + data = fp.read(16) except OSError as e: trace(f"_read_pyc({source}): OSError {e}") return None # Check for invalid or out of date pyc file. - if len(data) != (16 if has_flags else 12): + if len(data) != (16): trace("_read_pyc(%s): invalid pyc (too short)" % source) return None if data[:4] != importlib.util.MAGIC_NUMBER: trace("_read_pyc(%s): invalid pyc (bad magic number)" % source) return None - if has_flags and data[4:8] != b"\x00\x00\x00\x00": + if data[4:8] != b"\x00\x00\x00\x00": trace("_read_pyc(%s): invalid pyc (unsupported flags)" % source) return None - mtime_data = data[8 if has_flags else 4 : 12 if has_flags else 8] + mtime_data = data[8:12] if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF: trace("_read_pyc(%s): out of date" % source) return None - size_data = data[12 if has_flags else 8 : 16 if has_flags else 12] + size_data = data[12:16] if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF: trace("_read_pyc(%s): invalid pyc (incorrect size)" % source) return None @@ -514,7 +492,7 @@ def _call_assertion_pass(lineno: int, orig: str, expl: str) -> None: def _check_if_assertion_pass_impl() -> bool: """Check if any plugins implement the pytest_assertion_pass hook - in order not to generate explanation unecessarily (might be expensive).""" + in order not to generate explanation unnecessarily (might be expensive).""" return True if util._assertion_pass else False diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 19f1089c20a..fc5dfdbd5ba 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -10,12 +10,13 @@ from typing import Mapping from typing import Optional from typing import Sequence +from unicodedata import normalize import _pytest._code from _pytest import outcomes from _pytest._io.saferepr import _pformat_dispatch -from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import saferepr +from _pytest._io.saferepr import saferepr_unlimited from _pytest.config import Config # The _reprcompare attribute on the util module is used by the new assertion @@ -135,20 +136,53 @@ def isiterable(obj: Any) -> bool: return False -def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]: +def has_default_eq( + obj: object, +) -> bool: + """Check if an instance of an object contains the default eq + + First, we check if the object's __eq__ attribute has __code__, + if so, we check the equally of the method code filename (__code__.co_filename) + to the default one generated by the dataclass and attr module + for dataclasses the default co_filename is , for attrs class, the __eq__ should contain "attrs eq generated" + """ + # inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68 + if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"): + code_filename = obj.__eq__.__code__.co_filename + + if isattrs(obj): + return "attrs generated eq" in code_filename + + return code_filename == "" # data class + return True + + +def assertrepr_compare( + config, op: str, left: Any, right: Any, use_ascii: bool = False +) -> Optional[List[str]]: """Return specialised explanations for some operators/operands.""" verbose = config.getoption("verbose") + + # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. + # See issue #3246. + use_ascii = ( + isinstance(left, str) + and isinstance(right, str) + and normalize("NFD", left) == normalize("NFD", right) + ) + if verbose > 1: - left_repr = safeformat(left) - right_repr = safeformat(right) + left_repr = saferepr_unlimited(left, use_ascii=use_ascii) + right_repr = saferepr_unlimited(right, use_ascii=use_ascii) else: # XXX: "15 chars indentation" is wrong # ("E AssertionError: assert "); should use term width. maxsize = ( 80 - 15 - len(op) - 2 ) // 2 # 15 chars indentation, 1 space around op - left_repr = saferepr(left, maxsize=maxsize) - right_repr = saferepr(right, maxsize=maxsize) + + left_repr = saferepr(left, maxsize=maxsize, use_ascii=use_ascii) + right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii) summary = f"{left_repr} {op} {right_repr}" @@ -202,8 +236,6 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]: explanation = _compare_eq_set(left, right, verbose) elif isdict(left) and isdict(right): explanation = _compare_eq_dict(left, right, verbose) - elif verbose > 0: - explanation = _compare_eq_verbose(left, right) if isiterable(left) and isiterable(right): expl = _compare_eq_iterable(left, right, verbose) @@ -260,18 +292,6 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: return explanation -def _compare_eq_verbose(left: Any, right: Any) -> List[str]: - keepends = True - left_lines = repr(left).splitlines(keepends) - right_lines = repr(right).splitlines(keepends) - - explanation: List[str] = [] - explanation += ["+" + line for line in left_lines] - explanation += ["-" + line for line in right_lines] - - return explanation - - def _surrounding_parens_on_own_lines(lines: List[str]) -> None: """Move opening/closing parenthesis/bracket to own lines.""" opening = lines[0][:1] @@ -287,8 +307,8 @@ def _surrounding_parens_on_own_lines(lines: List[str]) -> None: def _compare_eq_iterable( left: Iterable[Any], right: Iterable[Any], verbose: int = 0 ) -> List[str]: - if not verbose and not running_on_ci(): - return ["Use -v to get the full diff"] + if verbose <= 0 and not running_on_ci(): + return ["Use -v to get more diff"] # dynamic import to speedup pytest import difflib @@ -427,9 +447,13 @@ def _compare_eq_dict( def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]: + if not has_default_eq(left): + return [] if isdatacls(left): - all_fields = left.__dataclass_fields__ - fields_to_check = [field for field, info in all_fields.items() if info.compare] + import dataclasses + + all_fields = dataclasses.fields(left) + fields_to_check = [info.name for info in all_fields if info.compare] elif isattrs(left): all_fields = left.__attrs_attrs__ fields_to_check = [field.name for field in all_fields if getattr(field, "eq")] diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 681d02b4093..c236dd4176b 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -32,7 +32,6 @@ from _pytest.python import Package from _pytest.reports import TestReport - README_CONTENT = """\ # pytest cache directory # @@ -157,7 +156,7 @@ def get(self, key: str, default): """ path = self._getvaluepath(key) try: - with path.open("r") as f: + with path.open("r", encoding="UTF-8") as f: return json.load(f) except (ValueError, OSError): return default @@ -184,9 +183,9 @@ def set(self, key: str, value: object) -> None: return if not cache_dir_exists_already: self._ensure_supporting_files() - data = json.dumps(value, indent=2) + data = json.dumps(value, ensure_ascii=False, indent=2) try: - f = path.open("w") + f = path.open("w", encoding="UTF-8") except OSError: self.warn("cache could not write path {path}", path=path, _ispytest=True) else: @@ -196,7 +195,7 @@ def set(self, key: str, value: object) -> None: def _ensure_supporting_files(self) -> None: """Create supporting files in the cache dir that are not really part of the cache.""" readme_path = self._cachedir / "README.md" - readme_path.write_text(README_CONTENT) + readme_path.write_text(README_CONTENT, encoding="UTF-8") gitignore_path = self._cachedir.joinpath(".gitignore") msg = "# Created by pytest automatically.\n*\n" @@ -440,7 +439,7 @@ def pytest_addoption(parser: Parser) -> None: "--last-failed", action="store_true", dest="lf", - help="rerun only the tests that failed " + help="Rerun only the tests that failed " "at the last run (or all if none failed)", ) group.addoption( @@ -448,7 +447,7 @@ def pytest_addoption(parser: Parser) -> None: "--failed-first", action="store_true", dest="failedfirst", - help="run all tests, but run the last failures first.\n" + help="Run all tests, but run the last failures first. " "This may re-order tests and thus lead to " "repeated fixture setup/teardown.", ) @@ -457,7 +456,7 @@ def pytest_addoption(parser: Parser) -> None: "--new-first", action="store_true", dest="newfirst", - help="run tests from new files first, then the rest of the tests " + help="Run tests from new files first, then the rest of the tests " "sorted by file mtime", ) group.addoption( @@ -466,7 +465,7 @@ def pytest_addoption(parser: Parser) -> None: nargs="?", dest="cacheshow", help=( - "show cache contents, don't perform collection or tests. " + "Show cache contents, don't perform collection or tests. " "Optional argument: glob (default: '*')." ), ) @@ -474,12 +473,12 @@ def pytest_addoption(parser: Parser) -> None: "--cache-clear", action="store_true", dest="cacheclear", - help="remove all cache contents at start of test run.", + help="Remove all cache contents at start of test run", ) cache_dir_default = ".pytest_cache" if "TOX_ENV_DIR" in os.environ: cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default) - parser.addini("cache_dir", default=cache_dir_default, help="cache directory path.") + parser.addini("cache_dir", default=cache_dir_default, help="Cache directory path") group.addoption( "--lfnf", "--last-failed-no-failures", @@ -487,12 +486,12 @@ def pytest_addoption(parser: Parser) -> None: dest="last_failed_no_failures", choices=("all", "none"), default="all", - help="which tests to run with no previously (known) failures.", + help="Which tests to run with no previously (known) failures", ) def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: - if config.option.cacheshow: + if config.option.cacheshow and not config.option.help: from _pytest.main import wrap_session return wrap_session(config, cacheshow) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 884f035e299..6131a46df47 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -42,14 +42,14 @@ def pytest_addoption(parser: Parser) -> None: default="fd", metavar="method", choices=["fd", "sys", "no", "tee-sys"], - help="per-test capturing method: one of fd|sys|no|tee-sys.", + help="Per-test capturing method: one of fd|sys|no|tee-sys", ) group._addoption( "-s", action="store_const", const="no", dest="capture", - help="shortcut for --capture=no.", + help="Shortcut for --capture=no", ) @@ -68,8 +68,8 @@ def _colorama_workaround() -> None: pass -def _py36_windowsconsoleio_workaround(stream: TextIO) -> None: - """Workaround for Windows Unicode console handling on Python>=3.6. +def _windowsconsoleio_workaround(stream: TextIO) -> None: + """Workaround for Windows Unicode console handling. Python 3.6 implemented Unicode console handling for Windows. This works by reading/writing to the raw console handle using @@ -112,7 +112,7 @@ def _reopen_stdio(f, mode): buffering = -1 return io.TextIOWrapper( - open(os.dup(f.fileno()), mode, buffering), # type: ignore[arg-type] + open(os.dup(f.fileno()), mode, buffering), f.encoding, f.errors, f.newlines, @@ -128,7 +128,7 @@ def _reopen_stdio(f, mode): def pytest_load_initial_conftests(early_config: Config): ns = early_config.known_args_namespace if ns.capture == "fd": - _py36_windowsconsoleio_workaround(sys.stdout) + _windowsconsoleio_workaround(sys.stdout) _colorama_workaround() pluginmanager = early_config.pluginmanager capman = CaptureManager(ns.capture) @@ -203,12 +203,39 @@ def __iter__(self): def fileno(self) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()") + def flush(self) -> None: + raise UnsupportedOperation("redirected stdin is pseudofile, has no flush()") + def isatty(self) -> bool: return False def close(self) -> None: pass + def readable(self) -> bool: + return False + + def seek(self, offset: int) -> int: + raise UnsupportedOperation("redirected stdin is pseudofile, has no seek(int)") + + def seekable(self) -> bool: + return False + + def tell(self) -> int: + raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") + + def truncate(self, size: int) -> None: + raise UnsupportedOperation("cannont truncate stdin") + + def write(self, *args) -> None: + raise UnsupportedOperation("cannot write to stdin") + + def writelines(self, *args) -> None: + raise UnsupportedOperation("Cannot write to stdin") + + def writable(self) -> bool: + return False + @property def buffer(self): return self @@ -354,7 +381,7 @@ def __init__(self, targetfd: int) -> None: self.targetfd_save = os.dup(targetfd) if targetfd == 0: - self.tmpfile = open(os.devnull) + self.tmpfile = open(os.devnull, encoding="utf-8") self.syscapture = SysCapture(targetfd) else: self.tmpfile = EncodedFile( @@ -876,11 +903,22 @@ def disabled(self) -> Generator[None, None, None]: @fixture def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: - """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. + r"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsys.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_output(capsys): + print("hello") + captured = capsys.readouterr() + assert captured.out == "hello\n" """ capman = request.config.pluginmanager.getplugin("capturemanager") capture_fixture = CaptureFixture[str](SysCapture, request, _ispytest=True) @@ -893,11 +931,22 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: @fixture def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: - """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. + r"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``bytes`` objects. + + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_output(capsysbinary): + print("hello") + captured = capsysbinary.readouterr() + assert captured.out == b"hello\n" """ capman = request.config.pluginmanager.getplugin("capturemanager") capture_fixture = CaptureFixture[bytes](SysCaptureBinary, request, _ispytest=True) @@ -910,11 +959,22 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, @fixture def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: - """Enable text capturing of writes to file descriptors ``1`` and ``2``. + r"""Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfd): + os.system('echo "hello"') + captured = capfd.readouterr() + assert captured.out == "hello\n" """ capman = request.config.pluginmanager.getplugin("capturemanager") capture_fixture = CaptureFixture[str](FDCapture, request, _ispytest=True) @@ -927,11 +987,23 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: @fixture def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: - """Enable bytes capturing of writes to file descriptors ``1`` and ``2``. + r"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``byte`` objects. + + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfdbinary): + os.system('echo "hello"') + captured = capfdbinary.readouterr() + assert captured.out == b"hello\n" + """ capman = request.config.pluginmanager.getplugin("capturemanager") capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request, _ispytest=True) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 7703dee8c5a..211407b2374 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -4,13 +4,13 @@ import inspect import os import sys -from contextlib import contextmanager from inspect import Parameter from inspect import signature from pathlib import Path from typing import Any from typing import Callable from typing import Generic +from typing import NoReturn from typing import Optional from typing import Tuple from typing import TYPE_CHECKING @@ -18,10 +18,20 @@ from typing import Union import attr + import py +# fmt: off +# Workaround for https://github.com/sphinx-doc/sphinx/issues/10351. +# If `overload` is imported from `compat` instead of from `typing`, +# Sphinx doesn't recognize it as `overload` and the API docs for +# overloaded functions look good again. But type checkers handle +# it fine. +# fmt: on +if True: + from typing import overload as overload + if TYPE_CHECKING: - from typing import NoReturn from typing_extensions import Final @@ -51,9 +61,11 @@ class NotSetType(enum.Enum): # fmt: on if sys.version_info >= (3, 8): - from importlib import metadata as importlib_metadata + import importlib.metadata + + importlib_metadata = importlib.metadata else: - import importlib_metadata # noqa: F401 + import importlib_metadata as importlib_metadata # noqa: F401 def _format_args(func: Callable[..., Any]) -> str: @@ -186,17 +198,6 @@ def getfuncargnames( return arg_names -if sys.version_info < (3, 7): - - @contextmanager - def nullcontext(): - yield - - -else: - from contextlib import nullcontext as nullcontext # noqa: F401 - - def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]: # Note: this code intentionally mirrors the code at the beginning of # getfuncargnames, to get the arguments which were excluded from its result @@ -355,7 +356,6 @@ def final(f): if sys.version_info >= (3, 8): from functools import cached_property as cached_property else: - from typing import overload from typing import Type class cached_property(Generic[_S, _T]): @@ -413,5 +413,5 @@ def __get__(self, instance, owner=None): # previously. # # This also work for Enums (if you use `is` to compare) and Literals. -def assert_never(value: "NoReturn") -> "NoReturn": +def assert_never(value: NoReturn) -> NoReturn: assert False, f"Unhandled value: {value} ({type(value).__name__})" diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index eadce78fa65..bd2611df6b1 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1,9 +1,9 @@ """Command line options, ini-file and conftest.py processing.""" import argparse import collections.abc -import contextlib import copy import enum +import glob import inspect import os import re @@ -14,6 +14,7 @@ from functools import lru_cache from pathlib import Path from textwrap import dedent +from types import FunctionType from types import TracebackType from typing import Any from typing import Callable @@ -58,6 +59,7 @@ from _pytest.pathlib import resolve_package_path from _pytest.stash import Stash from _pytest.warning_types import PytestConfigWarning +from _pytest.warning_types import warn_explicit_for if TYPE_CHECKING: @@ -255,7 +257,7 @@ def directory_arg(path: str, optname: str) -> str: "warnings", "logging", "reports", - "pythonpath", + "python_path", *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []), "faulthandler", ) @@ -310,7 +312,9 @@ def _prepareconfig( elif isinstance(args, os.PathLike): args = [os.fspath(args)] elif not isinstance(args, list): - msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})" + msg = ( # type:ignore[unreachable] + "`args` parameter expected to be a list of strings, got: {!r} (type: {})" + ) raise TypeError(msg.format(args, type(args))) config = get_config(args, plugins) @@ -331,6 +335,46 @@ def _prepareconfig( raise +def _get_directory(path: Path) -> Path: + """Get the directory of a path - itself if already a directory.""" + if path.is_file(): + return path.parent + else: + return path + + +def _get_legacy_hook_marks( + method: Any, + hook_type: str, + opt_names: Tuple[str, ...], +) -> Dict[str, bool]: + if TYPE_CHECKING: + # abuse typeguard from importlib to avoid massive method type union thats lacking a alias + assert inspect.isroutine(method) + known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])} + must_warn: list[str] = [] + opts: dict[str, bool] = {} + for opt_name in opt_names: + opt_attr = getattr(method, opt_name, AttributeError) + if opt_attr is not AttributeError: + must_warn.append(f"{opt_name}={opt_attr}") + opts[opt_name] = True + elif opt_name in known_marks: + must_warn.append(f"{opt_name}=True") + opts[opt_name] = True + else: + opts[opt_name] = False + if must_warn: + hook_opts = ", ".join(must_warn) + message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( + type=hook_type, + fullname=method.__qualname__, + hook_opts=hook_opts, + ) + warn_explicit_for(cast(FunctionType, method), message) + return opts + + @final class PytestPluginManager(PluginManager): """A :py:class:`pluggy.PluginManager ` with @@ -345,14 +389,24 @@ def __init__(self) -> None: import _pytest.assertion super().__init__("pytest") - # The objects are module objects, only used generically. - self._conftest_plugins: Set[types.ModuleType] = set() - # State related to local conftest plugins. + # -- State related to local conftest plugins. + # All loaded conftest modules. + self._conftest_plugins: Set[types.ModuleType] = set() + # All conftest modules applicable for a directory. + # This includes the directory's own conftest modules as well + # as those of its parent directories. self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {} - self._conftestpath2mod: Dict[Path, types.ModuleType] = {} + # Cutoff directory above which conftests are no longer discovered. self._confcutdir: Optional[Path] = None + # If set, conftest loading is skipped. self._noconftest = False + + # _getconftestmodules()'s call to _get_directory() causes a stat + # storm when it's called potentially thousands of times in a test + # session (#9478), often with the same path, so cache it. + self._get_directory = lru_cache(256)(_get_directory) + self._duplicatepaths: Set[Path] = set() # plugins that were explicitly skipped with pytest.skip @@ -394,40 +448,29 @@ def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str): if name == "pytest_plugins": return - method = getattr(plugin, name) opts = super().parse_hookimpl_opts(plugin, name) + if opts is not None: + return opts + method = getattr(plugin, name) # Consider only actual functions for hooks (#3775). if not inspect.isroutine(method): return - # Collect unmarked hooks as long as they have the `pytest_' prefix. - if opts is None and name.startswith("pytest_"): - opts = {} - if opts is not None: - # TODO: DeprecationWarning, people should use hookimpl - # https://github.com/pytest-dev/pytest/issues/4562 - known_marks = {m.name for m in getattr(method, "pytestmark", [])} - - for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): - opts.setdefault(name, hasattr(method, name) or name in known_marks) - return opts + return _get_legacy_hook_marks( + method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") + ) def parse_hookspec_opts(self, module_or_class, name: str): opts = super().parse_hookspec_opts(module_or_class, name) if opts is None: method = getattr(module_or_class, name) - if name.startswith("pytest_"): - # todo: deprecate hookspec hacks - # https://github.com/pytest-dev/pytest/issues/4562 - known_marks = {m.name for m in getattr(method, "pytestmark", [])} - opts = { - "firstresult": hasattr(method, "firstresult") - or "firstresult" in known_marks, - "historic": hasattr(method, "historic") - or "historic" in known_marks, - } + opts = _get_legacy_hook_marks( + method, + "spec", + ("firstresult", "historic"), + ) return opts def register( @@ -469,12 +512,14 @@ def pytest_configure(self, config: "Config") -> None: config.addinivalue_line( "markers", "tryfirst: mark a hook implementation function such that the " - "plugin machinery will try to call it first/as early as possible.", + "plugin machinery will try to call it first/as early as possible. " + "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.", ) config.addinivalue_line( "markers", "trylast: mark a hook implementation function such that the " - "plugin machinery will try to call it last/as late as possible.", + "plugin machinery will try to call it last/as late as possible. " + "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.", ) self._configured = True @@ -514,6 +559,15 @@ def _set_initial_conftests( if not foundanchor: self._try_load_conftest(current, namespace.importmode, rootpath) + def _is_in_confcutdir(self, path: Path) -> bool: + """Whether a path is within the confcutdir. + + When false, should not load conftest. + """ + if self._confcutdir is None: + return True + return path not in self._confcutdir.parents + def _try_load_conftest( self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path ) -> None: @@ -526,33 +580,28 @@ def _try_load_conftest( def _getconftestmodules( self, path: Path, importmode: Union[str, ImportMode], rootpath: Path - ) -> List[types.ModuleType]: + ) -> Sequence[types.ModuleType]: if self._noconftest: return [] - if path.is_file(): - directory = path.parent - else: - directory = path + directory = self._get_directory(path) # Optimization: avoid repeated searches in the same directory. # Assumes always called with same importmode and rootpath. existing_clist = self._dirpath2confmods.get(directory) - if existing_clist: + if existing_clist is not None: return existing_clist # XXX these days we may rather want to use config.rootpath # and allow users to opt into looking into the rootdir parent # directories instead of requiring to specify confcutdir. clist = [] - confcutdir_parents = self._confcutdir.parents if self._confcutdir else [] for parent in reversed((directory, *directory.parents)): - if parent in confcutdir_parents: - continue - conftestpath = parent / "conftest.py" - if conftestpath.is_file(): - mod = self._importconftest(conftestpath, importmode, rootpath) - clist.append(mod) + if self._is_in_confcutdir(parent): + conftestpath = parent / "conftest.py" + if conftestpath.is_file(): + mod = self._importconftest(conftestpath, importmode, rootpath) + clist.append(mod) self._dirpath2confmods[directory] = clist return clist @@ -574,15 +623,9 @@ def _rget_with_confmod( def _importconftest( self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path ) -> types.ModuleType: - # Use a resolved Path object as key to avoid loading the same conftest - # twice with build systems that create build directories containing - # symlinks to actual files. - # Using Path().resolve() is better than py.path.realpath because - # it resolves to the correct path/drive in case-insensitive file systems (#5792) - key = conftestpath.resolve() - - with contextlib.suppress(KeyError): - return self._conftestpath2mod[key] + existing = self.get_plugin(str(conftestpath)) + if existing is not None: + return cast(types.ModuleType, existing) pkgpath = resolve_package_path(conftestpath) if pkgpath is None: @@ -598,11 +641,10 @@ def _importconftest( self._check_non_top_pytest_plugins(mod, conftestpath) self._conftest_plugins.add(mod) - self._conftestpath2mod[key] = mod dirpath = conftestpath.parent if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): - if path and dirpath in path.parents or path == dirpath: + if dirpath in path.parents or path == dirpath: assert mod not in mods mods.append(mod) self.trace(f"loading conftestmodule {mod!r}") @@ -819,7 +861,8 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: if is_simple_module: module_name, _ = os.path.splitext(fn) # we ignore "setup.py" at the root of the distribution - if module_name != "setup": + # as well as editable installation finder modules made by setuptools + if module_name != "setup" and not module_name.startswith("__editable__"): seen_some = True yield module_name elif is_package: @@ -883,6 +926,19 @@ class InvocationParams: dir: Path """The directory from which :func:`pytest.main` was invoked.""" + class ArgsSource(enum.Enum): + """Indicates the source of the test arguments. + + .. versionadded:: 7.2 + """ + + #: Command line arguments. + ARGS = enum.auto() + #: Invocation directory. + INCOVATION_DIR = enum.auto() + #: 'testpaths' configuration value. + TESTPATHS = enum.auto() + def __init__( self, pluginmanager: PytestPluginManager, @@ -942,6 +998,8 @@ def __init__( self.hook.pytest_addoption.call_historic( kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) ) + self.args_source = Config.ArgsSource.ARGS + self.args: List[str] = [] if TYPE_CHECKING: from _pytest.cacheprovider import Cache @@ -970,7 +1028,7 @@ def inipath(self) -> Optional[Path]: def add_cleanup(self, func: Callable[[], None]) -> None: """Add a function to be called when the config object gets out of - use (usually coninciding with pytest_unconfigure).""" + use (usually coinciding with pytest_unconfigure).""" self._cleanup.append(func) def _do_configure(self) -> None: @@ -1085,11 +1143,11 @@ def _initini(self, args: Sequence[str]) -> None: self.inicfg = inicfg self._parser.extra_info["rootdir"] = str(self.rootpath) self._parser.extra_info["inifile"] = str(self.inipath) - self._parser.addini("addopts", "extra command line options", "args") - self._parser.addini("minversion", "minimally required pytest version") + self._parser.addini("addopts", "Extra command line options", "args") + self._parser.addini("minversion", "Minimally required pytest version") self._parser.addini( "required_plugins", - "plugins that must be present for pytest to run", + "Plugins that must be present for pytest to run", type="args", default=[], ) @@ -1281,8 +1339,8 @@ def _get_unknown_ini_keys(self) -> List[str]: def parse(self, args: List[str], addopts: bool = True) -> None: # Parse given cmdline arguments into this config object. - assert not hasattr( - self, "args" + assert ( + self.args == [] ), "can only parse cmdline args at most once per Config object" self.hook.pytest_addhooks.call_historic( kwargs=dict(pluginmanager=self.pluginmanager) @@ -1292,15 +1350,25 @@ def parse(self, args: List[str], addopts: bool = True) -> None: self.hook.pytest_cmdline_preparse(config=self, args=args) self._parser.after_preparse = True # type: ignore try: + source = Config.ArgsSource.ARGS args = self._parser.parse_setoption( args, self.option, namespace=self.option ) if not args: if self.invocation_params.dir == self.rootpath: - args = self.getini("testpaths") + source = Config.ArgsSource.TESTPATHS + testpaths: List[str] = self.getini("testpaths") + if self.known_args_namespace.pyargs: + args = testpaths + else: + args = [] + for path in testpaths: + args.extend(sorted(glob.iglob(path, recursive=True))) if not args: + source = Config.ArgsSource.INCOVATION_DIR args = [str(self.invocation_params.dir)] self.args = args + self.args_source = source except PrintHelp: pass @@ -1330,14 +1398,6 @@ def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: if records: frame = sys._getframe(stacklevel - 1) location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name - self.hook.pytest_warning_captured.call_historic( - kwargs=dict( - warning_message=records[0], - when="config", - item=None, - location=location, - ) - ) self.hook.pytest_warning_recorded.call_historic( kwargs=dict( warning_message=records[0], @@ -1437,6 +1497,7 @@ def _getconftest_pathlist( ) except KeyError: return None + assert mod.__file__ is not None modpath = Path(mod.__file__).parent values: List[Path] = [] for relroot in relroots: @@ -1582,7 +1643,7 @@ def _strtobool(val: str) -> bool: @lru_cache(maxsize=50) def parse_warning_filter( arg: str, *, escape: bool -) -> Tuple[str, str, Type[Warning], str, int]: +) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]: """Parse a warnings filter string. This is copied from warnings._setoption with the following changes: @@ -1624,7 +1685,7 @@ def parse_warning_filter( parts.append("") action_, message, category_, module, lineno_ = (s.strip() for s in parts) try: - action: str = warnings._getaction(action_) # type: ignore[attr-defined] + action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined] except warnings._OptionError as e: raise UsageError(error_template.format(error=str(e))) try: diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index b0bb3f168ff..d3f01916b61 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -9,6 +9,7 @@ from typing import Dict from typing import List from typing import Mapping +from typing import NoReturn from typing import Optional from typing import Sequence from typing import Tuple @@ -24,7 +25,6 @@ from _pytest.deprecated import check_ispytest if TYPE_CHECKING: - from typing import NoReturn from typing_extensions import Literal FILE_OR_DIR = "file_or_dir" @@ -48,7 +48,7 @@ def __init__( _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self._anonymous = OptionGroup("custom options", parser=self, _ispytest=True) + self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True) self._groups: List[OptionGroup] = [] self._processopt = processopt self._usage = usage @@ -66,14 +66,15 @@ def getgroup( ) -> "OptionGroup": """Get (or create) a named option Group. - :name: Name of the option group. - :description: Long description for --help output. - :after: Name of another group, used for ordering --help output. + :param name: Name of the option group. + :param description: Long description for --help output. + :param after: Name of another group, used for ordering --help output. + :returns: The option group. The returned group object has an ``addoption`` method with the same signature as :func:`parser.addoption ` but will be shown in the respective group in the output of - ``pytest. --help``. + ``pytest --help``. """ for group in self._groups: if group.name == name: @@ -89,10 +90,11 @@ def getgroup( def addoption(self, *opts: str, **attrs: Any) -> None: """Register a command line option. - :opts: Option names, can be short or long options. - :attrs: Same attributes which the ``add_argument()`` function of the - `argparse library `_ - accepts. + :param opts: + Option names, can be short or long options. + :param attrs: + Same attributes as the argparse library's :py:func:`add_argument() + ` function accepts. After command line parsing, options are available on the pytest config object via ``config.option.NAME`` where ``NAME`` is usually set @@ -148,7 +150,10 @@ def parse_known_args( args: Sequence[Union[str, "os.PathLike[str]"]], namespace: Optional[argparse.Namespace] = None, ) -> argparse.Namespace: - """Parse and return a namespace object with known arguments at this point.""" + """Parse the known arguments at this point. + + :returns: An argparse namespace object. + """ return self.parse_known_and_unknown_args(args, namespace=namespace)[0] def parse_known_and_unknown_args( @@ -156,8 +161,13 @@ def parse_known_and_unknown_args( args: Sequence[Union[str, "os.PathLike[str]"]], namespace: Optional[argparse.Namespace] = None, ) -> Tuple[argparse.Namespace, List[str]]: - """Parse and return a namespace object with known arguments, and - the remaining arguments unknown at this point.""" + """Parse the known arguments at this point, and also return the + remaining unknown arguments. + + :returns: + A tuple containing an argparse namespace object for the known + arguments, and a list of the unknown arguments. + """ optparser = self._getparser() strargs = [os.fspath(x) for x in args] return optparser.parse_known_args(strargs, namespace=namespace) @@ -169,13 +179,13 @@ def addini( type: Optional[ "Literal['string', 'paths', 'pathlist', 'args', 'linelist', 'bool']" ] = None, - default=None, + default: Any = None, ) -> None: """Register an ini-file option. - :name: + :param name: Name of the ini-variable. - :type: + :param type: Type of the variable. Can be: * ``string``: a string @@ -189,7 +199,7 @@ def addini( The ``paths`` variable type. Defaults to ``string`` if ``None`` or not passed. - :default: + :param default: Default value if no ini-file option exists but is queried. The value of ini-variables can be retrieved via a call to @@ -227,7 +237,7 @@ class Argument: _typ_map = {"int": int, "string": str, "float": float, "complex": complex} def __init__(self, *names: str, **attrs: Any) -> None: - """Store parms in private vars for use in add_argument.""" + """Store params in private vars for use in add_argument.""" self._attrs = attrs self._short_opts: List[str] = [] self._long_opts: List[str] = [] @@ -354,24 +364,30 @@ def __init__( self.options: List[Argument] = [] self.parser = parser - def addoption(self, *optnames: str, **attrs: Any) -> None: + def addoption(self, *opts: str, **attrs: Any) -> None: """Add an option to this group. If a shortened version of a long option is specified, it will be suppressed in the help. ``addoption('--twowords', '--two-words')`` results in help showing ``--two-words`` only, but ``--twowords`` gets accepted **and** the automatic destination is in ``args.twowords``. + + :param opts: + Option names, can be short or long options. + :param attrs: + Same attributes as the argparse library's :py:func:`add_argument() + ` function accepts. """ - conflict = set(optnames).intersection( + conflict = set(opts).intersection( name for opt in self.options for name in opt.names() ) if conflict: raise ValueError("option names %s already added" % conflict) - option = Argument(*optnames, **attrs) + option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=False) - def _addoption(self, *optnames: str, **attrs: Any) -> None: - option = Argument(*optnames, **attrs) + def _addoption(self, *opts: str, **attrs: Any) -> None: + option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=True) def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None: @@ -403,7 +419,7 @@ def __init__( # an usage error to provide more contextual information to the user. self.extra_info = extra_info if extra_info else {} - def error(self, message: str) -> "NoReturn": + def error(self, message: str) -> NoReturn: """Transform argparse error message into UsageError.""" msg = f"{self.prog}: error: {message}" diff --git a/src/_pytest/config/compat.py b/src/_pytest/config/compat.py index 8f82dd9f9d7..ba267d21505 100644 --- a/src/_pytest/config/compat.py +++ b/src/_pytest/config/compat.py @@ -23,7 +23,7 @@ class PathAwareHookProxy: this helper wraps around hook callers until pluggy supports fixingcalls, this one will do - it currently doesnt return full hook caller proxies for fixed hooks, + it currently doesn't return full hook caller proxies for fixed hooks, this may have to be changed later depending on bugs """ diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 89ade5f23b9..234b9e12906 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -1,4 +1,5 @@ import os +import sys from pathlib import Path from typing import Dict from typing import Iterable @@ -64,13 +65,16 @@ def load_config_dict_from_file( # '.toml' files are considered if they contain a [tool.pytest.ini_options] table. elif filepath.suffix == ".toml": - import tomli + if sys.version_info >= (3, 11): + import tomllib + else: + import tomli as tomllib toml_text = filepath.read_text(encoding="utf-8") try: - config = tomli.loads(toml_text) - except tomli.TOMLDecodeError as exc: - raise UsageError(str(exc)) from exc + config = tomllib.loads(toml_text) + except tomllib.TOMLDecodeError as exc: + raise UsageError(f"{filepath}: {exc}") from exc result = config.get("tool", {}).get("pytest", {}).get("ini_options", None) if result is not None: @@ -92,6 +96,7 @@ def locate_config( and return a tuple of (rootdir, inifile, cfg-dict).""" config_names = [ "pytest.ini", + ".pytest.ini", "pyproject.toml", "tox.ini", "setup.cfg", @@ -198,8 +203,7 @@ def determine_setup( else: cwd = Path.cwd() rootdir = get_common_ancestor([cwd, ancestor]) - is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/" - if is_fs_root: + if is_fs_root(rootdir): rootdir = ancestor if rootdir_cmd_arg: rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg)) @@ -211,3 +215,11 @@ def determine_setup( ) assert rootdir is not None return rootdir, inipath, inicfg or {} + + +def is_fs_root(p: Path) -> bool: + r""" + Return True if the given path is pointing to the root of the + file system ("/" on Unix and "C:\\" on Windows for example). + """ + return os.path.splitdrive(str(p))[1] == os.sep diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 452fb18ac34..a3f80802cab 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -3,6 +3,7 @@ import functools import sys import types +import unittest from typing import Any from typing import Callable from typing import Generator @@ -46,21 +47,21 @@ def pytest_addoption(parser: Parser) -> None: "--pdb", dest="usepdb", action="store_true", - help="start the interactive Python debugger on errors or KeyboardInterrupt.", + help="Start the interactive Python debugger on errors or KeyboardInterrupt", ) group._addoption( "--pdbcls", dest="usepdb_cls", metavar="modulename:classname", type=_validate_usepdb_cls, - help="specify a custom interactive Python debugger for use with --pdb." + help="Specify a custom interactive Python debugger for use with --pdb." "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb", ) group._addoption( "--trace", dest="trace", action="store_true", - help="Immediately break when running each test.", + help="Immediately break when running each test", ) @@ -293,7 +294,9 @@ def pytest_exception_interact( sys.stdout.write(out) sys.stdout.write(err) assert call.excinfo is not None - _enter_pdb(node, call.excinfo, report) + + if not isinstance(call.excinfo.value, unittest.SkipTest): + _enter_pdb(node, call.excinfo, report) def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: tb = _postmortem_traceback(excinfo) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 5f1edd60727..b9c10df7a00 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -11,7 +11,6 @@ from warnings import warn from _pytest.warning_types import PytestDeprecationWarning -from _pytest.warning_types import PytestRemovedIn7Warning from _pytest.warning_types import PytestRemovedIn8Warning from _pytest.warning_types import UnformattedWarning @@ -23,19 +22,22 @@ "pytest_faulthandler", } - -FILLFUNCARGS = UnformattedWarning( - PytestRemovedIn7Warning, - "{name} is deprecated, use " - "function._request._fillfixtures() instead if you cannot avoid reaching into internals.", +NOSE_SUPPORT = UnformattedWarning( + PytestRemovedIn8Warning, + "Support for nose tests is deprecated and will be removed in a future release.\n" + "{nodeid} is using nose method: `{method}` ({stage})\n" + "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", ) -PYTEST_COLLECT_MODULE = UnformattedWarning( - PytestRemovedIn7Warning, - "pytest.collect.{name} was moved to pytest.{name}\n" - "Please update to the new name.", +NOSE_SUPPORT_METHOD = UnformattedWarning( + PytestRemovedIn8Warning, + "Support for nose tests is deprecated and will be removed in a future release.\n" + "{nodeid} is using nose-specific method: `{method}(self)`\n" + "To remove this warning, rename it to `{method}_method(self)`\n" + "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", ) + # This can be* removed pytest 8, but it's harmless and common, so no rush to remove. # * If you're in the future: "could have been". YIELD_FIXTURE = PytestDeprecationWarning( @@ -43,20 +45,6 @@ "Use @pytest.fixture instead; they are the same." ) -MINUS_K_DASH = PytestRemovedIn7Warning( - "The `-k '-expr'` syntax to -k is deprecated.\nUse `-k 'not expr'` instead." -) - -MINUS_K_COLON = PytestRemovedIn7Warning( - "The `-k 'expr:'` syntax to -k is deprecated.\n" - "Please open an issue if you use this and want a replacement." -) - -WARNING_CAPTURED_HOOK = PytestRemovedIn7Warning( - "The pytest_warning_captured is deprecated and will be removed in a future release.\n" - "Please use pytest_warning_recorded instead." -) - WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning( "The pytest_cmdline_preparse hook is deprecated and will be removed in a future release. \n" "Please use pytest_load_initial_conftests hook instead." @@ -74,11 +62,6 @@ # This deprecation is never really meant to be removed. PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") -UNITTEST_SKIP_DURING_COLLECTION = PytestRemovedIn8Warning( - "Raising unittest.SkipTest to skip tests during collection is deprecated. " - "Use pytest.skip() instead." -) - ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning( 'pytest now uses argparse. "%default" should be changed to "%(default)s"', ) @@ -115,8 +98,10 @@ ) WARNS_NONE_ARG = PytestRemovedIn8Warning( - "Passing None to catch any warning has been deprecated, pass no arguments instead:\n" - " Replace pytest.warns(None) by simply pytest.warns()." + "Passing None has been deprecated.\n" + "See https://docs.pytest.org/en/latest/how-to/capture-warnings.html" + "#additional-use-cases-of-warnings-in-tests" + " for alternatives in common use cases." ) KEYWORD_MSG_ARG = UnformattedWarning( @@ -128,6 +113,14 @@ "The pytest.Instance collector type is deprecated and is no longer used. " "See https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector", ) +HOOK_LEGACY_MARKING = UnformattedWarning( + PytestDeprecationWarning, + "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n" + "Please use the pytest.hook{type}({hook_opts}) decorator instead\n" + " to configure the hooks.\n" + " See https://docs.pytest.org/en/latest/deprecations.html" + "#configuring-hook-specs-impls-using-markers", +) # You want to make some `__init__` or function "private". # diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 0784f431b8e..771f0890d88 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -23,7 +23,6 @@ from typing import TYPE_CHECKING from typing import Union -import pytest from _pytest import outcomes from _pytest._code.code import ExceptionInfo from _pytest._code.code import ReprFileLocation @@ -32,11 +31,15 @@ from _pytest.compat import safe_getattr from _pytest.config import Config from _pytest.config.argparsing import Parser +from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.nodes import Collector +from _pytest.nodes import Item from _pytest.outcomes import OutcomeException +from _pytest.outcomes import skip from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import import_path +from _pytest.python import Module from _pytest.python_api import approx from _pytest.warning_types import PytestWarning @@ -66,26 +69,26 @@ def pytest_addoption(parser: Parser) -> None: parser.addini( "doctest_optionflags", - "option flags for doctests", + "Option flags for doctests", type="args", default=["ELLIPSIS"], ) parser.addini( - "doctest_encoding", "encoding used for doctest files", default="utf-8" + "doctest_encoding", "Encoding used for doctest files", default="utf-8" ) group = parser.getgroup("collect") group.addoption( "--doctest-modules", action="store_true", default=False, - help="run doctests in all .py modules", + help="Run doctests in all .py modules", dest="doctestmodules", ) group.addoption( "--doctest-report", type=str.lower, default="udiff", - help="choose another output format for diffs on doctest failure", + help="Choose another output format for diffs on doctest failure", choices=DOCTEST_REPORT_CHOICES, dest="doctestreport", ) @@ -94,21 +97,21 @@ def pytest_addoption(parser: Parser) -> None: action="append", default=[], metavar="pat", - help="doctests file matching pattern, default: test*.txt", + help="Doctests file matching pattern, default: test*.txt", dest="doctestglob", ) group.addoption( "--doctest-ignore-import-errors", action="store_true", default=False, - help="ignore doctest ImportErrors", + help="Ignore doctest ImportErrors", dest="doctest_ignore_import_errors", ) group.addoption( "--doctest-continue-on-failure", action="store_true", default=False, - help="for a given doctest, continue to run after the first failure", + help="For a given doctest, continue to run after the first failure", dest="doctest_continue_on_failure", ) @@ -246,7 +249,7 @@ def _get_runner( ) -class DoctestItem(pytest.Item): +class DoctestItem(Item): def __init__( self, name: str, @@ -411,7 +414,7 @@ def _get_continue_on_failure(config): return continue_on_failure -class DoctestTextfile(pytest.Module): +class DoctestTextfile(Module): obj = None def collect(self) -> Iterable[DoctestItem]: @@ -449,7 +452,7 @@ def _check_all_skipped(test: "doctest.DocTest") -> None: all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) if all_skipped: - pytest.skip("all tests skipped by +SKIP option") + skip("all tests skipped by +SKIP option") def _is_mocked(obj: object) -> bool: @@ -491,7 +494,7 @@ def _mock_aware_unwrap( inspect.unwrap = real_unwrap -class DoctestModule(pytest.Module): +class DoctestModule(Module): def collect(self) -> Iterable[DoctestItem]: import doctest @@ -542,10 +545,14 @@ def _find( ) else: try: - module = import_path(self.path, root=self.config.rootpath) + module = import_path( + self.path, + root=self.config.rootpath, + mode=self.config.getoption("importmode"), + ) except ImportError: if self.config.getvalue("doctest_ignore_import_errors"): - pytest.skip("unable to import module %r" % self.path) + skip("unable to import module %r" % self.path) else: raise # Uses internal doctest module parsing mechanism. @@ -656,7 +663,7 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str: precision = 0 if fraction is None else len(fraction) if exponent is not None: precision -= int(exponent) - if float(w.group()) == approx(float(g.group()), abs=10 ** -precision): + if float(w.group()) == approx(float(g.group()), abs=10**-precision): # They're close enough. Replace the text we actually # got with the text we want, so that it will match when we # check the string literally. @@ -727,8 +734,19 @@ def _get_report_choice(key: str) -> int: }[key] -@pytest.fixture(scope="session") +@fixture(scope="session") def doctest_namespace() -> Dict[str, Any]: """Fixture that returns a :py:class:`dict` that will be injected into the - namespace of doctests.""" + namespace of doctests. + + Usually this fixture is used in conjunction with another ``autouse`` fixture: + + .. code-block:: python + + @pytest.fixture(autouse=True) + def add_np(doctest_namespace): + doctest_namespace["np"] = numpy + + For more details: :ref:`doctest_namespace`. + """ return dict() diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index aaee307ff2c..b9c925582ca 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -18,7 +18,7 @@ def pytest_addoption(parser: Parser) -> None: help = ( "Dump the traceback of all threads if a test takes " - "more than TIMEOUT seconds to finish." + "more than TIMEOUT seconds to finish" ) parser.addini("faulthandler_timeout", help, default=0.0) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index fddff931c51..7ef261b969a 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -18,8 +18,8 @@ from typing import Iterator from typing import List from typing import MutableMapping +from typing import NoReturn from typing import Optional -from typing import overload from typing import Sequence from typing import Set from typing import Tuple @@ -47,17 +47,18 @@ from _pytest.compat import getlocation from _pytest.compat import is_generator from _pytest.compat import NOTSET +from _pytest.compat import overload from _pytest.compat import safe_getattr from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.deprecated import FILLFUNCARGS from _pytest.deprecated import YIELD_FIXTURE from _pytest.mark import Mark from _pytest.mark import ParameterSet from _pytest.mark.structures import MarkDecorator from _pytest.outcomes import fail +from _pytest.outcomes import skip from _pytest.outcomes import TEST_OUTCOME from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath @@ -68,12 +69,10 @@ if TYPE_CHECKING: from typing import Deque - from typing import NoReturn from _pytest.scope import _ScopeName from _pytest.main import Session from _pytest.python import CallSpec2 - from _pytest.python import Function from _pytest.python import Metafunc @@ -225,15 +224,10 @@ def add_funcarg_pseudo_fixture_def( def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: """Return fixturemarker or None if it doesn't exist or raised exceptions.""" - try: - fixturemarker: Optional[FixtureFunctionMarker] = getattr( - obj, "_pytestfixturefunction", None - ) - except TEST_OUTCOME: - # some objects raise errors like request (from flask import request) - # we don't expect them to be fixture functions - return None - return fixturemarker + return cast( + Optional[FixtureFunctionMarker], + safe_getattr(obj, "_pytestfixturefunction", None), + ) # Parametrized fixture key, helper alias for code below. @@ -352,42 +346,7 @@ def reorder_items_atscope( return items_done -def _fillfuncargs(function: "Function") -> None: - """Fill missing fixtures for a test function, old public API (deprecated).""" - warnings.warn(FILLFUNCARGS.format(name="pytest._fillfuncargs()"), stacklevel=2) - _fill_fixtures_impl(function) - - -def fillfixtures(function: "Function") -> None: - """Fill missing fixtures for a test function (deprecated).""" - warnings.warn( - FILLFUNCARGS.format(name="_pytest.fixtures.fillfixtures()"), stacklevel=2 - ) - _fill_fixtures_impl(function) - - -def _fill_fixtures_impl(function: "Function") -> None: - """Internal implementation to fill fixtures on the given function object.""" - try: - request = function._request - except AttributeError: - # XXX this special code path is only expected to execute - # with the oejskit plugin. It uses classes with funcargs - # and we thus have to work a bit to allow this. - fm = function.session._fixturemanager - assert function.parent is not None - fi = fm.getfixtureinfo(function.parent, function.obj, None) - function._fixtureinfo = fi - request = function._request = FixtureRequest(function, _ispytest=True) - fm.session._setupstate.setup(function) - request._fillfixtures() - # Prune out funcargs for jstests. - function.funcargs = {name: function.funcargs[name] for name in fi.argnames} - else: - request._fillfixtures() - - -def get_direct_param_fixture_func(request): +def get_direct_param_fixture_func(request: "FixtureRequest") -> Any: return request.param @@ -449,6 +408,15 @@ def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None: self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() self._arg2index: Dict[str, int] = {} self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager + # Notes on the type of `param`: + # -`request.param` is only defined in parametrized fixtures, and will raise + # AttributeError otherwise. Python typing has no notion of "undefined", so + # this cannot be reflected in the type. + # - Technically `param` is only (possibly) defined on SubRequest, not + # FixtureRequest, but the typing of that is still in flux so this cheats. + # - In the future we might consider using a generic for the param type, but + # for now just using Any. + self.param: Any @property def scope(self) -> "_ScopeName": @@ -528,6 +496,7 @@ def module(self): @property def path(self) -> Path: + """Path where the test function was collected.""" if self.scope not in ("function", "class", "module", "package"): raise AttributeError(f"path not available in {self.scope}-scoped context") # TODO: Remove ignore once _pyfuncitem is properly typed. @@ -545,8 +514,8 @@ def session(self) -> "Session": return self._pyfuncitem.session # type: ignore[no-any-return] def addfinalizer(self, finalizer: Callable[[], object]) -> None: - """Add finalizer/teardown function to be called after the last test - within the requesting test context finished execution.""" + """Add finalizer/teardown function to be called without arguments after + the last test within the requesting test context finished execution.""" # XXX usually this method is shadowed by fixturedef specific ones. self._addfinalizer(finalizer, scope=self.scope) @@ -561,13 +530,16 @@ def applymarker(self, marker: Union[str, MarkDecorator]) -> None: on all function invocations. :param marker: - A :class:`pytest.MarkDecorator` object created by a call - to ``pytest.mark.NAME(...)``. + An object created by a call to ``pytest.mark.NAME(...)``. """ self.node.add_marker(marker) - def raiseerror(self, msg: Optional[str]) -> "NoReturn": - """Raise a FixtureLookupError with the given message.""" + def raiseerror(self, msg: Optional[str]) -> NoReturn: + """Raise a FixtureLookupError exception. + + :param msg: + An optional custom error message. + """ raise self._fixturemanager.FixtureLookupError(None, self, msg) def _fillfixtures(self) -> None: @@ -585,11 +557,20 @@ def getfixturevalue(self, argname: str) -> Any: setup time, you may use this function to retrieve it inside a fixture or test function body. + This method can be used during the test setup phase or the test run + phase, but during the test teardown phase a fixture's value may not + be available. + + :param argname: + The fixture name. :raises pytest.FixtureLookupError: If the given fixture could not be found. """ fixturedef = self._get_active_fixturedef(argname) - assert fixturedef.cached_result is not None + assert fixturedef.cached_result is not None, ( + f'The fixture value for "{argname}" is not available. ' + "This can happen when the fixture has already been torn down." + ) return fixturedef.cached_result[0] def _get_active_fixturedef( @@ -634,8 +615,17 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: funcitem = self._pyfuncitem scope = fixturedef._scope try: - param = funcitem.callspec.getparam(argname) - except (AttributeError, ValueError): + callspec = funcitem.callspec + except AttributeError: + callspec = None + if callspec is not None and argname in callspec.params: + param = callspec.params[argname] + param_index = callspec.indices[argname] + # If a parametrize invocation set a scope it will override + # the static scope defined with the fixture function. + with suppress(KeyError): + scope = callspec._arg2scope[argname] + else: param = NOTSET param_index = 0 has_params = fixturedef.params is not None @@ -675,12 +665,6 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: ) ) fail(msg, pytrace=False) - else: - param_index = funcitem.callspec.indices[argname] - # If a parametrize invocation set a scope it will override - # the static scope defined with the fixture function. - with suppress(KeyError): - scope = funcitem.callspec._arg2scope[argname] subrequest = SubRequest( self, scope, param, param_index, fixturedef, _ispytest=True @@ -790,8 +774,8 @@ def __repr__(self) -> str: return f"" def addfinalizer(self, finalizer: Callable[[], object]) -> None: - """Add finalizer/teardown function to be called after the last test - within the requesting test context finished execution.""" + """Add finalizer/teardown function to be called without arguments after + the last test within the requesting test context finished execution.""" self._fixturedef.addfinalizer(finalizer) def _schedule_finalizers( @@ -898,7 +882,7 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1)) -def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn": +def fail_fixturefunc(fixturefunc, msg: str) -> NoReturn: fs, lineno = getfslineno(fixturefunc) location = f"{fs}:{lineno + 1}" source = _pytest._code.Source(fixturefunc) @@ -964,7 +948,7 @@ def _eval_scope_callable( @final class FixtureDef(Generic[FixtureValue]): - """A container for a factory definition.""" + """A container for a fixture definition.""" def __init__( self, @@ -976,33 +960,56 @@ def __init__( params: Optional[Sequence[object]], unittest: bool = False, ids: Optional[ - Union[ - Tuple[Union[None, str, float, int, bool], ...], - Callable[[Any], Optional[object]], - ] + Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] ] = None, ) -> None: self._fixturemanager = fixturemanager + # The "base" node ID for the fixture. + # + # This is a node ID prefix. A fixture is only available to a node (e.g. + # a `Function` item) if the fixture's baseid is a parent of the node's + # nodeid (see the `iterparentnodeids` function for what constitutes a + # "parent" and a "prefix" in this context). + # + # For a fixture found in a Collector's object (e.g. a `Module`s module, + # a `Class`'s class), the baseid is the Collector's nodeid. + # + # For a fixture found in a conftest plugin, the baseid is the conftest's + # directory path relative to the rootdir. + # + # For other plugins, the baseid is the empty string (always matches). self.baseid = baseid or "" + # Whether the fixture was found from a node or a conftest in the + # collection tree. Will be false for fixtures defined in non-conftest + # plugins. self.has_location = baseid is not None + # The fixture factory function. self.func = func + # The name by which the fixture may be requested. self.argname = argname if scope is None: scope = Scope.Function elif callable(scope): scope = _eval_scope_callable(scope, argname, fixturemanager.config) - if isinstance(scope, str): scope = Scope.from_user( scope, descr=f"Fixture '{func.__name__}'", where=baseid ) self._scope = scope + # If the fixture is directly parametrized, the parameter values. self.params: Optional[Sequence[object]] = params - self.argnames: Tuple[str, ...] = getfuncargnames( - func, name=argname, is_method=unittest - ) - self.unittest = unittest + # If the fixture is directly parametrized, a tuple of explicit IDs to + # assign to the parameter values, or a callable to generate an ID given + # a parameter value. self.ids = ids + # The names requested by the fixtures. + self.argnames = getfuncargnames(func, name=argname, is_method=unittest) + # Whether the fixture was collected from a unittest TestCase class. + # Note that it really only makes sense to define autouse fixtures in + # unittest TestCases. + self.unittest = unittest + # If the fixture was executed, the current value of the fixture. + # Can change if the fixture is executed with different parameters. self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None self._finalizers: List[Callable[[], object]] = [] @@ -1029,8 +1036,8 @@ def finish(self, request: SubRequest) -> None: if exc: raise exc finally: - hook = self._fixturemanager.session.gethookproxy(request.node.path) - hook.pytest_fixture_post_finalizer(fixturedef=self, request=request) + ihook = request.node.ihook + ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request) # Even if finalization fails, we invalidate the cached fixture # value and remove all finalizers because they may be bound methods # which will keep instances alive. @@ -1064,8 +1071,8 @@ def execute(self, request: SubRequest) -> FixtureValue: self.finish(request) assert self.cached_result is None - hook = self._fixturemanager.session.gethookproxy(request.node.path) - result = hook.pytest_fixture_setup(fixturedef=self, request=request) + ihook = request.node.ihook + result = ihook.pytest_fixture_setup(fixturedef=self, request=request) return result def cache_key(self, request: SubRequest) -> object: @@ -1123,6 +1130,10 @@ def pytest_fixture_setup( except TEST_OUTCOME: exc_info = sys.exc_info() assert exc_info[0] is not None + if isinstance( + exc_info[1], skip.Exception + ) and not fixturefunc.__name__.startswith("xunit_setup"): + exc_info[1]._use_item_location = True # type: ignore[attr-defined] fixturedef.cached_result = (None, my_cache_key, exc_info) raise fixturedef.cached_result = (result, my_cache_key, None) @@ -1130,18 +1141,8 @@ def pytest_fixture_setup( def _ensure_immutable_ids( - ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] - ], -) -> Optional[ - Union[ - Tuple[Union[None, str, float, int, bool], ...], - Callable[[Any], Optional[object]], - ] -]: + ids: Optional[Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]] +) -> Optional[Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]]: if ids is None: return None if callable(ids): @@ -1185,9 +1186,8 @@ class FixtureFunctionMarker: scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter) autouse: bool = False - ids: Union[ - Tuple[Union[None, str, float, int, bool], ...], - Callable[[Any], Optional[object]], + ids: Optional[ + Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] ] = attr.ib( default=None, converter=_ensure_immutable_ids, @@ -1228,10 +1228,7 @@ def fixture( params: Optional[Iterable[object]] = ..., autouse: bool = ..., ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] ] = ..., name: Optional[str] = ..., ) -> FixtureFunction: @@ -1239,34 +1236,28 @@ def fixture( @overload -def fixture( +def fixture( # noqa: F811 fixture_function: None = ..., *, scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., params: Optional[Iterable[object]] = ..., autouse: bool = ..., ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] ] = ..., name: Optional[str] = None, ) -> FixtureFunctionMarker: ... -def fixture( +def fixture( # noqa: F811 fixture_function: Optional[FixtureFunction] = None, *, scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function", params: Optional[Iterable[object]] = None, autouse: bool = False, ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] ] = None, name: Optional[str] = None, ) -> Union[FixtureFunctionMarker, FixtureFunction]: @@ -1308,7 +1299,7 @@ def fixture( the fixture. :param ids: - List of string ids each corresponding to the params so that they are + Sequence of ids each corresponding to the params so that they are part of the test id. If no ids are provided they will be generated automatically from the params. @@ -1381,7 +1372,7 @@ def pytest_addoption(parser: Parser) -> None: "usefixtures", type="args", default=[], - help="list of default fixtures to be used with this project", + help="List of default fixtures to be used with this project", ) diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index aca2cd391e4..151bc6dff95 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -49,7 +49,7 @@ def pytest_addoption(parser: Parser) -> None: action="count", default=0, dest="version", - help="display pytest version and information about plugins. " + help="Display pytest version and information about plugins. " "When given twice, also display information about plugins.", ) group._addoption( @@ -57,7 +57,7 @@ def pytest_addoption(parser: Parser) -> None: "--help", action=HelpAction, dest="help", - help="show help message and configuration info", + help="Show help message and configuration info", ) group._addoption( "-p", @@ -65,7 +65,7 @@ def pytest_addoption(parser: Parser) -> None: dest="plugins", default=[], metavar="name", - help="early-load given plugin module name or entry point (multi-allowed).\n" + help="Early-load given plugin module name or entry point (multi-allowed). " "To avoid loading of plugins, use the `no:` prefix, e.g. " "`no:doctest`.", ) @@ -74,7 +74,7 @@ def pytest_addoption(parser: Parser) -> None: "--trace-config", action="store_true", default=False, - help="trace considerations of conftest.py files.", + help="Trace considerations of conftest.py files", ) group.addoption( "--debug", @@ -83,16 +83,17 @@ def pytest_addoption(parser: Parser) -> None: const="pytestdebug.log", dest="debug", metavar="DEBUG_FILE_NAME", - help="store internal tracing debug information in this log file.\n" - "This file is opened with 'w' and truncated as a result, care advised.\n" - "Defaults to 'pytestdebug.log'.", + help="Store internal tracing debug information in this log file. " + "This file is opened with 'w' and truncated as a result, care advised. " + "Default: pytestdebug.log.", ) group._addoption( "-o", "--override-ini", dest="override_ini", action="append", - help='override ini option with "option=value" style, e.g. `-o xfail_strict=True -o cache_dir=cache`.', + help='Override ini option with "option=value" style, ' + "e.g. `-o xfail_strict=True -o cache_dir=cache`.", ) @@ -203,12 +204,12 @@ def showhelp(config: Config) -> None: tw.line(indent + line) tw.line() - tw.line("environment variables:") + tw.line("Environment variables:") vars = [ - ("PYTEST_ADDOPTS", "extra command line options"), - ("PYTEST_PLUGINS", "comma-separated plugins to load during startup"), - ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "set to disable plugin auto-loading"), - ("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"), + ("PYTEST_ADDOPTS", "Extra command line options"), + ("PYTEST_PLUGINS", "Comma-separated plugins to load during startup"), + ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "Set to disable plugin auto-loading"), + ("PYTEST_DEBUG", "Set to enable debug tracing of pytest's internals"), ] for name, help in vars: tw.line(f" {name:<24} {help}") diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index b65be981560..cc0828dd19e 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -13,7 +13,6 @@ from pluggy import HookspecMarker -from _pytest.deprecated import WARNING_CAPTURED_HOOK from _pytest.deprecated import WARNING_CMDLINE_PREPARSE_HOOK if TYPE_CHECKING: @@ -34,10 +33,10 @@ from _pytest.nodes import Collector from _pytest.nodes import Item from _pytest.outcomes import Exit + from _pytest.python import Class from _pytest.python import Function from _pytest.python import Metafunc from _pytest.python import Module - from _pytest.python import PyCollector from _pytest.reports import CollectReport from _pytest.reports import TestReport from _pytest.runner import CallInfo @@ -144,7 +143,7 @@ def pytest_configure(config: "Config") -> None: def pytest_cmdline_parse( pluginmanager: "PytestPluginManager", args: List[str] ) -> Optional["Config"]: - """Return an initialized config object, parsing the specified args. + """Return an initialized :class:`~pytest.Config`, parsing the specified args. Stops at first non-None result, see :ref:`firstresult`. @@ -153,8 +152,9 @@ def pytest_cmdline_parse( ``plugins`` arg when using `pytest.main`_ to perform an in-process test run. - :param pytest.PytestPluginManager pluginmanager: The pytest plugin manager. - :param List[str] args: List of arguments passed on the command line. + :param pluginmanager: The pytest plugin manager. + :param args: List of arguments passed on the command line. + :returns: A pytest config object. """ @@ -163,13 +163,13 @@ def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None: """(**Deprecated**) modify command line arguments before option parsing. This hook is considered deprecated and will be removed in a future pytest version. Consider - using :func:`pytest_load_initial_conftests` instead. + using :hook:`pytest_load_initial_conftests` instead. .. note:: This hook will not be called for ``conftest.py`` files, only for setuptools plugins. - :param pytest.Config config: The pytest config object. - :param List[str] args: Arguments passed on the command line. + :param config: The pytest config object. + :param args: Arguments passed on the command line. """ @@ -180,7 +180,8 @@ def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: Stops at first non-None result, see :ref:`firstresult`. - :param pytest.Config config: The pytest config object. + :param config: The pytest config object. + :returns: The exit code. """ @@ -193,9 +194,9 @@ def pytest_load_initial_conftests( .. note:: This hook will not be called for ``conftest.py`` files, only for setuptools plugins. - :param pytest.Config early_config: The pytest config object. - :param List[str] args: Arguments passed on the command line. - :param pytest.Parser parser: To add command line options. + :param early_config: The pytest config object. + :param args: Arguments passed on the command line. + :param parser: To add command line options. """ @@ -237,7 +238,7 @@ def pytest_collection(session: "Session") -> Optional[object]: for example the terminal plugin uses it to start displaying the collection counter (and returns `None`). - :param pytest.Session session: The pytest session object. + :param session: The pytest session object. """ @@ -247,16 +248,16 @@ def pytest_collection_modifyitems( """Called after collection has been performed. May filter or re-order the items in-place. - :param pytest.Session session: The pytest session object. - :param pytest.Config config: The pytest config object. - :param List[pytest.Item] items: List of item objects. + :param session: The pytest session object. + :param config: The pytest config object. + :param items: List of item objects. """ def pytest_collection_finish(session: "Session") -> None: """Called after collection has been performed and modified. - :param pytest.Session session: The pytest session object. + :param session: The pytest session object. """ @@ -271,9 +272,9 @@ def pytest_ignore_collect( Stops at first non-None result, see :ref:`firstresult`. - :param pathlib.Path collection_path : The path to analyze. - :param LEGACY_PATH path: The path to analyze (deprecated). - :param pytest.Config config: The pytest config object. + :param collection_path: The path to analyze. + :param path: The path to analyze (deprecated). + :param config: The pytest config object. .. versionchanged:: 7.0.0 The ``collection_path`` parameter was added as a :class:`pathlib.Path` @@ -285,12 +286,12 @@ def pytest_ignore_collect( def pytest_collect_file( file_path: Path, path: "LEGACY_PATH", parent: "Collector" ) -> "Optional[Collector]": - """Create a Collector for the given path, or None if not relevant. + """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. The new node needs to have the specified ``parent`` as a parent. - :param pathlib.Path file_path: The path to analyze. - :param LEGACY_PATH path: The path to collect (deprecated). + :param file_path: The path to analyze. + :param path: The path to collect (deprecated). .. versionchanged:: 7.0.0 The ``file_path`` parameter was added as a :class:`pathlib.Path` @@ -303,21 +304,36 @@ def pytest_collect_file( def pytest_collectstart(collector: "Collector") -> None: - """Collector starts collecting.""" + """Collector starts collecting. + + :param collector: + The collector. + """ def pytest_itemcollected(item: "Item") -> None: - """We just collected a test item.""" + """We just collected a test item. + + :param item: + The item. + """ def pytest_collectreport(report: "CollectReport") -> None: - """Collector finished collecting.""" + """Collector finished collecting. + + :param report: + The collect report. + """ def pytest_deselected(items: Sequence["Item"]) -> None: """Called for deselected test items, e.g. by keyword. May be called multiple times. + + :param items: + The items. """ @@ -327,6 +343,9 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor a :class:`~pytest.CollectReport`. Stops at first non-None result, see :ref:`firstresult`. + + :param collector: + The collector. """ @@ -339,16 +358,16 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor def pytest_pycollect_makemodule( module_path: Path, path: "LEGACY_PATH", parent ) -> Optional["Module"]: - """Return a Module collector or None for the given path. + """Return a :class:`pytest.Module` collector or None for the given path. This hook will be called for each matching test module path. - The pytest_collect_file hook needs to be used if you want to + The :hook:`pytest_collect_file` hook needs to be used if you want to create test modules for files that do not match as a test module. Stops at first non-None result, see :ref:`firstresult`. - :param pathlib.Path module_path: The path of the module to collect. - :param LEGACY_PATH path: The path of the module to collect (deprecated). + :param module_path: The path of the module to collect. + :param path: The path of the module to collect (deprecated). .. versionchanged:: 7.0.0 The ``module_path`` parameter was added as a :class:`pathlib.Path` @@ -360,11 +379,20 @@ def pytest_pycollect_makemodule( @hookspec(firstresult=True) def pytest_pycollect_makeitem( - collector: "PyCollector", name: str, obj: object + collector: Union["Module", "Class"], name: str, obj: object ) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]: """Return a custom item/collector for a Python object in a module, or None. Stops at first non-None result, see :ref:`firstresult`. + + :param collector: + The module/class collector. + :param name: + The name of the object in the module/class. + :param obj: + The object. + :returns: + The created items/collectors. """ @@ -373,11 +401,18 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: """Call underlying test function. Stops at first non-None result, see :ref:`firstresult`. + + :param pyfuncitem: + The function item. """ def pytest_generate_tests(metafunc: "Metafunc") -> None: - """Generate (multiple) parametrized calls to a test function.""" + """Generate (multiple) parametrized calls to a test function. + + :param metafunc: + The :class:`~pytest.Metafunc` helper for the test function. + """ @hookspec(firstresult=True) @@ -392,7 +427,7 @@ def pytest_make_parametrize_id( Stops at first non-None result, see :ref:`firstresult`. - :param pytest.Config config: The pytest config object. + :param config: The pytest config object. :param val: The parametrized value. :param str argname: The automatic parameter name produced by pytest. """ @@ -417,7 +452,7 @@ def pytest_runtestloop(session: "Session") -> Optional[object]: If at any point ``session.shouldfail`` or ``session.shouldstop`` are set, the loop is terminated after the runtest protocol for the current item is finished. - :param pytest.Session session: The pytest session object. + :param session: The pytest session object. Stops at first non-None result, see :ref:`firstresult`. The return value is not used, but only stops further processing. @@ -467,9 +502,9 @@ def pytest_runtest_logstart( ) -> None: """Called at the start of running the runtest protocol for a single item. - See :func:`pytest_runtest_protocol` for a description of the runtest protocol. + See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. - :param str nodeid: Full node ID of the item. + :param nodeid: Full node ID of the item. :param location: A tuple of ``(filename, lineno, testname)``. """ @@ -479,9 +514,9 @@ def pytest_runtest_logfinish( ) -> None: """Called at the end of running the runtest protocol for a single item. - See :func:`pytest_runtest_protocol` for a description of the runtest protocol. + See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. - :param str nodeid: Full node ID of the item. + :param nodeid: Full node ID of the item. :param location: A tuple of ``(filename, lineno, testname)``. """ @@ -493,6 +528,9 @@ def pytest_runtest_setup(item: "Item") -> None: parents (which haven't been setup yet). This includes obtaining the values of fixtures required by the item (which haven't been obtained yet). + + :param item: + The item. """ @@ -500,6 +538,9 @@ def pytest_runtest_call(item: "Item") -> None: """Called to run the test for test item (the call phase). The default implementation calls ``item.runtest()``. + + :param item: + The item. """ @@ -511,6 +552,8 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: includes running the teardown phase of fixtures required by the item (if they go out of scope). + :param item: + The item. :param nextitem: The scheduled-to-be-next test item (None if no further test item is scheduled). This argument is used to perform exact teardowns, i.e. @@ -526,8 +569,9 @@ def pytest_runtest_makereport( """Called to create a :class:`~pytest.TestReport` for each of the setup, call and teardown runtest phases of a test item. - See :func:`pytest_runtest_protocol` for a description of the runtest protocol. + See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. + :param item: The item. :param call: The :class:`~pytest.CallInfo` for the phase. Stops at first non-None result, see :ref:`firstresult`. @@ -538,7 +582,7 @@ def pytest_runtest_logreport(report: "TestReport") -> None: """Process the :class:`~pytest.TestReport` produced for each of the setup, call and teardown runtest phases of an item. - See :func:`pytest_runtest_protocol` for a description of the runtest protocol. + See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. """ @@ -548,7 +592,11 @@ def pytest_report_to_serializable( report: Union["CollectReport", "TestReport"], ) -> Optional[Dict[str, Any]]: """Serialize the given report object into a data structure suitable for - sending over the wire, e.g. converted to JSON.""" + sending over the wire, e.g. converted to JSON. + + :param config: The pytest config object. + :param report: The report. + """ @hookspec(firstresult=True) @@ -557,7 +605,10 @@ def pytest_report_from_serializable( data: Dict[str, Any], ) -> Optional[Union["CollectReport", "TestReport"]]: """Restore a report object previously serialized with - :func:`pytest_report_to_serializable`.""" + :hook:`pytest_report_to_serializable`. + + :param config: The pytest config object. + """ # ------------------------------------------------------------------------- @@ -571,7 +622,12 @@ def pytest_fixture_setup( ) -> Optional[object]: """Perform fixture setup execution. - :returns: The return value of the call to the fixture function. + :param fixturdef: + The fixture definition object. + :param request: + The fixture request object. + :returns: + The return value of the call to the fixture function. Stops at first non-None result, see :ref:`firstresult`. @@ -587,7 +643,13 @@ def pytest_fixture_post_finalizer( ) -> None: """Called after fixture teardown, but before the cache is cleared, so the fixture result ``fixturedef.cached_result`` is still available (not - ``None``).""" + ``None``). + + :param fixturdef: + The fixture definition object. + :param request: + The fixture request object. + """ # ------------------------------------------------------------------------- @@ -599,7 +661,7 @@ def pytest_sessionstart(session: "Session") -> None: """Called after the ``Session`` object has been created and before performing collection and entering the run test loop. - :param pytest.Session session: The pytest session object. + :param session: The pytest session object. """ @@ -609,15 +671,15 @@ def pytest_sessionfinish( ) -> None: """Called after whole test run finished, right before returning the exit status to the system. - :param pytest.Session session: The pytest session object. - :param int exitstatus: The status which pytest will return to the system. + :param session: The pytest session object. + :param exitstatus: The status which pytest will return to the system. """ def pytest_unconfigure(config: "Config") -> None: """Called before test process is exited. - :param pytest.Config config: The pytest config object. + :param config: The pytest config object. """ @@ -636,7 +698,10 @@ def pytest_assertrepr_compare( *in* a string will be escaped. Note that all but the first line will be indented slightly, the intention is for the first line to be a summary. - :param pytest.Config config: The pytest config object. + :param config: The pytest config object. + :param op: The operator, e.g. `"=="`, `"!="`, `"not in"`. + :param left: The left operand. + :param right: The right operand. """ @@ -661,10 +726,10 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No You need to **clean the .pyc** files in your project directory and interpreter libraries when enabling this option, as assertions will require to be re-written. - :param pytest.Item item: pytest item object of current test. - :param int lineno: Line number of the assert statement. - :param str orig: String with the original assertion. - :param str expl: String with the assert explanation. + :param item: pytest item object of current test. + :param lineno: Line number of the assert statement. + :param orig: String with the original assertion. + :param expl: String with the assert explanation. """ @@ -678,9 +743,9 @@ def pytest_report_header( ) -> Union[str, List[str]]: """Return a string or list of strings to be displayed as header info for terminal reporting. - :param pytest.Config config: The pytest config object. - :param Path start_path: The starting dir. - :param LEGACY_PATH startdir: The starting dir (deprecated). + :param config: The pytest config object. + :param start_path: The starting dir. + :param startdir: The starting dir (deprecated). .. note:: @@ -715,9 +780,9 @@ def pytest_report_collectionfinish( .. versionadded:: 3.2 - :param pytest.Config config: The pytest config object. - :param Path start_path: The starting dir. - :param LEGACY_PATH startdir: The starting dir (deprecated). + :param config: The pytest config object. + :param start_path: The starting dir. + :param startdir: The starting dir (deprecated). :param items: List of pytest items that are going to be executed; this list should not be modified. .. note:: @@ -756,6 +821,7 @@ def pytest_report_teststatus( :param report: The report object whose status is to be returned. :param config: The pytest config object. + :returns: The test status. Stops at first non-None result, see :ref:`firstresult`. """ @@ -768,50 +834,15 @@ def pytest_terminal_summary( ) -> None: """Add a section to terminal summary reporting. - :param _pytest.terminal.TerminalReporter terminalreporter: The internal terminal reporter object. - :param int exitstatus: The exit status that will be reported back to the OS. - :param pytest.Config config: The pytest config object. + :param terminalreporter: The internal terminal reporter object. + :param exitstatus: The exit status that will be reported back to the OS. + :param config: The pytest config object. .. versionadded:: 4.2 The ``config`` parameter. """ -@hookspec(historic=True, warn_on_impl=WARNING_CAPTURED_HOOK) -def pytest_warning_captured( - warning_message: "warnings.WarningMessage", - when: "Literal['config', 'collect', 'runtest']", - item: Optional["Item"], - location: Optional[Tuple[str, int, str]], -) -> None: - """(**Deprecated**) Process a warning captured by the internal pytest warnings plugin. - - .. deprecated:: 6.0 - - This hook is considered deprecated and will be removed in a future pytest version. - Use :func:`pytest_warning_recorded` instead. - - :param warnings.WarningMessage warning_message: - The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains - the same attributes as the parameters of :py:func:`warnings.showwarning`. - - :param str when: - Indicates when the warning was captured. Possible values: - - * ``"config"``: during pytest configuration/initialization stage. - * ``"collect"``: during test collection. - * ``"runtest"``: during test execution. - - :param pytest.Item|None item: - The item being executed if ``when`` is ``"runtest"``, otherwise ``None``. - - :param tuple location: - When available, holds information about the execution context of the captured - warning (filename, linenumber, function). ``function`` evaluates to - when the execution context is at the module level. - """ - - @hookspec(historic=True) def pytest_warning_recorded( warning_message: "warnings.WarningMessage", @@ -821,21 +852,21 @@ def pytest_warning_recorded( ) -> None: """Process a warning captured by the internal pytest warnings plugin. - :param warnings.WarningMessage warning_message: + :param warning_message: The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains the same attributes as the parameters of :py:func:`warnings.showwarning`. - :param str when: + :param when: Indicates when the warning was captured. Possible values: * ``"config"``: during pytest configuration/initialization stage. * ``"collect"``: during test collection. * ``"runtest"``: during test execution. - :param str nodeid: + :param nodeid: Full id of the item. - :param tuple|None location: + :param location: When available, holds information about the execution context of the captured warning (filename, linenumber, function). ``function`` evaluates to when the execution context is at the module level. @@ -860,7 +891,7 @@ def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]: .. versionadded:: 6.2 - :param pytest.Config config: The pytest config object. + :param config: The pytest config object. :returns: A dictionary of additional globals to add. """ @@ -878,13 +909,19 @@ def pytest_internalerror( Return True to suppress the fallback handling of printing an INTERNALERROR message directly to sys.stderr. + + :param excrepr: The exception repr object. + :param excinfo: The exception info. """ def pytest_keyboard_interrupt( excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]", ) -> None: - """Called for keyboard interrupt.""" + """Called for keyboard interrupt. + + :param excinfo: The exception info. + """ def pytest_exception_interact( @@ -895,14 +932,21 @@ def pytest_exception_interact( """Called when an exception was raised which can potentially be interactively handled. - May be called during collection (see :py:func:`pytest_make_collect_report`), + May be called during collection (see :hook:`pytest_make_collect_report`), in which case ``report`` is a :class:`CollectReport`. - May be called during runtest of an item (see :py:func:`pytest_runtest_protocol`), + May be called during runtest of an item (see :hook:`pytest_runtest_protocol`), in which case ``report`` is a :class:`TestReport`. This hook is not called if the exception that was raised is an internal exception like ``skip.Exception``. + + :param node: + The item or collector. + :param call: + The call information. Contains the exception. + :param report: + The collection or test report. """ @@ -912,8 +956,8 @@ def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: Can be used by plugins to take special action just before the python debugger enters interactive mode. - :param pytest.Config config: The pytest config object. - :param pdb.Pdb pdb: The Pdb instance. + :param config: The pytest config object. + :param pdb: The Pdb instance. """ @@ -923,6 +967,6 @@ def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None: Can be used by plugins to take special action just after the python debugger leaves interactive mode. - :param pytest.Config config: The pytest config object. - :param pdb.Pdb pdb: The Pdb instance. + :param config: The pytest config object. + :param pdb: The Pdb instance. """ diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 13688251f6a..9242d46d9df 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -92,7 +92,7 @@ def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None: self.xml = xml self.add_stats = self.xml.add_stats self.family = self.xml.family - self.duration = 0 + self.duration = 0.0 self.properties: List[Tuple[str, str]] = [] self.nodes: List[ET.Element] = [] self.attrs: Dict[str, str] = {} @@ -231,7 +231,7 @@ def append_error(self, report: TestReport) -> None: msg = f'failed on teardown with "{reason}"' else: msg = f'failed on setup with "{reason}"' - self._add_simple("error", msg, str(report.longrepr)) + self._add_simple("error", bin_xml_escape(msg), str(report.longrepr)) def append_skipped(self, report: TestReport) -> None: if hasattr(report, "wasxfail"): @@ -256,7 +256,7 @@ def append_skipped(self, report: TestReport) -> None: def finalize(self) -> None: data = self.to_xml() self.__dict__.clear() - # Type ignored becuase mypy doesn't like overriding a method. + # Type ignored because mypy doesn't like overriding a method. # Also the return value doesn't match... self.to_xml = lambda: data # type: ignore[assignment] @@ -354,7 +354,10 @@ def test_foo(record_testsuite_property): record_testsuite_property("ARCH", "PPC") record_testsuite_property("STORAGE_TYPE", "CEPH") - ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. + :param name: + The property name. + :param value: + The property value. Will be converted to a string. .. warning:: @@ -386,7 +389,7 @@ def pytest_addoption(parser: Parser) -> None: metavar="path", type=functools.partial(filename_arg, optname="--junitxml"), default=None, - help="create junit-xml style report file at given path.", + help="Create junit-xml style report file at given path", ) group.addoption( "--junitprefix", @@ -394,7 +397,7 @@ def pytest_addoption(parser: Parser) -> None: action="store", metavar="str", default=None, - help="prepend prefix to classnames in junit-xml output", + help="Prepend prefix to classnames in junit-xml output", ) parser.addini( "junit_suite_name", "Test suite name for JUnit report", default="pytest" @@ -642,8 +645,8 @@ def pytest_sessionstart(self) -> None: def pytest_sessionfinish(self) -> None: dirname = os.path.dirname(os.path.abspath(self.logfile)) - if not os.path.isdir(dirname): - os.makedirs(dirname) + # exist_ok avoids filesystem race conditions between checking path existence and requesting creation + os.makedirs(dirname, exist_ok=True) with open(self.logfile, "w", encoding="utf-8") as logfile: suite_stop_time = timing.time() diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index 743c06d55c4..f71e7e96ead 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -10,13 +10,26 @@ import attr from iniconfig import SectionWrapper -import pytest +from _pytest.cacheprovider import Cache from _pytest.compat import final from _pytest.compat import LEGACY_PATH from _pytest.compat import legacy_path +from _pytest.config import Config +from _pytest.config import hookimpl +from _pytest.config import PytestPluginManager from _pytest.deprecated import check_ispytest +from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureRequest +from _pytest.main import Session +from _pytest.monkeypatch import MonkeyPatch +from _pytest.nodes import Collector +from _pytest.nodes import Item from _pytest.nodes import Node +from _pytest.pytester import HookRecorder +from _pytest.pytester import Pytester +from _pytest.pytester import RunResult from _pytest.terminal import TerminalReporter +from _pytest.tmpdir import TempPathFactory if TYPE_CHECKING: from typing_extensions import Final @@ -35,10 +48,10 @@ class Testdir: __test__ = False - CLOSE_STDIN: "Final" = pytest.Pytester.CLOSE_STDIN - TimeoutExpired: "Final" = pytest.Pytester.TimeoutExpired + CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN + TimeoutExpired: "Final" = Pytester.TimeoutExpired - def __init__(self, pytester: pytest.Pytester, *, _ispytest: bool = False) -> None: + def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) self._pytester = pytester @@ -64,10 +77,10 @@ def plugins(self, plugins): self._pytester.plugins = plugins @property - def monkeypatch(self) -> pytest.MonkeyPatch: + def monkeypatch(self) -> MonkeyPatch: return self._pytester._monkeypatch - def make_hook_recorder(self, pluginmanager) -> pytest.HookRecorder: + def make_hook_recorder(self, pluginmanager) -> HookRecorder: """See :meth:`Pytester.make_hook_recorder`.""" return self._pytester.make_hook_recorder(pluginmanager) @@ -131,9 +144,7 @@ def copy_example(self, name=None) -> LEGACY_PATH: """See :meth:`Pytester.copy_example`.""" return legacy_path(self._pytester.copy_example(name)) - def getnode( - self, config: pytest.Config, arg - ) -> Optional[Union[pytest.Item, pytest.Collector]]: + def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]: """See :meth:`Pytester.getnode`.""" return self._pytester.getnode(config, arg) @@ -141,9 +152,7 @@ def getpathnode(self, path): """See :meth:`Pytester.getpathnode`.""" return self._pytester.getpathnode(path) - def genitems( - self, colitems: List[Union[pytest.Item, pytest.Collector]] - ) -> List[pytest.Item]: + def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]: """See :meth:`Pytester.genitems`.""" return self._pytester.genitems(colitems) @@ -165,19 +174,19 @@ def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False): *args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc ) - def runpytest_inprocess(self, *args, **kwargs) -> pytest.RunResult: + def runpytest_inprocess(self, *args, **kwargs) -> RunResult: """See :meth:`Pytester.runpytest_inprocess`.""" return self._pytester.runpytest_inprocess(*args, **kwargs) - def runpytest(self, *args, **kwargs) -> pytest.RunResult: + def runpytest(self, *args, **kwargs) -> RunResult: """See :meth:`Pytester.runpytest`.""" return self._pytester.runpytest(*args, **kwargs) - def parseconfig(self, *args) -> pytest.Config: + def parseconfig(self, *args) -> Config: """See :meth:`Pytester.parseconfig`.""" return self._pytester.parseconfig(*args) - def parseconfigure(self, *args) -> pytest.Config: + def parseconfigure(self, *args) -> Config: """See :meth:`Pytester.parseconfigure`.""" return self._pytester.parseconfigure(*args) @@ -196,8 +205,8 @@ def getmodulecol(self, source, configargs=(), withinit=False): ) def collect_by_name( - self, modcol: pytest.Collector, name: str - ) -> Optional[Union[pytest.Item, pytest.Collector]]: + self, modcol: Collector, name: str + ) -> Optional[Union[Item, Collector]]: """See :meth:`Pytester.collect_by_name`.""" return self._pytester.collect_by_name(modcol, name) @@ -212,11 +221,11 @@ def popen( """See :meth:`Pytester.popen`.""" return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw) - def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> pytest.RunResult: + def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult: """See :meth:`Pytester.run`.""" return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin) - def runpython(self, script) -> pytest.RunResult: + def runpython(self, script) -> RunResult: """See :meth:`Pytester.runpython`.""" return self._pytester.runpython(script) @@ -224,7 +233,7 @@ def runpython_c(self, command): """See :meth:`Pytester.runpython_c`.""" return self._pytester.runpython_c(command) - def runpytest_subprocess(self, *args, timeout=None) -> pytest.RunResult: + def runpytest_subprocess(self, *args, timeout=None) -> RunResult: """See :meth:`Pytester.runpytest_subprocess`.""" return self._pytester.runpytest_subprocess(*args, timeout=timeout) @@ -245,13 +254,10 @@ def __str__(self) -> str: return str(self.tmpdir) -pytest.Testdir = Testdir # type: ignore[attr-defined] - - class LegacyTestdirPlugin: @staticmethod - @pytest.fixture - def testdir(pytester: pytest.Pytester) -> Testdir: + @fixture + def testdir(pytester: Pytester) -> Testdir: """ Identical to :fixture:`pytester`, and provides an instance whose methods return legacy ``LEGACY_PATH`` objects instead when applicable. @@ -264,39 +270,43 @@ def testdir(pytester: pytest.Pytester) -> Testdir: @final @attr.s(init=False, auto_attribs=True) class TempdirFactory: - """Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH`` - for :class:``TempPathFactory``.""" + """Backward compatibility wrapper that implements :class:`py.path.local` + for :class:`TempPathFactory`. - _tmppath_factory: pytest.TempPathFactory + .. note:: + These days, it is preferred to use ``tmp_path_factory``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + + """ + + _tmppath_factory: TempPathFactory def __init__( - self, tmppath_factory: pytest.TempPathFactory, *, _ispytest: bool = False + self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False ) -> None: check_ispytest(_ispytest) self._tmppath_factory = tmppath_factory def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object.""" + """Same as :meth:`TempPathFactory.mktemp`, but returns a :class:`py.path.local` object.""" return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) def getbasetemp(self) -> LEGACY_PATH: - """Backward compat wrapper for ``_tmppath_factory.getbasetemp``.""" + """Same as :meth:`TempPathFactory.getbasetemp`, but returns a :class:`py.path.local` object.""" return legacy_path(self._tmppath_factory.getbasetemp().resolve()) -pytest.TempdirFactory = TempdirFactory # type: ignore[attr-defined] - - class LegacyTmpdirPlugin: @staticmethod - @pytest.fixture(scope="session") - def tmpdir_factory(request: pytest.FixtureRequest) -> TempdirFactory: + @fixture(scope="session") + def tmpdir_factory(request: FixtureRequest) -> TempdirFactory: """Return a :class:`pytest.TempdirFactory` instance for the test session.""" # Set dynamically by pytest_configure(). return request.config._tmpdirhandler # type: ignore @staticmethod - @pytest.fixture + @fixture def tmpdir(tmp_path: Path) -> LEGACY_PATH: """Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary @@ -309,12 +319,17 @@ def tmpdir(tmp_path: Path) -> LEGACY_PATH: The returned object is a `legacy_path`_ object. + .. note:: + These days, it is preferred to use ``tmp_path``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + .. _legacy_path: https://py.readthedocs.io/en/latest/path.html """ return legacy_path(tmp_path) -def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH: +def Cache_makedir(self: Cache, name: str) -> LEGACY_PATH: """Return a directory path object with the given name. Same as :func:`mkdir`, but returns a legacy py path instance. @@ -322,7 +337,7 @@ def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH: return legacy_path(self.mkdir(name)) -def FixtureRequest_fspath(self: pytest.FixtureRequest) -> LEGACY_PATH: +def FixtureRequest_fspath(self: FixtureRequest) -> LEGACY_PATH: """(deprecated) The file system path of the test module which collected this test.""" return legacy_path(self.path) @@ -337,7 +352,7 @@ def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH: return legacy_path(self.startpath) -def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH: +def Config_invocation_dir(self: Config) -> LEGACY_PATH: """The directory from which pytest was invoked. Prefer to use :attr:`invocation_params.dir `, @@ -348,7 +363,7 @@ def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH: return legacy_path(str(self.invocation_params.dir)) -def Config_rootdir(self: pytest.Config) -> LEGACY_PATH: +def Config_rootdir(self: Config) -> LEGACY_PATH: """The path to the :ref:`rootdir `. Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`. @@ -358,7 +373,7 @@ def Config_rootdir(self: pytest.Config) -> LEGACY_PATH: return legacy_path(str(self.rootpath)) -def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]: +def Config_inifile(self: Config) -> Optional[LEGACY_PATH]: """The path to the :ref:`configfile `. Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. @@ -368,7 +383,7 @@ def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]: return legacy_path(str(self.inipath)) if self.inipath else None -def Session_stardir(self: pytest.Session) -> LEGACY_PATH: +def Session_stardir(self: Session) -> LEGACY_PATH: """The path from which pytest was invoked. Prefer to use ``startpath`` which is a :class:`pathlib.Path`. @@ -400,35 +415,17 @@ def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None: self.path = Path(value) -@pytest.hookimpl -def pytest_configure(config: pytest.Config) -> None: - mp = pytest.MonkeyPatch() - config.add_cleanup(mp.undo) - - if config.pluginmanager.has_plugin("tmpdir"): - # Create TmpdirFactory and attach it to the config object. - # - # This is to comply with existing plugins which expect the handler to be - # available at pytest_configure time, but ideally should be moved entirely - # to the tmpdir_factory session fixture. - try: - tmp_path_factory = config._tmp_path_factory # type: ignore[attr-defined] - except AttributeError: - # tmpdir plugin is blocked. - pass - else: - _tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True) - mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False) - - config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir") +@hookimpl(tryfirst=True) +def pytest_load_initial_conftests(early_config: Config) -> None: + """Monkeypatch legacy path attributes in several classes, as early as possible.""" + mp = MonkeyPatch() + early_config.add_cleanup(mp.undo) # Add Cache.makedir(). - mp.setattr(pytest.Cache, "makedir", Cache_makedir, raising=False) + mp.setattr(Cache, "makedir", Cache_makedir, raising=False) # Add FixtureRequest.fspath property. - mp.setattr( - pytest.FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False - ) + mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False) # Add TerminalReporter.startdir property. mp.setattr( @@ -436,26 +433,45 @@ def pytest_configure(config: pytest.Config) -> None: ) # Add Config.{invocation_dir,rootdir,inifile} properties. - mp.setattr( - pytest.Config, "invocation_dir", property(Config_invocation_dir), raising=False - ) - mp.setattr(pytest.Config, "rootdir", property(Config_rootdir), raising=False) - mp.setattr(pytest.Config, "inifile", property(Config_inifile), raising=False) + mp.setattr(Config, "invocation_dir", property(Config_invocation_dir), raising=False) + mp.setattr(Config, "rootdir", property(Config_rootdir), raising=False) + mp.setattr(Config, "inifile", property(Config_inifile), raising=False) # Add Session.startdir property. - mp.setattr(pytest.Session, "startdir", property(Session_stardir), raising=False) + mp.setattr(Session, "startdir", property(Session_stardir), raising=False) # Add pathlist configuration type. - mp.setattr(pytest.Config, "_getini_unknown_type", Config__getini_unknown_type) + mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type) # Add Node.fspath property. mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False) -@pytest.hookimpl -def pytest_plugin_registered( - plugin: object, manager: pytest.PytestPluginManager -) -> None: +@hookimpl +def pytest_configure(config: Config) -> None: + """Installs the LegacyTmpdirPlugin if the ``tmpdir`` plugin is also installed.""" + if config.pluginmanager.has_plugin("tmpdir"): + mp = MonkeyPatch() + config.add_cleanup(mp.undo) + # Create TmpdirFactory and attach it to the config object. + # + # This is to comply with existing plugins which expect the handler to be + # available at pytest_configure time, but ideally should be moved entirely + # to the tmpdir_factory session fixture. + try: + tmp_path_factory = config._tmp_path_factory # type: ignore[attr-defined] + except AttributeError: + # tmpdir plugin is blocked. + pass + else: + _tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True) + mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False) + + config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir") + + +@hookimpl +def pytest_plugin_registered(plugin: object, manager: PytestPluginManager) -> None: # pytester is not loaded by default and is commonly loaded from a conftest, # so checking for it in `pytest_configure` is not enough. is_pytester = plugin is manager.get_plugin("pytester") diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 31ad8301076..f9091399f2c 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -1,9 +1,10 @@ """Access and control log capturing.""" +import io import logging import os import re -import sys from contextlib import contextmanager +from contextlib import nullcontext from io import StringIO from pathlib import Path from typing import AbstractSet @@ -13,6 +14,7 @@ from typing import Mapping from typing import Optional from typing import Tuple +from typing import TYPE_CHECKING from typing import TypeVar from typing import Union @@ -20,7 +22,6 @@ from _pytest._io import TerminalWriter from _pytest.capture import CaptureManager from _pytest.compat import final -from _pytest.compat import nullcontext from _pytest.config import _strtobool from _pytest.config import Config from _pytest.config import create_terminal_writer @@ -34,6 +35,12 @@ from _pytest.stash import StashKey from _pytest.terminal import TerminalReporter +if TYPE_CHECKING: + logging_StreamHandler = logging.StreamHandler[StringIO] + + from typing_extensions import Literal +else: + logging_StreamHandler = logging.StreamHandler DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s" DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S" @@ -212,7 +219,7 @@ def pytest_addoption(parser: Parser) -> None: def add_option_ini(option, dest, default=None, type=None, **kwargs): parser.addini( - dest, default=default, type=type, help="default value for " + option + dest, default=default, type=type, help="Default value for " + option ) group.addoption(option, dest=dest, **kwargs) @@ -222,8 +229,8 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs): default=None, metavar="LEVEL", help=( - "level of messages to catch/display.\n" - "Not set by default, so it depends on the root/parent log handler's" + "Level of messages to catch/display." + " Not set by default, so it depends on the root/parent log handler's" ' effective level, where it is "WARNING" by default.' ), ) @@ -231,58 +238,58 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs): "--log-format", dest="log_format", default=DEFAULT_LOG_FORMAT, - help="log format as used by the logging module.", + help="Log format used by the logging module", ) add_option_ini( "--log-date-format", dest="log_date_format", default=DEFAULT_LOG_DATE_FORMAT, - help="log date format as used by the logging module.", + help="Log date format used by the logging module", ) parser.addini( "log_cli", default=False, type="bool", - help='enable log display during test run (also known as "live logging").', + help='Enable log display during test run (also known as "live logging")', ) add_option_ini( - "--log-cli-level", dest="log_cli_level", default=None, help="cli logging level." + "--log-cli-level", dest="log_cli_level", default=None, help="CLI logging level" ) add_option_ini( "--log-cli-format", dest="log_cli_format", default=None, - help="log format as used by the logging module.", + help="Log format used by the logging module", ) add_option_ini( "--log-cli-date-format", dest="log_cli_date_format", default=None, - help="log date format as used by the logging module.", + help="Log date format used by the logging module", ) add_option_ini( "--log-file", dest="log_file", default=None, - help="path to a file when logging will be written to.", + help="Path to a file when logging will be written to", ) add_option_ini( "--log-file-level", dest="log_file_level", default=None, - help="log file logging level.", + help="Log file logging level", ) add_option_ini( "--log-file-format", dest="log_file_format", default=DEFAULT_LOG_FORMAT, - help="log format as used by the logging module.", + help="Log format used by the logging module", ) add_option_ini( "--log-file-date-format", dest="log_file_date_format", default=DEFAULT_LOG_DATE_FORMAT, - help="log date format as used by the logging module.", + help="Log date format used by the logging module", ) add_option_ini( "--log-auto-indent", @@ -322,11 +329,9 @@ def __exit__(self, type, value, traceback): root_logger.removeHandler(self.handler) -class LogCaptureHandler(logging.StreamHandler): +class LogCaptureHandler(logging_StreamHandler): """A logging handler that stores log records and the log text.""" - stream: StringIO - def __init__(self) -> None: """Create a new log handler.""" super().__init__(StringIO()) @@ -341,6 +346,10 @@ def reset(self) -> None: self.records = [] self.stream = StringIO() + def clear(self) -> None: + self.records.clear() + self.stream = StringIO() + def handleError(self, record: logging.LogRecord) -> None: if logging.raiseExceptions: # Fail the test if the log message is bad (emit failed). @@ -375,20 +384,19 @@ def _finalize(self) -> None: @property def handler(self) -> LogCaptureHandler: - """Get the logging handler used by the fixture. - - :rtype: LogCaptureHandler - """ + """Get the logging handler used by the fixture.""" return self._item.stash[caplog_handler_key] - def get_records(self, when: str) -> List[logging.LogRecord]: + def get_records( + self, when: "Literal['setup', 'call', 'teardown']" + ) -> List[logging.LogRecord]: """Get the logging records for one of the possible test phases. - :param str when: - Which test phase to obtain the records from. Valid values are: "setup", "call" and "teardown". + :param when: + Which test phase to obtain the records from. + Valid values are: "setup", "call" and "teardown". :returns: The list of captured records at the given stage. - :rtype: List[logging.LogRecord] .. versionadded:: 3.4 """ @@ -436,7 +444,7 @@ def messages(self) -> List[str]: def clear(self) -> None: """Reset the list of log records and the captured log text.""" - self.handler.reset() + self.handler.clear() def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: """Set the level of a logger for the duration of a test. @@ -445,8 +453,8 @@ def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> Non The levels of the loggers changed by this function will be restored to their initial values at the end of the test. - :param int level: The level. - :param str logger: The logger to update. If not given, the root logger. + :param level: The level. + :param logger: The logger to update. If not given, the root logger. """ logger_obj = logging.getLogger(logger) # Save the original log-level to restore it during teardown. @@ -464,8 +472,8 @@ def at_level( the end of the 'with' statement the level is restored to its original value. - :param int level: The level. - :param str logger: The logger to update. If not given, the root logger. + :param level: The level. + :param logger: The logger to update. If not given, the root logger. """ logger_obj = logging.getLogger(logger) orig_level = logger_obj.level @@ -621,20 +629,11 @@ def set_log_path(self, fname: str) -> None: if not fpath.parent.exists(): fpath.parent.mkdir(exist_ok=True, parents=True) - stream = fpath.open(mode="w", encoding="UTF-8") - if sys.version_info >= (3, 7): - old_stream = self.log_file_handler.setStream(stream) - else: - old_stream = self.log_file_handler.stream - self.log_file_handler.acquire() - try: - self.log_file_handler.flush() - self.log_file_handler.stream = stream - finally: - self.log_file_handler.release() + # https://github.com/python/mypy/issues/11193 + stream: io.TextIOWrapper = fpath.open(mode="w", encoding="UTF-8") # type: ignore[assignment] + old_stream = self.log_file_handler.setStream(stream) if old_stream: - # https://github.com/python/typeshed/pull/5663 - old_stream.close() # type:ignore[attr-defined] + old_stream.close() def _log_cli_enabled(self): """Return whether live logging is enabled.""" @@ -758,7 +757,7 @@ def handleError(self, record: logging.LogRecord) -> None: pass -class _LiveLoggingStreamHandler(logging.StreamHandler): +class _LiveLoggingStreamHandler(logging_StreamHandler): """A logging StreamHandler used by the live logging feature: it will write a newline before the first log message in each test. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 57407fe5495..61fb7eaa4e3 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -12,7 +12,6 @@ from typing import Iterator from typing import List from typing import Optional -from typing import overload from typing import Sequence from typing import Set from typing import Tuple @@ -25,6 +24,7 @@ import _pytest._code from _pytest import nodes from _pytest.compat import final +from _pytest.compat import overload from _pytest.config import Config from _pytest.config import directory_arg from _pytest.config import ExitCode @@ -51,7 +51,7 @@ def pytest_addoption(parser: Parser) -> None: parser.addini( "norecursedirs", - "directory patterns to avoid for recursion", + "Directory patterns to avoid for recursion", type="args", default=[ "*.egg", @@ -67,26 +67,26 @@ def pytest_addoption(parser: Parser) -> None: ) parser.addini( "testpaths", - "directories to search for tests when no files or directories are given in the " - "command line.", + "Directories to search for tests when no files or directories are given on the " + "command line", type="args", default=[], ) - group = parser.getgroup("general", "running and selection options") + group = parser.getgroup("general", "Running and selection options") group._addoption( "-x", "--exitfirst", action="store_const", dest="maxfail", const=1, - help="exit instantly on first error or failed test.", + help="Exit instantly on first error or failed test", ) group = parser.getgroup("pytest-warnings") group.addoption( "-W", "--pythonwarnings", action="append", - help="set which warnings to report, see -W option of python itself.", + help="Set which warnings to report, see -W option of Python itself", ) parser.addini( "filterwarnings", @@ -102,37 +102,39 @@ def pytest_addoption(parser: Parser) -> None: type=int, dest="maxfail", default=0, - help="exit after first num failures or errors.", + help="Exit after first num failures or errors", ) group._addoption( "--strict-config", action="store_true", - help="any warnings encountered while parsing the `pytest` section of the configuration file raise errors.", + help="Any warnings encountered while parsing the `pytest` section of the " + "configuration file raise errors", ) group._addoption( "--strict-markers", action="store_true", - help="markers not registered in the `markers` section of the configuration file raise errors.", + help="Markers not registered in the `markers` section of the configuration " + "file raise errors", ) group._addoption( "--strict", action="store_true", - help="(deprecated) alias to --strict-markers.", + help="(Deprecated) alias to --strict-markers", ) group._addoption( "-c", metavar="file", type=str, dest="inifilename", - help="load configuration from `file` instead of trying to locate one of the implicit " - "configuration files.", + help="Load configuration from `file` instead of trying to locate one of the " + "implicit configuration files", ) group._addoption( "--continue-on-collection-errors", action="store_true", default=False, dest="continue_on_collection_errors", - help="Force test execution even if collection errors occur.", + help="Force test execution even if collection errors occur", ) group._addoption( "--rootdir", @@ -149,30 +151,30 @@ def pytest_addoption(parser: Parser) -> None: "--collect-only", "--co", action="store_true", - help="only collect tests, don't execute them.", + help="Only collect tests, don't execute them", ) group.addoption( "--pyargs", action="store_true", - help="try to interpret all arguments as python packages.", + help="Try to interpret all arguments as Python packages", ) group.addoption( "--ignore", action="append", metavar="path", - help="ignore path during collection (multi-allowed).", + help="Ignore path during collection (multi-allowed)", ) group.addoption( "--ignore-glob", action="append", metavar="path", - help="ignore path pattern during collection (multi-allowed).", + help="Ignore path pattern during collection (multi-allowed)", ) group.addoption( "--deselect", action="append", metavar="nodeid_prefix", - help="deselect item (via node id prefix) during collection (multi-allowed).", + help="Deselect item (via node id prefix) during collection (multi-allowed)", ) group.addoption( "--confcutdir", @@ -180,14 +182,14 @@ def pytest_addoption(parser: Parser) -> None: default=None, metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"), - help="only load conftest.py's relative to specified dir.", + help="Only load conftest.py's relative to specified dir", ) group.addoption( "--noconftest", action="store_true", dest="noconftest", default=False, - help="Don't load any conftest.py files.", + help="Don't load any conftest.py files", ) group.addoption( "--keepduplicates", @@ -195,7 +197,7 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="keepduplicates", default=False, - help="Keep duplicate tests.", + help="Keep duplicate tests", ) group.addoption( "--collect-in-virtualenv", @@ -209,8 +211,8 @@ def pytest_addoption(parser: Parser) -> None: default="prepend", choices=["prepend", "append", "importlib"], dest="importmode", - help="prepend/append to sys.path when importing test modules and conftest files, " - "default is to prepend.", + help="Prepend/append to sys.path when importing test modules and conftest " + "files. Default: prepend.", ) group = parser.getgroup("debugconfig", "test session debugging and configuration") @@ -221,8 +223,8 @@ def pytest_addoption(parser: Parser) -> None: type=validate_basetemp, metavar="dir", help=( - "base temporary directory for this test run." - "(warning: this directory is removed if it exists)" + "Base temporary directory for this test run. " + "(Warning: this directory is removed if it exists.)" ), ) @@ -595,18 +597,17 @@ def perform_collect( ... @overload - def perform_collect( + def perform_collect( # noqa: F811 self, args: Optional[Sequence[str]] = ..., genitems: bool = ... ) -> Sequence[Union[nodes.Item, nodes.Collector]]: ... - def perform_collect( + def perform_collect( # noqa: F811 self, args: Optional[Sequence[str]] = None, genitems: bool = True ) -> Sequence[Union[nodes.Item, nodes.Collector]]: """Perform the collection phase for this session. - This is called by the default - :func:`pytest_collection <_pytest.hookspec.pytest_collection>` hook + This is called by the default :hook:`pytest_collection` hook implementation; see the documentation of this hook for more details. For testing purposes, it may also be called directly on a fresh ``Session``. @@ -646,9 +647,14 @@ def perform_collect( self.trace.root.indent -= 1 if self._notfound: errors = [] - for arg, cols in self._notfound: - line = f"(no name {arg!r} in any of {cols!r})" - errors.append(f"not found: {arg}\n{line}") + for arg, collectors in self._notfound: + if collectors: + errors.append( + f"not found: {arg}\n(no name {arg!r} in any of {collectors!r})" + ) + else: + errors.append(f"found no collectors for {arg}") + raise UsageError(*errors) if not genitems: items = rep.result @@ -690,9 +696,8 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # No point in finding packages when collecting doctests. if not self.config.getoption("doctestmodules", False): pm = self.config.pluginmanager - confcutdir = pm._confcutdir for parent in (argpath, *argpath.parents): - if confcutdir and parent in confcutdir.parents: + if not pm._is_in_confcutdir(argpath): break if parent.is_dir(): @@ -872,7 +877,10 @@ def resolve_collection_argument( If the path doesn't exist, raise UsageError. If the path is a directory and selection parts are present, raise UsageError. """ - strpath, *parts = str(arg).split("::") + base, squacket, rest = str(arg).partition("[") + strpath, *parts = base.split("::") + if parts: + parts[-1] = f"{parts[-1]}{squacket}{rest}" if as_pypath: strpath = search_pypath(strpath) fspath = invocation_path / strpath diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 7e082f2e6e0..6717d1135ee 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -1,5 +1,4 @@ """Generic mechanism for marking and selecting python functions.""" -import warnings from typing import AbstractSet from typing import Collection from typing import List @@ -23,8 +22,6 @@ from _pytest.config import hookimpl from _pytest.config import UsageError from _pytest.config.argparsing import Parser -from _pytest.deprecated import MINUS_K_COLON -from _pytest.deprecated import MINUS_K_DASH from _pytest.stash import StashKey if TYPE_CHECKING: @@ -65,8 +62,8 @@ def test_eval(test_input, expected): assert eval(test_input) == expected :param values: Variable args of the values of the parameter set, in order. - :keyword marks: A single mark or a list of marks to be applied to this parameter set. - :keyword str id: The id to attribute to this parameter set. + :param marks: A single mark or a list of marks to be applied to this parameter set. + :param id: The id to attribute to this parameter set. """ return ParameterSet.param(*values, marks=marks, id=id) @@ -79,8 +76,8 @@ def pytest_addoption(parser: Parser) -> None: dest="keyword", default="", metavar="EXPRESSION", - help="only run tests which match the given substring expression. " - "An expression is a python evaluatable expression " + help="Only run tests which match the given substring expression. " + "An expression is a Python evaluatable expression " "where all names are substring-matched against test names " "and their parent classes. Example: -k 'test_method or test_" "other' matches all test functions and classes whose name " @@ -99,7 +96,7 @@ def pytest_addoption(parser: Parser) -> None: dest="markexpr", default="", metavar="MARKEXPR", - help="only run tests matching given mark expression.\n" + help="Only run tests matching given mark expression. " "For example: -m 'mark1 and not mark2'.", ) @@ -109,8 +106,8 @@ def pytest_addoption(parser: Parser) -> None: help="show markers (builtin, plugin and per-project ones).", ) - parser.addini("markers", "markers for test functions", "linelist") - parser.addini(EMPTY_PARAMETERSET_OPTION, "default marker for empty parametersets") + parser.addini("markers", "Markers for test functions", "linelist") + parser.addini(EMPTY_PARAMETERSET_OPTION, "Default marker for empty parametersets") @hookimpl(tryfirst=True) @@ -189,27 +186,14 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None: if not keywordexpr: return - if keywordexpr.startswith("-"): - # To be removed in pytest 8.0.0. - warnings.warn(MINUS_K_DASH, stacklevel=2) - keywordexpr = "not " + keywordexpr[1:] - selectuntil = False - if keywordexpr[-1:] == ":": - # To be removed in pytest 8.0.0. - warnings.warn(MINUS_K_COLON, stacklevel=2) - selectuntil = True - keywordexpr = keywordexpr[:-1] - expr = _parse_expression(keywordexpr, "Wrong expression passed to '-k'") remaining = [] deselected = [] for colitem in items: - if keywordexpr and not expr.evaluate(KeywordMatcher.from_item(colitem)): + if not expr.evaluate(KeywordMatcher.from_item(colitem)): deselected.append(colitem) else: - if selectuntil: - keywordexpr = None remaining.append(colitem) if deselected: diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 9d57e944b92..0a2e7c65676 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -21,15 +21,12 @@ from typing import Callable from typing import Iterator from typing import Mapping +from typing import NoReturn from typing import Optional from typing import Sequence -from typing import TYPE_CHECKING import attr -if TYPE_CHECKING: - from typing import NoReturn - __all__ = [ "Expression", @@ -117,7 +114,7 @@ def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]: self.reject((type,)) return None - def reject(self, expected: Sequence[TokenType]) -> "NoReturn": + def reject(self, expected: Sequence[TokenType]) -> NoReturn: raise ParseError( self.current.pos + 1, "expected {}; got {}".format( @@ -190,7 +187,7 @@ def __len__(self) -> int: class Expression: """A compiled match expression as used by -k and -m. - The expression can be evaulated against different matchers. + The expression can be evaluated against different matchers. """ __slots__ = ("code",) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 0e42cd8de5f..5186c9ea3b6 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -72,16 +72,11 @@ def get_empty_parameterset_mark( return mark -class ParameterSet( - NamedTuple( - "ParameterSet", - [ - ("values", Sequence[Union[object, NotSetType]]), - ("marks", Collection[Union["MarkDecorator", "Mark"]]), - ("id", Optional[str]), - ], - ) -): +class ParameterSet(NamedTuple): + values: Sequence[Union[object, NotSetType]] + marks: Collection[Union["MarkDecorator", "Mark"]] + id: Optional[str] + @classmethod def param( cls, @@ -131,12 +126,12 @@ def extract_from( @staticmethod def _parse_parametrize_args( - argnames: Union[str, List[str], Tuple[str, ...]], + argnames: Union[str, Sequence[str]], argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], *args, **kwargs, - ) -> Tuple[Union[List[str], Tuple[str, ...]], bool]: - if not isinstance(argnames, (tuple, list)): + ) -> Tuple[Sequence[str], bool]: + if isinstance(argnames, str): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 else: @@ -155,12 +150,12 @@ def _parse_parametrize_parameters( @classmethod def _for_parametrize( cls, - argnames: Union[str, List[str], Tuple[str, ...]], + argnames: Union[str, Sequence[str]], argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], func, config: Config, nodeid: str, - ) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]: + ) -> Tuple[Sequence[str], List["ParameterSet"]]: argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues @@ -360,12 +355,35 @@ def __call__(self, *args: object, **kwargs: object): return self.with_args(*args, **kwargs) -def get_unpacked_marks(obj: object) -> Iterable[Mark]: - """Obtain the unpacked marks that are stored on an object.""" - mark_list = getattr(obj, "pytestmark", []) - if not isinstance(mark_list, list): - mark_list = [mark_list] - return normalize_mark_list(mark_list) +def get_unpacked_marks( + obj: Union[object, type], + *, + consider_mro: bool = True, +) -> List[Mark]: + """Obtain the unpacked marks that are stored on an object. + + If obj is a class and consider_mro is true, return marks applied to + this class and all of its super-classes in MRO order. If consider_mro + is false, only return marks applied directly to this class. + """ + if isinstance(obj, type): + if not consider_mro: + mark_lists = [obj.__dict__.get("pytestmark", [])] + else: + mark_lists = [x.__dict__.get("pytestmark", []) for x in obj.__mro__] + mark_list = [] + for item in mark_lists: + if isinstance(item, list): + mark_list.extend(item) + else: + mark_list.append(item) + else: + mark_attribute = getattr(obj, "pytestmark", []) + if isinstance(mark_attribute, list): + mark_list = mark_attribute + else: + mark_list = [mark_attribute] + return list(normalize_mark_list(mark_list)) def normalize_mark_list( @@ -393,7 +411,7 @@ def store_mark(obj, mark: Mark) -> None: assert isinstance(mark, Mark), mark # Always reassign name to avoid updating pytestmark in a reference that # was only borrowed. - obj.pytestmark = [*get_unpacked_marks(obj), mark] + obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark] # Typing for builtin pytest marks. This is cheating; it gives builtin marks @@ -402,7 +420,7 @@ def store_mark(obj, mark: Mark) -> None: from _pytest.scope import _ScopeName class _SkipMarkDecorator(MarkDecorator): - @overload # type: ignore[override,misc] + @overload # type: ignore[override,misc,no-overload-impl] def __call__(self, arg: Markable) -> Markable: ... @@ -420,7 +438,7 @@ def __call__( # type: ignore[override] ... class _XfailMarkDecorator(MarkDecorator): - @overload # type: ignore[override,misc] + @overload # type: ignore[override,misc,no-overload-impl] def __call__(self, arg: Markable) -> Markable: ... @@ -439,7 +457,7 @@ def __call__( class _ParametrizeMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - argnames: Union[str, List[str], Tuple[str, ...]], + argnames: Union[str, Sequence[str]], argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], *, indirect: Union[bool, Sequence[str]] = ..., diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 31f95a95ab2..c6e29ac7642 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -29,21 +29,26 @@ def monkeypatch() -> Generator["MonkeyPatch", None, None]: """A convenient fixture for monkey-patching. - The fixture provides these methods to modify objects, dictionaries or - os.environ:: - - monkeypatch.setattr(obj, name, value, raising=True) - monkeypatch.delattr(obj, name, raising=True) - monkeypatch.setitem(mapping, name, value) - monkeypatch.delitem(obj, name, raising=True) - monkeypatch.setenv(name, value, prepend=None) - monkeypatch.delenv(name, raising=True) - monkeypatch.syspath_prepend(path) - monkeypatch.chdir(path) + The fixture provides these methods to modify objects, dictionaries, or + :data:`os.environ`: + + * :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` + * :meth:`monkeypatch.delattr(obj, name, raising=True) ` + * :meth:`monkeypatch.setitem(mapping, name, value) ` + * :meth:`monkeypatch.delitem(obj, name, raising=True) ` + * :meth:`monkeypatch.setenv(name, value, prepend=None) ` + * :meth:`monkeypatch.delenv(name, raising=True) ` + * :meth:`monkeypatch.syspath_prepend(path) ` + * :meth:`monkeypatch.chdir(path) ` + * :meth:`monkeypatch.context() ` All modifications will be undone after the requesting test function or - fixture has finished. The ``raising`` parameter determines if a KeyError - or AttributeError will be raised if the set/deletion operation has no target. + fixture has finished. The ``raising`` parameter determines if a :class:`KeyError` + or :class:`AttributeError` will be raised if the set/deletion operation does not have the + specified target. + + To undo modifications done by the fixture in a contained scope, + use :meth:`context() `. """ mpatch = MonkeyPatch() yield mpatch @@ -55,7 +60,7 @@ def resolve(name: str) -> object: parts = name.split(".") used = parts.pop(0) - found = __import__(used) + found: object = __import__(used) for part in parts: used += "." + part try: @@ -115,7 +120,7 @@ class MonkeyPatch: Returned by the :fixture:`monkeypatch` fixture. - :versionchanged:: 6.2 + .. versionchanged:: 6.2 Can now also be used directly as `pytest.MonkeyPatch()`, for when the fixture is not available. In this case, use :meth:`with MonkeyPatch.context() as mp: ` or remember to call @@ -182,16 +187,40 @@ def setattr( value: object = notset, raising: bool = True, ) -> None: - """Set attribute value on target, memorizing the old value. + """ + Set attribute value on target, memorizing the old value. + + For example: + + .. code-block:: python + + import os + + monkeypatch.setattr(os, "getcwd", lambda: "/") + + The code above replaces the :func:`os.getcwd` function by a ``lambda`` which + always returns ``"/"``. - For convenience you can specify a string as ``target`` which + For convenience, you can specify a string as ``target`` which will be interpreted as a dotted import path, with the last part - being the attribute name. For example, - ``monkeypatch.setattr("os.getcwd", lambda: "/")`` - would set the ``getcwd`` function of the ``os`` module. + being the attribute name: - Raises AttributeError if the attribute does not exist, unless + .. code-block:: python + + monkeypatch.setattr("os.getcwd", lambda: "/") + + Raises :class:`AttributeError` if the attribute does not exist, unless ``raising`` is set to False. + + **Where to patch** + + ``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one. + There can be many names pointing to any individual object, so for patching to work you must ensure + that you patch the name used by the system under test. + + See the section :ref:`Where to patch ` in the :mod:`unittest.mock` + docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but + applies to ``monkeypatch.setattr`` as well. """ __tracebackhide__ = True import inspect @@ -338,7 +367,8 @@ def syspath_prepend(self, path) -> None: def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None: """Change the current working directory to the specified path. - Path can be a string or a path object. + :param path: + The path to change into. """ if self._cwd is None: self._cwd = os.getcwd() @@ -353,11 +383,14 @@ def undo(self) -> None: There is generally no need to call `undo()`, since it is called automatically during tear-down. - Note that the same `monkeypatch` fixture is used across a - single test function invocation. If `monkeypatch` is used both by - the test function itself and one of the test fixtures, - calling `undo()` will undo all of the changes made in - both functions. + .. note:: + The same `monkeypatch` fixture is used across a + single test function invocation. If `monkeypatch` is used both by + the test function itself and one of the test fixtures, + calling `undo()` will undo all of the changes made in + both functions. + + Prefer to use :meth:`context() ` instead. """ for obj, name, value in reversed(self._setattr): if value is not notset: diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 53dea04e795..cfb9b5a3634 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -111,7 +111,7 @@ def _imply_path( NODE_CTOR_FSPATH_ARG.format( node_type_name=node_type.__name__, ), - stacklevel=3, + stacklevel=6, ) if path is not None: if fspath is not None: @@ -145,7 +145,10 @@ def _create(self, *k, **kw): warnings.warn( PytestDeprecationWarning( - f"{self} is not using a cooperative constructor and only takes {set(known_kw)}" + f"{self} is not using a cooperative constructor and only takes {set(known_kw)}.\n" + "See https://docs.pytest.org/en/stable/deprecations.html" + "#constructors-of-custom-pytest-node-subclasses-should-take-kwargs " + "for more details." ) ) @@ -190,7 +193,7 @@ def __init__( nodeid: Optional[str] = None, ) -> None: #: A unique name within the scope of the parent node. - self.name = name + self.name: str = name #: The parent collector node. self.parent = parent @@ -205,7 +208,7 @@ def __init__( if session: #: The pytest session this node is part of. - self.session = session + self.session: Session = session else: if not parent: raise TypeError("session or parent must be provided") @@ -236,9 +239,7 @@ def __init__( #: A place where plugins can store information on the node for their #: own use. - #: - #: :type: Stash - self.stash = Stash() + self.stash: Stash = Stash() # Deprecated alias. Was never public. Can be removed in a few releases. self._store = self.stash @@ -323,7 +324,10 @@ def teardown(self) -> None: def listchain(self) -> List["Node"]: """Return list of all parent collectors up to self, starting from - the root of collection tree.""" + the root of collection tree. + + :returns: The nodes. + """ chain = [] item: Optional[Node] = self while item is not None: @@ -337,6 +341,8 @@ def add_marker( ) -> None: """Dynamically add a marker object to the node. + :param marker: + The marker. :param append: Whether to append the marker, or prepend it. """ @@ -358,6 +364,7 @@ def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. + :returns: An iterator of the markers of the node. """ return (x[1] for x in self.iter_markers_with_node(name=name)) @@ -404,7 +411,8 @@ def listnames(self) -> List[str]: return [x.name for x in self.listchain()] def addfinalizer(self, fin: Callable[[], object]) -> None: - """Register a function to be called when this node is finalized. + """Register a function to be called without arguments when this node is + finalized. This method can only be called when this node is active in a setup chain, for example during self.setup(). @@ -413,7 +421,11 @@ def addfinalizer(self, fin: Callable[[], object]) -> None: def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]: """Get the next parent node (including self) which is an instance of - the given class.""" + the given class. + + :param cls: The node class to search for. + :returns: The node, if found. + """ current: Optional[Node] = self while current and not isinstance(current, cls): current = current.parent @@ -653,20 +665,6 @@ class Item(Node): nextitem = None - def __init_subclass__(cls) -> None: - problems = ", ".join( - base.__name__ for base in cls.__bases__ if issubclass(base, Collector) - ) - if problems: - warnings.warn( - f"{cls.__name__} is an Item subclass and should not be a collector, " - f"however its bases {problems} are collectors.\n" - "Please split the Collectors and the Item into separate node types.\n" - "Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n" - "example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/", - PytestWarning, - ) - def __init__( self, name, @@ -694,6 +692,37 @@ def __init__( #: for this test. self.user_properties: List[Tuple[str, object]] = [] + self._check_item_and_collector_diamond_inheritance() + + def _check_item_and_collector_diamond_inheritance(self) -> None: + """ + Check if the current type inherits from both File and Collector + at the same time, emitting a warning accordingly (#8447). + """ + cls = type(self) + + # We inject an attribute in the type to avoid issuing this warning + # for the same class more than once, which is not helpful. + # It is a hack, but was deemed acceptable in order to avoid + # flooding the user in the common case. + attr_name = "_pytest_diamond_inheritance_warning_shown" + if getattr(cls, attr_name, False): + return + setattr(cls, attr_name, True) + + problems = ", ".join( + base.__name__ for base in cls.__bases__ if issubclass(base, Collector) + ) + if problems: + warnings.warn( + f"{cls.__name__} is an Item subclass and should not be a collector, " + f"however its bases {problems} are collectors.\n" + "Please split the Collectors and the Item into separate node types.\n" + "Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n" + "example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/", + PytestWarning, + ) + def runtest(self) -> None: """Run the test case for this item. diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py index b0699d22bd8..273bd045fb6 100644 --- a/src/_pytest/nose.py +++ b/src/_pytest/nose.py @@ -1,5 +1,8 @@ """Run testsuites written for nose.""" +import warnings + from _pytest.config import hookimpl +from _pytest.deprecated import NOSE_SUPPORT from _pytest.fixtures import getfixturemarker from _pytest.nodes import Item from _pytest.python import Function @@ -18,8 +21,8 @@ def pytest_runtest_setup(item: Item) -> None: # see https://github.com/python/mypy/issues/2608 func = item - call_optional(func.obj, "setup") - func.addfinalizer(lambda: call_optional(func.obj, "teardown")) + call_optional(func.obj, "setup", func.nodeid) + func.addfinalizer(lambda: call_optional(func.obj, "teardown", func.nodeid)) # NOTE: Module- and class-level fixtures are handled in python.py # with `pluginmanager.has_plugin("nose")` checks. @@ -27,7 +30,7 @@ def pytest_runtest_setup(item: Item) -> None: # it's not straightforward. -def call_optional(obj: object, name: str) -> bool: +def call_optional(obj: object, name: str, nodeid: str) -> bool: method = getattr(obj, name, None) if method is None: return False @@ -36,6 +39,11 @@ def call_optional(obj: object, name: str) -> bool: return False if not callable(method): return False + # Warn about deprecation of this plugin. + method_name = getattr(method, "__name__", str(method)) + warnings.warn( + NOSE_SUPPORT.format(nodeid=nodeid, method=method_name, stage=name), stacklevel=2 + ) # If there are any problems allow the exception to raise rather than # silently ignoring it. method() diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 25206fe0e85..3efd1de7fbb 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -5,6 +5,7 @@ from typing import Any from typing import Callable from typing import cast +from typing import NoReturn from typing import Optional from typing import Type from typing import TypeVar @@ -14,7 +15,6 @@ TYPE_CHECKING = False # Avoid circular import through compat. if TYPE_CHECKING: - from typing import NoReturn from typing_extensions import Protocol else: # typing.Protocol is only available starting from Python 3.8. It is also @@ -115,7 +115,7 @@ def decorate(func: _F) -> _WithException[_F, _ET]: @_with_exception(Exit) def exit( reason: str = "", returncode: Optional[int] = None, *, msg: Optional[str] = None -) -> "NoReturn": +) -> NoReturn: """Exit testing process. :param reason: @@ -146,7 +146,7 @@ def exit( @_with_exception(Skipped) def skip( reason: str = "", *, allow_module_level: bool = False, msg: Optional[str] = None -) -> "NoReturn": +) -> NoReturn: """Skip an executing test with the given message. This function should be called only during testing (setup, call or teardown) or @@ -157,8 +157,12 @@ def skip( The message to show the user as reason for the skip. :param allow_module_level: - Allows this function to be called at module level, skipping the rest - of the module. Defaults to False. + Allows this function to be called at module level. + Raising the skip exception at module level will stop + the execution of the module and prevent the collection of all tests in the module, + even those defined before the `skip` call. + + Defaults to False. :param msg: Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. @@ -176,9 +180,7 @@ def skip( @_with_exception(Failed) -def fail( - reason: str = "", pytrace: bool = True, msg: Optional[str] = None -) -> "NoReturn": +def fail(reason: str = "", pytrace: bool = True, msg: Optional[str] = None) -> NoReturn: """Explicitly fail an executing test with the given message. :param reason: @@ -238,11 +240,14 @@ class XFailed(Failed): @_with_exception(XFailed) -def xfail(reason: str = "") -> "NoReturn": +def xfail(reason: str = "") -> NoReturn: """Imperatively xfail an executing test or setup function with the given reason. This function should be called only during testing (setup, call or teardown). + :param reason: + The message to show the user as reason for the xfail. + .. note:: It is better to use the :ref:`pytest.mark.xfail ref` marker when possible to declare a test to be xfailed under certain conditions @@ -258,12 +263,12 @@ def importorskip( """Import and return the requested module ``modname``, or skip the current test if the module cannot be imported. - :param str modname: + :param modname: The name of the module to import. - :param str minversion: + :param minversion: If given, the imported module's ``__version__`` attribute must be at least this minimal version, otherwise the test is still skipped. - :param str reason: + :param reason: If given, this reason is shown as the message when the module cannot be imported. diff --git a/src/_pytest/pastebin.py b/src/_pytest/pastebin.py index 385b3022cc0..22c7a622373 100644 --- a/src/_pytest/pastebin.py +++ b/src/_pytest/pastebin.py @@ -24,7 +24,7 @@ def pytest_addoption(parser: Parser) -> None: dest="pastebin", default=None, choices=["failed", "all"], - help="send failed|all info to bpaste.net pastebin service.", + help="Send failed|all info to bpaste.net pastebin service", ) diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index b44753e1a41..3086fa78250 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -464,14 +464,14 @@ def import_path( * `mode == ImportMode.prepend`: the directory containing the module (or package, taking `__init__.py` files into account) will be put at the *start* of `sys.path` before - being imported with `__import__. + being imported with `importlib.import_module`. * `mode == ImportMode.append`: same as `prepend`, but the directory will be appended to the end of `sys.path`, if not already in `sys.path`. * `mode == ImportMode.importlib`: uses more fine control mechanisms provided by `importlib` - to import the module, which avoids having to use `__import__` and muck with `sys.path` - at all. It effectively allows having same-named test modules in different places. + to import the module, which avoids having to muck with `sys.path` at all. It effectively + allows having same-named test modules in different places. :param root: Used as an anchor when mode == ImportMode.importlib to obtain @@ -539,6 +539,9 @@ def import_path( ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "") if ignore != "1": module_file = mod.__file__ + if module_file is None: + raise ImportPathMismatchError(module_name, module_file, path) + if module_file.endswith((".pyc", ".pyo")): module_file = module_file[:-1] if module_file.endswith(os.path.sep + "__init__.py"): @@ -562,7 +565,6 @@ def import_path( def _is_same(f1: str, f2: str) -> bool: return Path(f1) == Path(f2) or os.path.samefile(f1, f2) - else: def _is_same(f1: str, f2: str) -> bool: @@ -601,11 +603,20 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> module_parts = module_name.split(".") while module_name: if module_name not in modules: - module = ModuleType( - module_name, - doc="Empty module created by pytest's importmode=importlib.", - ) - modules[module_name] = module + try: + # If sys.meta_path is empty, calling import_module will issue + # a warning and raise ModuleNotFoundError. To avoid the + # warning, we check sys.meta_path explicitly and raise the error + # ourselves to fall back to creating a dummy module. + if not sys.meta_path: + raise ModuleNotFoundError + importlib.import_module(module_name) + except ModuleNotFoundError: + module = ModuleType( + module_name, + doc="Empty module created by pytest's importmode=importlib.", + ) + modules[module_name] = module module_parts.pop(-1) module_name = ".".join(module_parts) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 42e71ff917e..a9299944dec 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -89,7 +89,7 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="lsof", default=False, - help="run FD checks if lsof is available", + help="Run FD checks if lsof is available", ) parser.addoption( @@ -98,13 +98,13 @@ def pytest_addoption(parser: Parser) -> None: dest="runpytest", choices=("inprocess", "subprocess"), help=( - "run pytest sub runs in tests using an 'inprocess' " + "Run pytest sub runs in tests using an 'inprocess' " "or 'subprocess' (python -m main) method" ), ) parser.addini( - "pytester_example_dir", help="directory to take the pytester example files from" + "pytester_example_dir", help="Directory to take the pytester example files from" ) @@ -128,7 +128,7 @@ def get_open_files(self) -> List[Tuple[str, str]]: stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True, - universal_newlines=True, + text=True, ).stdout def isopen(line: str) -> bool: @@ -477,7 +477,9 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: @fixture -def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pytester": +def pytester( + request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch +) -> "Pytester": """ Facilities to write tests/configuration files, execute pytest in isolation, and match against expected output, perfect for black-box testing of pytest plugins. @@ -488,7 +490,7 @@ def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pyt It is particularly useful for testing plugins. It is similar to the :fixture:`tmp_path` fixture but provides methods which aid in testing pytest itself. """ - return Pytester(request, tmp_path_factory, _ispytest=True) + return Pytester(request, tmp_path_factory, monkeypatch, _ispytest=True) @fixture @@ -596,11 +598,15 @@ def assert_outcomes( errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: int = 0, - deselected: int = 0, + warnings: Optional[int] = None, + deselected: Optional[int] = None, ) -> None: - """Assert that the specified outcomes appear with the respective - numbers (0 means it didn't occur) in the text output from a test run.""" + """ + Assert that the specified outcomes appear with the respective + numbers (0 means it didn't occur) in the text output from a test run. + + ``warnings`` and ``deselected`` are only checked if not None. + """ __tracebackhide__ = True from _pytest.pytester_assertions import assert_outcomes @@ -655,17 +661,7 @@ class Pytester: against expected output, perfect for black-box testing of pytest plugins. It attempts to isolate the test run from external factors as much as possible, modifying - the current working directory to ``path`` and environment variables during initialization. - - Attributes: - - :ivar Path path: temporary directory path used to create files/run tests from, etc. - - :ivar plugins: - A list of plugins to use with :py:meth:`parseconfig` and - :py:meth:`runpytest`. Initially this is an empty list but plugins can - be added to the list. The type of items to add to the list depends on - the method using them so refer to them for details. + the current working directory to :attr:`path` and environment variables during initialization. """ __test__ = False @@ -679,6 +675,7 @@ def __init__( self, request: FixtureRequest, tmp_path_factory: TempPathFactory, + monkeypatch: MonkeyPatch, *, _ispytest: bool = False, ) -> None: @@ -693,6 +690,10 @@ def __init__( name = request.node.name self._name = name self._path: Path = tmp_path_factory.mktemp(name, numbered=True) + #: A list of plugins to use with :py:meth:`parseconfig` and + #: :py:meth:`runpytest`. Initially this is an empty list but plugins can + #: be added to the list. The type of items to add to the list depends on + #: the method using them so refer to them for details. self.plugins: List[Union[str, _PluggyPlugin]] = [] self._cwd_snapshot = CwdSnapshot() self._sys_path_snapshot = SysPathsSnapshot() @@ -702,7 +703,7 @@ def __init__( self._method = self._request.config.getoption("--runpytest") self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True) - self._monkeypatch = mp = MonkeyPatch() + self._monkeypatch = mp = monkeypatch mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot)) # Ensure no unexpected caching via tox. mp.delenv("TOX_ENV_DIR", raising=False) @@ -717,7 +718,7 @@ def __init__( @property def path(self) -> Path: - """Temporary directory where files are created and pytest is executed.""" + """Temporary directory path used to create files/run tests from, etc.""" return self._path def __repr__(self) -> str: @@ -734,7 +735,6 @@ def _finalize(self) -> None: self._sys_modules_snapshot.restore() self._sys_path_snapshot.restore() self._cwd_snapshot.restore() - self._monkeypatch.undo() def __take_sys_modules_snapshot(self) -> SysModulesSnapshot: # Some zope modules used by twisted-related tests keep internal state @@ -749,7 +749,7 @@ def preserve_module(name): return SysModulesSnapshot(preserve=preserve_module) def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder: - """Create a new :py:class:`HookRecorder` for a PluginManager.""" + """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`.""" pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True) self._request.addfinalizer(reprec.finish_recording) return reprec @@ -798,7 +798,7 @@ def to_text(s: Union[Any, bytes]) -> str: def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: r"""Create new text file(s) in the test directory. - :param str ext: + :param ext: The extension the file(s) should use, including the dot, e.g. `.py`. :param args: All args are treated as strings and joined using newlines. @@ -807,6 +807,8 @@ def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: :param kwargs: Each keyword is the name of a file, while the value of it will be written as contents of the file. + :returns: + The first created file. Examples: @@ -826,11 +828,19 @@ def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: return self._makefile(ext, args, kwargs) def makeconftest(self, source: str) -> Path: - """Write a contest.py file with 'source' as contents.""" + """Write a contest.py file. + + :param source: The contents. + :returns: The conftest.py file. + """ return self.makepyfile(conftest=source) def makeini(self, source: str) -> Path: - """Write a tox.ini file with 'source' as contents.""" + """Write a tox.ini file. + + :param source: The contents. + :returns: The tox.ini file. + """ return self.makefile(".ini", tox=source) def getinicfg(self, source: str) -> SectionWrapper: @@ -839,7 +849,10 @@ def getinicfg(self, source: str) -> SectionWrapper: return IniConfig(str(p))["pytest"] def makepyprojecttoml(self, source: str) -> Path: - """Write a pyproject.toml file with 'source' as contents. + """Write a pyproject.toml file. + + :param source: The contents. + :returns: The pyproject.ini file. .. versionadded:: 6.0 """ @@ -892,19 +905,28 @@ def syspathinsert( This is undone automatically when this object dies at the end of each test. + + :param path: + The path. """ if path is None: path = self.path self._monkeypatch.syspath_prepend(str(path)) - def mkdir(self, name: str) -> Path: - """Create a new (sub)directory.""" + def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path: + """Create a new (sub)directory. + + :param name: + The name of the directory, relative to the pytester path. + :returns: + The created directory. + """ p = self.path / name p.mkdir() return p - def mkpydir(self, name: str) -> Path: + def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path: """Create a new python package. This creates a (sub)directory with an empty ``__init__.py`` file so it @@ -918,14 +940,15 @@ def mkpydir(self, name: str) -> Path: def copy_example(self, name: Optional[str] = None) -> Path: """Copy file from project's directory into the testdir. - :param str name: The name of the file to copy. - :return: path to the copied directory (inside ``self.path``). - + :param name: + The name of the file to copy. + :return: + Path to the copied directory (inside ``self.path``). """ - example_dir = self._request.config.getini("pytester_example_dir") - if example_dir is None: + example_dir_ = self._request.config.getini("pytester_example_dir") + if example_dir_ is None: raise ValueError("pytester_example_dir is unset, can't copy examples") - example_dir = self._request.config.rootpath / example_dir + example_dir: Path = self._request.config.rootpath / example_dir_ for extra_element in self._request.node.iter_markers("pytester_example_path"): assert extra_element.args @@ -961,14 +984,16 @@ def copy_example(self, name: Optional[str] = None) -> Path: def getnode( self, config: Config, arg: Union[str, "os.PathLike[str]"] - ) -> Optional[Union[Collector, Item]]: - """Return the collection node of a file. + ) -> Union[Collector, Item]: + """Get the collection node of a file. - :param pytest.Config config: + :param config: A pytest config. See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it. - :param os.PathLike[str] arg: + :param arg: Path to the file. + :returns: + The node. """ session = Session.from_config(config) assert "::" not in str(arg) @@ -978,13 +1003,18 @@ def getnode( config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def getpathnode(self, path: Union[str, "os.PathLike[str]"]): + def getpathnode( + self, path: Union[str, "os.PathLike[str]"] + ) -> Union[Collector, Item]: """Return the collection node of a file. This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to create the (configured) pytest Config instance. - :param os.PathLike[str] path: Path to the file. + :param path: + Path to the file. + :returns: + The node. """ path = Path(path) config = self.parseconfigure(path) @@ -1000,6 +1030,11 @@ def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: This recurses into the collection node and returns a list of all the test items contained within. + + :param colitems: + The collection nodes. + :returns: + The collected items. """ session = colitems[0].session result: List[Item] = [] @@ -1186,15 +1221,16 @@ def _ensure_basetemp( return new_args def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: - """Return a new pytest Config instance from given commandline args. + """Return a new pytest :class:`pytest.Config` instance from given + commandline args. - This invokes the pytest bootstrapping code in _pytest.config to create - a new :py:class:`_pytest.core.PluginManager` and call the - pytest_cmdline_parse hook to create a new - :py:class:`pytest.Config` instance. + This invokes the pytest bootstrapping code in _pytest.config to create a + new :py:class:`pytest.PytestPluginManager` and call the + :hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config` + instance. - If :py:attr:`plugins` has been populated they should be plugin modules - to be registered with the PluginManager. + If :attr:`plugins` has been populated they should be plugin modules + to be registered with the plugin manager. """ import _pytest.config @@ -1212,7 +1248,8 @@ def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: """Return a new pytest configured Config instance. Returns a new :py:class:`pytest.Config` instance like - :py:meth:`parseconfig`, but also calls the pytest_configure hook. + :py:meth:`parseconfig`, but also calls the :hook:`pytest_configure` + hook. """ config = self.parseconfig(*args) config._do_configure() @@ -1231,6 +1268,8 @@ def getitem( The module source. :param funcname: The name of the test function for which to return a test item. + :returns: + The test item. """ items = self.getitems(source) for item in items: @@ -1288,7 +1327,7 @@ def collect_by_name( ) -> Optional[Union[Item, Collector]]: """Return the collection node for name from the module collection. - Searchs a module collection node for a collection node matching the + Searches a module collection node for a collection node matching the given name. :param modcol: A module collection node; see :py:meth:`getmodulecol`. @@ -1371,6 +1410,8 @@ def run( - Otherwise, it is passed through to :py:class:`subprocess.Popen`. For further information in this case, consult the document of the ``stdin`` parameter in :py:class:`subprocess.Popen`. + :returns: + The result. """ __tracebackhide__ = True @@ -1457,6 +1498,8 @@ def runpytest_subprocess( :param timeout: The period in seconds after which to timeout and raise :py:class:`Pytester.TimeoutExpired`. + :returns: + The result. """ __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) diff --git a/src/_pytest/pytester_assertions.py b/src/_pytest/pytester_assertions.py index 6a5aabece47..657e4db5fc3 100644 --- a/src/_pytest/pytester_assertions.py +++ b/src/_pytest/pytester_assertions.py @@ -4,6 +4,7 @@ # hence cannot be subject to assertion rewriting, which requires a # module to not be already imported. from typing import Dict +from typing import Optional from typing import Sequence from typing import Tuple from typing import Union @@ -42,8 +43,8 @@ def assert_outcomes( errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: int = 0, - deselected: int = 0, + warnings: Optional[int] = None, + deselected: Optional[int] = None, ) -> None: """Assert that the specified outcomes appear with the respective numbers (0 means it didn't occur) in the text output from a test run.""" @@ -56,8 +57,6 @@ def assert_outcomes( "errors": outcomes.get("errors", 0), "xpassed": outcomes.get("xpassed", 0), "xfailed": outcomes.get("xfailed", 0), - "warnings": outcomes.get("warnings", 0), - "deselected": outcomes.get("deselected", 0), } expected = { "passed": passed, @@ -66,7 +65,11 @@ def assert_outcomes( "errors": errors, "xpassed": xpassed, "xfailed": xfailed, - "warnings": warnings, - "deselected": deselected, } + if warnings is not None: + obtained["warnings"] = outcomes.get("warnings", 0) + expected["warnings"] = warnings + if deselected is not None: + obtained["deselected"] = outcomes.get("deselected", 0) + expected["deselected"] = deselected assert obtained == expected diff --git a/src/_pytest/python.py b/src/_pytest/python.py index b557cd8fa77..75d64fbdf67 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -59,6 +59,7 @@ from _pytest.deprecated import check_ispytest from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH from _pytest.deprecated import INSTANCE_COLLECTOR +from _pytest.deprecated import NOSE_SUPPORT_METHOD from _pytest.fixtures import FuncFixtureInfo from _pytest.main import Session from _pytest.mark import MARK_GEN @@ -77,10 +78,12 @@ from _pytest.pathlib import visit from _pytest.scope import Scope from _pytest.warning_types import PytestCollectionWarning +from _pytest.warning_types import PytestReturnNotNoneWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning if TYPE_CHECKING: from typing_extensions import Literal + from _pytest.scope import _ScopeName @@ -95,7 +98,7 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="showfixtures", default=False, - help="show available fixtures, sorted by plugin appearance " + help="Show available fixtures, sorted by plugin appearance " "(fixtures with leading '_' are only shown with '-v')", ) group.addoption( @@ -103,32 +106,32 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="show_fixtures_per_test", default=False, - help="show fixtures per test", + help="Show fixtures per test", ) parser.addini( "python_files", type="args", # NOTE: default is also used in AssertionRewritingHook. default=["test_*.py", "*_test.py"], - help="glob-style file patterns for Python test module discovery", + help="Glob-style file patterns for Python test module discovery", ) parser.addini( "python_classes", type="args", default=["Test"], - help="prefixes or glob names for Python test class discovery", + help="Prefixes or glob names for Python test class discovery", ) parser.addini( "python_functions", type="args", default=["test"], - help="prefixes or glob names for Python test function and method discovery", + help="Prefixes or glob names for Python test function and method discovery", ) parser.addini( "disable_test_id_escaping_and_forfeit_all_rights_to_community_support", type="bool", default=False, - help="disable string escape non-ascii characters, might cause unwanted " + help="Disable string escape non-ASCII characters, might cause unwanted " "side effects(use at your own risk)", ) @@ -192,6 +195,13 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: result = testfunction(**testargs) if hasattr(result, "__await__") or hasattr(result, "__aiter__"): async_warn_and_skip(pyfuncitem.nodeid) + elif result is not None: + warnings.warn( + PytestReturnNotNoneWarning( + f"Expected None, but {pyfuncitem.nodeid} returned {result!r}, which will be an error in a " + "future version of pytest. Did you mean to use `assert` instead of `return`?" + ) + ) return True @@ -224,11 +234,15 @@ def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module": @hookimpl(trylast=True) -def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object): +def pytest_pycollect_makeitem( + collector: Union["Module", "Class"], name: str, obj: object +) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]: + assert isinstance(collector, (Class, Module)), type(collector) # Nothing was collected elsewhere, let's do it here. if safe_isclass(obj): if collector.istestclass(obj, name): - return Class.from_parent(collector, name=name, obj=obj) + klass: Class = Class.from_parent(collector, name=name, obj=obj) + return klass elif collector.istestfunction(obj, name): # mock seems to store unbound methods (issue473), normalize it. obj = getattr(obj, "__func__", obj) @@ -247,15 +261,16 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object): ) elif getattr(obj, "__test__", True): if is_generator(obj): - res = Function.from_parent(collector, name=name) + res: Function = Function.from_parent(collector, name=name) reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format( name=name ) res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) res.warn(PytestCollectionWarning(reason)) + return res else: - res = list(collector._genfunctions(name, obj)) - return res + return list(collector._genfunctions(name, obj)) + return None class PyobjMixin(nodes.Node): @@ -278,6 +293,16 @@ def cls(self): node = self.getparent(Class) return node.obj if node is not None else None + @property + def instance(self): + """Python instance object the function is bound to. + + Returns None if not a test method, e.g. for a standalone test function, + a staticmethod, a class or a module. + """ + node = self.getparent(Function) + return getattr(node.obj, "__self__", None) if node is not None else None + @property def obj(self): """Underlying Python object.""" @@ -288,6 +313,9 @@ def obj(self): # used to avoid Function marker duplication if self._ALLOW_MARKERS: self.own_markers.extend(get_unpacked_marks(self.obj)) + # This assumes that `obj` is called before there is a chance + # to add custom keys to `self.keywords`, so no fear of overriding. + self.keywords.update((mark.name, mark) for mark in self.own_markers) return obj @obj.setter @@ -325,6 +353,7 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str if isinstance(compat_co_firstlineno, int): # nose compatibility file_path = sys.modules[obj.__module__].__file__ + assert file_path is not None if file_path.endswith(".pyc"): file_path = file_path[:-1] path: Union["os.PathLike[str]", str] = file_path @@ -409,7 +438,7 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: for basecls in self.obj.__mro__: dicts.append(basecls.__dict__) - # In each class, nodes should be definition ordered. Since Python 3.6, + # In each class, nodes should be definition ordered. # __dict__ is definition ordered. seen: Set[str] = set() dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = [] @@ -517,12 +546,18 @@ def _inject_setup_module_fixture(self) -> None: self.obj, ("setUpModule", "setup_module") ) if setup_module is None and has_nose: + # The name "setup" is too common - only treat as fixture if callable. setup_module = _get_first_non_fixture_func(self.obj, ("setup",)) + if not callable(setup_module): + setup_module = None teardown_module = _get_first_non_fixture_func( self.obj, ("tearDownModule", "teardown_module") ) if teardown_module is None and has_nose: teardown_module = _get_first_non_fixture_func(self.obj, ("teardown",)) + # Same as "setup" above - only treat as fixture if callable. + if not callable(teardown_module): + teardown_module = None if setup_module is None and teardown_module is None: return @@ -813,7 +848,7 @@ def _inject_setup_class_fixture(self) -> None: other fixtures (#517). """ setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",)) - teardown_class = getattr(self.obj, "teardown_class", None) + teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",)) if setup_class is None and teardown_class is None: return @@ -838,20 +873,24 @@ def _inject_setup_method_fixture(self) -> None: """Inject a hidden autouse, function scoped fixture into the collected class object that invokes setup_method/teardown_method if either or both are available. - Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with + Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ has_nose = self.config.pluginmanager.has_plugin("nose") setup_name = "setup_method" setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) + emit_nose_setup_warning = False if setup_method is None and has_nose: setup_name = "setup" + emit_nose_setup_warning = True setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) teardown_name = "teardown_method" - teardown_method = getattr(self.obj, teardown_name, None) + teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) + emit_nose_teardown_warning = False if teardown_method is None and has_nose: teardown_name = "teardown" - teardown_method = getattr(self.obj, teardown_name, None) + emit_nose_teardown_warning = True + teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) if setup_method is None and teardown_method is None: return @@ -866,10 +905,24 @@ def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]: if setup_method is not None: func = getattr(self, setup_name) _call_with_optional_argument(func, method) + if emit_nose_setup_warning: + warnings.warn( + NOSE_SUPPORT_METHOD.format( + nodeid=request.node.nodeid, method="setup" + ), + stacklevel=2, + ) yield if teardown_method is not None: func = getattr(self, teardown_name) _call_with_optional_argument(func, method) + if emit_nose_teardown_warning: + warnings.warn( + NOSE_SUPPORT_METHOD.format( + nodeid=request.node.nodeid, method="teardown" + ), + stacklevel=2, + ) self.obj.__pytest_setup_method = xunit_setup_method_fixture @@ -880,11 +933,7 @@ class InstanceDummy: only to ignore it; this dummy class keeps them working. This will be removed in pytest 8.""" - pass - -# Note: module __getattr__ only works on Python>=3.7. Unfortunately -# we can't provide this deprecation warning on Python 3.6. def __getattr__(name: str) -> object: if name == "Instance": warnings.warn(INSTANCE_COLLECTOR, 2) @@ -906,6 +955,159 @@ def hasnew(obj: object) -> bool: return False +@final +@attr.s(frozen=True, auto_attribs=True, slots=True) +class IdMaker: + """Make IDs for a parametrization.""" + + # The argnames of the parametrization. + argnames: Sequence[str] + # The ParameterSets of the parametrization. + parametersets: Sequence[ParameterSet] + # Optionally, a user-provided callable to make IDs for parameters in a + # ParameterSet. + idfn: Optional[Callable[[Any], Optional[object]]] + # Optionally, explicit IDs for ParameterSets by index. + ids: Optional[Sequence[Optional[object]]] + # Optionally, the pytest config. + # Used for controlling ASCII escaping, and for calling the + # :hook:`pytest_make_parametrize_id` hook. + config: Optional[Config] + # Optionally, the ID of the node being parametrized. + # Used only for clearer error messages. + nodeid: Optional[str] + # Optionally, the ID of the function being parametrized. + # Used only for clearer error messages. + func_name: Optional[str] + + def make_unique_parameterset_ids(self) -> List[str]: + """Make a unique identifier for each ParameterSet, that may be used to + identify the parametrization in a node ID. + + Format is -...-[counter], where prm_x_token is + - user-provided id, if given + - else an id derived from the value, applicable for certain types + - else + The counter suffix is appended only in case a string wouldn't be unique + otherwise. + """ + resolved_ids = list(self._resolve_ids()) + # All IDs must be unique! + if len(resolved_ids) != len(set(resolved_ids)): + # Record the number of occurrences of each ID. + id_counts = Counter(resolved_ids) + # Map the ID to its next suffix. + id_suffixes: Dict[str, int] = defaultdict(int) + # Suffix non-unique IDs to make them unique. + for index, id in enumerate(resolved_ids): + if id_counts[id] > 1: + resolved_ids[index] = f"{id}{id_suffixes[id]}" + id_suffixes[id] += 1 + return resolved_ids + + def _resolve_ids(self) -> Iterable[str]: + """Resolve IDs for all ParameterSets (may contain duplicates).""" + for idx, parameterset in enumerate(self.parametersets): + if parameterset.id is not None: + # ID provided directly - pytest.param(..., id="...") + yield parameterset.id + elif self.ids and idx < len(self.ids) and self.ids[idx] is not None: + # ID provided in the IDs list - parametrize(..., ids=[...]). + yield self._idval_from_value_required(self.ids[idx], idx) + else: + # ID not provided - generate it. + yield "-".join( + self._idval(val, argname, idx) + for val, argname in zip(parameterset.values, self.argnames) + ) + + def _idval(self, val: object, argname: str, idx: int) -> str: + """Make an ID for a parameter in a ParameterSet.""" + idval = self._idval_from_function(val, argname, idx) + if idval is not None: + return idval + idval = self._idval_from_hook(val, argname) + if idval is not None: + return idval + idval = self._idval_from_value(val) + if idval is not None: + return idval + return self._idval_from_argname(argname, idx) + + def _idval_from_function( + self, val: object, argname: str, idx: int + ) -> Optional[str]: + """Try to make an ID for a parameter in a ParameterSet using the + user-provided id callable, if given.""" + if self.idfn is None: + return None + try: + id = self.idfn(val) + except Exception as e: + prefix = f"{self.nodeid}: " if self.nodeid is not None else "" + msg = "error raised while trying to determine id of parameter '{}' at position {}" + msg = prefix + msg.format(argname, idx) + raise ValueError(msg) from e + if id is None: + return None + return self._idval_from_value(id) + + def _idval_from_hook(self, val: object, argname: str) -> Optional[str]: + """Try to make an ID for a parameter in a ParameterSet by calling the + :hook:`pytest_make_parametrize_id` hook.""" + if self.config: + id: Optional[str] = self.config.hook.pytest_make_parametrize_id( + config=self.config, val=val, argname=argname + ) + return id + return None + + def _idval_from_value(self, val: object) -> Optional[str]: + """Try to make an ID for a parameter in a ParameterSet from its value, + if the value type is supported.""" + if isinstance(val, STRING_TYPES): + return _ascii_escaped_by_config(val, self.config) + elif val is None or isinstance(val, (float, int, bool, complex)): + return str(val) + elif isinstance(val, Pattern): + return ascii_escaped(val.pattern) + elif val is NOTSET: + # Fallback to default. Note that NOTSET is an enum.Enum. + pass + elif isinstance(val, enum.Enum): + return str(val) + elif isinstance(getattr(val, "__name__", None), str): + # Name of a class, function, module, etc. + name: str = getattr(val, "__name__") + return name + return None + + def _idval_from_value_required(self, val: object, idx: int) -> str: + """Like _idval_from_value(), but fails if the type is not supported.""" + id = self._idval_from_value(val) + if id is not None: + return id + + # Fail. + if self.func_name is not None: + prefix = f"In {self.func_name}: " + elif self.nodeid is not None: + prefix = f"In {self.nodeid}: " + else: + prefix = "" + msg = ( + f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. " + "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__." + ) + fail(msg, pytrace=False) + + @staticmethod + def _idval_from_argname(argname: str, idx: int) -> str: + """Make an ID for a parameter in a ParameterSet from the argument name + and the index of the ParameterSet.""" + return str(argname) + str(idx) + + @final @attr.s(frozen=True, slots=True, auto_attribs=True) class CallSpec2: @@ -980,7 +1182,7 @@ def id(self) -> str: @final class Metafunc: - """Objects passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook. + """Objects passed to the :hook:`pytest_generate_tests` hook. They help to inspect a test function and to generate tests according to test configuration or values specified in the class or module where a @@ -1024,14 +1226,11 @@ def __init__( def parametrize( self, - argnames: Union[str, List[str], Tuple[str, ...]], + argnames: Union[str, Sequence[str]], argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], indirect: Union[bool, Sequence[str]] = False, ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] ] = None, scope: "Optional[_ScopeName]" = None, *, @@ -1098,7 +1297,7 @@ def parametrize( It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ - argnames, parameters = ParameterSet._for_parametrize( + argnames, parametersets = ParameterSet._for_parametrize( argnames, argvalues, self.function, @@ -1130,8 +1329,8 @@ def parametrize( if generated_ids is not None: ids = generated_ids - ids = self._resolve_arg_ids( - argnames, ids, parameters, nodeid=self.definition.nodeid + ids = self._resolve_parameter_set_ids( + argnames, ids, parametersets, nodeid=self.definition.nodeid ) # Store used (possibly generated) ids with parametrize Marks. @@ -1143,7 +1342,9 @@ def parametrize( # of all calls. newcalls = [] for callspec in self._calls or [CallSpec2()]: - for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)): + for param_index, (param_id, param_set) in enumerate( + zip(ids, parametersets) + ): newcallspec = callspec.setmulti( valtypes=arg_values_types, argnames=argnames, @@ -1156,27 +1357,29 @@ def parametrize( newcalls.append(newcallspec) self._calls = newcalls - def _resolve_arg_ids( + def _resolve_parameter_set_ids( self, argnames: Sequence[str], ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] + Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] ], - parameters: Sequence[ParameterSet], + parametersets: Sequence[ParameterSet], nodeid: str, ) -> List[str]: - """Resolve the actual ids for the given argnames, based on the ``ids`` parameter given - to ``parametrize``. + """Resolve the actual ids for the given parameter sets. - :param List[str] argnames: List of argument names passed to ``parametrize()``. - :param ids: The ids parameter of the parametrized call (see docs). - :param List[ParameterSet] parameters: The list of parameter values, same size as ``argnames``. - :param str str: The nodeid of the item that generated this parametrized call. - :rtype: List[str] - :returns: The list of ids for each argname given. + :param argnames: + Argument names passed to ``parametrize()``. + :param ids: + The `ids` parameter of the ``parametrize()`` call (see docs). + :param parametersets: + The parameter sets, each containing a set of values corresponding + to ``argnames``. + :param nodeid str: + The nodeid of the definition item that generated this + parametrization. + :returns: + List with ids for each parameter set given. """ if ids is None: idfn = None @@ -1186,15 +1389,24 @@ def _resolve_arg_ids( ids_ = None else: idfn = None - ids_ = self._validate_ids(ids, parameters, self.function.__name__) - return idmaker(argnames, parameters, idfn, ids_, self.config, nodeid=nodeid) + ids_ = self._validate_ids(ids, parametersets, self.function.__name__) + id_maker = IdMaker( + argnames, + parametersets, + idfn, + ids_, + self.config, + nodeid=nodeid, + func_name=self.function.__name__, + ) + return id_maker.make_unique_parameterset_ids() def _validate_ids( self, - ids: Iterable[Union[None, str, float, int, bool]], - parameters: Sequence[ParameterSet], + ids: Iterable[Optional[object]], + parametersets: Sequence[ParameterSet], func_name: str, - ) -> List[Union[None, str]]: + ) -> List[Optional[object]]: try: num_ids = len(ids) # type: ignore[arg-type] except TypeError: @@ -1202,29 +1414,14 @@ def _validate_ids( iter(ids) except TypeError as e: raise TypeError("ids must be a callable or an iterable") from e - num_ids = len(parameters) + num_ids = len(parametersets) # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849 - if num_ids != len(parameters) and num_ids != 0: + if num_ids != len(parametersets) and num_ids != 0: msg = "In {}: {} parameter sets specified, with different number of ids: {}" - fail(msg.format(func_name, len(parameters), num_ids), pytrace=False) - - new_ids = [] - for idx, id_value in enumerate(itertools.islice(ids, num_ids)): - if id_value is None or isinstance(id_value, str): - new_ids.append(id_value) - elif isinstance(id_value, (float, int, bool)): - new_ids.append(str(id_value)) - else: - msg = ( # type: ignore[unreachable] - "In {}: ids must be list of string/float/int/bool, " - "found: {} (type: {!r}) at index {}" - ) - fail( - msg.format(func_name, saferepr(id_value), type(id_value), idx), - pytrace=False, - ) - return new_ids + fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False) + + return list(itertools.islice(ids, num_ids)) def _resolve_arg_value_types( self, @@ -1344,105 +1541,6 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) - return val if escape_option else ascii_escaped(val) # type: ignore -def _idval( - val: object, - argname: str, - idx: int, - idfn: Optional[Callable[[Any], Optional[object]]], - nodeid: Optional[str], - config: Optional[Config], -) -> str: - if idfn: - try: - generated_id = idfn(val) - if generated_id is not None: - val = generated_id - except Exception as e: - prefix = f"{nodeid}: " if nodeid is not None else "" - msg = "error raised while trying to determine id of parameter '{}' at position {}" - msg = prefix + msg.format(argname, idx) - raise ValueError(msg) from e - elif config: - hook_id: Optional[str] = config.hook.pytest_make_parametrize_id( - config=config, val=val, argname=argname - ) - if hook_id: - return hook_id - - if isinstance(val, STRING_TYPES): - return _ascii_escaped_by_config(val, config) - elif val is None or isinstance(val, (float, int, bool, complex)): - return str(val) - elif isinstance(val, Pattern): - return ascii_escaped(val.pattern) - elif val is NOTSET: - # Fallback to default. Note that NOTSET is an enum.Enum. - pass - elif isinstance(val, enum.Enum): - return str(val) - elif isinstance(getattr(val, "__name__", None), str): - # Name of a class, function, module, etc. - name: str = getattr(val, "__name__") - return name - return str(argname) + str(idx) - - -def _idvalset( - idx: int, - parameterset: ParameterSet, - argnames: Iterable[str], - idfn: Optional[Callable[[Any], Optional[object]]], - ids: Optional[List[Union[None, str]]], - nodeid: Optional[str], - config: Optional[Config], -) -> str: - if parameterset.id is not None: - return parameterset.id - id = None if ids is None or idx >= len(ids) else ids[idx] - if id is None: - this_id = [ - _idval(val, argname, idx, idfn, nodeid=nodeid, config=config) - for val, argname in zip(parameterset.values, argnames) - ] - return "-".join(this_id) - else: - return _ascii_escaped_by_config(id, config) - - -def idmaker( - argnames: Iterable[str], - parametersets: Iterable[ParameterSet], - idfn: Optional[Callable[[Any], Optional[object]]] = None, - ids: Optional[List[Union[None, str]]] = None, - config: Optional[Config] = None, - nodeid: Optional[str] = None, -) -> List[str]: - resolved_ids = [ - _idvalset( - valindex, parameterset, argnames, idfn, ids, config=config, nodeid=nodeid - ) - for valindex, parameterset in enumerate(parametersets) - ] - - # All IDs must be unique! - unique_ids = set(resolved_ids) - if len(unique_ids) != len(resolved_ids): - - # Record the number of occurrences of each test ID. - test_id_counts = Counter(resolved_ids) - - # Map the test ID to its next suffix. - test_id_suffixes: Dict[str, int] = defaultdict(int) - - # Suffix non-unique IDs to make them unique. - for index, test_id in enumerate(resolved_ids): - if test_id_counts[test_id] > 1: - resolved_ids[index] = f"{test_id}{test_id_suffixes[test_id]}" - test_id_suffixes[test_id] += 1 - - return resolved_ids - - def _pretty_fixture_path(func) -> str: cwd = Path.cwd() loc = Path(getlocation(func, str(cwd))) @@ -1614,7 +1712,7 @@ def __init__( config: Optional[Config] = None, callspec: Optional[CallSpec2] = None, callobj=NOTSET, - keywords=None, + keywords: Optional[Mapping[str, Any]] = None, session: Optional[Session] = None, fixtureinfo: Optional[FuncFixtureInfo] = None, originalname: Optional[str] = None, @@ -1635,31 +1733,20 @@ def __init__( # Note: when FunctionDefinition is introduced, we should change ``originalname`` # to a readonly property that returns FunctionDefinition.name. - self.keywords.update(self.obj.__dict__) self.own_markers.extend(get_unpacked_marks(self.obj)) if callspec: self.callspec = callspec - # this is total hostile and a mess - # keywords are broken by design by now - # this will be redeemed later - for mark in callspec.marks: - # feel free to cry, this was broken for years before - # and keywords cant fix it per design - self.keywords[mark.name] = mark - self.own_markers.extend(normalize_mark_list(callspec.marks)) - if keywords: - self.keywords.update(keywords) + self.own_markers.extend(callspec.marks) # todo: this is a hell of a hack # https://github.com/pytest-dev/pytest/issues/4569 - - self.keywords.update( - { - mark.name: True - for mark in self.iter_markers() - if mark.name not in self.keywords - } - ) + # Note: the order of the updates is important here; indicates what + # takes priority (ctor argument over function attributes over markers). + # Take own_markers only; NodeKeywords handles parent traversal on its own. + self.keywords.update((mark.name, mark) for mark in self.own_markers) + self.keywords.update(self.obj.__dict__) + if keywords: + self.keywords.update(keywords) if fixtureinfo is None: fixtureinfo = self.session._fixturemanager.getfixtureinfo( @@ -1683,15 +1770,6 @@ def function(self): """Underlying python 'function' object.""" return getimfunc(self.obj) - @property - def instance(self): - """Python instance object the function is bound to. - - Returns None if not a test method, e.g. for a standalone test function - or a staticmethod. - """ - return getattr(self.obj, "__self__", None) - def _getobj(self): assert self.parent is not None if isinstance(self.parent, Class): diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 26f78c66ad4..7afc2d96570 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,5 +1,6 @@ import math import pprint +from collections.abc import Collection from collections.abc import Sized from decimal import Decimal from numbers import Complex @@ -7,12 +8,10 @@ from typing import Any from typing import Callable from typing import cast -from typing import Generic -from typing import Iterable +from typing import ContextManager from typing import List from typing import Mapping from typing import Optional -from typing import overload from typing import Pattern from typing import Sequence from typing import Tuple @@ -28,6 +27,7 @@ import _pytest._code from _pytest.compat import final from _pytest.compat import STRING_TYPES +from _pytest.compat import overload from _pytest.outcomes import fail @@ -101,6 +101,7 @@ def __eq__(self, actual) -> bool: ) def __bool__(self): + __tracebackhide__ = True raise AssertionError( "approx() is not supported in a boolean context.\nDid you mean: `assert a == approx(b)`?" ) @@ -130,12 +131,13 @@ def _check_type(self) -> None: # a numeric type. For this reason, the default is to do nothing. The # classes that deal with sequences should reimplement this method to # raise if there are any non-numeric elements in the sequence. - pass -def _recursive_list_map(f, x): - if isinstance(x, list): - return [_recursive_list_map(f, xi) for xi in x] +def _recursive_sequence_map(f, x): + """Recursively map a function over a sequence of arbitrary depth""" + if isinstance(x, (list, tuple)): + seq_type = type(x) + return seq_type(_recursive_sequence_map(f, xi) for xi in x) else: return f(x) @@ -144,7 +146,9 @@ class ApproxNumpy(ApproxBase): """Perform approximate comparisons where the expected value is numpy array.""" def __repr__(self) -> str: - list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist()) + list_scalars = _recursive_sequence_map( + self._approx_scalar, self.expected.tolist() + ) return f"approx({list_scalars!r})" def _repr_compare(self, other_side: "ndarray") -> List[str]: @@ -164,7 +168,7 @@ def get_value_from_nested_list( return value np_array_shape = self.expected.shape - approx_side_as_list = _recursive_list_map( + approx_side_as_seq = _recursive_sequence_map( self._approx_scalar, self.expected.tolist() ) @@ -179,7 +183,7 @@ def get_value_from_nested_list( max_rel_diff = -math.inf different_ids = [] for index in itertools.product(*(range(i) for i in np_array_shape)): - approx_value = get_value_from_nested_list(approx_side_as_list, index) + approx_value = get_value_from_nested_list(approx_side_as_seq, index) other_value = get_value_from_nested_list(other_side, index) if approx_value != other_value: abs_diff = abs(approx_value.expected - other_value) @@ -194,7 +198,7 @@ def get_value_from_nested_list( ( str(index), str(get_value_from_nested_list(other_side, index)), - str(get_value_from_nested_list(approx_side_as_list, index)), + str(get_value_from_nested_list(approx_side_as_seq, index)), ) for index in different_ids ] @@ -265,10 +269,16 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]: max_abs_diff = max( max_abs_diff, abs(approx_value.expected - other_value) ) - max_rel_diff = max( - max_rel_diff, - abs((approx_value.expected - other_value) / approx_value.expected), - ) + if approx_value.expected == 0.0: + max_rel_diff = math.inf + else: + max_rel_diff = max( + max_rel_diff, + abs( + (approx_value.expected - other_value) + / approx_value.expected + ), + ) different_ids.append(approx_key) message_data = [ @@ -306,12 +316,12 @@ def _check_type(self) -> None: raise TypeError(msg.format(key, value, pprint.pformat(self.expected))) -class ApproxSequencelike(ApproxBase): +class ApproxSequenceLike(ApproxBase): """Perform approximate comparisons where the expected value is a sequence of numbers.""" def __repr__(self) -> str: seq_type = type(self.expected) - if seq_type not in (tuple, list, set): + if seq_type not in (tuple, list): seq_type = list return "approx({!r})".format( seq_type(self._approx_scalar(x) for x in self.expected) @@ -319,7 +329,6 @@ def __repr__(self) -> str: def _repr_compare(self, other_side: Sequence[float]) -> List[str]: import math - import numpy as np if len(self.expected) != len(other_side): return [ @@ -327,7 +336,7 @@ def _repr_compare(self, other_side: Sequence[float]) -> List[str]: f"Lengths: {len(self.expected)} and {len(other_side)}", ] - approx_side_as_map = _recursive_list_map(self._approx_scalar, self.expected) + approx_side_as_map = _recursive_sequence_map(self._approx_scalar, self.expected) number_of_elements = len(approx_side_as_map) max_abs_diff = -math.inf @@ -340,7 +349,7 @@ def _repr_compare(self, other_side: Sequence[float]) -> List[str]: abs_diff = abs(approx_value.expected - other_value) max_abs_diff = max(max_abs_diff, abs_diff) if other_value == 0.0: - max_rel_diff = np.inf + max_rel_diff = math.inf else: max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value)) different_ids.append(i) @@ -515,10 +524,10 @@ class ApproxDecimal(ApproxScalar): def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: - """Assert that two numbers (or two sets of numbers) are equal to each other + """Assert that two numbers (or two ordered sequences of numbers) are equal to each other within some tolerance. - Due to the :std:doc:`tutorial/floatingpoint`, numbers that we + Due to the :doc:`python:tutorial/floatingpoint`, numbers that we would intuitively expect to be equal are not always so:: >>> 0.1 + 0.2 == 0.3 @@ -547,16 +556,11 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: >>> 0.1 + 0.2 == approx(0.3) True - The same syntax also works for sequences of numbers:: + The same syntax also works for ordered sequences of numbers:: >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6)) True - Dictionary *values*:: - - >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6}) - True - ``numpy`` arrays:: >>> import numpy as np # doctest: +SKIP @@ -569,6 +573,20 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP True + Only ordered sequences are supported, because ``approx`` needs + to infer the relative position of the sequences without ambiguity. This means + ``sets`` and other unordered sequences are not supported. + + Finally, dictionary *values* can also be compared:: + + >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6}) + True + + The comparison will be true if both mappings have the same keys and their + respective values match the expected tolerances. + + **Tolerances** + By default, ``approx`` considers numbers within a relative tolerance of ``1e-6`` (i.e. one part in a million) of its expected value to be equal. This treatment would lead to surprising results if the expected value was @@ -658,6 +676,11 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: specialised test helpers in :std:doc:`numpy:reference/routines.testing` if you need support for comparisons, NaNs, or ULP-based tolerances. + To match strings using regex, you can use + `Matches `_ + from the + `re_assert package `_. + .. warning:: .. versionchanged:: 3.2 @@ -708,12 +731,19 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: expected = _as_numpy_array(expected) cls = ApproxNumpy elif ( - isinstance(expected, Iterable) + hasattr(expected, "__getitem__") and isinstance(expected, Sized) # Type ignored because the error is wrong -- not unreachable. and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] ): - cls = ApproxSequencelike + cls = ApproxSequenceLike + elif ( + isinstance(expected, Collection) + # Type ignored because the error is wrong -- not unreachable. + and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] + ): + msg = f"pytest.approx() only supports ordered sequences, but got: {repr(expected)}" + raise TypeError(msg) else: cls = ApproxScalar @@ -762,7 +792,7 @@ def raises( @overload -def raises( +def raises( # noqa: F811 expected_exception: Union[Type[E], Tuple[Type[E], ...]], func: Callable[..., Any], *args: Any, @@ -771,18 +801,21 @@ def raises( ... -def raises( +def raises( # noqa: F811 expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any ) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]: - r"""Assert that a code block/function call raises ``expected_exception`` - or raise a failure exception otherwise. + r"""Assert that a code block/function call raises an exception. - :kwparam match: + :param typing.Type[E] | typing.Tuple[typing.Type[E], ...] expected_exception: + The excpected exception type, or a tuple if one of multiple possible + exception types are excepted. + :kwparam str | typing.Pattern[str] | None match: If specified, a string containing a regular expression, or a regular expression object, that is tested against the string - representation of the exception using :py:func:`re.search`. To match a literal - string that may contain :std:ref:`special characters `, the pattern can - first be escaped with :py:func:`re.escape`. + representation of the exception using :func:`re.search`. + + To match a literal string that may contain :ref:`special characters + `, the pattern can first be escaped with :func:`re.escape`. (This is only used when :py:func:`pytest.raises` is used as a context manager, and passed through to the function otherwise. @@ -884,6 +917,12 @@ def raises( """ __tracebackhide__ = True + if not expected_exception: + raise ValueError( + f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. " + f"Raising exceptions is already understood as failing the test, so you don't need " + f"any special code to say 'this should never raise an exception'." + ) if isinstance(expected_exception, type): excepted_exceptions: Tuple[Type[E], ...] = (expected_exception,) else: @@ -924,7 +963,7 @@ def raises( @final -class RaisesContext(Generic[E]): +class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]): def __init__( self, expected_exception: Union[Type[E], Tuple[Type[E], ...]], diff --git a/src/_pytest/pythonpath.py b/src/_pytest/python_path.py similarity index 100% rename from src/_pytest/pythonpath.py rename to src/_pytest/python_path.py diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 175b571a80c..d76ea020f19 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -1,6 +1,7 @@ """Record warnings during test function execution.""" import re import warnings +from pprint import pformat from types import TracebackType from typing import Any from typing import Callable @@ -8,7 +9,6 @@ from typing import Iterator from typing import List from typing import Optional -from typing import overload from typing import Pattern from typing import Tuple from typing import Type @@ -16,6 +16,7 @@ from typing import Union from _pytest.compat import final +from _pytest.compat import overload from _pytest.deprecated import check_ispytest from _pytest.deprecated import WARNS_NONE_ARG from _pytest.fixtures import fixture @@ -29,7 +30,7 @@ def recwarn() -> Generator["WarningsRecorder", None, None]: """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. - See https://docs.python.org/library/how-to/capture-warnings.html for information + See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information on warning categories. """ wrec = WarningsRecorder(_ispytest=True) @@ -46,11 +47,13 @@ def deprecated_call( @overload -def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: +def deprecated_call( # noqa: F811 + func: Callable[..., T], *args: Any, **kwargs: Any +) -> T: ... -def deprecated_call( +def deprecated_call( # noqa: F811 func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any ) -> Union["WarningsRecorder", Any]: """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning``. @@ -92,7 +95,7 @@ def warns( @overload -def warns( +def warns( # noqa: F811 expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]], func: Callable[..., T], *args: Any, @@ -101,7 +104,7 @@ def warns( ... -def warns( +def warns( # noqa: F811 expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, *args: Any, match: Optional[Union[str, Pattern[str]]] = None, @@ -109,15 +112,15 @@ def warns( ) -> Union["WarningsChecker", Any]: r"""Assert that code raises a particular class of warning. - Specifically, the parameter ``expected_warning`` can be a warning class or - sequence of warning classes, and the inside the ``with`` block must issue a warning of that class or - classes. + Specifically, the parameter ``expected_warning`` can be a warning class or sequence + of warning classes, and the code inside the ``with`` block must issue at least one + warning of that class or classes. - This helper produces a list of :class:`warnings.WarningMessage` objects, - one for each warning raised. + This helper produces a list of :class:`warnings.WarningMessage` objects, one for + each warning raised (regardless of whether it is an ``expected_warning`` or not). - This function can be used as a context manager, or any of the other ways - :func:`pytest.raises` can be used:: + This function can be used as a context manager, which will capture all the raised + warnings inside it:: >>> import pytest >>> with pytest.warns(RuntimeWarning): @@ -138,14 +141,23 @@ def warns( ... Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... + **Using with** ``pytest.mark.parametrize`` + + When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests + such that some runs raise a warning and others do not. + + This could be achieved in the same way as with exceptions, see + :ref:`parametrizing_conditional_raising` for an example. + """ __tracebackhide__ = True if not args: if kwargs: - msg = "Unexpected keyword arguments passed to pytest.warns: " - msg += ", ".join(sorted(kwargs)) - msg += "\nUse context-manager form instead?" - raise TypeError(msg) + argnames = ", ".join(sorted(kwargs)) + raise TypeError( + f"Unexpected keyword arguments passed to pytest.warns: {argnames}" + "\nUse context-manager form instead?" + ) return WarningsChecker(expected_warning, match_expr=match, _ispytest=True) else: func = args[0] @@ -155,10 +167,17 @@ def warns( return func(*args[1:], **kwargs) -class WarningsRecorder(warnings.catch_warnings): +class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] """A context manager to record raised warnings. + Each recorded warning is an instance of :class:`warnings.WarningMessage`. + Adapted from `warnings.catch_warnings`. + + .. note:: + ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated + differently; see :ref:`ensuring_function_triggers`. + """ def __init__(self, *, _ispytest: bool = False) -> None: @@ -191,7 +210,7 @@ def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage": if issubclass(w.category, cls): return self._list.pop(i) __tracebackhide__ = True - raise AssertionError("%r not found in warning list" % cls) + raise AssertionError(f"{cls!r} not found in warning list") def clear(self) -> None: """Clear the list of recorded warnings.""" @@ -202,7 +221,7 @@ def clear(self) -> None: def __enter__(self) -> "WarningsRecorder": # type: ignore if self._entered: __tracebackhide__ = True - raise RuntimeError("Cannot enter %r twice" % self) + raise RuntimeError(f"Cannot enter {self!r} twice") _list = super().__enter__() # record=True means it's None. assert _list is not None @@ -218,7 +237,7 @@ def __exit__( ) -> None: if not self._entered: __tracebackhide__ = True - raise RuntimeError("Cannot exit %r without entering first" % self) + raise RuntimeError(f"Cannot exit {self!r} without entering first") super().__exit__(exc_type, exc_val, exc_tb) @@ -268,16 +287,17 @@ def __exit__( __tracebackhide__ = True + def found_str(): + return pformat([record.message for record in self], indent=2) + # only check if we're not currently handling an exception if exc_type is None and exc_val is None and exc_tb is None: if self.expected_warning is not None: if not any(issubclass(r.category, self.expected_warning) for r in self): __tracebackhide__ = True fail( - "DID NOT WARN. No warnings of type {} were emitted. " - "The list of emitted warnings is: {}.".format( - self.expected_warning, [each.message for each in self] - ) + f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n" + f"The list of emitted warnings is: {found_str()}." ) elif self.match_expr is not None: for r in self: @@ -286,11 +306,8 @@ def __exit__( break else: fail( - "DID NOT WARN. No warnings of type {} matching" - " ('{}') were emitted. The list of emitted warnings" - " is: {}.".format( - self.expected_warning, - self.match_expr, - [each.message for each in self], - ) + f"""\ +DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted. + Regex: {self.match_expr} + Emitted warnings: {found_str()}""" ) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index a68e68bc526..c35f7087e41 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -7,6 +7,8 @@ from typing import Iterable from typing import Iterator from typing import List +from typing import Mapping +from typing import NoReturn from typing import Optional from typing import Tuple from typing import Type @@ -35,7 +37,6 @@ from _pytest.outcomes import skip if TYPE_CHECKING: - from typing import NoReturn from typing_extensions import Literal from _pytest.runner import CallInfo @@ -228,7 +229,7 @@ def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: def _report_unserialization_failure( type_name: str, report_class: Type[BaseReport], reportdict -) -> "NoReturn": +) -> NoReturn: url = "https://github.com/pytest-dev/pytest/issues" stream = StringIO() pprint("-" * 100, stream=stream) @@ -254,7 +255,7 @@ def __init__( self, nodeid: str, location: Tuple[str, Optional[int], str], - keywords, + keywords: Mapping[str, Any], outcome: "Literal['passed', 'failed', 'skipped']", longrepr: Union[ None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr @@ -275,7 +276,7 @@ def __init__( #: A name -> value dictionary containing all keywords and #: markers associated with a test invocation. - self.keywords = keywords + self.keywords: Mapping[str, Any] = keywords #: Test outcome, always one of "passed", "failed", "skipped". self.outcome = outcome @@ -297,7 +298,7 @@ def __init__( self.sections = list(sections) #: Time it took to run just the test. - self.duration = duration + self.duration: float = duration self.__dict__.update(extra) @@ -308,7 +309,11 @@ def __repr__(self) -> str: @classmethod def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": - """Create and fill a TestReport with standard item and call info.""" + """Create and fill a TestReport with standard item and call info. + + :param item: The item. + :param call: The call info. + """ when = call.when # Remove "collect" from the Literal type -- only for collection calls. assert when != "collect" diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index e43dd2dc818..584c3229d5f 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -2,7 +2,6 @@ import bdb import os import sys -import warnings from typing import Callable from typing import cast from typing import Dict @@ -28,7 +27,6 @@ from _pytest.compat import final from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.deprecated import UNITTEST_SKIP_DURING_COLLECTION from _pytest.nodes import Collector from _pytest.nodes import Item from _pytest.nodes import Node @@ -48,14 +46,14 @@ def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("terminal reporting", "reporting", after="general") + group = parser.getgroup("terminal reporting", "Reporting", after="general") group.addoption( "--durations", action="store", type=int, default=None, metavar="N", - help="show N slowest setup/test durations (N=0 for all).", + help="Show N slowest setup/test durations (N=0 for all)", ) group.addoption( "--durations-min", @@ -63,7 +61,8 @@ def pytest_addoption(parser: Parser) -> None: type=float, default=0.005, metavar="N", - help="Minimal duration in seconds for inclusion in slowest list. Default 0.005", + help="Minimal duration in seconds for inclusion in slowest list. " + "Default: 0.005.", ) @@ -379,11 +378,6 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport: # Type ignored because unittest is loaded dynamically. skip_exceptions.append(unittest.SkipTest) # type: ignore if isinstance(call.excinfo.value, tuple(skip_exceptions)): - if unittest is not None and isinstance( - call.excinfo.value, unittest.SkipTest # type: ignore[attr-defined] - ): - warnings.warn(UNITTEST_SKIP_DURING_COLLECTION, stacklevel=2) - outcome = "skipped" r_ = collector._repr_failure_py(call.excinfo, "line") assert isinstance(r_, ExceptionChainRepr), repr(r_) diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index 531131ce726..583590d6b70 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -18,13 +18,13 @@ def pytest_addoption(parser: Parser) -> None: "--setuponly", "--setup-only", action="store_true", - help="only setup fixtures, do not execute tests.", + help="Only setup fixtures, do not execute tests", ) group.addoption( "--setupshow", "--setup-show", action="store_true", - help="show setup of fixtures while executing tests.", + help="Show setup of fixtures while executing tests", ) diff --git a/src/_pytest/setupplan.py b/src/_pytest/setupplan.py index 9ba81ccaf0a..1a4ebdd99ca 100644 --- a/src/_pytest/setupplan.py +++ b/src/_pytest/setupplan.py @@ -15,8 +15,8 @@ def pytest_addoption(parser: Parser) -> None: "--setupplan", "--setup-plan", action="store_true", - help="show what fixtures and tests would be executed but " - "don't execute anything.", + help="Show what fixtures and tests would be executed but " + "don't execute anything", ) diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index ac7216f8385..b20442350d6 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -31,12 +31,12 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="runxfail", default=False, - help="report the results of xfail tests as if they were not marked", + help="Report the results of xfail tests as if they were not marked", ) parser.addini( "xfail_strict", - "default for the strict parameter of xfail " + "Default for the strict parameter of xfail " "markers when not given explicitly (default: False)", default=False, type="bool", diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index 4d95a96b872..74ad9dbd4dd 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -23,7 +23,7 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", default=False, dest="stepwise", - help="exit on test failure and continue from last failing test next time", + help="Exit on test failure and continue from last failing test next time", ) group.addoption( "--sw-skip", @@ -31,8 +31,8 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", default=False, dest="stepwise_skip", - help="ignore the first failing test but stop on the next failing test.\n" - "implicitly enables --stepwise.", + help="Ignore the first failing test but stop on the next failing test. " + "Implicitly enables --stepwise.", ) @@ -48,6 +48,10 @@ def pytest_configure(config: Config) -> None: def pytest_sessionfinish(session: Session) -> None: if not session.config.getoption("stepwise"): assert session.config.cache is not None + if hasattr(session.config, "workerinput"): + # Do not update cache if this process is a xdist worker to prevent + # race conditions (#10641). + return # Clear the list of failing tests if the plugin is not active. session.config.cache.set(STEPWISE_CACHE_DIR, []) @@ -119,4 +123,8 @@ def pytest_report_collectionfinish(self) -> Optional[str]: return None def pytest_sessionfinish(self) -> None: + if hasattr(self.config, "workerinput"): + # Do not update cache if this process is a xdist worker to prevent + # race conditions (#10641). + return self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index ccbd84d7d71..d967a3ee6f1 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -35,7 +35,9 @@ from _pytest import timing from _pytest._code import ExceptionInfo from _pytest._code.code import ExceptionRepr +from _pytest._io import TerminalWriter from _pytest._io.wcwidth import wcswidth +from _pytest.assertion.util import running_on_ci from _pytest.compat import final from _pytest.config import _PluggyPlugin from _pytest.config import Config @@ -110,28 +112,28 @@ def __call__( def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("terminal reporting", "reporting", after="general") + group = parser.getgroup("terminal reporting", "Reporting", after="general") group._addoption( "-v", "--verbose", action="count", default=0, dest="verbose", - help="increase verbosity.", + help="Increase verbosity", ) group._addoption( "--no-header", action="store_true", default=False, dest="no_header", - help="disable header", + help="Disable header", ) group._addoption( "--no-summary", action="store_true", default=False, dest="no_summary", - help="disable summary", + help="Disable summary", ) group._addoption( "-q", @@ -139,14 +141,14 @@ def pytest_addoption(parser: Parser) -> None: action=MoreQuietAction, default=0, dest="verbose", - help="decrease verbosity.", + help="Decrease verbosity", ) group._addoption( "--verbosity", dest="verbose", type=int, default=0, - help="set verbosity. Default is 0.", + help="Set verbosity. Default: 0.", ) group._addoption( "-r", @@ -154,7 +156,7 @@ def pytest_addoption(parser: Parser) -> None: dest="reportchars", default=_REPORTCHARS_DEFAULT, metavar="chars", - help="show extra test summary info as specified by chars: (f)ailed, " + help="Show extra test summary info as specified by chars: (f)ailed, " "(E)rror, (s)kipped, (x)failed, (X)passed, " "(p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. " "(w)arnings are enabled by default (see --disable-warnings), " @@ -166,7 +168,7 @@ def pytest_addoption(parser: Parser) -> None: default=False, dest="disable_warnings", action="store_true", - help="disable warnings summary", + help="Disable warnings summary", ) group._addoption( "-l", @@ -174,7 +176,13 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", dest="showlocals", default=False, - help="show locals in tracebacks (disabled by default).", + help="Show locals in tracebacks (disabled by default)", + ) + group._addoption( + "--no-showlocals", + action="store_false", + dest="showlocals", + help="Hide locals in tracebacks (negate --showlocals passed through addopts)", ) group._addoption( "--tb", @@ -183,7 +191,7 @@ def pytest_addoption(parser: Parser) -> None: dest="tbstyle", default="auto", choices=["auto", "long", "short", "no", "line", "native"], - help="traceback print mode (auto/long/short/line/native/no).", + help="Traceback print mode (auto/long/short/line/native/no)", ) group._addoption( "--show-capture", @@ -192,14 +200,14 @@ def pytest_addoption(parser: Parser) -> None: choices=["no", "stdout", "stderr", "log", "all"], default="all", help="Controls how captured stdout/stderr/log is shown on failed tests. " - "Default is 'all'.", + "Default: all.", ) group._addoption( "--fulltrace", "--full-trace", action="store_true", default=False, - help="don't cut any tracebacks (default is to cut).", + help="Don't cut any tracebacks (default is to cut)", ) group._addoption( "--color", @@ -208,18 +216,20 @@ def pytest_addoption(parser: Parser) -> None: dest="color", default="auto", choices=["yes", "no", "auto"], - help="color terminal output (yes/no/auto).", + help="Color terminal output (yes/no/auto)", ) group._addoption( "--code-highlight", default="yes", choices=["yes", "no"], - help="Whether code should be highlighted (only if --color is also enabled)", + help="Whether code should be highlighted (only if --color is also enabled). " + "Default: yes.", ) parser.addini( "console_output_style", - help='console output: "classic", or with additional progress information ("progress" (percentage) | "count").', + help='Console output: "classic", or with additional progress information ' + '("progress" (percentage) | "count")', default="progress", ) @@ -542,15 +552,21 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: if not running_xdist: self.write_ensure_prefix(line, word, **markup) if rep.skipped or hasattr(report, "wasxfail"): - available_width = ( - (self._tw.fullwidth - self._tw.width_of_current_line) - - len(" [100%]") - - 1 - ) reason = _get_raw_skip_reason(rep) - reason_ = _format_trimmed(" ({})", reason, available_width) - if reason and reason_ is not None: - self._tw.write(reason_) + if self.config.option.verbose < 2: + available_width = ( + (self._tw.fullwidth - self._tw.width_of_current_line) + - len(" [100%]") + - 1 + ) + formatted_reason = _format_trimmed( + " ({})", reason, available_width + ) + else: + formatted_reason = f" ({reason})" + + if reason and formatted_reason is not None: + self._tw.write(formatted_reason) if self._show_progress_info: self._write_progress_information_filling_space() else: @@ -657,7 +673,7 @@ def report_collect(self, final: bool = False) -> None: errors = len(self.stats.get("error", [])) skipped = len(self.stats.get("skipped", [])) deselected = len(self.stats.get("deselected", [])) - selected = self._numcollected - errors - skipped - deselected + selected = self._numcollected - deselected line = "collected " if final else "collecting " line += ( str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s") @@ -668,7 +684,7 @@ def report_collect(self, final: bool = False) -> None: line += " / %d deselected" % deselected if skipped: line += " / %d skipped" % skipped - if self._numcollected > selected > 0: + if self._numcollected > selected: line += " / %d selected" % selected if self.isatty: self.rewrite(line, bold=True, erase=True) @@ -722,8 +738,8 @@ def pytest_report_header(self, config: Config) -> List[str]: if config.inipath: line += ", configfile: " + bestrelpath(config.rootpath, config.inipath) - testpaths: List[str] = config.getini("testpaths") - if config.invocation_params.dir == config.rootpath and config.args == testpaths: + if config.args_source == Config.ArgsSource.TESTPATHS: + testpaths: List[str] = config.getini("testpaths") line += ", testpaths: {}".format(", ".join(testpaths)) result = [line] @@ -1068,33 +1084,43 @@ def short_test_summary(self) -> None: if not self.reportchars: return - def show_simple(stat, lines: List[str]) -> None: + def show_simple(lines: List[str], *, stat: str) -> None: failed = self.stats.get(stat, []) if not failed: return - termwidth = self._tw.fullwidth config = self.config for rep in failed: - line = _get_line_with_reprcrash_message(config, rep, termwidth) + color = _color_for_type.get(stat, _color_for_type_default) + line = _get_line_with_reprcrash_message( + config, rep, self._tw, {color: True} + ) lines.append(line) def show_xfailed(lines: List[str]) -> None: xfailed = self.stats.get("xfailed", []) for rep in xfailed: verbose_word = rep._get_verbose_word(self.config) - pos = _get_pos(self.config, rep) - lines.append(f"{verbose_word} {pos}") + markup_word = self._tw.markup( + verbose_word, **{_color_for_type["warnings"]: True} + ) + nodeid = _get_node_id_with_markup(self._tw, self.config, rep) + line = f"{markup_word} {nodeid}" reason = rep.wasxfail if reason: - lines.append(" " + str(reason)) + line += " - " + str(reason) + + lines.append(line) def show_xpassed(lines: List[str]) -> None: xpassed = self.stats.get("xpassed", []) for rep in xpassed: verbose_word = rep._get_verbose_word(self.config) - pos = _get_pos(self.config, rep) + markup_word = self._tw.markup( + verbose_word, **{_color_for_type["warnings"]: True} + ) + nodeid = _get_node_id_with_markup(self._tw, self.config, rep) reason = rep.wasxfail - lines.append(f"{verbose_word} {pos} {reason}") + lines.append(f"{markup_word} {nodeid} {reason}") def show_skipped(lines: List[str]) -> None: skipped: List[CollectReport] = self.stats.get("skipped", []) @@ -1102,24 +1128,27 @@ def show_skipped(lines: List[str]) -> None: if not fskips: return verbose_word = skipped[0]._get_verbose_word(self.config) + markup_word = self._tw.markup( + verbose_word, **{_color_for_type["warnings"]: True} + ) + prefix = "Skipped: " for num, fspath, lineno, reason in fskips: - if reason.startswith("Skipped: "): - reason = reason[9:] + if reason.startswith(prefix): + reason = reason[len(prefix) :] if lineno is not None: lines.append( - "%s [%d] %s:%d: %s" - % (verbose_word, num, fspath, lineno, reason) + "%s [%d] %s:%d: %s" % (markup_word, num, fspath, lineno, reason) ) else: - lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) + lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason)) REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = { "x": show_xfailed, "X": show_xpassed, - "f": partial(show_simple, "failed"), + "f": partial(show_simple, stat="failed"), "s": show_skipped, - "p": partial(show_simple, "passed"), - "E": partial(show_simple, "error"), + "p": partial(show_simple, stat="passed"), + "E": partial(show_simple, stat="error"), } lines: List[str] = [] @@ -1129,7 +1158,7 @@ def show_skipped(lines: List[str]) -> None: action(lines) if lines: - self.write_sep("=", "short test summary info") + self.write_sep("=", "short test summary info", cyan=True, bold=True) for line in lines: self.write_line(line) @@ -1243,9 +1272,14 @@ def _build_collect_only_summary_stats_line( return parts, main_color -def _get_pos(config: Config, rep: BaseReport): +def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport): nodeid = config.cwd_relative_nodeid(rep.nodeid) - return nodeid + path, *parts = nodeid.split("::") + if parts: + parts_markup = tw.markup("::".join(parts), bold=True) + return path + "::" + parts_markup + else: + return path def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]: @@ -1274,13 +1308,14 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str def _get_line_with_reprcrash_message( - config: Config, rep: BaseReport, termwidth: int + config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: Dict[str, bool] ) -> str: """Get summary line for a report, trying to add reprcrash message.""" verbose_word = rep._get_verbose_word(config) - pos = _get_pos(config, rep) + word = tw.markup(verbose_word, **word_markup) + node = _get_node_id_with_markup(tw, config, rep) - line = f"{verbose_word} {pos}" + line = f"{word} {node}" line_width = wcswidth(line) try: @@ -1289,8 +1324,11 @@ def _get_line_with_reprcrash_message( except AttributeError: pass else: - available_width = termwidth - line_width - msg = _format_trimmed(" - {}", msg, available_width) + if not running_on_ci(): + available_width = tw.fullwidth - line_width + msg = _format_trimmed(" - {}", msg, available_width) + else: + msg = f" - {msg}" if msg is not None: line += msg diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index f901fd5727c..9497a0d49da 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -100,7 +100,11 @@ def mktemp(self, basename: str, numbered: bool = True) -> Path: return p def getbasetemp(self) -> Path: - """Return the base temporary directory, creating it if needed.""" + """Return the base temporary directory, creating it if needed. + + :returns: + The base temporary directory. + """ if self._basetemp is not None: return self._basetemp @@ -158,9 +162,10 @@ def getbasetemp(self) -> Path: def get_user() -> Optional[str]: """Return the current user name, or None if getuser() does not work in the current environment (see #1010).""" - import getpass - try: + # In some exotic environments, getpass may not be importable. + import getpass + return getpass.getuser() except (ImportError, KeyError): return None diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 108095bfcbe..c2df986530c 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -27,7 +27,7 @@ from _pytest.outcomes import xfail from _pytest.python import Class from _pytest.python import Function -from _pytest.python import PyCollector +from _pytest.python import Module from _pytest.runner import CallInfo from _pytest.scope import Scope @@ -42,7 +42,7 @@ def pytest_pycollect_makeitem( - collector: PyCollector, name: str, obj: object + collector: Union[Module, Class], name: str, obj: object ) -> Optional["UnitTestCase"]: # Has unittest been imported and is obj a subclass of its TestCase? try: @@ -185,6 +185,15 @@ class TestCaseFunction(Function): _excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None _testcase: Optional["unittest.TestCase"] = None + def _getobj(self): + assert self.parent is not None + # Unlike a regular Function in a Class, where `item.obj` returns + # a *bound* method (attached to an instance), TestCaseFunction's + # `obj` returns an *unbound* method (not attached to an instance). + # This inconsistency is probably not desirable, but needs some + # consideration before changing. + return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined] + def setup(self) -> None: # A bound method to be called during teardown() if set (see 'runtest()'). self._explicit_tearDown: Optional[Callable[[], None]] = None @@ -307,7 +316,10 @@ def runtest(self) -> None: # Arguably we could always postpone tearDown(), but this changes the moment where the # TestCase instance interacts with the results object, so better to only do it # when absolutely needed. - if self.config.getoption("usepdb") and not _is_skipped(self.obj): + # We need to consider if the test itself is skipped, or the whole class. + assert isinstance(self.parent, UnitTestCase) + skipped = _is_skipped(self.obj) or _is_skipped(self.parent.obj) + if self.config.getoption("usepdb") and not skipped: self._explicit_tearDown = self._testcase.tearDown setattr(self._testcase, "tearDown", lambda *args: None) diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 2a97a319789..620860c1bc4 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -1,3 +1,6 @@ +import inspect +import warnings +from types import FunctionType from typing import Any from typing import Generic from typing import Type @@ -48,16 +51,14 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning): __module__ = "pytest" -@final -class PytestRemovedIn7Warning(PytestDeprecationWarning): - """Warning class for features that will be removed in pytest 7.""" +class PytestRemovedIn8Warning(PytestDeprecationWarning): + """Warning class for features that will be removed in pytest 8.""" __module__ = "pytest" -@final -class PytestRemovedIn8Warning(PytestDeprecationWarning): - """Warning class for features that will be removed in pytest 8.""" +class PytestReturnNotNoneWarning(PytestRemovedIn8Warning): + """Warning emitted when a test function is returning value other than None.""" __module__ = "pytest" @@ -82,7 +83,7 @@ def simple(cls, apiname: str) -> "PytestExperimentalApiWarning": @final -class PytestUnhandledCoroutineWarning(PytestWarning): +class PytestUnhandledCoroutineWarning(PytestReturnNotNoneWarning): """Warning emitted for an unhandled coroutine. A coroutine was encountered when collecting test functions, but was not @@ -143,3 +144,28 @@ class UnformattedWarning(Generic[_W]): def format(self, **kwargs: Any) -> _W: """Return an instance of the warning category, formatted with given kwargs.""" return self.category(self.template.format(**kwargs)) + + +def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None: + """ + Issue the warning :param:`message` for the definition of the given :param:`method` + + this helps to log warnigns for functions defined prior to finding an issue with them + (like hook wrappers being marked in a legacy mechanism) + """ + lineno = method.__code__.co_firstlineno + filename = inspect.getfile(method) + module = method.__module__ + mod_globals = method.__globals__ + try: + warnings.warn_explicit( + message, + type(message), + filename=filename, + module=module, + registry=mod_globals.setdefault("__warningregistry__", {}), + lineno=lineno, + ) + except Warning as w: + # If warnings are errors (e.g. -Werror), location information gets lost, so we add it to the message. + raise type(w)(f"{w}\n at {filename}:{lineno}") from None diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index c0c946cbde5..4aaa9445293 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -49,8 +49,6 @@ def catch_warnings_for_item( warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning) - warnings.filterwarnings("error", category=pytest.PytestRemovedIn7Warning) - apply_warning_filters(config_filters, cmdline_filters) # apply filters from "filterwarnings" marks @@ -63,14 +61,6 @@ def catch_warnings_for_item( yield for warning_message in log: - ihook.pytest_warning_captured.call_historic( - kwargs=dict( - warning_message=warning_message, - when=when, - item=item, - location=None, - ) - ) ihook.pytest_warning_recorded.call_historic( kwargs=dict( warning_message=warning_message, @@ -91,6 +81,23 @@ def warning_record_to_str(warning_message: warnings.WarningMessage) -> str: warning_message.lineno, warning_message.line, ) + if warning_message.source is not None: + try: + import tracemalloc + except ImportError: + pass + else: + tb = tracemalloc.get_object_traceback(warning_message.source) + if tb is not None: + formatted_tb = "\n".join(tb.format()) + # Use a leading new line to better separate the (large) output + # from the traceback to the previous warning text. + msg += f"\nObject allocated at:\n{formatted_tb}" + else: + # No need for a leading new line. + url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings" + msg += "Enable tracemalloc to get traceback where the object was allocated.\n" + msg += f"See {url} for more info." return msg diff --git a/src/py.py b/src/py.py new file mode 100644 index 00000000000..7813c9b93cd --- /dev/null +++ b/src/py.py @@ -0,0 +1,10 @@ +# shim for pylib going away +# if pylib is installed this file will get skipped +# (`py/__init__.py` has higher precedence) +import sys + +import _pytest._py.error as error +import _pytest._py.path as path + +sys.modules["py.error"] = error +sys.modules["py.path"] = path diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 7a596cd3783..f25ecde9c47 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -1,6 +1,5 @@ # PYTHON_ARGCOMPLETE_OK """pytest: unit and functional testing with Python.""" -from . import collect from _pytest import __version__ from _pytest import version_tuple from _pytest._code import ExceptionInfo @@ -19,12 +18,14 @@ from _pytest.config.argparsing import OptionGroup from _pytest.config.argparsing import Parser from _pytest.debugging import pytestPDB as __pytestPDB -from _pytest.fixtures import _fillfuncargs +from _pytest.doctest import DoctestItem from _pytest.fixtures import fixture from _pytest.fixtures import FixtureLookupError from _pytest.fixtures import FixtureRequest from _pytest.fixtures import yield_fixture from _pytest.freeze_support import freeze_includes +from _pytest.legacypath import TempdirFactory +from _pytest.legacypath import Testdir from _pytest.logging import LogCaptureFixture from _pytest.main import Session from _pytest.mark import Mark @@ -68,8 +69,8 @@ from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestExperimentalApiWarning -from _pytest.warning_types import PytestRemovedIn7Warning from _pytest.warning_types import PytestRemovedIn8Warning +from _pytest.warning_types import PytestReturnNotNoneWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning from _pytest.warning_types import PytestUnhandledThreadExceptionWarning from _pytest.warning_types import PytestUnknownMarkWarning @@ -81,19 +82,18 @@ __all__ = [ "__version__", - "_fillfuncargs", "approx", "Cache", "CallInfo", "CaptureFixture", "Class", "cmdline", - "collect", "Collector", "CollectReport", "Config", "console_main", "deprecated_call", + "DoctestItem", "exit", "ExceptionInfo", "ExitCode", @@ -129,8 +129,8 @@ "PytestConfigWarning", "PytestDeprecationWarning", "PytestExperimentalApiWarning", - "PytestRemovedIn7Warning", "PytestRemovedIn8Warning", + "PytestReturnNotNoneWarning", "Pytester", "PytestPluginManager", "PytestUnhandledCoroutineWarning", @@ -148,7 +148,9 @@ "Stash", "StashKey", "version_tuple", + "TempdirFactory", "TempPathFactory", + "Testdir", "TestReport", "UsageError", "WarningsRecorder", diff --git a/src/pytest/collect.py b/src/pytest/collect.py deleted file mode 100644 index 4b2b5818066..00000000000 --- a/src/pytest/collect.py +++ /dev/null @@ -1,38 +0,0 @@ -import sys -import warnings -from types import ModuleType -from typing import Any -from typing import List - -import pytest -from _pytest.deprecated import PYTEST_COLLECT_MODULE - -COLLECT_FAKEMODULE_ATTRIBUTES = [ - "Collector", - "Module", - "Function", - "Session", - "Item", - "Class", - "File", - "_fillfuncargs", -] - - -class FakeCollectModule(ModuleType): - def __init__(self) -> None: - super().__init__("pytest.collect") - self.__all__ = list(COLLECT_FAKEMODULE_ATTRIBUTES) - self.__pytest = pytest - - def __dir__(self) -> List[str]: - return dir(super()) + self.__all__ - - def __getattr__(self, name: str) -> Any: - if name not in self.__all__: - raise AttributeError(name) - warnings.warn(PYTEST_COLLECT_MODULE.format(name=name), stacklevel=2) - return getattr(pytest, name) - - -sys.modules["pytest.collect"] = FakeCollectModule() diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py new file mode 100644 index 00000000000..31c10b16021 --- /dev/null +++ b/testing/_py/test_local.py @@ -0,0 +1,1556 @@ +import multiprocessing +import os +import sys +import time +from unittest import mock + +import pytest +from py import error +from py.path import local + + +class CommonFSTests: + def test_constructor_equality(self, path1): + p = path1.__class__(path1) + assert p == path1 + + def test_eq_nonstring(self, path1): + p1 = path1.join("sampledir") + p2 = path1.join("sampledir") + assert p1 == p2 + + def test_new_identical(self, path1): + assert path1 == path1.new() + + def test_join(self, path1): + p = path1.join("sampledir") + strp = str(p) + assert strp.endswith("sampledir") + assert strp.startswith(str(path1)) + + def test_join_normalized(self, path1): + newpath = path1.join(path1.sep + "sampledir") + strp = str(newpath) + assert strp.endswith("sampledir") + assert strp.startswith(str(path1)) + newpath = path1.join((path1.sep * 2) + "sampledir") + strp = str(newpath) + assert strp.endswith("sampledir") + assert strp.startswith(str(path1)) + + def test_join_noargs(self, path1): + newpath = path1.join() + assert path1 == newpath + + def test_add_something(self, path1): + p = path1.join("sample") + p = p + "dir" + assert p.check() + assert p.exists() + assert p.isdir() + assert not p.isfile() + + def test_parts(self, path1): + newpath = path1.join("sampledir", "otherfile") + par = newpath.parts()[-3:] + assert par == [path1, path1.join("sampledir"), newpath] + + revpar = newpath.parts(reverse=True)[:3] + assert revpar == [newpath, path1.join("sampledir"), path1] + + def test_common(self, path1): + other = path1.join("sampledir") + x = other.common(path1) + assert x == path1 + + # def test_parents_nonexisting_file(self, path1): + # newpath = path1 / 'dirnoexist' / 'nonexisting file' + # par = list(newpath.parents()) + # assert par[:2] == [path1 / 'dirnoexist', path1] + + def test_basename_checks(self, path1): + newpath = path1.join("sampledir") + assert newpath.check(basename="sampledir") + assert newpath.check(notbasename="xyz") + assert newpath.basename == "sampledir" + + def test_basename(self, path1): + newpath = path1.join("sampledir") + assert newpath.check(basename="sampledir") + assert newpath.basename, "sampledir" + + def test_dirname(self, path1): + newpath = path1.join("sampledir") + assert newpath.dirname == str(path1) + + def test_dirpath(self, path1): + newpath = path1.join("sampledir") + assert newpath.dirpath() == path1 + + def test_dirpath_with_args(self, path1): + newpath = path1.join("sampledir") + assert newpath.dirpath("x") == path1.join("x") + + def test_newbasename(self, path1): + newpath = path1.join("samplefile") + newbase = newpath.new(basename="samplefile2") + assert newbase.basename == "samplefile2" + assert newbase.dirpath() == newpath.dirpath() + + def test_not_exists(self, path1): + assert not path1.join("does_not_exist").check() + assert path1.join("does_not_exist").check(exists=0) + + def test_exists(self, path1): + assert path1.join("samplefile").check() + assert path1.join("samplefile").check(exists=1) + assert path1.join("samplefile").exists() + assert path1.join("samplefile").isfile() + assert not path1.join("samplefile").isdir() + + def test_dir(self, path1): + # print repr(path1.join("sampledir")) + assert path1.join("sampledir").check(dir=1) + assert path1.join("samplefile").check(notdir=1) + assert not path1.join("samplefile").check(dir=1) + assert path1.join("samplefile").exists() + assert not path1.join("samplefile").isdir() + assert path1.join("samplefile").isfile() + + def test_fnmatch_file(self, path1): + assert path1.join("samplefile").check(fnmatch="s*e") + assert path1.join("samplefile").fnmatch("s*e") + assert not path1.join("samplefile").fnmatch("s*x") + assert not path1.join("samplefile").check(fnmatch="s*x") + + # def test_fnmatch_dir(self, path1): + + # pattern = path1.sep.join(['s*file']) + # sfile = path1.join("samplefile") + # assert sfile.check(fnmatch=pattern) + + def test_relto(self, path1): + p = path1.join("sampledir", "otherfile") + assert p.relto(path1) == p.sep.join(["sampledir", "otherfile"]) + assert p.check(relto=path1) + assert path1.check(notrelto=p) + assert not path1.check(relto=p) + + def test_bestrelpath(self, path1): + curdir = path1 + sep = curdir.sep + s = curdir.bestrelpath(curdir) + assert s == "." + s = curdir.bestrelpath(curdir.join("hello", "world")) + assert s == "hello" + sep + "world" + + s = curdir.bestrelpath(curdir.dirpath().join("sister")) + assert s == ".." + sep + "sister" + assert curdir.bestrelpath(curdir.dirpath()) == ".." + + assert curdir.bestrelpath("hello") == "hello" + + def test_relto_not_relative(self, path1): + l1 = path1.join("bcde") + l2 = path1.join("b") + assert not l1.relto(l2) + assert not l2.relto(l1) + + def test_listdir(self, path1): + p = path1.listdir() + assert path1.join("sampledir") in p + assert path1.join("samplefile") in p + with pytest.raises(error.ENOTDIR): + path1.join("samplefile").listdir() + + def test_listdir_fnmatchstring(self, path1): + p = path1.listdir("s*dir") + assert len(p) + assert p[0], path1.join("sampledir") + + def test_listdir_filter(self, path1): + p = path1.listdir(lambda x: x.check(dir=1)) + assert path1.join("sampledir") in p + assert not path1.join("samplefile") in p + + def test_listdir_sorted(self, path1): + p = path1.listdir(lambda x: x.check(basestarts="sample"), sort=True) + assert path1.join("sampledir") == p[0] + assert path1.join("samplefile") == p[1] + assert path1.join("samplepickle") == p[2] + + def test_visit_nofilter(self, path1): + lst = [] + for i in path1.visit(): + lst.append(i.relto(path1)) + assert "sampledir" in lst + assert path1.sep.join(["sampledir", "otherfile"]) in lst + + def test_visit_norecurse(self, path1): + lst = [] + for i in path1.visit(None, lambda x: x.basename != "sampledir"): + lst.append(i.relto(path1)) + assert "sampledir" in lst + assert not path1.sep.join(["sampledir", "otherfile"]) in lst + + @pytest.mark.parametrize( + "fil", + ["*dir", "*dir", pytest.mark.skip("sys.version_info <" " (3,6)")(b"*dir")], + ) + def test_visit_filterfunc_is_string(self, path1, fil): + lst = [] + for i in path1.visit(fil): + lst.append(i.relto(path1)) + assert len(lst), 2 + assert "sampledir" in lst + assert "otherdir" in lst + + def test_visit_ignore(self, path1): + p = path1.join("nonexisting") + assert list(p.visit(ignore=error.ENOENT)) == [] + + def test_visit_endswith(self, path1): + p = [] + for i in path1.visit(lambda x: x.check(endswith="file")): + p.append(i.relto(path1)) + assert path1.sep.join(["sampledir", "otherfile"]) in p + assert "samplefile" in p + + def test_cmp(self, path1): + path1 = path1.join("samplefile") + path2 = path1.join("samplefile2") + assert (path1 < path2) == ("samplefile" < "samplefile2") + assert not (path1 < path1) + + def test_simple_read(self, path1): + x = path1.join("samplefile").read("r") + assert x == "samplefile\n" + + def test_join_div_operator(self, path1): + newpath = path1 / "/sampledir" / "/test//" + newpath2 = path1.join("sampledir", "test") + assert newpath == newpath2 + + def test_ext(self, path1): + newpath = path1.join("sampledir.ext") + assert newpath.ext == ".ext" + newpath = path1.join("sampledir") + assert not newpath.ext + + def test_purebasename(self, path1): + newpath = path1.join("samplefile.py") + assert newpath.purebasename == "samplefile" + + def test_multiple_parts(self, path1): + newpath = path1.join("samplefile.py") + dirname, purebasename, basename, ext = newpath._getbyspec( + "dirname,purebasename,basename,ext" + ) + assert str(path1).endswith(dirname) # be careful with win32 'drive' + assert purebasename == "samplefile" + assert basename == "samplefile.py" + assert ext == ".py" + + def test_dotted_name_ext(self, path1): + newpath = path1.join("a.b.c") + ext = newpath.ext + assert ext == ".c" + assert newpath.ext == ".c" + + def test_newext(self, path1): + newpath = path1.join("samplefile.py") + newext = newpath.new(ext=".txt") + assert newext.basename == "samplefile.txt" + assert newext.purebasename == "samplefile" + + def test_readlines(self, path1): + fn = path1.join("samplefile") + contents = fn.readlines() + assert contents == ["samplefile\n"] + + def test_readlines_nocr(self, path1): + fn = path1.join("samplefile") + contents = fn.readlines(cr=0) + assert contents == ["samplefile", ""] + + def test_file(self, path1): + assert path1.join("samplefile").check(file=1) + + def test_not_file(self, path1): + assert not path1.join("sampledir").check(file=1) + assert path1.join("sampledir").check(file=0) + + def test_non_existent(self, path1): + assert path1.join("sampledir.nothere").check(dir=0) + assert path1.join("sampledir.nothere").check(file=0) + assert path1.join("sampledir.nothere").check(notfile=1) + assert path1.join("sampledir.nothere").check(notdir=1) + assert path1.join("sampledir.nothere").check(notexists=1) + assert not path1.join("sampledir.nothere").check(notfile=0) + + # pattern = path1.sep.join(['s*file']) + # sfile = path1.join("samplefile") + # assert sfile.check(fnmatch=pattern) + + def test_size(self, path1): + url = path1.join("samplefile") + assert url.size() > len("samplefile") + + def test_mtime(self, path1): + url = path1.join("samplefile") + assert url.mtime() > 0 + + def test_relto_wrong_type(self, path1): + with pytest.raises(TypeError): + path1.relto(42) + + def test_load(self, path1): + p = path1.join("samplepickle") + obj = p.load() + assert type(obj) is dict + assert obj.get("answer", None) == 42 + + def test_visit_filesonly(self, path1): + p = [] + for i in path1.visit(lambda x: x.check(file=1)): + p.append(i.relto(path1)) + assert "sampledir" not in p + assert path1.sep.join(["sampledir", "otherfile"]) in p + + def test_visit_nodotfiles(self, path1): + p = [] + for i in path1.visit(lambda x: x.check(dotfile=0)): + p.append(i.relto(path1)) + assert "sampledir" in p + assert path1.sep.join(["sampledir", "otherfile"]) in p + assert ".dotfile" not in p + + def test_visit_breadthfirst(self, path1): + lst = [] + for i in path1.visit(bf=True): + lst.append(i.relto(path1)) + for i, p in enumerate(lst): + if path1.sep in p: + for j in range(i, len(lst)): + assert path1.sep in lst[j] + break + else: + pytest.fail("huh") + + def test_visit_sort(self, path1): + lst = [] + for i in path1.visit(bf=True, sort=True): + lst.append(i.relto(path1)) + for i, p in enumerate(lst): + if path1.sep in p: + break + assert lst[:i] == sorted(lst[:i]) + assert lst[i:] == sorted(lst[i:]) + + def test_endswith(self, path1): + def chk(p): + return p.check(endswith="pickle") + + assert not chk(path1) + assert not chk(path1.join("samplefile")) + assert chk(path1.join("somepickle")) + + def test_copy_file(self, path1): + otherdir = path1.join("otherdir") + initpy = otherdir.join("__init__.py") + copied = otherdir.join("copied") + initpy.copy(copied) + try: + assert copied.check() + s1 = initpy.read() + s2 = copied.read() + assert s1 == s2 + finally: + if copied.check(): + copied.remove() + + def test_copy_dir(self, path1): + otherdir = path1.join("otherdir") + copied = path1.join("newdir") + try: + otherdir.copy(copied) + assert copied.check(dir=1) + assert copied.join("__init__.py").check(file=1) + s1 = otherdir.join("__init__.py").read() + s2 = copied.join("__init__.py").read() + assert s1 == s2 + finally: + if copied.check(dir=1): + copied.remove(rec=1) + + def test_remove_file(self, path1): + d = path1.ensure("todeleted") + assert d.check() + d.remove() + assert not d.check() + + def test_remove_dir_recursive_by_default(self, path1): + d = path1.ensure("to", "be", "deleted") + assert d.check() + p = path1.join("to") + p.remove() + assert not p.check() + + def test_ensure_dir(self, path1): + b = path1.ensure_dir("001", "002") + assert b.basename == "002" + assert b.isdir() + + def test_mkdir_and_remove(self, path1): + tmpdir = path1 + with pytest.raises(error.EEXIST): + tmpdir.mkdir("sampledir") + new = tmpdir.join("mktest1") + new.mkdir() + assert new.check(dir=1) + new.remove() + + new = tmpdir.mkdir("mktest") + assert new.check(dir=1) + new.remove() + assert tmpdir.join("mktest") == new + + def test_move_file(self, path1): + p = path1.join("samplefile") + newp = p.dirpath("moved_samplefile") + p.move(newp) + try: + assert newp.check(file=1) + assert not p.check() + finally: + dp = newp.dirpath() + if hasattr(dp, "revert"): + dp.revert() + else: + newp.move(p) + assert p.check() + + def test_move_dir(self, path1): + source = path1.join("sampledir") + dest = path1.join("moveddir") + source.move(dest) + assert dest.check(dir=1) + assert dest.join("otherfile").check(file=1) + assert not source.join("sampledir").check() + + def test_fspath_protocol_match_strpath(self, path1): + assert path1.__fspath__() == path1.strpath + + def test_fspath_func_match_strpath(self, path1): + from os import fspath + + assert fspath(path1) == path1.strpath + + @pytest.mark.skip("sys.version_info < (3,6)") + def test_fspath_open(self, path1): + f = path1.join("opentestfile") + open(f) + + @pytest.mark.skip("sys.version_info < (3,6)") + def test_fspath_fsencode(self, path1): + from os import fsencode + + assert fsencode(path1) == fsencode(path1.strpath) + + +def setuptestfs(path): + if path.join("samplefile").check(): + return + # print "setting up test fs for", repr(path) + samplefile = path.ensure("samplefile") + samplefile.write("samplefile\n") + + execfile = path.ensure("execfile") + execfile.write("x=42") + + execfilepy = path.ensure("execfile.py") + execfilepy.write("x=42") + + d = {1: 2, "hello": "world", "answer": 42} + path.ensure("samplepickle").dump(d) + + sampledir = path.ensure("sampledir", dir=1) + sampledir.ensure("otherfile") + + otherdir = path.ensure("otherdir", dir=1) + otherdir.ensure("__init__.py") + + module_a = otherdir.ensure("a.py") + module_a.write("from .b import stuff as result\n") + module_b = otherdir.ensure("b.py") + module_b.write('stuff="got it"\n') + module_c = otherdir.ensure("c.py") + module_c.write( + """import py; +import otherdir.a +value = otherdir.a.result +""" + ) + module_d = otherdir.ensure("d.py") + module_d.write( + """import py; +from otherdir import a +value2 = a.result +""" + ) + + +win32only = pytest.mark.skipif( + "not (sys.platform == 'win32' or getattr(os, '_name', None) == 'nt')" +) +skiponwin32 = pytest.mark.skipif( + "sys.platform == 'win32' or getattr(os, '_name', None) == 'nt'" +) + +ATIME_RESOLUTION = 0.01 + + +@pytest.fixture(scope="session") +def path1(tmpdir_factory): + path = tmpdir_factory.mktemp("path") + setuptestfs(path) + yield path + assert path.join("samplefile").check() + + +@pytest.fixture +def fake_fspath_obj(request): + class FakeFSPathClass: + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + + return FakeFSPathClass(os.path.join("this", "is", "a", "fake", "path")) + + +def batch_make_numbered_dirs(rootdir, repeats): + for i in range(repeats): + dir_ = local.make_numbered_dir(prefix="repro-", rootdir=rootdir) + file_ = dir_.join("foo") + file_.write("%s" % i) + actual = int(file_.read()) + assert actual == i, f"int(file_.read()) is {actual} instead of {i}" + dir_.join(".lock").remove(ignore_errors=True) + return True + + +class TestLocalPath(CommonFSTests): + def test_join_normpath(self, tmpdir): + assert tmpdir.join(".") == tmpdir + p = tmpdir.join("../%s" % tmpdir.basename) + assert p == tmpdir + p = tmpdir.join("..//%s/" % tmpdir.basename) + assert p == tmpdir + + @skiponwin32 + def test_dirpath_abs_no_abs(self, tmpdir): + p = tmpdir.join("foo") + assert p.dirpath("/bar") == tmpdir.join("bar") + assert tmpdir.dirpath("/bar", abs=True) == local("/bar") + + def test_gethash(self, tmpdir): + from hashlib import md5 + from hashlib import sha1 as sha + + fn = tmpdir.join("testhashfile") + data = b"hello" + fn.write(data, mode="wb") + assert fn.computehash("md5") == md5(data).hexdigest() + assert fn.computehash("sha1") == sha(data).hexdigest() + with pytest.raises(ValueError): + fn.computehash("asdasd") + + def test_remove_removes_readonly_file(self, tmpdir): + readonly_file = tmpdir.join("readonly").ensure() + readonly_file.chmod(0) + readonly_file.remove() + assert not readonly_file.check(exists=1) + + def test_remove_removes_readonly_dir(self, tmpdir): + readonly_dir = tmpdir.join("readonlydir").ensure(dir=1) + readonly_dir.chmod(int("500", 8)) + readonly_dir.remove() + assert not readonly_dir.check(exists=1) + + def test_remove_removes_dir_and_readonly_file(self, tmpdir): + readonly_dir = tmpdir.join("readonlydir").ensure(dir=1) + readonly_file = readonly_dir.join("readonlyfile").ensure() + readonly_file.chmod(0) + readonly_dir.remove() + assert not readonly_dir.check(exists=1) + + def test_remove_routes_ignore_errors(self, tmpdir, monkeypatch): + lst = [] + monkeypatch.setattr("shutil.rmtree", lambda *args, **kwargs: lst.append(kwargs)) + tmpdir.remove() + assert not lst[0]["ignore_errors"] + for val in (True, False): + lst[:] = [] + tmpdir.remove(ignore_errors=val) + assert lst[0]["ignore_errors"] == val + + def test_initialize_curdir(self): + assert str(local()) == os.getcwd() + + @skiponwin32 + def test_chdir_gone(self, path1): + p = path1.ensure("dir_to_be_removed", dir=1) + p.chdir() + p.remove() + pytest.raises(error.ENOENT, local) + assert path1.chdir() is None + assert os.getcwd() == str(path1) + + with pytest.raises(error.ENOENT): + with p.as_cwd(): + raise NotImplementedError + + @skiponwin32 + def test_chdir_gone_in_as_cwd(self, path1): + p = path1.ensure("dir_to_be_removed", dir=1) + p.chdir() + p.remove() + + with path1.as_cwd() as old: + assert old is None + + def test_as_cwd(self, path1): + dir = path1.ensure("subdir", dir=1) + old = local() + with dir.as_cwd() as x: + assert x == old + assert local() == dir + assert os.getcwd() == str(old) + + def test_as_cwd_exception(self, path1): + old = local() + dir = path1.ensure("subdir", dir=1) + with pytest.raises(ValueError): + with dir.as_cwd(): + raise ValueError() + assert old == local() + + def test_initialize_reldir(self, path1): + with path1.as_cwd(): + p = local("samplefile") + assert p.check() + + def test_tilde_expansion(self, monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + p = local("~", expanduser=True) + assert p == os.path.expanduser("~") + + @pytest.mark.skipif( + not sys.platform.startswith("win32"), reason="case insensitive only on windows" + ) + def test_eq_hash_are_case_insensitive_on_windows(self): + a = local("/some/path") + b = local("/some/PATH") + assert a == b + assert hash(a) == hash(b) + assert a in {b} + assert a in {b: "b"} + + def test_eq_with_strings(self, path1): + path1 = path1.join("sampledir") + path2 = str(path1) + assert path1 == path2 + assert path2 == path1 + path3 = path1.join("samplefile") + assert path3 != path2 + assert path2 != path3 + + def test_eq_with_none(self, path1): + assert path1 != None # noqa: E711 + + def test_eq_non_ascii_unicode(self, path1): + path2 = path1.join("temp") + path3 = path1.join("ação") + path4 = path1.join("ディレクトリ") + + assert path2 != path3 + assert path2 != path4 + assert path4 != path3 + + def test_gt_with_strings(self, path1): + path2 = path1.join("sampledir") + path3 = str(path1.join("ttt")) + assert path3 > path2 + assert path2 < path3 + assert path2 < "ttt" + assert "ttt" > path2 + path4 = path1.join("aaa") + lst = [path2, path4, path3] + assert sorted(lst) == [path4, path2, path3] + + def test_open_and_ensure(self, path1): + p = path1.join("sub1", "sub2", "file") + with p.open("w", ensure=1) as f: + f.write("hello") + assert p.read() == "hello" + + def test_write_and_ensure(self, path1): + p = path1.join("sub1", "sub2", "file") + p.write("hello", ensure=1) + assert p.read() == "hello" + + @pytest.mark.parametrize("bin", (False, True)) + def test_dump(self, tmpdir, bin): + path = tmpdir.join("dumpfile%s" % int(bin)) + try: + d = {"answer": 42} + path.dump(d, bin=bin) + f = path.open("rb+") + import pickle + + dnew = pickle.load(f) + assert d == dnew + finally: + f.close() + + def test_setmtime(self): + import tempfile + import time + + try: + fd, name = tempfile.mkstemp() + os.close(fd) + except AttributeError: + name = tempfile.mktemp() + open(name, "w").close() + try: + mtime = int(time.time()) - 100 + path = local(name) + assert path.mtime() != mtime + path.setmtime(mtime) + assert path.mtime() == mtime + path.setmtime() + assert path.mtime() != mtime + finally: + os.remove(name) + + def test_normpath(self, path1): + new1 = path1.join("/otherdir") + new2 = path1.join("otherdir") + assert str(new1) == str(new2) + + def test_mkdtemp_creation(self): + d = local.mkdtemp() + try: + assert d.check(dir=1) + finally: + d.remove(rec=1) + + def test_tmproot(self): + d = local.mkdtemp() + tmproot = local.get_temproot() + try: + assert d.check(dir=1) + assert d.dirpath() == tmproot + finally: + d.remove(rec=1) + + def test_chdir(self, tmpdir): + old = local() + try: + res = tmpdir.chdir() + assert str(res) == str(old) + assert os.getcwd() == str(tmpdir) + finally: + old.chdir() + + def test_ensure_filepath_withdir(self, tmpdir): + newfile = tmpdir.join("test1", "test") + newfile.ensure() + assert newfile.check(file=1) + newfile.write("42") + newfile.ensure() + s = newfile.read() + assert s == "42" + + def test_ensure_filepath_withoutdir(self, tmpdir): + newfile = tmpdir.join("test1file") + t = newfile.ensure() + assert t == newfile + assert newfile.check(file=1) + + def test_ensure_dirpath(self, tmpdir): + newfile = tmpdir.join("test1", "testfile") + t = newfile.ensure(dir=1) + assert t == newfile + assert newfile.check(dir=1) + + def test_ensure_non_ascii_unicode(self, tmpdir): + newfile = tmpdir.join("ação", "ディレクトリ") + t = newfile.ensure(dir=1) + assert t == newfile + assert newfile.check(dir=1) + + @pytest.mark.xfail(run=False, reason="unreliable est for long filenames") + def test_long_filenames(self, tmpdir): + if sys.platform == "win32": + pytest.skip("win32: work around needed for path length limit") + # see http://codespeak.net/pipermail/py-dev/2008q2/000922.html + + # testing paths > 260 chars (which is Windows' limitation, but + # depending on how the paths are used), but > 4096 (which is the + # Linux' limitation) - the behaviour of paths with names > 4096 chars + # is undetermined + newfilename = "/test" * 60 + l1 = tmpdir.join(newfilename) + l1.ensure(file=True) + l1.write("foo") + l2 = tmpdir.join(newfilename) + assert l2.read() == "foo" + + def test_visit_depth_first(self, tmpdir): + tmpdir.ensure("a", "1") + tmpdir.ensure("b", "2") + p3 = tmpdir.ensure("breadth") + lst = list(tmpdir.visit(lambda x: x.check(file=1))) + assert len(lst) == 3 + # check that breadth comes last + assert lst[2] == p3 + + def test_visit_rec_fnmatch(self, tmpdir): + p1 = tmpdir.ensure("a", "123") + tmpdir.ensure(".b", "345") + lst = list(tmpdir.visit("???", rec="[!.]*")) + assert len(lst) == 1 + # check that breadth comes last + assert lst[0] == p1 + + def test_fnmatch_file_abspath(self, tmpdir): + b = tmpdir.join("a", "b") + assert b.fnmatch(os.sep.join("ab")) + pattern = os.sep.join([str(tmpdir), "*", "b"]) + assert b.fnmatch(pattern) + + def test_sysfind(self): + name = sys.platform == "win32" and "cmd" or "test" + x = local.sysfind(name) + assert x.check(file=1) + assert local.sysfind("jaksdkasldqwe") is None + assert local.sysfind(name, paths=[]) is None + x2 = local.sysfind(name, paths=[x.dirpath()]) + assert x2 == x + + def test_fspath_protocol_other_class(self, fake_fspath_obj): + # py.path is always absolute + py_path = local(fake_fspath_obj) + str_path = fake_fspath_obj.__fspath__() + assert py_path.check(endswith=str_path) + assert py_path.join(fake_fspath_obj).strpath == os.path.join( + py_path.strpath, str_path + ) + + def test_make_numbered_dir_multiprocess_safe(self, tmpdir): + # https://github.com/pytest-dev/py/issues/30 + with multiprocessing.Pool() as pool: + results = [ + pool.apply_async(batch_make_numbered_dirs, [tmpdir, 100]) + for _ in range(20) + ] + for r in results: + assert r.get() + + +class TestExecutionOnWindows: + pytestmark = win32only + + def test_sysfind_bat_exe_before(self, tmpdir, monkeypatch): + monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep) + tmpdir.ensure("hello") + h = tmpdir.ensure("hello.bat") + x = local.sysfind("hello") + assert x == h + + +class TestExecution: + pytestmark = skiponwin32 + + def test_sysfind_no_permisson_ignored(self, monkeypatch, tmpdir): + noperm = tmpdir.ensure("noperm", dir=True) + monkeypatch.setenv("PATH", str(noperm), prepend=":") + noperm.chmod(0) + try: + assert local.sysfind("jaksdkasldqwe") is None + finally: + noperm.chmod(0o644) + + def test_sysfind_absolute(self): + x = local.sysfind("test") + assert x.check(file=1) + y = local.sysfind(str(x)) + assert y.check(file=1) + assert y == x + + def test_sysfind_multiple(self, tmpdir, monkeypatch): + monkeypatch.setenv( + "PATH", "{}:{}".format(tmpdir.ensure("a"), tmpdir.join("b")), prepend=":" + ) + tmpdir.ensure("b", "a") + x = local.sysfind("a", checker=lambda x: x.dirpath().basename == "b") + assert x.basename == "a" + assert x.dirpath().basename == "b" + assert local.sysfind("a", checker=lambda x: None) is None + + def test_sysexec(self): + x = local.sysfind("ls") + out = x.sysexec("-a") + for x in local().listdir(): + assert out.find(x.basename) != -1 + + def test_sysexec_failing(self): + try: + from py._process.cmdexec import ExecutionFailed # py library + except ImportError: + ExecutionFailed = RuntimeError # py vendored + x = local.sysfind("false") + with pytest.raises(ExecutionFailed): + x.sysexec("aksjdkasjd") + + def test_make_numbered_dir(self, tmpdir): + tmpdir.ensure("base.not_an_int", dir=1) + for i in range(10): + numdir = local.make_numbered_dir( + prefix="base.", rootdir=tmpdir, keep=2, lock_timeout=0 + ) + assert numdir.check() + assert numdir.basename == "base.%d" % i + if i >= 1: + assert numdir.new(ext=str(i - 1)).check() + if i >= 2: + assert numdir.new(ext=str(i - 2)).check() + if i >= 3: + assert not numdir.new(ext=str(i - 3)).check() + + def test_make_numbered_dir_case(self, tmpdir): + """make_numbered_dir does not make assumptions on the underlying + filesystem based on the platform and will assume it _could_ be case + insensitive. + + See issues: + - https://github.com/pytest-dev/pytest/issues/708 + - https://github.com/pytest-dev/pytest/issues/3451 + """ + d1 = local.make_numbered_dir( + prefix="CAse.", + rootdir=tmpdir, + keep=2, + lock_timeout=0, + ) + d2 = local.make_numbered_dir( + prefix="caSE.", + rootdir=tmpdir, + keep=2, + lock_timeout=0, + ) + assert str(d1).lower() != str(d2).lower() + assert str(d2).endswith(".1") + + def test_make_numbered_dir_NotImplemented_Error(self, tmpdir, monkeypatch): + def notimpl(x, y): + raise NotImplementedError(42) + + monkeypatch.setattr(os, "symlink", notimpl) + x = tmpdir.make_numbered_dir(rootdir=tmpdir, lock_timeout=0) + assert x.relto(tmpdir) + assert x.check() + + def test_locked_make_numbered_dir(self, tmpdir): + for i in range(10): + numdir = local.make_numbered_dir(prefix="base2.", rootdir=tmpdir, keep=2) + assert numdir.check() + assert numdir.basename == "base2.%d" % i + for j in range(i): + assert numdir.new(ext=str(j)).check() + + def test_error_preservation(self, path1): + pytest.raises(EnvironmentError, path1.join("qwoeqiwe").mtime) + pytest.raises(EnvironmentError, path1.join("qwoeqiwe").read) + + # def test_parentdirmatch(self): + # local.parentdirmatch('std', startmodule=__name__) + # + + +class TestImport: + @pytest.fixture(autouse=True) + def preserve_sys(self): + with mock.patch.dict(sys.modules): + with mock.patch.object(sys, "path", list(sys.path)): + yield + + def test_pyimport(self, path1): + obj = path1.join("execfile.py").pyimport() + assert obj.x == 42 + assert obj.__name__ == "execfile" + + def test_pyimport_renamed_dir_creates_mismatch(self, tmpdir, monkeypatch): + p = tmpdir.ensure("a", "test_x123.py") + p.pyimport() + tmpdir.join("a").move(tmpdir.join("b")) + with pytest.raises(tmpdir.ImportMismatchError): + tmpdir.join("b", "test_x123.py").pyimport() + + # Errors can be ignored. + monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1") + tmpdir.join("b", "test_x123.py").pyimport() + + # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. + monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0") + with pytest.raises(tmpdir.ImportMismatchError): + tmpdir.join("b", "test_x123.py").pyimport() + + def test_pyimport_messy_name(self, tmpdir): + # http://bitbucket.org/hpk42/py-trunk/issue/129 + path = tmpdir.ensure("foo__init__.py") + path.pyimport() + + def test_pyimport_dir(self, tmpdir): + p = tmpdir.join("hello_123") + p_init = p.ensure("__init__.py") + m = p.pyimport() + assert m.__name__ == "hello_123" + m = p_init.pyimport() + assert m.__name__ == "hello_123" + + def test_pyimport_execfile_different_name(self, path1): + obj = path1.join("execfile.py").pyimport(modname="0x.y.z") + assert obj.x == 42 + assert obj.__name__ == "0x.y.z" + + def test_pyimport_a(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("a.py").pyimport() + assert mod.result == "got it" + assert mod.__name__ == "otherdir.a" + + def test_pyimport_b(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("b.py").pyimport() + assert mod.stuff == "got it" + assert mod.__name__ == "otherdir.b" + + def test_pyimport_c(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("c.py").pyimport() + assert mod.value == "got it" + + def test_pyimport_d(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("d.py").pyimport() + assert mod.value2 == "got it" + + def test_pyimport_and_import(self, tmpdir): + tmpdir.ensure("xxxpackage", "__init__.py") + mod1path = tmpdir.ensure("xxxpackage", "module1.py") + mod1 = mod1path.pyimport() + assert mod1.__name__ == "xxxpackage.module1" + from xxxpackage import module1 + + assert module1 is mod1 + + def test_pyimport_check_filepath_consistency(self, monkeypatch, tmpdir): + name = "pointsback123" + ModuleType = type(os) + p = tmpdir.ensure(name + ".py") + for ending in (".pyc", "$py.class", ".pyo"): + mod = ModuleType(name) + pseudopath = tmpdir.ensure(name + ending) + mod.__file__ = str(pseudopath) + monkeypatch.setitem(sys.modules, name, mod) + newmod = p.pyimport() + assert mod == newmod + monkeypatch.undo() + mod = ModuleType(name) + pseudopath = tmpdir.ensure(name + "123.py") + mod.__file__ = str(pseudopath) + monkeypatch.setitem(sys.modules, name, mod) + excinfo = pytest.raises(pseudopath.ImportMismatchError, p.pyimport) + modname, modfile, orig = excinfo.value.args + assert modname == name + assert modfile == pseudopath + assert orig == p + assert issubclass(pseudopath.ImportMismatchError, ImportError) + + def test_issue131_pyimport_on__init__(self, tmpdir): + # __init__.py files may be namespace packages, and thus the + # __file__ of an imported module may not be ourselves + # see issue + p1 = tmpdir.ensure("proja", "__init__.py") + p2 = tmpdir.ensure("sub", "proja", "__init__.py") + m1 = p1.pyimport() + m2 = p2.pyimport() + assert m1 == m2 + + def test_ensuresyspath_append(self, tmpdir): + root1 = tmpdir.mkdir("root1") + file1 = root1.ensure("x123.py") + assert str(root1) not in sys.path + file1.pyimport(ensuresyspath="append") + assert str(root1) == sys.path[-1] + assert str(root1) not in sys.path[:-1] + + +class TestImportlibImport: + OPTS = {"ensuresyspath": "importlib"} + + def test_pyimport(self, path1): + obj = path1.join("execfile.py").pyimport(**self.OPTS) + assert obj.x == 42 + assert obj.__name__ == "execfile" + + def test_pyimport_dir_fails(self, tmpdir): + p = tmpdir.join("hello_123") + p.ensure("__init__.py") + with pytest.raises(ImportError): + p.pyimport(**self.OPTS) + + def test_pyimport_execfile_different_name(self, path1): + obj = path1.join("execfile.py").pyimport(modname="0x.y.z", **self.OPTS) + assert obj.x == 42 + assert obj.__name__ == "0x.y.z" + + def test_pyimport_relative_import_fails(self, path1): + otherdir = path1.join("otherdir") + with pytest.raises(ImportError): + otherdir.join("a.py").pyimport(**self.OPTS) + + def test_pyimport_doesnt_use_sys_modules(self, tmpdir): + p = tmpdir.ensure("file738jsk.py") + mod = p.pyimport(**self.OPTS) + assert mod.__name__ == "file738jsk" + assert "file738jsk" not in sys.modules + + +def test_pypkgdir(tmpdir): + pkg = tmpdir.ensure("pkg1", dir=1) + pkg.ensure("__init__.py") + pkg.ensure("subdir/__init__.py") + assert pkg.pypkgpath() == pkg + assert pkg.join("subdir", "__init__.py").pypkgpath() == pkg + + +def test_pypkgdir_unimportable(tmpdir): + pkg = tmpdir.ensure("pkg1-1", dir=1) # unimportable + pkg.ensure("__init__.py") + subdir = pkg.ensure("subdir/__init__.py").dirpath() + assert subdir.pypkgpath() == subdir + assert subdir.ensure("xyz.py").pypkgpath() == subdir + assert not pkg.pypkgpath() + + +def test_isimportable(): + try: + from py.path import isimportable # py vendored version + except ImportError: + from py._path.local import isimportable # py library + + assert not isimportable("") + assert isimportable("x") + assert isimportable("x1") + assert isimportable("x_1") + assert isimportable("_") + assert isimportable("_1") + assert not isimportable("x-1") + assert not isimportable("x:1") + + +def test_homedir_from_HOME(monkeypatch): + path = os.getcwd() + monkeypatch.setenv("HOME", path) + assert local._gethomedir() == local(path) + + +def test_homedir_not_exists(monkeypatch): + monkeypatch.delenv("HOME", raising=False) + monkeypatch.delenv("HOMEDRIVE", raising=False) + homedir = local._gethomedir() + assert homedir is None + + +def test_samefile(tmpdir): + assert tmpdir.samefile(tmpdir) + p = tmpdir.ensure("hello") + assert p.samefile(p) + with p.dirpath().as_cwd(): + assert p.samefile(p.basename) + if sys.platform == "win32": + p1 = p.__class__(str(p).lower()) + p2 = p.__class__(str(p).upper()) + assert p1.samefile(p2) + + +@pytest.mark.skipif(not hasattr(os, "symlink"), reason="os.symlink not available") +def test_samefile_symlink(tmpdir): + p1 = tmpdir.ensure("foo.txt") + p2 = tmpdir.join("linked.txt") + try: + os.symlink(str(p1), str(p2)) + except (OSError, NotImplementedError) as e: + # on Windows this might fail if the user doesn't have special symlink permissions + # pypy3 on Windows doesn't implement os.symlink and raises NotImplementedError + pytest.skip(str(e.args[0])) + + assert p1.samefile(p2) + + +def test_listdir_single_arg(tmpdir): + tmpdir.ensure("hello") + assert tmpdir.listdir("hello")[0].basename == "hello" + + +def test_mkdtemp_rootdir(tmpdir): + dtmp = local.mkdtemp(rootdir=tmpdir) + assert tmpdir.listdir() == [dtmp] + + +class TestWINLocalPath: + pytestmark = win32only + + def test_owner_group_not_implemented(self, path1): + with pytest.raises(NotImplementedError): + path1.stat().owner + with pytest.raises(NotImplementedError): + path1.stat().group + + def test_chmod_simple_int(self, path1): + mode = path1.stat().mode + # Ensure that we actually change the mode to something different. + path1.chmod(mode == 0 and 1 or 0) + try: + print(path1.stat().mode) + print(mode) + assert path1.stat().mode != mode + finally: + path1.chmod(mode) + assert path1.stat().mode == mode + + def test_path_comparison_lowercase_mixed(self, path1): + t1 = path1.join("a_path") + t2 = path1.join("A_path") + assert t1 == t1 + assert t1 == t2 + + def test_relto_with_mixed_case(self, path1): + t1 = path1.join("a_path", "fiLe") + t2 = path1.join("A_path") + assert t1.relto(t2) == "fiLe" + + def test_allow_unix_style_paths(self, path1): + t1 = path1.join("a_path") + assert t1 == str(path1) + "\\a_path" + t1 = path1.join("a_path/") + assert t1 == str(path1) + "\\a_path" + t1 = path1.join("dir/a_path") + assert t1 == str(path1) + "\\dir\\a_path" + + def test_sysfind_in_currentdir(self, path1): + cmd = local.sysfind("cmd") + root = cmd.new(dirname="", basename="") # c:\ in most installations + with root.as_cwd(): + x = local.sysfind(cmd.relto(root)) + assert x.check(file=1) + + def test_fnmatch_file_abspath_posix_pattern_on_win32(self, tmpdir): + # path-matching patterns might contain a posix path separator '/' + # Test that we can match that pattern on windows. + import posixpath + + b = tmpdir.join("a", "b") + assert b.fnmatch(posixpath.sep.join("ab")) + pattern = posixpath.sep.join([str(tmpdir), "*", "b"]) + assert b.fnmatch(pattern) + + +class TestPOSIXLocalPath: + pytestmark = skiponwin32 + + def test_hardlink(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write("Hello") + nlink = filepath.stat().nlink + linkpath.mklinkto(filepath) + assert filepath.stat().nlink == nlink + 1 + + def test_symlink_are_identical(self, tmpdir): + filepath = tmpdir.join("file") + filepath.write("Hello") + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(filepath) + assert linkpath.readlink() == str(filepath) + + def test_symlink_isfile(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write("") + linkpath.mksymlinkto(filepath) + assert linkpath.check(file=1) + assert not linkpath.check(link=0, file=1) + assert linkpath.islink() + + def test_symlink_relative(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write("Hello") + linkpath.mksymlinkto(filepath, absolute=False) + assert linkpath.readlink() == "file" + assert filepath.read() == linkpath.read() + + def test_symlink_not_existing(self, tmpdir): + linkpath = tmpdir.join("testnotexisting") + assert not linkpath.check(link=1) + assert linkpath.check(link=0) + + def test_relto_with_root(self, path1, tmpdir): + y = path1.join("x").relto(local("/")) + assert y[0] == str(path1)[1] + + def test_visit_recursive_symlink(self, tmpdir): + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(tmpdir) + visitor = tmpdir.visit(None, lambda x: x.check(link=0)) + assert list(visitor) == [linkpath] + + def test_symlink_isdir(self, tmpdir): + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(tmpdir) + assert linkpath.check(dir=1) + assert not linkpath.check(link=0, dir=1) + + def test_symlink_remove(self, tmpdir): + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(linkpath) # point to itself + assert linkpath.check(link=1) + linkpath.remove() + assert not linkpath.check() + + def test_realpath_file(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write("") + linkpath.mksymlinkto(filepath) + realpath = linkpath.realpath() + assert realpath.basename == "file" + + def test_owner(self, path1, tmpdir): + from pwd import getpwuid + from grp import getgrgid + + stat = path1.stat() + assert stat.path == path1 + + uid = stat.uid + gid = stat.gid + owner = getpwuid(uid)[0] + group = getgrgid(gid)[0] + + assert uid == stat.uid + assert owner == stat.owner + assert gid == stat.gid + assert group == stat.group + + def test_stat_helpers(self, tmpdir, monkeypatch): + path1 = tmpdir.ensure("file") + stat1 = path1.stat() + stat2 = tmpdir.stat() + assert stat1.isfile() + assert stat2.isdir() + assert not stat1.islink() + assert not stat2.islink() + + def test_stat_non_raising(self, tmpdir): + path1 = tmpdir.join("file") + pytest.raises(error.ENOENT, lambda: path1.stat()) + res = path1.stat(raising=False) + assert res is None + + def test_atime(self, tmpdir): + import time + + path = tmpdir.ensure("samplefile") + now = time.time() + atime1 = path.atime() + # we could wait here but timer resolution is very + # system dependent + path.read() + time.sleep(ATIME_RESOLUTION) + atime2 = path.atime() + time.sleep(ATIME_RESOLUTION) + duration = time.time() - now + assert (atime2 - atime1) <= duration + + def test_commondir(self, path1): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = path1.join("something") + p2 = path1.join("otherthing") + assert p1.common(p2) == path1 + assert p2.common(p1) == path1 + + def test_commondir_nocommon(self, path1): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = path1.join("something") + p2 = local(path1.sep + "blabla") + assert p1.common(p2) == "/" + + def test_join_to_root(self, path1): + root = path1.parts()[0] + assert len(str(root)) == 1 + assert str(root.join("a")) == "/a" + + def test_join_root_to_root_with_no_abs(self, path1): + nroot = path1.join("/") + assert str(path1) == str(nroot) + assert path1 == nroot + + def test_chmod_simple_int(self, path1): + mode = path1.stat().mode + path1.chmod(int(mode / 2)) + try: + assert path1.stat().mode != mode + finally: + path1.chmod(mode) + assert path1.stat().mode == mode + + def test_chmod_rec_int(self, path1): + # XXX fragile test + def recfilter(x): + return x.check(dotfile=0, link=0) + + oldmodes = {} + for x in path1.visit(rec=recfilter): + oldmodes[x] = x.stat().mode + path1.chmod(int("772", 8), rec=recfilter) + try: + for x in path1.visit(rec=recfilter): + assert x.stat().mode & int("777", 8) == int("772", 8) + finally: + for x, y in oldmodes.items(): + x.chmod(y) + + def test_copy_archiving(self, tmpdir): + unicode_fn = "something-\342\200\223.txt" + f = tmpdir.ensure("a", unicode_fn) + a = f.dirpath() + oldmode = f.stat().mode + newmode = oldmode ^ 1 + f.chmod(newmode) + b = tmpdir.join("b") + a.copy(b, mode=True) + assert b.join(f.basename).stat().mode == newmode + + def test_copy_stat_file(self, tmpdir): + src = tmpdir.ensure("src") + dst = tmpdir.join("dst") + # a small delay before the copy + time.sleep(ATIME_RESOLUTION) + src.copy(dst, stat=True) + oldstat = src.stat() + newstat = dst.stat() + assert oldstat.mode == newstat.mode + assert (dst.atime() - src.atime()) < ATIME_RESOLUTION + assert (dst.mtime() - src.mtime()) < ATIME_RESOLUTION + + def test_copy_stat_dir(self, tmpdir): + test_files = ["a", "b", "c"] + src = tmpdir.join("src") + for f in test_files: + src.join(f).write(f, ensure=True) + dst = tmpdir.join("dst") + # a small delay before the copy + time.sleep(ATIME_RESOLUTION) + src.copy(dst, stat=True) + for f in test_files: + oldstat = src.join(f).stat() + newstat = dst.join(f).stat() + assert (newstat.atime - oldstat.atime) < ATIME_RESOLUTION + assert (newstat.mtime - oldstat.mtime) < ATIME_RESOLUTION + assert oldstat.mode == newstat.mode + + def test_chown_identity(self, path1): + owner = path1.stat().owner + group = path1.stat().group + path1.chown(owner, group) + + def test_chown_dangling_link(self, path1): + owner = path1.stat().owner + group = path1.stat().group + x = path1.join("hello") + x.mksymlinkto("qlwkejqwlek") + try: + path1.chown(owner, group, rec=1) + finally: + x.remove(rec=0) + + def test_chown_identity_rec_mayfail(self, path1): + owner = path1.stat().owner + group = path1.stat().group + path1.chown(owner, group) + + +class TestUnicodePy2Py3: + def test_join_ensure(self, tmpdir, monkeypatch): + if sys.version_info >= (3, 0) and "LANG" not in os.environ: + pytest.skip("cannot run test without locale") + x = local(tmpdir.strpath) + part = "hällo" + y = x.ensure(part) + assert x.join(part) == y + + def test_listdir(self, tmpdir): + if sys.version_info >= (3, 0) and "LANG" not in os.environ: + pytest.skip("cannot run test without locale") + x = local(tmpdir.strpath) + part = "hällo" + y = x.ensure(part) + assert x.listdir(part)[0] == y + + @pytest.mark.xfail(reason="changing read/write might break existing usages") + def test_read_write(self, tmpdir): + x = tmpdir.join("hello") + part = "hällo" + x.write(part) + assert x.read() == part + x.write(part.encode(sys.getdefaultencoding())) + assert x.read() == part.encode(sys.getdefaultencoding()) + + +class TestBinaryAndTextMethods: + def test_read_binwrite(self, tmpdir): + x = tmpdir.join("hello") + part = "hällo" + part_utf8 = part.encode("utf8") + x.write_binary(part_utf8) + assert x.read_binary() == part_utf8 + s = x.read_text(encoding="utf8") + assert s == part + assert isinstance(s, str) + + def test_read_textwrite(self, tmpdir): + x = tmpdir.join("hello") + part = "hällo" + part_utf8 = part.encode("utf8") + x.write_text(part, encoding="utf8") + assert x.read_binary() == part_utf8 + assert x.read_text(encoding="utf8") == part + + def test_default_encoding(self, tmpdir): + x = tmpdir.join("hello") + # Can't use UTF8 as the default encoding (ASCII) doesn't support it + part = "hello" + x.write_text(part, "ascii") + s = x.read_text("ascii") + assert s == part + assert type(s) == type(part) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index bfd1fe6e668..73d0cc785c2 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -186,8 +186,7 @@ def test_not_collectable_arguments(self, pytester: Pytester) -> None: assert result.ret == ExitCode.USAGE_ERROR result.stderr.fnmatch_lines( [ - f"ERROR: not found: {p2}", - f"(no name {str(p2)!r} in any of [[][]])", + f"ERROR: found no collectors for {p2}", "", ] ) @@ -695,7 +694,14 @@ def test_cmdline_python_namespace_package( # mixed module and filenames: monkeypatch.chdir("world") - result = pytester.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world") + + # pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages. + # While we could change the test to use implicit namespace packages, seems better + # to still ensure the old declaration via declare_namespace still works. + ignore_w = r"-Wignore:Deprecated call to `pkg_resources.declare_namespace" + result = pytester.runpytest( + "--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", ignore_w + ) assert result.ret == 0 result.stdout.fnmatch_lines( [ @@ -1238,8 +1244,6 @@ def test(): " def check():", "> assert 1 == 2", "E assert 1 == 2", - "E +1", - "E -2", "", "pdb.py:2: AssertionError", "*= 1 failed in *", @@ -1281,7 +1285,7 @@ def test_simple(): reason="Windows raises `OSError: [Errno 22] Invalid argument` instead", ) def test_no_brokenpipeerror_message(pytester: Pytester) -> None: - """Ensure that the broken pipe error message is supressed. + """Ensure that the broken pipe error message is suppressed. In some Python versions, it reaches sys.unraisablehook, in others a BrokenPipeError exception is propagated, but either way it prints @@ -1295,3 +1299,14 @@ def test_no_brokenpipeerror_message(pytester: Pytester) -> None: # Cleanup. popen.stderr.close() + + +def test_function_return_non_none_warning(testdir) -> None: + testdir.makepyfile( + """ + def test_stuff(): + return "something" + """ + ) + res = testdir.runpytest() + res.stdout.fnmatch_lines(["*Did you mean to use `assert` instead of `return`?*"]) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 61aa4406ad2..e428b9c5ca9 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -420,18 +420,20 @@ def test_division_zero(): excinfo.match(r'[123]+') """ ) - result = pytester.runpytest() + result = pytester.runpytest("--tb=short") assert result.ret != 0 - exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'." - result.stdout.fnmatch_lines([f"E * AssertionError: {exc_msg}"]) + match = [ + r"E .* AssertionError: Regex pattern did not match.", + r"E .* Regex: '\[123\]\+'", + r"E .* Input: 'division by zero'", + ] + result.stdout.re_match_lines(match) result.stdout.no_fnmatch_line("*__tracebackhide__ = True*") result = pytester.runpytest("--fulltrace") assert result.ret != 0 - result.stdout.fnmatch_lines( - ["*__tracebackhide__ = True*", f"E * AssertionError: {exc_msg}"] - ) + result.stdout.re_match_lines([r".*__tracebackhide__ = True.*", *match]) class TestFormattedExcinfo: @@ -1468,3 +1470,90 @@ def __getattr__(self, attr): with pytest.raises(RuntimeError) as excinfo: RecursionDepthError().trigger assert "maximum recursion" in str(excinfo.getrepr()) + + +def _exceptiongroup_common( + pytester: Pytester, + outer_chain: str, + inner_chain: str, + native: bool, +) -> None: + pre_raise = "exceptiongroup." if not native else "" + pre_catch = pre_raise if sys.version_info < (3, 11) else "" + filestr = f""" + {"import exceptiongroup" if not native else ""} + import pytest + + def f(): raise ValueError("From f()") + def g(): raise BaseException("From g()") + + def inner(inner_chain): + excs = [] + for callback in [f, g]: + try: + callback() + except BaseException as err: + excs.append(err) + if excs: + if inner_chain == "none": + raise {pre_raise}BaseExceptionGroup("Oops", excs) + try: + raise SyntaxError() + except SyntaxError as e: + if inner_chain == "from": + raise {pre_raise}BaseExceptionGroup("Oops", excs) from e + else: + raise {pre_raise}BaseExceptionGroup("Oops", excs) + + def outer(outer_chain, inner_chain): + try: + inner(inner_chain) + except {pre_catch}BaseExceptionGroup as e: + if outer_chain == "none": + raise + if outer_chain == "from": + raise IndexError() from e + else: + raise IndexError() + + + def test(): + outer("{outer_chain}", "{inner_chain}") + """ + pytester.makepyfile(test_excgroup=filestr) + result = pytester.runpytest() + match_lines = [] + if inner_chain in ("another", "from"): + match_lines.append(r"SyntaxError: ") + + match_lines += [ + r" + Exception Group Traceback (most recent call last):", + rf" \| {pre_catch}BaseExceptionGroup: Oops \(2 sub-exceptions\)", + r" \| ValueError: From f\(\)", + r" \| BaseException: From g\(\)", + r"=* short test summary info =*", + ] + if outer_chain in ("another", "from"): + match_lines.append(r"FAILED test_excgroup.py::test - IndexError") + else: + match_lines.append( + rf"FAILED test_excgroup.py::test - {pre_catch}BaseExceptionGroup: Oops \(2.*" + ) + result.stdout.re_match_lines(match_lines) + + +@pytest.mark.skipif( + sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented" +) +@pytest.mark.parametrize("outer_chain", ["none", "from", "another"]) +@pytest.mark.parametrize("inner_chain", ["none", "from", "another"]) +def test_native_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None: + _exceptiongroup_common(pytester, outer_chain, inner_chain, native=True) + + +@pytest.mark.parametrize("outer_chain", ["none", "from", "another"]) +@pytest.mark.parametrize("inner_chain", ["none", "from", "another"]) +def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None: + # with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it + pytest.importorskip("exceptiongroup") + _exceptiongroup_common(pytester, outer_chain, inner_chain, native=False) diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 53e1bb9856b..52417f2f837 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -1,16 +1,13 @@ # flake8: noqa # disable flake check on this file because some constructs are strange # or redundant on purpose and can't be disable on a line-by-line basis -import ast import inspect import linecache import sys import textwrap from pathlib import Path -from types import CodeType from typing import Any from typing import Dict -from typing import Optional import pytest from _pytest._code import Code @@ -332,8 +329,7 @@ def test_findsource(monkeypatch) -> None: lines = ["if 1:\n", " def x():\n", " pass\n"] co = compile("".join(lines), filename, "exec") - # Type ignored because linecache.cache is private. - monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename)) # type: ignore[attr-defined] + monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename)) src, lineno = findsource(co) assert src is not None @@ -484,7 +480,7 @@ def deco_fixture(): src = inspect.getsource(deco_fixture) assert src == " @pytest.fixture\n def deco_fixture():\n assert False\n" - # currenly Source does not unwrap decorators, testing the + # currently Source does not unwrap decorators, testing the # existing behavior here for explicitness, but perhaps we should revisit/change this # in the future assert str(Source(deco_fixture)).startswith("@functools.wraps(function)") @@ -618,6 +614,19 @@ def something(): assert str(source) == "def func(): raise ValueError(42)" +def test_decorator() -> None: + s = """\ +def foo(f): + pass + +@foo +def bar(): + pass + """ + source = getstatement(3, s) + assert "@foo" in str(source) + + def XXX_test_expression_multiline() -> None: source = """\ something diff --git a/testing/conftest.py b/testing/conftest.py index 107aad86b25..8a9816799d9 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -157,6 +157,7 @@ class ColorMapping: "number": "\x1b[94m", "str": "\x1b[33m", "print": "\x1b[96m", + "endline": "\x1b[90m\x1b[39;49;00m", } RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()} diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 7d7e6d31240..3ceed7f5a2c 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -2,7 +2,6 @@ import sys import warnings from pathlib import Path -from unittest import mock import pytest from _pytest import deprecated @@ -11,13 +10,6 @@ from pytest import PytestDeprecationWarning -@pytest.mark.parametrize("attribute", pytest.collect.__all__) # type: ignore -# false positive due to dynamic attribute -def test_pytest_collect_module_deprecated(attribute) -> None: - with pytest.warns(DeprecationWarning, match=attribute): - getattr(pytest.collect, attribute) - - @pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS)) @pytest.mark.filterwarnings("default") def test_external_plugins_integrated(pytester: Pytester, plugin) -> None: @@ -28,52 +20,52 @@ def test_external_plugins_integrated(pytester: Pytester, plugin) -> None: pytester.parseconfig("-p", plugin) -def test_fillfuncargs_is_deprecated() -> None: - with pytest.warns( - pytest.PytestDeprecationWarning, - match=re.escape( - "pytest._fillfuncargs() is deprecated, use " - "function._request._fillfixtures() instead if you cannot avoid reaching into internals." - ), - ): - pytest._fillfuncargs(mock.Mock()) +def test_hookspec_via_function_attributes_are_deprecated(): + from _pytest.config import PytestPluginManager + pm = PytestPluginManager() -def test_fillfixtures_is_deprecated() -> None: - import _pytest.fixtures + class DeprecatedHookMarkerSpec: + def pytest_bad_hook(self): + pass + + pytest_bad_hook.historic = False # type: ignore[attr-defined] with pytest.warns( - pytest.PytestDeprecationWarning, - match=re.escape( - "_pytest.fixtures.fillfixtures() is deprecated, use " - "function._request._fillfixtures() instead if you cannot avoid reaching into internals." - ), - ): - _pytest.fixtures.fillfixtures(mock.Mock()) + PytestDeprecationWarning, + match=r"Please use the pytest\.hookspec\(historic=False\) decorator", + ) as recorder: + pm.add_hookspecs(DeprecatedHookMarkerSpec) + (record,) = recorder + assert ( + record.lineno + == DeprecatedHookMarkerSpec.pytest_bad_hook.__code__.co_firstlineno + ) + assert record.filename == __file__ -def test_minus_k_dash_is_deprecated(pytester: Pytester) -> None: - threepass = pytester.makepyfile( - test_threepass=""" - def test_one(): assert 1 - def test_two(): assert 1 - def test_three(): assert 1 - """ - ) - result = pytester.runpytest("-k=-test_two", threepass) - result.stdout.fnmatch_lines(["*The `-k '-expr'` syntax*deprecated*"]) +def test_hookimpl_via_function_attributes_are_deprecated(): + from _pytest.config import PytestPluginManager + pm = PytestPluginManager() + + class DeprecatedMarkImplPlugin: + def pytest_runtest_call(self): + pass -def test_minus_k_colon_is_deprecated(pytester: Pytester) -> None: - threepass = pytester.makepyfile( - test_threepass=""" - def test_one(): assert 1 - def test_two(): assert 1 - def test_three(): assert 1 - """ + pytest_runtest_call.tryfirst = True # type: ignore[attr-defined] + + with pytest.warns( + PytestDeprecationWarning, + match=r"Please use the pytest.hookimpl\(tryfirst=True\)", + ) as recorder: + pm.register(DeprecatedMarkImplPlugin()) + (record,) = recorder + assert ( + record.lineno + == DeprecatedMarkImplPlugin.pytest_runtest_call.__code__.co_firstlineno ) - result = pytester.runpytest("-k", "test_two:", threepass) - result.stdout.fnmatch_lines(["*The `-k 'expr:'` syntax*deprecated*"]) + assert record.filename == __file__ def test_fscollector_gethookproxy_isinitpath(pytester: Pytester) -> None: @@ -142,23 +134,6 @@ def __init__(self, foo: int, *, _ispytest: bool = False) -> None: PrivateInit(10, _ispytest=True) -def test_raising_unittest_skiptest_during_collection_is_deprecated( - pytester: Pytester, -) -> None: - pytester.makepyfile( - """ - import unittest - raise unittest.SkipTest() - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: Raising unittest.SkipTest*", - ] - ) - - @pytest.mark.parametrize("hooktype", ["hook", "ihook"]) def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request): path = legacy_path(tmp_path) @@ -194,8 +169,10 @@ def test_warns_none_is_deprecated(): with pytest.warns( PytestDeprecationWarning, match=re.escape( - "Passing None to catch any warning has been deprecated, pass no arguments instead:\n " - "Replace pytest.warns(None) by simply pytest.warns()." + "Passing None has been deprecated.\n" + "See https://docs.pytest.org/en/latest/how-to/capture-warnings.html" + "#additional-use-cases-of-warnings-in-tests" + " for alternatives in common use cases." ), ): with pytest.warns(None): # type: ignore[call-overload] @@ -290,10 +267,6 @@ def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: ) -@pytest.mark.skipif( - sys.version_info < (3, 7), - reason="This deprecation can only be emitted on python>=3.7", -) def test_importing_instance_is_deprecated(pytester: Pytester) -> None: with pytest.warns( pytest.PytestDeprecationWarning, @@ -306,3 +279,62 @@ def test_importing_instance_is_deprecated(pytester: Pytester) -> None: match=re.escape("The pytest.Instance collector type is deprecated"), ): from _pytest.python import Instance # noqa: F401 + + +@pytest.mark.filterwarnings("default") +def test_nose_deprecated_with_setup(pytester: Pytester) -> None: + pytest.importorskip("nose") + pytester.makepyfile( + """ + from nose.tools import with_setup + + def setup_fn_no_op(): + ... + + def teardown_fn_no_op(): + ... + + @with_setup(setup_fn_no_op, teardown_fn_no_op) + def test_omits_warnings(): + ... + """ + ) + output = pytester.runpytest() + message = [ + "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", + "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)", + "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", + "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `teardown_fn_no_op` (teardown)", + ] + output.stdout.fnmatch_lines(message) + output.assert_outcomes(passed=1) + + +@pytest.mark.filterwarnings("default") +def test_nose_deprecated_setup_teardown(pytester: Pytester) -> None: + pytest.importorskip("nose") + pytester.makepyfile( + """ + class Test: + + def setup(self): + ... + + def teardown(self): + ... + + def test(self): + ... + """ + ) + output = pytester.runpytest() + message = [ + "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", + "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `setup(self)`", + "*To remove this warning, rename it to `setup_method(self)`", + "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", + "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `teardown(self)`", + "*To remove this warning, rename it to `teardown_method(self)`", + ] + output.stdout.fnmatch_lines(message) + output.assert_outcomes(passed=1) diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py new file mode 100644 index 00000000000..e026fe3d192 --- /dev/null +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass +from dataclasses import field + + +def test_dataclasses() -> None: + @dataclass + class SimpleDataObject: + field_a: int = field() + field_b: str = field() + + def __eq__(self, __o: object) -> bool: + return super().__eq__(__o) + + left = SimpleDataObject(1, "b") + right = SimpleDataObject(1, "c") + + assert left == right diff --git a/testing/example_scripts/dataclasses/test_compare_initvar.py b/testing/example_scripts/dataclasses/test_compare_initvar.py new file mode 100644 index 00000000000..d859634ddd5 --- /dev/null +++ b/testing/example_scripts/dataclasses/test_compare_initvar.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass +from dataclasses import InitVar + + +@dataclass +class Foo: + init_only: InitVar[int] + real_attr: int + + +def test_demonstrate(): + assert Foo(1, 2) == Foo(1, 3) diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py index 63d3af822b1..24746bc2235 100644 --- a/testing/io/test_saferepr.py +++ b/testing/io/test_saferepr.py @@ -2,6 +2,7 @@ from _pytest._io.saferepr import _pformat_dispatch from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr +from _pytest._io.saferepr import saferepr_unlimited def test_simple_repr(): @@ -179,3 +180,23 @@ def __repr__(self): assert saferepr(SomeClass()).startswith( "<[RuntimeError() raised in repr()] SomeClass object at 0x" ) + + +def test_saferepr_unlimited(): + dict5 = {f"v{i}": i for i in range(5)} + assert saferepr_unlimited(dict5) == "{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4}" + + dict_long = {f"v{i}": i for i in range(1_000)} + r = saferepr_unlimited(dict_long) + assert "..." not in r + assert "\n" not in r + + +def test_saferepr_unlimited_exc(): + class A: + def __repr__(self): + raise ValueError(42) + + assert saferepr_unlimited(A()).startswith( + "<[ValueError(42) raised in repr()] A object at 0x" + ) diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index 4866c94a558..b5a04a99f18 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -56,7 +56,7 @@ def test_terminalwriter_not_unicode() -> None: file = io.TextIOWrapper(buffer, encoding="cp1252") tw = terminalwriter.TerminalWriter(file) tw.write("hello 🌀 wôrld אבג", flush=True) - assert buffer.getvalue() == br"hello \U0001f300 w\xf4rld \u05d0\u05d1\u05d2" + assert buffer.getvalue() == rb"hello \U0001f300 w\xf4rld \u05d0\u05d1\u05d2" win32 = int(sys.platform == "win32") @@ -254,7 +254,7 @@ def test_combining(self) -> None: pytest.param( True, True, - "{kw}assert{hl-reset} {number}0{hl-reset}\n", + "{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n", id="with markup and code_highlight", ), pytest.param( diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index bcb20de5805..e9e73d05f98 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -172,6 +172,24 @@ def test_caplog_captures_for_all_stages(caplog, logging_during_setup_and_teardow assert set(caplog._item.stash[caplog_records_key]) == {"setup", "call"} +def test_clear_for_call_stage(caplog, logging_during_setup_and_teardown): + logger.info("a_call_log") + assert [x.message for x in caplog.get_records("call")] == ["a_call_log"] + assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] + assert set(caplog._item.stash[caplog_records_key]) == {"setup", "call"} + + caplog.clear() + + assert caplog.get_records("call") == [] + assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] + assert set(caplog._item.stash[caplog_records_key]) == {"setup", "call"} + + logging.info("a_call_log_after_clear") + assert [x.message for x in caplog.get_records("call")] == ["a_call_log_after_clear"] + assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] + assert set(caplog._item.stash[caplog_records_key]) == {"setup", "call"} + + def test_ini_controls_global_log_level(pytester: Pytester) -> None: pytester.makepyfile( """ diff --git a/testing/plugins_integration/pytest.ini b/testing/plugins_integration/pytest.ini index b42b07d145a..3bacdef62ab 100644 --- a/testing/plugins_integration/pytest.ini +++ b/testing/plugins_integration/pytest.ini @@ -1,5 +1,6 @@ [pytest] addopts = --strict-markers +asyncio_mode = strict filterwarnings = error::pytest.PytestWarning ignore:.*.fspath is deprecated and will be replaced by .*.path.*:pytest.PytestDeprecationWarning diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 90b253cc6d3..79156133348 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,15 +1,15 @@ -anyio[curio,trio]==3.4.0 -django==3.2.9 -pytest-asyncio==0.16.0 -pytest-bdd==5.0.0 -pytest-cov==3.0.0 -pytest-django==4.5.1 +anyio[curio,trio]==3.6.1 +django==4.1.2 +pytest-asyncio==0.19.0 +pytest-bdd==6.0.1 +pytest-cov==4.0.0 +pytest-django==4.5.2 pytest-flakes==4.0.5 pytest-html==3.1.1 -pytest-mock==3.6.1 +pytest-mock==3.10.0 pytest-rerunfailures==10.2 -pytest-sugar==0.9.4 +pytest-sugar==0.9.5 pytest-trio==0.7.0 -pytest-twisted==1.13.4 -twisted==21.7.0 +pytest-twisted==1.14.0 +twisted==22.8.0 pytest-xvfb==2.0.0 diff --git a/testing/python/approx.py b/testing/python/approx.py index 0d411d8a6da..631e52b56ac 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1,14 +1,15 @@ import operator -import sys from contextlib import contextmanager from decimal import Decimal from fractions import Fraction +from math import sqrt from operator import eq from operator import ne from typing import Optional import pytest from _pytest.pytester import Pytester +from _pytest.python_api import _recursive_sequence_map from pytest import approx inf, nan = float("inf"), float("nan") @@ -93,9 +94,7 @@ def do_assert(lhs, rhs, expected_message, verbosity_level=0): class TestApprox: - def test_error_messages(self, assert_approx_raises_regex): - np = pytest.importorskip("numpy") - + def test_error_messages_native_dtypes(self, assert_approx_raises_regex): assert_approx_raises_regex( 2.0, 1.0, @@ -136,6 +135,34 @@ def test_error_messages(self, assert_approx_raises_regex): ], ) + assert_approx_raises_regex( + (1, 2.2, 4), + (1, 3.2, 4), + [ + r" comparison failed. Mismatched elements: 1 / 3:", + rf" Max absolute difference: {SOME_FLOAT}", + rf" Max relative difference: {SOME_FLOAT}", + r" Index \| Obtained\s+\| Expected ", + rf" 1 \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", + ], + ) + + # Specific test for comparison with 0.0 (relative diff will be 'inf') + assert_approx_raises_regex( + [0.0], + [1.0], + [ + r" comparison failed. Mismatched elements: 1 / 1:", + rf" Max absolute difference: {SOME_FLOAT}", + r" Max relative difference: inf", + r" Index \| Obtained\s+\| Expected ", + rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", + ], + ) + + def test_error_messages_numpy_dtypes(self, assert_approx_raises_regex): + np = pytest.importorskip("numpy") + a = np.linspace(0, 100, 20) b = np.linspace(0, 100, 20) a[10] += 0.5 @@ -176,18 +203,6 @@ def test_error_messages(self, assert_approx_raises_regex): ) # Specific test for comparison with 0.0 (relative diff will be 'inf') - assert_approx_raises_regex( - [0.0], - [1.0], - [ - r" comparison failed. Mismatched elements: 1 / 1:", - rf" Max absolute difference: {SOME_FLOAT}", - r" Max relative difference: inf", - r" Index \| Obtained\s+\| Expected ", - rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", - ], - ) - assert_approx_raises_regex( np.array([0.0]), np.array([1.0]), @@ -615,6 +630,19 @@ def test_dict_nonnumeric(self): def test_dict_vs_other(self): assert 1 != approx({"a": 0}) + def test_dict_for_div_by_zero(self, assert_approx_raises_regex): + assert_approx_raises_regex( + {"foo": 42.0}, + {"foo": 0.0}, + [ + r" comparison failed. Mismatched elements: 1 / 1:", + rf" Max absolute difference: {SOME_FLOAT}", + r" Max relative difference: inf", + r" Index \| Obtained\s+\| Expected ", + rf" foo | {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", + ], + ) + def test_numpy_array(self): np = pytest.importorskip("numpy") @@ -772,7 +800,7 @@ def test_foo(): def test_expected_value_type_error(self, x, name): with pytest.raises( TypeError, - match=fr"pytest.approx\(\) does not support nested {name}:", + match=rf"pytest.approx\(\) does not support nested {name}:", ): approx(x) @@ -810,7 +838,6 @@ def test_nonnumeric_false_if_unequal(self, x): assert 1.0 != approx([None]) assert None != approx([1.0]) # noqa: E711 - @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires ordered dicts") def test_nonnumeric_dict_repr(self): """Dicts with non-numerics and infinites have no tolerances""" x1 = {"foo": 1.0000005, "bar": None, "foobar": inf} @@ -860,13 +887,49 @@ def test_numpy_scalar_with_array(self): assert approx(expected, rel=5e-7, abs=0) == actual assert approx(expected, rel=5e-8, abs=0) != actual - def test_generic_sized_iterable_object(self): - class MySizedIterable: - def __iter__(self): - return iter([1, 2, 3, 4]) + def test_generic_ordered_sequence(self): + class MySequence: + def __getitem__(self, i): + return [1, 2, 3, 4][i] def __len__(self): return 4 - expected = MySizedIterable() - assert [1, 2, 3, 4] == approx(expected) + expected = MySequence() + assert [1, 2, 3, 4] == approx(expected, abs=1e-4) + + expected_repr = "approx([1 ± 1.0e-06, 2 ± 2.0e-06, 3 ± 3.0e-06, 4 ± 4.0e-06])" + assert repr(approx(expected)) == expected_repr + + def test_allow_ordered_sequences_only(self) -> None: + """pytest.approx() should raise an error on unordered sequences (#9692).""" + with pytest.raises(TypeError, match="only supports ordered sequences"): + assert {1, 2, 3} == approx({1, 2, 3}) + + +class TestRecursiveSequenceMap: + def test_map_over_scalar(self): + assert _recursive_sequence_map(sqrt, 16) == 4 + + def test_map_over_empty_list(self): + assert _recursive_sequence_map(sqrt, []) == [] + + def test_map_over_list(self): + assert _recursive_sequence_map(sqrt, [4, 16, 25, 676]) == [2, 4, 5, 26] + + def test_map_over_tuple(self): + assert _recursive_sequence_map(sqrt, (4, 16, 25, 676)) == (2, 4, 5, 26) + + def test_map_over_nested_lists(self): + assert _recursive_sequence_map(sqrt, [4, [25, 64], [[49]]]) == [ + 2, + [5, 8], + [[7]], + ] + + def test_map_over_mixed_sequence(self): + assert _recursive_sequence_map(sqrt, [4, (25, 64), [(49)]]) == [ + 2, + (5, 8), + [(7)], + ] diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index f29ca1dfa59..d996f80bb93 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -103,10 +103,6 @@ class T: @pytest.mark.pytester_example_path("fixtures/fill_fixtures") class TestFillFixtures: - def test_fillfuncargs_exposed(self): - # used by oejskit, kept for compatibility - assert pytest._fillfuncargs == fixtures._fillfuncargs - def test_funcarg_lookupfails(self, pytester: Pytester) -> None: pytester.copy_example() result = pytester.runpytest() # "--collect-only") @@ -3342,6 +3338,10 @@ def test_funcarg_compat(self, pytester: Pytester) -> None: config = pytester.parseconfigure("--funcargs") assert config.option.showfixtures + def test_show_help(self, pytester: Pytester) -> None: + result = pytester.runpytest("--fixtures", "--help") + assert not result.ret + def test_show_fixtures(self, pytester: Pytester) -> None: result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines( diff --git a/testing/python/integration.py b/testing/python/integration.py index d138b726638..6b5c53c98c4 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -1,84 +1,10 @@ -from typing import Any - import pytest -from _pytest import runner from _pytest._code import getfslineno from _pytest.fixtures import getfixturemarker from _pytest.pytester import Pytester from _pytest.python import Function -class TestOEJSKITSpecials: - def test_funcarg_non_pycollectobj( - self, pytester: Pytester, recwarn - ) -> None: # rough jstests usage - pytester.makeconftest( - """ - import pytest - def pytest_pycollect_makeitem(collector, name, obj): - if name == "MyClass": - return MyCollector.from_parent(collector, name=name) - class MyCollector(pytest.Collector): - def reportinfo(self): - return self.path, 3, "xyz" - """ - ) - modcol = pytester.getmodulecol( - """ - import pytest - @pytest.fixture - def arg1(request): - return 42 - class MyClass(object): - pass - """ - ) - # this hook finds funcarg factories - rep = runner.collect_one_node(collector=modcol) - # TODO: Don't treat as Any. - clscol: Any = rep.result[0] - clscol.obj = lambda arg1: None - clscol.funcargs = {} - pytest._fillfuncargs(clscol) - assert clscol.funcargs["arg1"] == 42 - - def test_autouse_fixture( - self, pytester: Pytester, recwarn - ) -> None: # rough jstests usage - pytester.makeconftest( - """ - import pytest - def pytest_pycollect_makeitem(collector, name, obj): - if name == "MyClass": - return MyCollector.from_parent(collector, name=name) - class MyCollector(pytest.Collector): - def reportinfo(self): - return self.path, 3, "xyz" - """ - ) - modcol = pytester.getmodulecol( - """ - import pytest - @pytest.fixture(autouse=True) - def hello(): - pass - @pytest.fixture - def arg1(request): - return 42 - class MyClass(object): - pass - """ - ) - # this hook finds funcarg factories - rep = runner.collect_one_node(modcol) - # TODO: Don't treat as Any. - clscol: Any = rep.result[0] - clscol.obj = lambda: None - clscol.funcargs = {} - pytest._fillfuncargs(clscol) - assert not clscol.funcargs - - def test_wrapped_getfslineno() -> None: def func(): pass diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index fc0082eb6b9..2fed22718b0 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -24,8 +24,7 @@ from _pytest.compat import NOTSET from _pytest.outcomes import fail from _pytest.pytester import Pytester -from _pytest.python import _idval -from _pytest.python import idmaker +from _pytest.python import IdMaker from _pytest.scope import Scope @@ -107,8 +106,8 @@ def gen() -> Iterator[Union[int, None, Exc]]: with pytest.raises( fail.Exception, match=( - r"In func: ids must be list of string/float/int/bool, found:" - r" Exc\(from_gen\) \(type: \) at index 2" + r"In func: ids contains unsupported value Exc\(from_gen\) \(type: \) at index 2. " + r"Supported types are: .*" ), ): metafunc.parametrize("x", [1, 2, 3], ids=gen()) # type: ignore[arg-type] @@ -286,7 +285,7 @@ class A: deadline=400.0 ) # very close to std deadline and CI boxes are not reliable in CPU power def test_idval_hypothesis(self, value) -> None: - escaped = _idval(value, "a", 6, None, nodeid=None, config=None) + escaped = IdMaker([], [], None, None, None, None, None)._idval(value, "a", 6) assert isinstance(escaped, str) escaped.encode("ascii") @@ -308,7 +307,10 @@ def test_unicode_idval(self) -> None: ), ] for val, expected in values: - assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected + assert ( + IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6) + == expected + ) def test_unicode_idval_with_config(self) -> None: """Unit test for expected behavior to obtain ids with @@ -336,7 +338,7 @@ def getini(self, name): ("ação", MockConfig({option: False}), "a\\xe7\\xe3o"), ] for val, config, expected in values: - actual = _idval(val, "a", 6, None, nodeid=None, config=config) + actual = IdMaker([], [], None, None, config, None, None)._idval(val, "a", 6) assert actual == expected def test_bytes_idval(self) -> None: @@ -349,7 +351,10 @@ def test_bytes_idval(self) -> None: ("αρά".encode(), r"\xce\xb1\xcf\x81\xce\xac"), ] for val, expected in values: - assert _idval(val, "a", 6, idfn=None, nodeid=None, config=None) == expected + assert ( + IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6) + == expected + ) def test_class_or_function_idval(self) -> None: """Unit test for the expected behavior to obtain ids for parametrized @@ -363,7 +368,10 @@ def test_function(): values = [(TestClass, "TestClass"), (test_function, "test_function")] for val, expected in values: - assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected + assert ( + IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6) + == expected + ) def test_notset_idval(self) -> None: """Test that a NOTSET value (used by an empty parameterset) generates @@ -371,29 +379,47 @@ def test_notset_idval(self) -> None: Regression test for #7686. """ - assert _idval(NOTSET, "a", 0, None, nodeid=None, config=None) == "a0" + assert ( + IdMaker([], [], None, None, None, None, None)._idval(NOTSET, "a", 0) == "a0" + ) def test_idmaker_autoname(self) -> None: """#250""" - result = idmaker( - ("a", "b"), [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)] - ) + result = IdMaker( + ("a", "b"), + [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)], + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["string-1.0", "st-ring-2.0"] - result = idmaker( - ("a", "b"), [pytest.param(object(), 1.0), pytest.param(object(), object())] - ) + result = IdMaker( + ("a", "b"), + [pytest.param(object(), 1.0), pytest.param(object(), object())], + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["a0-1.0", "a1-b1"] # unicode mixing, issue250 - result = idmaker(("a", "b"), [pytest.param({}, b"\xc3\xb4")]) + result = IdMaker( + ("a", "b"), [pytest.param({}, b"\xc3\xb4")], None, None, None, None, None + ).make_unique_parameterset_ids() assert result == ["a0-\\xc3\\xb4"] def test_idmaker_with_bytes_regex(self) -> None: - result = idmaker(("a"), [pytest.param(re.compile(b"foo"), 1.0)]) + result = IdMaker( + ("a"), [pytest.param(re.compile(b"foo"), 1.0)], None, None, None, None, None + ).make_unique_parameterset_ids() assert result == ["foo"] def test_idmaker_native_strings(self) -> None: - result = idmaker( + result = IdMaker( ("a", "b"), [ pytest.param(1.0, -1.1), @@ -410,7 +436,12 @@ def test_idmaker_native_strings(self) -> None: pytest.param(b"\xc3\xb4", "other"), pytest.param(1.0j, -2.0j), ], - ) + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == [ "1.0--1.1", "2--202", @@ -428,7 +459,7 @@ def test_idmaker_native_strings(self) -> None: ] def test_idmaker_non_printable_characters(self) -> None: - result = idmaker( + result = IdMaker( ("s", "n"), [ pytest.param("\x00", 1), @@ -438,23 +469,35 @@ def test_idmaker_non_printable_characters(self) -> None: pytest.param("\t", 5), pytest.param(b"\t", 6), ], - ) + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"] def test_idmaker_manual_ids_must_be_printable(self) -> None: - result = idmaker( + result = IdMaker( ("s",), [ pytest.param("x00", id="hello \x00"), pytest.param("x05", id="hello \x05"), ], - ) + None, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["hello \\x00", "hello \\x05"] def test_idmaker_enum(self) -> None: enum = pytest.importorskip("enum") e = enum.Enum("Foo", "one, two") - result = idmaker(("a", "b"), [pytest.param(e.one, e.two)]) + result = IdMaker( + ("a", "b"), [pytest.param(e.one, e.two)], None, None, None, None, None + ).make_unique_parameterset_ids() assert result == ["Foo.one-Foo.two"] def test_idmaker_idfn(self) -> None: @@ -465,15 +508,19 @@ def ids(val: object) -> Optional[str]: return repr(val) return None - result = idmaker( + result = IdMaker( ("a", "b"), [ pytest.param(10.0, IndexError()), pytest.param(20, KeyError()), pytest.param("three", [1, 2, 3]), ], - idfn=ids, - ) + ids, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["10.0-IndexError()", "20-KeyError()", "three-b2"] def test_idmaker_idfn_unique_names(self) -> None: @@ -482,15 +529,19 @@ def test_idmaker_idfn_unique_names(self) -> None: def ids(val: object) -> str: return "a" - result = idmaker( + result = IdMaker( ("a", "b"), [ pytest.param(10.0, IndexError()), pytest.param(20, KeyError()), pytest.param("three", [1, 2, 3]), ], - idfn=ids, - ) + ids, + None, + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["a-a0", "a-a1", "a-a2"] def test_idmaker_with_idfn_and_config(self) -> None: @@ -520,12 +571,15 @@ def getini(self, name): (MockConfig({option: False}), "a\\xe7\\xe3o"), ] for config, expected in values: - result = idmaker( + result = IdMaker( ("a",), [pytest.param("string")], - idfn=lambda _: "ação", - config=config, - ) + lambda _: "ação", + None, + config, + None, + None, + ).make_unique_parameterset_ids() assert result == [expected] def test_idmaker_with_ids_and_config(self) -> None: @@ -555,12 +609,9 @@ def getini(self, name): (MockConfig({option: False}), "a\\xe7\\xe3o"), ] for config, expected in values: - result = idmaker( - ("a",), - [pytest.param("string")], - ids=["ação"], - config=config, - ) + result = IdMaker( + ("a",), [pytest.param("string")], None, ["ação"], config, None, None + ).make_unique_parameterset_ids() assert result == [expected] def test_parametrize_ids_exception(self, pytester: Pytester) -> None: @@ -617,23 +668,39 @@ def test_int(arg): ) def test_idmaker_with_ids(self) -> None: - result = idmaker( - ("a", "b"), [pytest.param(1, 2), pytest.param(3, 4)], ids=["a", None] - ) + result = IdMaker( + ("a", "b"), + [pytest.param(1, 2), pytest.param(3, 4)], + None, + ["a", None], + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["a", "3-4"] def test_idmaker_with_paramset_id(self) -> None: - result = idmaker( + result = IdMaker( ("a", "b"), [pytest.param(1, 2, id="me"), pytest.param(3, 4, id="you")], - ids=["a", None], - ) + None, + ["a", None], + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["me", "you"] def test_idmaker_with_ids_unique_names(self) -> None: - result = idmaker( - ("a"), map(pytest.param, [1, 2, 3, 4, 5]), ids=["a", "a", "b", "c", "b"] - ) + result = IdMaker( + ("a"), + list(map(pytest.param, [1, 2, 3, 4, 5])), + None, + ["a", "a", "b", "c", "b"], + None, + None, + None, + ).make_unique_parameterset_ids() assert result == ["a0", "a1", "b0", "c", "b1"] def test_parametrize_indirect(self) -> None: @@ -1272,7 +1339,7 @@ def test_parametrized_ids_invalid_type(self, pytester: Pytester) -> None: """ import pytest - @pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, type)) + @pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, OSError())) def test_ids_numbers(x,expected): assert x * 2 == expected """ @@ -1280,8 +1347,8 @@ def test_ids_numbers(x,expected): result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "In test_ids_numbers: ids must be list of string/float/int/bool," - " found: (type: ) at index 2" + "In test_ids_numbers: ids contains unsupported value OSError() (type: ) at index 2. " + "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__." ] ) diff --git a/testing/python/raises.py b/testing/python/raises.py index 2d62e91091b..3dcec31eb1f 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -19,6 +19,16 @@ def test_raises_function(self): excinfo = pytest.raises(ValueError, int, "hello") assert "invalid literal" in str(excinfo.value) + def test_raises_does_not_allow_none(self): + with pytest.raises(ValueError, match="Expected an exception type or"): + # We're testing that this invalid usage gives a helpful error, + # so we can ignore Mypy telling us that None is invalid. + pytest.raises(expected_exception=None) # type: ignore + + def test_raises_does_not_allow_empty_tuple(self): + with pytest.raises(ValueError, match="Expected an exception type or"): + pytest.raises(expected_exception=()) + def test_raises_callable_no_exception(self) -> None: class A: def __call__(self): @@ -82,13 +92,9 @@ def test_raise_wrong_exception_passes_by(): def test_does_not_raise(self, pytester: Pytester) -> None: pytester.makepyfile( """ - from contextlib import contextmanager + from contextlib import nullcontext as does_not_raise import pytest - @contextmanager - def does_not_raise(): - yield - @pytest.mark.parametrize('example_input,expectation', [ (3, does_not_raise()), (2, does_not_raise()), @@ -107,13 +113,9 @@ def test_division(example_input, expectation): def test_does_not_raise_does_raise(self, pytester: Pytester) -> None: pytester.makepyfile( """ - from contextlib import contextmanager + from contextlib import nullcontext as does_not_raise import pytest - @contextmanager - def does_not_raise(): - yield - @pytest.mark.parametrize('example_input,expectation', [ (0, does_not_raise()), (1, pytest.raises(ZeroDivisionError)), @@ -191,10 +193,12 @@ def test_raises_match(self) -> None: int("asdf") msg = "with base 16" - expr = "Regex pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\".".format( - msg + expr = ( + "Regex pattern did not match.\n" + f" Regex: {msg!r}\n" + " Input: \"invalid literal for int() with base 10: 'asdf'\"" ) - with pytest.raises(AssertionError, match=re.escape(expr)): + with pytest.raises(AssertionError, match="(?m)" + re.escape(expr)): with pytest.raises(ValueError, match=msg): int("asdf", base=10) @@ -217,7 +221,7 @@ def test_match_failure_string_quoting(self): with pytest.raises(AssertionError, match="'foo"): raise AssertionError("'bar") (msg,) = excinfo.value.args - assert msg == 'Regex pattern "\'foo" does not match "\'bar".' + assert msg == '''Regex pattern did not match.\n Regex: "'foo"\n Input: "'bar"''' def test_match_failure_exact_string_message(self): message = "Oh here is a message with (42) numbers in parameters" @@ -226,9 +230,10 @@ def test_match_failure_exact_string_message(self): raise AssertionError(message) (msg,) = excinfo.value.args assert msg == ( - "Regex pattern 'Oh here is a message with (42) numbers in " - "parameters' does not match 'Oh here is a message with (42) " - "numbers in parameters'. Did you mean to `re.escape()` the regex?" + "Regex pattern did not match.\n" + " Regex: 'Oh here is a message with (42) numbers in parameters'\n" + " Input: 'Oh here is a message with (42) numbers in parameters'\n" + " Did you mean to `re.escape()` the regex?" ) def test_raises_match_wrong_type(self): diff --git a/testing/test_assertion.py b/testing/test_assertion.py index e8717590d53..75745922108 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -83,7 +83,7 @@ def test_dummy_failure(pytester): # how meta! "E assert {'failed': 1,... 'skipped': 0} == {'failed': 0,... 'skipped': 0}", "E Omitting 1 identical items, use -vv to show", "E Differing items:", - "E Use -v to get the full diff", + "E Use -v to get more diff", ] ) # XXX: unstable output. @@ -376,7 +376,7 @@ def test_bytes_diff_normal(self) -> None: assert diff == [ "b'spam' == b'eggs'", "At index 0 diff: b's' != b'e'", - "Use -v to get the full diff", + "Use -v to get more diff", ] def test_bytes_diff_verbose(self) -> None: @@ -444,11 +444,19 @@ def test_iterable_full_diff(self, left, right, expected) -> None: """ expl = callequal(left, right, verbose=0) assert expl is not None - assert expl[-1] == "Use -v to get the full diff" + assert expl[-1] == "Use -v to get more diff" verbose_expl = callequal(left, right, verbose=1) assert verbose_expl is not None assert "\n".join(verbose_expl).endswith(textwrap.dedent(expected).strip()) + def test_iterable_quiet(self) -> None: + expl = callequal([1, 2], [10, 2], verbose=-1) + assert expl == [ + "[1, 2] == [10, 2]", + "At index 0 diff: 1 != 10", + "Use -v to get more diff", + ] + def test_iterable_full_diff_ci( self, monkeypatch: MonkeyPatch, pytester: Pytester ) -> None: @@ -466,7 +474,7 @@ def test_full_diff(): monkeypatch.delenv("CI", raising=False) result = pytester.runpytest() - result.stdout.fnmatch_lines(["E Use -v to get the full diff"]) + result.stdout.fnmatch_lines(["E Use -v to get more diff"]) def test_list_different_lengths(self) -> None: expl = callequal([0, 1], [0, 1, 2]) @@ -699,32 +707,6 @@ def test_list_tuples(self) -> None: assert expl is not None assert len(expl) > 1 - def test_repr_verbose(self) -> None: - class Nums: - def __init__(self, nums): - self.nums = nums - - def __repr__(self): - return str(self.nums) - - list_x = list(range(5000)) - list_y = list(range(5000)) - list_y[len(list_y) // 2] = 3 - nums_x = Nums(list_x) - nums_y = Nums(list_y) - - assert callequal(nums_x, nums_y) is None - - expl = callequal(nums_x, nums_y, verbose=1) - assert expl is not None - assert "+" + repr(nums_x) in expl - assert "-" + repr(nums_y) in expl - - expl = callequal(nums_x, nums_y, verbose=2) - assert expl is not None - assert "+" + repr(nums_x) in expl - assert "-" + repr(nums_y) in expl - def test_list_bad_repr(self) -> None: class A: def __repr__(self): @@ -794,9 +776,26 @@ def test_mojibake(self) -> None: msg = "\n".join(expl) assert msg + def test_nfc_nfd_same_string(self) -> None: + # issue 3426 + left = "hyv\xe4" + right = "hyva\u0308" + expl = callequal(left, right) + assert expl == [ + r"'hyv\xe4' == 'hyva\u0308'", + f"- {str(right)}", + f"+ {str(left)}", + ] + + expl = callequal(left, right, verbose=2) + assert expl == [ + r"'hyv\xe4' == 'hyva\u0308'", + f"- {str(right)}", + f"+ {str(left)}", + ] + class TestAssert_reprcompare_dataclass: - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses(self, pytester: Pytester) -> None: p = pytester.copy_example("dataclasses/test_compare_dataclasses.py") result = pytester.runpytest(p) @@ -815,7 +814,6 @@ def test_dataclasses(self, pytester: Pytester) -> None: consecutive=True, ) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_recursive_dataclasses(self, pytester: Pytester) -> None: p = pytester.copy_example("dataclasses/test_compare_recursive_dataclasses.py") result = pytester.runpytest(p) @@ -834,7 +832,6 @@ def test_recursive_dataclasses(self, pytester: Pytester) -> None: consecutive=True, ) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_recursive_dataclasses_verbose(self, pytester: Pytester) -> None: p = pytester.copy_example("dataclasses/test_compare_recursive_dataclasses.py") result = pytester.runpytest(p, "-vv") @@ -854,8 +851,6 @@ def test_recursive_dataclasses_verbose(self, pytester: Pytester) -> None: "E ", "E Drill down into differing attribute a:", "E a: 10 != 20", - "E +10", - "E -20", "E ", "E Drill down into differing attribute b:", "E b: 'ten' != 'xxx'", @@ -867,7 +862,6 @@ def test_recursive_dataclasses_verbose(self, pytester: Pytester) -> None: consecutive=True, ) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses_verbose(self, pytester: Pytester) -> None: p = pytester.copy_example("dataclasses/test_compare_dataclasses_verbose.py") result = pytester.runpytest(p, "-vv") @@ -881,7 +875,6 @@ def test_dataclasses_verbose(self, pytester: Pytester) -> None: ] ) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses_with_attribute_comparison_off( self, pytester: Pytester ) -> None: @@ -891,7 +884,6 @@ def test_dataclasses_with_attribute_comparison_off( result = pytester.runpytest(p, "-vv") result.assert_outcomes(failed=0, passed=1) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_comparing_two_different_data_classes(self, pytester: Pytester) -> None: p = pytester.copy_example( "dataclasses/test_compare_two_different_dataclasses.py" @@ -899,6 +891,22 @@ def test_comparing_two_different_data_classes(self, pytester: Pytester) -> None: result = pytester.runpytest(p, "-vv") result.assert_outcomes(failed=0, passed=1) + def test_data_classes_with_custom_eq(self, pytester: Pytester) -> None: + p = pytester.copy_example( + "dataclasses/test_compare_dataclasses_with_custom_eq.py" + ) + # issue 9362 + result = pytester.runpytest(p, "-vv") + result.assert_outcomes(failed=1, passed=0) + result.stdout.no_re_match_line(".*Differing attributes.*") + + def test_data_classes_with_initvar(self, pytester: Pytester) -> None: + p = pytester.copy_example("dataclasses/test_compare_initvar.py") + # issue 9820 + result = pytester.runpytest(p, "-vv") + result.assert_outcomes(failed=1, passed=0) + result.stdout.no_re_match_line(".*AttributeError.*") + class TestAssert_reprcompare_attrsclass: def test_attrs(self) -> None: @@ -982,7 +990,6 @@ class SimpleDataObject: right = SimpleDataObject(1, "b") lines = callequal(left, right, verbose=2) - print(lines) assert lines is not None assert lines[2].startswith("Matching attributes:") assert "Omitting" not in lines[1] @@ -1007,6 +1014,36 @@ class SimpleDataObjectTwo: lines = callequal(left, right) assert lines is None + def test_attrs_with_auto_detect_and_custom_eq(self) -> None: + @attr.s( + auto_detect=True + ) # attr.s doesn’t ignore a custom eq if auto_detect=True + class SimpleDataObject: + field_a = attr.ib() + + def __eq__(self, other): # pragma: no cover + return super().__eq__(other) + + left = SimpleDataObject(1) + right = SimpleDataObject(2) + # issue 9362 + lines = callequal(left, right, verbose=2) + assert lines is None + + def test_attrs_with_custom_eq(self) -> None: + @attr.define(slots=False) + class SimpleDataObject: + field_a = attr.ib() + + def __eq__(self, other): # pragma: no cover + return super().__eq__(other) + + left = SimpleDataObject(1) + right = SimpleDataObject(2) + # issue 9362 + lines = callequal(left, right, verbose=2) + assert lines is None + class TestAssert_reprcompare_namedtuple: def test_namedtuple(self) -> None: @@ -1027,7 +1064,7 @@ def test_namedtuple(self) -> None: " b: 'b' != 'c'", " - c", " + b", - "Use -v to get the full diff", + "Use -v to get more diff", ] def test_comparing_two_different_namedtuple(self) -> None: @@ -1042,7 +1079,7 @@ def test_comparing_two_different_namedtuple(self) -> None: assert lines == [ "NT1(a=1, b='b') == NT2(a=2, b='b')", "At index 0 diff: 1 != 2", - "Use -v to get the full diff", + "Use -v to get more diff", ] @@ -1616,7 +1653,7 @@ def test_raise_assertion_error(): ) -def test_raise_assertion_error_raisin_repr(pytester: Pytester) -> None: +def test_raise_assertion_error_raising_repr(pytester: Pytester) -> None: pytester.makepyfile( """ class RaisingRepr(object): @@ -1627,9 +1664,7 @@ def test_raising_repr(): """ ) result = pytester.runpytest() - result.stdout.fnmatch_lines( - ["E AssertionError: "] - ) + result.stdout.fnmatch_lines(["E AssertionError: "]) def test_issue_1944(pytester: Pytester) -> None: @@ -1677,3 +1712,18 @@ def test(): "*= 1 failed in*", ] ) + + +def test_reprcompare_verbose_long() -> None: + a = {f"v{i}": i for i in range(11)} + b = a.copy() + b["v2"] += 10 + lines = callop("==", a, b, verbose=2) + assert lines is not None + assert lines[0] == ( + "{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4, 'v5': 5, " + "'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}" + " == " + "{'v0': 0, 'v1': 1, 'v2': 12, 'v3': 3, 'v4': 4, 'v5': 5, " + "'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}" + ) diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 61f5760e725..3c98392ed98 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -13,10 +13,12 @@ from pathlib import Path from typing import cast from typing import Dict +from typing import Generator from typing import List from typing import Mapping from typing import Optional from typing import Set +from unittest import mock import _pytest._code import pytest @@ -202,16 +204,8 @@ def f3() -> None: def f4() -> None: assert sys == 42 # type: ignore[comparison-overlap] - verbose = request.config.getoption("verbose") msg = getmsg(f4, {"sys": sys}) - if verbose > 0: - assert msg == ( - "assert == 42\n" - " +\n" - " -42" - ) - else: - assert msg == "assert sys == 42" + assert msg == "assert sys == 42" def f5() -> None: assert cls == 42 # type: ignore[name-defined] # noqa: F821 @@ -222,20 +216,7 @@ class X: msg = getmsg(f5, {"cls": X}) assert msg is not None lines = msg.splitlines() - if verbose > 1: - assert lines == [ - f"assert {X!r} == 42", - f" +{X!r}", - " -42", - ] - elif verbose > 0: - assert lines == [ - "assert .X'> == 42", - f" +{X!r}", - " -42", - ] - else: - assert lines == ["assert cls == 42"] + assert lines == ["assert cls == 42"] def test_assertrepr_compare_same_width(self, request) -> None: """Should use same width/truncation with same initial width.""" @@ -277,14 +258,11 @@ def f() -> None: msg = getmsg(f, {"cls": Y}) assert msg is not None lines = msg.splitlines() - if request.config.getoption("verbose") > 0: - assert lines == ["assert 3 == 2", " +3", " -2"] - else: - assert lines == [ - "assert 3 == 2", - " + where 3 = Y.foo", - " + where Y = cls()", - ] + assert lines == [ + "assert 3 == 2", + " + where 3 = Y.foo", + " + where Y = cls()", + ] def test_assert_already_has_message(self) -> None: def f(): @@ -661,10 +639,7 @@ def f(): assert len(values) == 11 msg = getmsg(f) - if request.config.getoption("verbose") > 0: - assert msg == "assert 10 == 11\n +10\n -11" - else: - assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])" + assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])" def test_custom_reprcompare(self, monkeypatch) -> None: def my_reprcompare1(op, left, right) -> str: @@ -730,10 +705,7 @@ def __repr__(self): msg = getmsg(f) assert msg is not None lines = util._format_lines([msg]) - if request.config.getoption("verbose") > 0: - assert lines == ["assert 0 == 1\n +0\n -1"] - else: - assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"] + assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"] def test_custom_repr_non_ascii(self) -> None: def f() -> None: @@ -1037,7 +1009,7 @@ def test_meta_path(): ) assert pytester.runpytest().ret == 0 - def test_write_pyc(self, pytester: Pytester, tmp_path, monkeypatch) -> None: + def test_write_pyc(self, pytester: Pytester, tmp_path) -> None: from _pytest.assertion.rewrite import _write_pyc from _pytest.assertion import AssertionState @@ -1049,27 +1021,8 @@ def test_write_pyc(self, pytester: Pytester, tmp_path, monkeypatch) -> None: co = compile("1", "f.py", "single") assert _write_pyc(state, co, os.stat(source_path), pycpath) - if sys.platform == "win32": - from contextlib import contextmanager - - @contextmanager - def atomic_write_failed(fn, mode="r", overwrite=False): - e = OSError() - e.errno = 10 - raise e - yield # type:ignore[unreachable] - - monkeypatch.setattr( - _pytest.assertion.rewrite, "atomic_write", atomic_write_failed - ) - else: - - def raise_oserror(*args): - raise OSError() - - monkeypatch.setattr("os.rename", raise_oserror) - - assert not _write_pyc(state, co, os.stat(source_path), pycpath) + with mock.patch.object(os, "replace", side_effect=OSError): + assert not _write_pyc(state, co, os.stat(source_path), pycpath) def test_resources_provider_for_loader(self, pytester: Pytester) -> None: """ @@ -1123,9 +1076,28 @@ def test_read_pyc(self, tmp_path: Path) -> None: assert _read_pyc(source, pyc) is None # no error - @pytest.mark.skipif( - sys.version_info < (3, 7), reason="Only the Python 3.7 format for simplicity" - ) + def test_read_pyc_success(self, tmp_path: Path, pytester: Pytester) -> None: + """ + Ensure that the _rewrite_test() -> _write_pyc() produces a pyc file + that can be properly read with _read_pyc() + """ + from _pytest.assertion import AssertionState + from _pytest.assertion.rewrite import _read_pyc + from _pytest.assertion.rewrite import _rewrite_test + from _pytest.assertion.rewrite import _write_pyc + + config = pytester.parseconfig() + state = AssertionState(config, "rewrite") + + fn = tmp_path / "source.py" + pyc = Path(str(fn) + "c") + + fn.write_text("def test(): assert True") + + source_stat, co = _rewrite_test(fn, config) + _write_pyc(state, co, source_stat, pyc) + assert _read_pyc(fn, pyc, state.trace) is not None + def test_read_pyc_more_invalid(self, tmp_path: Path) -> None: from _pytest.assertion.rewrite import _read_pyc @@ -1294,7 +1266,7 @@ def test_simple_failure(): @pytest.mark.skipif( - sys.maxsize <= (2 ** 31 - 1), reason="Causes OverflowError on 32bit systems" + sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems" ) @pytest.mark.parametrize("offset", [-1, +1]) def test_source_mtime_long_long(pytester: Pytester, offset) -> None: @@ -1313,7 +1285,7 @@ def test(): pass # use unsigned long timestamp which overflows signed long, # which was the cause of the bug # +1 offset also tests masking of 0xFFFFFFFF - timestamp = 2 ** 32 + offset + timestamp = 2**32 + offset os.utime(str(p), (timestamp, timestamp)) result = pytester.runpytest() assert result.ret == 0 @@ -1357,7 +1329,7 @@ class TestEarlyRewriteBailout: @pytest.fixture def hook( self, pytestconfig, monkeypatch, pytester: Pytester - ) -> AssertionRewritingHook: + ) -> Generator[AssertionRewritingHook, None, None]: """Returns a patched AssertionRewritingHook instance so we can configure its initial paths and track if PathFinder.find_spec has been called. """ @@ -1378,11 +1350,11 @@ def spy_find_spec(name, path): hook = AssertionRewritingHook(pytestconfig) # use default patterns, otherwise we inherit pytest's testing config - hook.fnpats[:] = ["test_*.py", "*_test.py"] - monkeypatch.setattr(hook, "_find_spec", spy_find_spec) - hook.set_session(StubSession()) # type: ignore[arg-type] - pytester.syspathinsert() - return hook + with mock.patch.object(hook, "fnpats", ["test_*.py", "*_test.py"]): + monkeypatch.setattr(hook, "_find_spec", spy_find_spec) + hook.set_session(StubSession()) # type: ignore[arg-type] + pytester.syspathinsert() + yield hook def test_basic(self, pytester: Pytester, hook: AssertionRewritingHook) -> None: """ @@ -1432,9 +1404,9 @@ def test_simple_failure(): } ) pytester.syspathinsert("tests") - hook.fnpats[:] = ["tests/**.py"] - assert hook.find_spec("file") is not None - assert self.find_spec_calls == ["file"] + with mock.patch.object(hook, "fnpats", ["tests/**.py"]): + assert hook.find_spec("file") is not None + assert self.find_spec_calls == ["file"] @pytest.mark.skipif( sys.platform.startswith("win32"), reason="cannot remove cwd on Windows" diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index cc6d547dfb1..c381a8448ce 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -773,7 +773,7 @@ def pytest_sessionfinish(): result = pytester.runpytest("--lf", "--lfnf", "none") result.stdout.fnmatch_lines( [ - "collected 2 items / 2 deselected", + "collected 2 items / 2 deselected / 0 selected", "run-last-failure: no previously failed tests, deselecting all items.", "deselected=2", "* 2 deselected in *", @@ -1249,3 +1249,8 @@ def test_cachedir_tag(pytester: Pytester) -> None: cache.set("foo", "bar") cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG") assert cachedir_tag_path.read_bytes() == CACHEDIR_TAG_CONTENT + + +def test_clioption_with_cacheshow_and_help(pytester: Pytester) -> None: + result = pytester.runpytest("--cache-show", "--help") + assert result.ret == 0 diff --git a/testing/test_capture.py b/testing/test_capture.py index 1bc1f2f8db2..00cab19330b 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -897,6 +897,15 @@ def test_dontreadfrominput() -> None: iter_f = iter(f) pytest.raises(OSError, next, iter_f) pytest.raises(UnsupportedOperation, f.fileno) + pytest.raises(UnsupportedOperation, f.flush) + assert not f.readable() + pytest.raises(UnsupportedOperation, f.seek, 0) + assert not f.seekable() + pytest.raises(UnsupportedOperation, f.tell) + pytest.raises(UnsupportedOperation, f.truncate, 0) + pytest.raises(UnsupportedOperation, f.write, b"") + pytest.raises(UnsupportedOperation, f.writelines, []) + assert not f.writable() f.close() # just for completeness @@ -1433,19 +1442,19 @@ def test_capattr(): not sys.platform.startswith("win"), reason="only on windows", ) -def test_py36_windowsconsoleio_workaround_non_standard_streams() -> None: +def test_windowsconsoleio_workaround_non_standard_streams() -> None: """ - Ensure _py36_windowsconsoleio_workaround function works with objects that + Ensure _windowsconsoleio_workaround function works with objects that do not implement the full ``io``-based stream protocol, for example execnet channels (#2666). """ - from _pytest.capture import _py36_windowsconsoleio_workaround + from _pytest.capture import _windowsconsoleio_workaround class DummyStream: def write(self, s): pass stream = cast(TextIO, DummyStream()) - _py36_windowsconsoleio_workaround(stream) + _windowsconsoleio_workaround(stream) def test_dontreadfrominput_has_encoding(pytester: Pytester) -> None: diff --git a/testing/test_collection.py b/testing/test_collection.py index 6fd9a708b4e..58e1d862a35 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -64,7 +64,7 @@ def test_fail(): assert 0 assert pytester.collect_by_name(modcol, "doesnotexist") is None - def test_getparent(self, pytester: Pytester) -> None: + def test_getparent_and_accessors(self, pytester: Pytester) -> None: modcol = pytester.getmodulecol( """ class TestClass: @@ -77,14 +77,21 @@ def test_foo(self): fn = pytester.collect_by_name(cls, "test_foo") assert isinstance(fn, pytest.Function) - module_parent = fn.getparent(pytest.Module) - assert module_parent is modcol + assert fn.getparent(pytest.Module) is modcol + assert modcol.module is not None + assert modcol.cls is None + assert modcol.instance is None - function_parent = fn.getparent(pytest.Function) - assert function_parent is fn + assert fn.getparent(pytest.Class) is cls + assert cls.module is not None + assert cls.cls is not None + assert cls.instance is None - class_parent = fn.getparent(pytest.Class) - assert class_parent is cls + assert fn.getparent(pytest.Function) is fn + assert fn.module is not None + assert fn.cls is not None + assert fn.instance is not None + assert fn.function is not None def test_getcustomfile_roundtrip(self, pytester: Pytester) -> None: hello = pytester.makefile(".xxx", hello="world") @@ -237,28 +244,32 @@ def test_testpaths_ini(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> No pytester.makeini( """ [pytest] - testpaths = gui uts + testpaths = */tests """ ) tmp_path = pytester.path - ensure_file(tmp_path / "env" / "test_1.py").write_text("def test_env(): pass") - ensure_file(tmp_path / "gui" / "test_2.py").write_text("def test_gui(): pass") - ensure_file(tmp_path / "uts" / "test_3.py").write_text("def test_uts(): pass") + ensure_file(tmp_path / "a" / "test_1.py").write_text("def test_a(): pass") + ensure_file(tmp_path / "b" / "tests" / "test_2.py").write_text( + "def test_b(): pass" + ) + ensure_file(tmp_path / "c" / "tests" / "test_3.py").write_text( + "def test_c(): pass" + ) # executing from rootdir only tests from `testpaths` directories # are collected items, reprec = pytester.inline_genitems("-v") - assert [x.name for x in items] == ["test_gui", "test_uts"] + assert [x.name for x in items] == ["test_b", "test_c"] # check that explicitly passing directories in the command-line # collects the tests - for dirname in ("env", "gui", "uts"): + for dirname in ("a", "b", "c"): items, reprec = pytester.inline_genitems(tmp_path.joinpath(dirname)) assert [x.name for x in items] == ["test_%s" % dirname] # changing cwd to each subdirectory and running pytest without # arguments collects the tests in that directory normally - for dirname in ("env", "gui", "uts"): + for dirname in ("a", "b", "c"): monkeypatch.chdir(pytester.path.joinpath(dirname)) items, reprec = pytester.inline_genitems() assert [x.name for x in items] == ["test_%s" % dirname] @@ -644,7 +655,7 @@ def test_global_file(self, pytester: Pytester) -> None: for parent in col.listchain(): assert parent.config is config - def test_pkgfile(self, pytester: Pytester) -> None: + def test_pkgfile(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None: """Verify nesting when a module is within a package. The parent chain should match: Module -> Package -> Session. Session's parent should always be None. @@ -653,7 +664,8 @@ def test_pkgfile(self, pytester: Pytester) -> None: subdir = tmp_path.joinpath("subdir") x = ensure_file(subdir / "x.py") ensure_file(subdir / "__init__.py") - with subdir.cwd(): + with monkeypatch.context() as mp: + mp.chdir(subdir) config = pytester.parseconfigure(x) col = pytester.getnode(config, x) assert col is not None @@ -874,6 +886,36 @@ def test_method(self): pass assert item.keywords["kw"] == "method" assert len(item.keywords) == len(set(item.keywords)) + def test_unpacked_marks_added_to_keywords(self, pytester: Pytester) -> None: + item = pytester.getitem( + """ + import pytest + pytestmark = pytest.mark.foo + class TestClass: + pytestmark = pytest.mark.bar + def test_method(self): pass + test_method.pytestmark = pytest.mark.baz + """, + "test_method", + ) + assert isinstance(item, pytest.Function) + cls = item.getparent(pytest.Class) + assert cls is not None + mod = item.getparent(pytest.Module) + assert mod is not None + + assert item.keywords["foo"] == pytest.mark.foo.mark + assert item.keywords["bar"] == pytest.mark.bar.mark + assert item.keywords["baz"] == pytest.mark.baz.mark + + assert cls.keywords["foo"] == pytest.mark.foo.mark + assert cls.keywords["bar"] == pytest.mark.bar.mark + assert "baz" not in cls.keywords + + assert mod.keywords["foo"] == pytest.mark.foo.mark + assert "bar" not in mod.keywords + assert "baz" not in mod.keywords + COLLECTION_ERROR_PY_FILES = dict( test_01_failure=""" @@ -1151,8 +1193,7 @@ def test_1(): """ % (str(subdir),) ) - with pytester.path.cwd(): - result = pytester.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed in*"]) assert result.ret == 0 @@ -1163,8 +1204,7 @@ def test_1(): testpaths = . """ ) - with pytester.path.cwd(): - result = pytester.runpytest("--collect-only") + result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines(["collected 1 item"]) @@ -1187,7 +1227,8 @@ def test_collect_pyargs_with_testpaths( ) ) monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep) - with root.cwd(): + with monkeypatch.context() as mp: + mp.chdir(root) result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines(["*1 passed in*"]) @@ -1470,6 +1511,35 @@ def test_modules_not_importable_as_side_effect(self, pytester: Pytester) -> None ] ) + def test_using_python_path(self, pytester: Pytester) -> None: + """ + Dummy modules created by insert_missing_modules should not get in + the way of modules that could be imported via python path (#9645). + """ + pytester.makeini( + """ + [pytest] + pythonpath = . + addopts = --import-mode importlib + """ + ) + pytester.makepyfile( + **{ + "tests/__init__.py": "", + "tests/conftest.py": "", + "tests/subpath/__init__.py": "", + "tests/subpath/helper.py": "", + "tests/subpath/test_something.py": """ + import tests.subpath.helper + + def test_something(): + assert True + """, + } + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines("*1 passed in*") + def test_does_not_crash_on_error_from_decorated_function(pytester: Pytester) -> None: """Regression test for an issue around bad exception formatting due to diff --git a/testing/test_compat.py b/testing/test_compat.py index 9f48a31d689..8a80fd625dc 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -1,4 +1,5 @@ import enum +import sys from functools import partial from functools import wraps from typing import TYPE_CHECKING @@ -91,6 +92,7 @@ def foo(x): assert get_real_func(partial(foo)) is foo +@pytest.mark.skipif(sys.version_info >= (3, 11), reason="couroutine removed") def test_is_generator_asyncio(pytester: Pytester) -> None: pytester.makepyfile( """ @@ -133,7 +135,7 @@ def test_is_generator_async_gen_syntax(pytester: Pytester) -> None: pytester.makepyfile( """ from _pytest.compat import is_generator - def test_is_generator_py36(): + def test_is_generator(): async def foo(): yield await foo() @@ -156,11 +158,11 @@ def raise_baseexception(self): @property def raise_exception(self): - raise Exception("exception should be catched") + raise Exception("exception should be caught") @property def raise_fail_outcome(self): - pytest.fail("fail should be catched") + pytest.fail("fail should be caught") def test_helper_failures() -> None: diff --git a/testing/test_config.py b/testing/test_config.py index 4435591164f..f5b6d7f9816 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -112,21 +112,26 @@ def test_tox_ini_wrong_version(self, pytester: Pytester) -> None: @pytest.mark.parametrize( "section, name", - [("tool:pytest", "setup.cfg"), ("pytest", "tox.ini"), ("pytest", "pytest.ini")], + [ + ("tool:pytest", "setup.cfg"), + ("pytest", "tox.ini"), + ("pytest", "pytest.ini"), + ("pytest", ".pytest.ini"), + ], ) def test_ini_names(self, pytester: Pytester, name, section) -> None: pytester.path.joinpath(name).write_text( textwrap.dedent( """ [{section}] - minversion = 1.0 + minversion = 3.36 """.format( section=section ) ) ) config = pytester.parseconfig() - assert config.getini("minversion") == "1.0" + assert config.getini("minversion") == "3.36" def test_pyproject_toml(self, pytester: Pytester) -> None: pytester.makepyprojecttoml( @@ -163,7 +168,17 @@ def test_ini_parse_error(self, pytester: Pytester) -> None: pytester.path.joinpath("pytest.ini").write_text("addopts = -x") result = pytester.runpytest() assert result.ret != 0 - result.stderr.fnmatch_lines(["ERROR: *pytest.ini:1: no section header defined"]) + result.stderr.fnmatch_lines("ERROR: *pytest.ini:1: no section header defined") + + def test_toml_parse_error(self, pytester: Pytester) -> None: + pytester.makepyprojecttoml( + """ + \\" + """ + ) + result = pytester.runpytest() + assert result.ret != 0 + result.stderr.fnmatch_lines("ERROR: *pyproject.toml: Invalid statement*") @pytest.mark.xfail(reason="probably not needed") def test_confcutdir(self, pytester: Pytester) -> None: @@ -386,7 +401,7 @@ def pytest_configure(config): pytest.param( """ [some_other_header] - required_plugins = wont be triggered + required_plugins = won't be triggered [pytest] """, "1.5", @@ -807,7 +822,7 @@ def test_confcutdir_check_isdir(self, pytester: Pytester) -> None: with pytest.raises(pytest.UsageError, match=exp_match): pytester.parseconfig("--confcutdir", pytester.path.joinpath("file")) with pytest.raises(pytest.UsageError, match=exp_match): - pytester.parseconfig("--confcutdir", pytester.path.joinpath("inexistant")) + pytester.parseconfig("--confcutdir", pytester.path.joinpath("nonexistent")) p = pytester.mkdir("dir") config = pytester.parseconfig("--confcutdir", p) @@ -827,6 +842,9 @@ def test_confcutdir_check_isdir(self, pytester: Pytester) -> None: (["src/bar/__init__.py"], ["bar"]), (["src/bar/__init__.py", "setup.py"], ["bar"]), (["source/python/bar/__init__.py", "setup.py"], ["bar"]), + # editable installation finder modules + (["__editable___xyz_finder.py"], []), + (["bar/__init__.py", "__editable___xyz_finder.py"], ["bar"]), ], ) def test_iter_rewritable_modules(self, names, expected) -> None: @@ -1264,15 +1282,21 @@ def pytest_load_initial_conftests(self): m = My() pm.register(m) hc = pm.hook.pytest_load_initial_conftests - values = hc._nonwrappers + hc._wrappers - expected = [ - "_pytest.config", - m.__module__, - "_pytest.pythonpath", - "_pytest.capture", - "_pytest.warnings", + hookimpls = [ + ( + hookimpl.function.__module__, + "wrapper" if hookimpl.hookwrapper else "nonwrapper", + ) + for hookimpl in hc.get_hookimpls() + ] + assert hookimpls == [ + ("_pytest.config", "nonwrapper"), + (m.__module__, "nonwrapper"), + ("_pytest.legacypath", "nonwrapper"), + ("_pytest.python_path", "nonwrapper"), + ("_pytest.capture", "wrapper"), + ("_pytest.warnings", "wrapper"), ] - assert [x.function.__module__ for x in values] == expected def test_get_plugin_specs_as_list() -> None: @@ -1839,8 +1863,7 @@ def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None assert result.ret == ExitCode.USAGE_ERROR result.stderr.fnmatch_lines( [ - "ERROR: not found: */test_config_blocked_default_plugins.py", - "(no name '*/test_config_blocked_default_plugins.py' in any of [])", + "ERROR: found no collectors for */test_config_blocked_default_plugins.py", ] ) return @@ -2102,8 +2125,8 @@ def test_debug_help(self, pytester: Pytester) -> None: result = pytester.runpytest("-h") result.stdout.fnmatch_lines( [ - "*store internal tracing debug information in this log*", - "*This file is opened with 'w' and truncated as a result*", - "*Defaults to 'pytestdebug.log'.", + "*Store internal tracing debug information in this log*", + "*file. This file is opened with 'w' and truncated as a*", + "*Default: pytestdebug.log.", ] ) diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 64c1014a533..d2bf860c6fe 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -114,6 +114,7 @@ def test_value_access_with_confmod(self, basedir: Path) -> None: "a", startdir, importmode="prepend", rootpath=Path(basedir) ) assert value == 1.5 + assert mod.__file__ is not None path = Path(mod.__file__) assert path.parent == basedir / "adir" / "b" assert path.stem == "conftest" @@ -145,10 +146,9 @@ def test_issue151_load_all_conftests(pytester: Pytester) -> None: p = pytester.mkdir(name) p.joinpath("conftest.py").touch() - conftest = PytestPluginManager() - conftest_setinitial(conftest, names) - d = list(conftest._conftestpath2mod.values()) - assert len(d) == len(names) + pm = PytestPluginManager() + conftest_setinitial(pm, names) + assert len(set(pm.get_plugins()) - {pm}) == len(names) def test_conftest_global_import(pytester: Pytester) -> None: @@ -191,18 +191,20 @@ def test_conftestcutdir(pytester: Pytester) -> None: conf.parent, importmode="prepend", rootpath=pytester.path ) assert len(values) == 0 - assert Path(conf) not in conftest._conftestpath2mod + assert not conftest.has_plugin(str(conf)) # but we can still import a conftest directly conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path) values = conftest._getconftestmodules( conf.parent, importmode="prepend", rootpath=pytester.path ) + assert values[0].__file__ is not None assert values[0].__file__.startswith(str(conf)) # and all sub paths get updated properly values = conftest._getconftestmodules( p, importmode="prepend", rootpath=pytester.path ) assert len(values) == 1 + assert values[0].__file__ is not None assert values[0].__file__.startswith(str(conf)) @@ -214,6 +216,7 @@ def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None: conf.parent, importmode="prepend", rootpath=pytester.path ) assert len(values) == 1 + assert values[0].__file__ is not None assert values[0].__file__.startswith(str(conf)) @@ -222,15 +225,15 @@ def test_setinitial_conftest_subdirs(pytester: Pytester, name: str) -> None: sub = pytester.mkdir(name) subconftest = sub.joinpath("conftest.py") subconftest.touch() - conftest = PytestPluginManager() - conftest_setinitial(conftest, [sub.parent], confcutdir=pytester.path) + pm = PytestPluginManager() + conftest_setinitial(pm, [sub.parent], confcutdir=pytester.path) key = subconftest.resolve() if name not in ("whatever", ".dotdir"): - assert key in conftest._conftestpath2mod - assert len(conftest._conftestpath2mod) == 1 + assert pm.has_plugin(str(key)) + assert len(set(pm.get_plugins()) - {pm}) == 1 else: - assert key not in conftest._conftestpath2mod - assert len(conftest._conftestpath2mod) == 0 + assert not pm.has_plugin(str(key)) + assert len(set(pm.get_plugins()) - {pm}) == 0 def test_conftest_confcutdir(pytester: Pytester) -> None: @@ -249,6 +252,34 @@ def pytest_addoption(parser): result.stdout.no_fnmatch_line("*warning: could not load initial*") +def test_installed_conftest_is_picked_up(pytester: Pytester, tmp_path: Path) -> None: + """When using `--pyargs` to run tests in an installed packages (located e.g. + in a site-packages in the PYTHONPATH), conftest files in there are picked + up. + + Regression test for #9767. + """ + # pytester dir - the source tree. + # tmp_path - the simulated site-packages dir (not in source tree). + + pytester.syspathinsert(tmp_path) + pytester.makepyprojecttoml("[tool.pytest.ini_options]") + tmp_path.joinpath("foo").mkdir() + tmp_path.joinpath("foo", "__init__.py").touch() + tmp_path.joinpath("foo", "conftest.py").write_text( + textwrap.dedent( + """\ + import pytest + @pytest.fixture + def fix(): return None + """ + ) + ) + tmp_path.joinpath("foo", "test_it.py").write_text("def test_it(fix): pass") + result = pytester.runpytest("--pyargs", "foo") + assert result.ret == 0 + + def test_conftest_symlink(pytester: Pytester) -> None: """`conftest.py` discovery follows normal path resolution and does not resolve symlinks.""" # Structure: @@ -522,7 +553,7 @@ def test_no_conftest(fxtr): ) ) print("created directory structure:") - for x in pytester.path.rglob(""): + for x in pytester.path.glob("**/"): print(" " + str(x.relative_to(pytester.path))) return {"runner": runner, "package": package, "swc": swc, "snc": snc} diff --git a/testing/test_debugging.py b/testing/test_debugging.py index a822bb57f58..eecc1e39fd3 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -8,14 +8,6 @@ from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester -try: - # Type ignored for Python <= 3.6. - breakpoint # type: ignore -except NameError: - SUPPORTS_BREAKPOINT_BUILTIN = False -else: - SUPPORTS_BREAKPOINT_BUILTIN = True - _ENVIRON_PYTHONBREAKPOINT = os.environ.get("PYTHONBREAKPOINT", "") @@ -28,9 +20,18 @@ def pdb_env(request): pytester._monkeypatch.setenv("PDBPP_HIJACK_PDB", "0") -def runpdb_and_get_report(pytester: Pytester, source: str): +def runpdb(pytester: Pytester, source: str): p = pytester.makepyfile(source) - result = pytester.runpytest_inprocess("--pdb", p) + return pytester.runpytest_inprocess("--pdb", p) + + +def runpdb_and_get_stdout(pytester: Pytester, source: str): + result = runpdb(pytester, source) + return result.stdout.str() + + +def runpdb_and_get_report(pytester: Pytester, source: str): + result = runpdb(pytester, source) reports = result.reprec.getreports("pytest_runtest_logreport") # type: ignore[attr-defined] assert len(reports) == 3, reports # setup/call/teardown return reports[1] @@ -132,6 +133,16 @@ def test_func(): assert rep.skipped assert len(pdblist) == 0 + def test_pdb_on_top_level_raise_skiptest(self, pytester, pdblist) -> None: + stdout = runpdb_and_get_stdout( + pytester, + """ + import unittest + raise unittest.SkipTest("This is a common way to skip an entire file.") + """, + ) + assert "entering PDB" not in stdout, stdout + def test_pdb_on_BdbQuit(self, pytester, pdblist) -> None: rep = runpdb_and_get_report( pytester, @@ -252,7 +263,7 @@ def test_pdb_print_captured_logs(self, pytester, showcapture: str) -> None: """ def test_1(): import logging - logging.warn("get " + "rekt") + logging.warning("get " + "rekt") assert False """ ) @@ -271,7 +282,7 @@ def test_pdb_print_captured_logs_nologging(self, pytester: Pytester) -> None: """ def test_1(): import logging - logging.warn("get " + "rekt") + logging.warning("get " + "rekt") assert False """ ) @@ -361,6 +372,7 @@ def test_pdb_prevent_ConftestImportFailure_hiding_exception( result = pytester.runpytest_subprocess("--pdb", ".") result.stdout.fnmatch_lines(["-> import unknown"]) + @pytest.mark.xfail(reason="#10042") def test_pdb_interaction_capturing_simple(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -529,6 +541,7 @@ def function_1(): assert "BdbQuit" not in rest assert "UNEXPECTED EXCEPTION" not in rest + @pytest.mark.xfail(reason="#10042") def test_pdb_interaction_capturing_twice(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -564,6 +577,7 @@ def test_1(): assert "1 failed" in rest self.flush(child) + @pytest.mark.xfail(reason="#10042") def test_pdb_with_injected_do_debug(self, pytester: Pytester) -> None: """Simulates pdbpp, which injects Pdb into do_debug, and uses self.__class__ in do_continue. @@ -911,14 +925,6 @@ def test_foo(): class TestDebuggingBreakpoints: - def test_supports_breakpoint_module_global(self) -> None: - """Test that supports breakpoint global marks on Python 3.7+.""" - if sys.version_info >= (3, 7): - assert SUPPORTS_BREAKPOINT_BUILTIN is True - - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) @pytest.mark.parametrize("arg", ["--pdb", ""]) def test_sys_breakpointhook_configure_and_unconfigure( self, pytester: Pytester, arg: str @@ -952,9 +958,6 @@ def test_nothing(): pass result = pytester.runpytest_subprocess(*args) result.stdout.fnmatch_lines(["*1 passed in *"]) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) def test_pdb_custom_cls(self, pytester: Pytester, custom_debugger_hook) -> None: p1 = pytester.makepyfile( """ @@ -969,9 +972,6 @@ def test_nothing(): assert custom_debugger_hook == ["init", "set_trace"] @pytest.mark.parametrize("arg", ["--pdb", ""]) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) def test_environ_custom_class( self, pytester: Pytester, custom_debugger_hook, arg: str ) -> None: @@ -1002,9 +1002,6 @@ def test_nothing(): pass result = pytester.runpytest_subprocess(*args) result.stdout.fnmatch_lines(["*1 passed in *"]) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) @pytest.mark.skipif( not _ENVIRON_PYTHONBREAKPOINT == "", reason="Requires breakpoint() default value", @@ -1025,9 +1022,7 @@ def test_1(): assert "reading from stdin while output" not in rest TestPDB.flush(child) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) + @pytest.mark.xfail(reason="#10042") def test_pdb_not_altered(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -1187,6 +1182,7 @@ def test_2(): @pytest.mark.parametrize("fixture", ("capfd", "capsys")) +@pytest.mark.xfail(reason="#10042") def test_pdb_suspends_fixture_capturing(pytester: Pytester, fixture: str) -> None: """Using "-s" with pytest should suspend/resume fixture capturing.""" p1 = pytester.makepyfile( diff --git a/testing/test_doctest.py b/testing/test_doctest.py index ca215e070fa..2f73feb8c4b 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,4 +1,5 @@ import inspect +import sys import textwrap from pathlib import Path from typing import Callable @@ -80,7 +81,7 @@ def test_collect_module_two_doctest_no_modulelevel( '# Empty' def my_func(): ">>> magic = 42 " - def unuseful(): + def useless(): ''' # This is a function # >>> # it doesn't have any doctest @@ -112,6 +113,28 @@ def test_simple_doctestfile(self, pytester: Pytester): reprec = pytester.inline_run(p) reprec.assertoutcome(failed=1) + def test_importmode(self, pytester: Pytester): + p = pytester.makepyfile( + **{ + "namespacepkg/innerpkg/__init__.py": "", + "namespacepkg/innerpkg/a.py": """ + def some_func(): + return 42 + """, + "namespacepkg/innerpkg/b.py": """ + from namespacepkg.innerpkg.a import some_func + def my_func(): + ''' + >>> my_func() + 42 + ''' + return some_func() + """, + } + ) + reprec = pytester.inline_run(p, "--doctest-modules", "--import-mode=importlib") + reprec.assertoutcome(passed=1) + def test_new_pattern(self, pytester: Pytester): p = pytester.maketxtfile( xdoc=""" @@ -200,6 +223,11 @@ def test_doctest_unexpected_exception(self, pytester: Pytester): "Traceback (most recent call last):", ' File "*/doctest.py", line *, in __run', " *", + *( + (" *^^^^*",) + if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) + else () + ), ' File "", line 1, in ', "ZeroDivisionError: division by zero", "*/test_doctest_unexpected_exception.txt:2: UnexpectedException", @@ -564,7 +592,7 @@ def my_func(): >>> magic - 42 0 ''' - def unuseful(): + def useless(): pass def another(): ''' @@ -801,12 +829,13 @@ def test_valid_setup_py(self, pytester: Pytester): """ p = pytester.makepyfile( setup=""" - from setuptools import setup, find_packages - setup(name='sample', - version='0.0', - description='description', - packages=find_packages() - ) + if __name__ == '__main__': + from setuptools import setup, find_packages + setup(name='sample', + version='0.0', + description='description', + packages=find_packages() + ) """ ) result = pytester.runpytest(p, "--doctest-modules") diff --git a/testing/test_error_diffs.py b/testing/test_error_diffs.py index 1668e929ab4..eb781210872 100644 --- a/testing/test_error_diffs.py +++ b/testing/test_error_diffs.py @@ -4,8 +4,6 @@ See https://github.com/pytest-dev/pytest/issues/3333 for details. """ -import sys - import pytest from _pytest.pytester import Pytester @@ -210,68 +208,61 @@ def test_this(): """, id='Test "not in" string', ), -] -if sys.version_info[:2] >= (3, 7): - TESTCASES.extend( - [ - pytest.param( - """ - from dataclasses import dataclass + pytest.param( + """ + from dataclasses import dataclass - @dataclass - class A: - a: int - b: str + @dataclass + class A: + a: int + b: str - def test_this(): - result = A(1, 'spam') - expected = A(2, 'spam') - assert result == expected - """, - """ - > assert result == expected - E AssertionError: assert A(a=1, b='spam') == A(a=2, b='spam') - E Matching attributes: - E ['b'] - E Differing attributes: - E ['a'] - E Drill down into differing attribute a: - E a: 1 != 2 - E +1 - E -2 - """, - id="Compare data classes", - ), - pytest.param( - """ - import attr + def test_this(): + result = A(1, 'spam') + expected = A(2, 'spam') + assert result == expected + """, + """ + > assert result == expected + E AssertionError: assert A(a=1, b='spam') == A(a=2, b='spam') + E Matching attributes: + E ['b'] + E Differing attributes: + E ['a'] + E Drill down into differing attribute a: + E a: 1 != 2 + """, + id="Compare data classes", + ), + pytest.param( + """ + import attr - @attr.s(auto_attribs=True) - class A: - a: int - b: str + @attr.s(auto_attribs=True) + class A: + a: int + b: str - def test_this(): - result = A(1, 'spam') - expected = A(1, 'eggs') - assert result == expected - """, - """ - > assert result == expected - E AssertionError: assert A(a=1, b='spam') == A(a=1, b='eggs') - E Matching attributes: - E ['a'] - E Differing attributes: - E ['b'] - E Drill down into differing attribute b: - E b: 'spam' != 'eggs' - E - eggs - E + spam - """, - id="Compare attrs classes", - ), - ] - ) + def test_this(): + result = A(1, 'spam') + expected = A(1, 'eggs') + assert result == expected + """, + """ + > assert result == expected + E AssertionError: assert A(a=1, b='spam') == A(a=1, b='eggs') + E Matching attributes: + E ['a'] + E Differing attributes: + E ['b'] + E Drill down into differing attribute b: + E b: 'spam' != 'eggs' + E - eggs + E + spam + """, + id="Compare attrs classes", + ), +] @pytest.mark.parametrize("code, expected", TESTCASES) diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index 3a2917261a2..8287de603ed 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -1,3 +1,4 @@ +import os from pathlib import Path from textwrap import dedent @@ -5,6 +6,7 @@ from _pytest.config import UsageError from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import get_dirs_from_args +from _pytest.config.findpaths import is_fs_root from _pytest.config.findpaths import load_config_dict_from_file @@ -133,3 +135,18 @@ def test_get_dirs_from_args(tmp_path): assert get_dirs_from_args( [str(fn), str(tmp_path / "does_not_exist"), str(d), option, xdist_rsync_option] ) == [fn.parent, d] + + +@pytest.mark.parametrize( + "path, expected", + [ + pytest.param( + f"e:{os.sep}", True, marks=pytest.mark.skipif("sys.platform != 'win32'") + ), + (f"{os.sep}", True), + (f"e:{os.sep}projects", False), + (f"{os.sep}projects", False), + ], +) +def test_is_fs_root(path: Path, expected: bool) -> None: + assert is_fs_root(Path(path)) is expected diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index 44c2c9295bf..ba89d0c4acf 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -30,11 +30,11 @@ def test_help(pytester: Pytester) -> None: assert result.ret == 0 result.stdout.fnmatch_lines( """ - -m MARKEXPR only run tests matching given mark expression. - For example: -m 'mark1 and not mark2'. - reporting: + -m MARKEXPR Only run tests matching given mark expression. For + example: -m 'mark1 and not mark2'. + Reporting: --durations=N * - -V, --version display pytest version and information about plugins. + -V, --version Display pytest version and information about plugins. When given twice, also display information about plugins. *setup.cfg* @@ -71,9 +71,9 @@ def pytest_addoption(parser): assert result.ret == 0 lines = [ " required_plugins (args):", - " plugins that must be present for pytest to run*", + " Plugins that must be present for pytest to run*", " test_ini (bool):*", - "environment variables:", + "Environment variables:", ] result.stdout.fnmatch_lines(lines, consecutive=True) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 02531e81435..b266c76d922 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1625,6 +1625,28 @@ def test_skip(): snode.assert_attr(message="1 <> 2") +def test_escaped_setup_teardown_error( + pytester: Pytester, run_and_parse: RunAndParse +) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.fixture() + def my_setup(): + raise Exception("error: \033[31mred\033[m") + + def test_esc(my_setup): + pass + """ + ) + _, dom = run_and_parse() + node = dom.find_first_by_tag("testcase") + snode = node.find_first_by_tag("error") + assert "#x1B[31mred#x1B[m" in snode["message"] + assert "#x1B[31mred#x1B[m" in snode.text + + @parametrize_families def test_logging_passing_tests_disabled_does_not_log_test_output( pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index 9ab139df467..fbfd88b7384 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -14,7 +14,7 @@ def test_item_fspath(pytester: pytest.Pytester) -> None: items2, hookrec = pytester.inline_genitems(item.nodeid) (item2,) = items2 assert item2.name == item.name - assert item2.fspath == item.fspath # type: ignore[attr-defined] + assert item2.fspath == item.fspath assert item2.path == item.path @@ -161,3 +161,20 @@ def test_overriden(pytestconfig): ) result = pytester.runpytest("--override-ini", "paths=foo/bar1.py foo/bar2.py", "-s") result.stdout.fnmatch_lines(["user_path:bar1.py", "user_path:bar2.py"]) + + +def test_inifile_from_cmdline_main_hook(pytester: pytest.Pytester) -> None: + """Ensure Config.inifile is available during pytest_cmdline_main (#9396).""" + p = pytester.makeini( + """ + [pytest] + """ + ) + pytester.makeconftest( + """ + def pytest_cmdline_main(config): + print("pytest_cmdline_main inifile =", config.inifile) + """ + ) + result = pytester.runpytest_subprocess("-s") + result.stdout.fnmatch_lines(f"*pytest_cmdline_main inifile = {p}") diff --git a/testing/test_main.py b/testing/test_main.py index 6a13633f6a9..71597626790 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -1,6 +1,7 @@ import argparse import os import re +import sys from pathlib import Path from typing import Optional @@ -44,16 +45,32 @@ def pytest_internalerror(excrepr, excinfo): assert result.ret == ExitCode.INTERNAL_ERROR assert result.stdout.lines[0] == "INTERNALERROR> Traceback (most recent call last):" + end_lines = ( + result.stdout.lines[-4:] + if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) + else result.stdout.lines[-3:] + ) + if exc == SystemExit: - assert result.stdout.lines[-3:] == [ + assert end_lines == [ f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart', 'INTERNALERROR> raise SystemExit("boom")', + *( + ("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",) + if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) + else () + ), "INTERNALERROR> SystemExit: boom", ] else: - assert result.stdout.lines[-3:] == [ + assert end_lines == [ f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart', 'INTERNALERROR> raise ValueError("boom")', + *( + ("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",) + if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) + else () + ), "INTERNALERROR> ValueError: boom", ] if returncode is False: @@ -171,6 +188,12 @@ def test_pypath(self, invocation_path: Path) -> None: invocation_path, "pkg::foo::bar", as_pypath=True ) + def test_parametrized_name_with_colons(self, invocation_path: Path) -> None: + ret = resolve_collection_argument( + invocation_path, "src/pkg/test.py::test[a::b]" + ) + assert ret == (invocation_path / "src/pkg/test.py", ["test[a::b]"]) + def test_does_not_exist(self, invocation_path: Path) -> None: """Given a file/module that does not exist raises UsageError.""" with pytest.raises( diff --git a/testing/test_mark.py b/testing/test_mark.py index da67d1ea7bc..e2d1a40c38a 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -823,25 +823,6 @@ def pytest_pycollect_makeitem(name): assert len(dlist) == 1 assert dlist[0].items[0].name == "test_1" - def test_select_starton(self, pytester: Pytester) -> None: - threepass = pytester.makepyfile( - test_threepass=""" - def test_one(): assert 1 - def test_two(): assert 1 - def test_three(): assert 1 - """ - ) - reprec = pytester.inline_run( - "-Wignore::pytest.PytestRemovedIn7Warning", "-k", "test_two:", threepass - ) - passed, skipped, failed = reprec.listoutcomes() - assert len(passed) == 2 - assert not failed - dlist = reprec.getcalls("pytest_deselected") - assert len(dlist) == 1 - item = dlist[0].items[0] - assert item.name == "test_one" - def test_keyword_extra(self, pytester: Pytester) -> None: p = pytester.makepyfile( """ @@ -1128,3 +1109,27 @@ def test_foo(): result = pytester.runpytest(foo, "-m", expr) result.stderr.fnmatch_lines([expected]) assert result.ret == ExitCode.USAGE_ERROR + + +def test_mark_mro() -> None: + xfail = pytest.mark.xfail + + @xfail("a") + class A: + pass + + @xfail("b") + class B: + pass + + @xfail("c") + class C(A, B): + pass + + from _pytest.mark.structures import get_unpacked_marks + + all_marks = get_unpacked_marks(C) + + assert all_marks == [xfail("c").mark, xfail("a").mark, xfail("b").mark] + + assert get_unpacked_marks(C, consider_mro=False) == [xfail("c").mark] diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 95521818021..b46ec05bbd2 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -50,21 +50,24 @@ class A: class TestSetattrWithImportPath: def test_string_expression(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr("os.path.abspath", lambda x: "hello2") - assert os.path.abspath("123") == "hello2" + with monkeypatch.context() as mp: + mp.setattr("os.path.abspath", lambda x: "hello2") + assert os.path.abspath("123") == "hello2" def test_string_expression_class(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr("_pytest.config.Config", 42) - import _pytest + with monkeypatch.context() as mp: + mp.setattr("_pytest.config.Config", 42) + import _pytest - assert _pytest.config.Config == 42 # type: ignore + assert _pytest.config.Config == 42 # type: ignore def test_unicode_string(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setattr("_pytest.config.Config", 42) - import _pytest + with monkeypatch.context() as mp: + mp.setattr("_pytest.config.Config", 42) + import _pytest - assert _pytest.config.Config == 42 # type: ignore - monkeypatch.delattr("_pytest.config.Config") + assert _pytest.config.Config == 42 # type: ignore + mp.delattr("_pytest.config.Config") def test_wrong_target(self, monkeypatch: MonkeyPatch) -> None: with pytest.raises(TypeError): @@ -80,14 +83,16 @@ def test_unknown_attr(self, monkeypatch: MonkeyPatch) -> None: def test_unknown_attr_non_raising(self, monkeypatch: MonkeyPatch) -> None: # https://github.com/pytest-dev/pytest/issues/746 - monkeypatch.setattr("os.path.qweqwe", 42, raising=False) - assert os.path.qweqwe == 42 # type: ignore + with monkeypatch.context() as mp: + mp.setattr("os.path.qweqwe", 42, raising=False) + assert os.path.qweqwe == 42 # type: ignore def test_delattr(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.delattr("os.path.abspath") - assert not hasattr(os.path, "abspath") - monkeypatch.undo() - assert os.path.abspath + with monkeypatch.context() as mp: + mp.delattr("os.path.abspath") + assert not hasattr(os.path, "abspath") + mp.undo() + assert os.path.abspath def test_delattr() -> None: @@ -420,6 +425,9 @@ class A: assert A.x == 1 +@pytest.mark.filterwarnings( + "ignore:Deprecated call to `pkg_resources.declare_namespace" +) def test_syspath_prepend_with_namespace_packages( pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: diff --git a/testing/test_nodes.py b/testing/test_nodes.py index c8afe0252be..df1439e1c49 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -1,3 +1,5 @@ +import re +import warnings from pathlib import Path from typing import cast from typing import List @@ -58,30 +60,31 @@ def test_subclassing_both_item_and_collector_deprecated( request, tmp_path: Path ) -> None: """ - Verifies we warn on diamond inheritance - as well as correctly managing legacy inheritance ctors with missing args - as found in plugins + Verifies we warn on diamond inheritance as well as correctly managing legacy + inheritance constructors with missing args as found in plugins. """ - with pytest.warns( - PytestWarning, - match=( - "(?m)SoWrong is an Item subclass and should not be a collector, however its bases File are collectors.\n" - "Please split the Collectors and the Item into separate node types.\n.*" - ), - ): + # We do not expect any warnings messages to issued during class definition. + with warnings.catch_warnings(): + warnings.simplefilter("error") class SoWrong(nodes.Item, nodes.File): def __init__(self, fspath, parent): """Legacy ctor with legacy call # don't wana see""" super().__init__(fspath, parent) - with pytest.warns( - PytestWarning, match=".*SoWrong.* not using a cooperative constructor.*" - ): + with pytest.warns(PytestWarning) as rec: SoWrong.from_parent( request.session, fspath=legacy_path(tmp_path / "broken.txt") ) + messages = [str(x.message) for x in rec] + assert any( + re.search(".*SoWrong.* not using a cooperative constructor.*", x) + for x in messages + ) + assert any( + re.search("(?m)SoWrong .* should not be a collector", x) for x in messages + ) @pytest.mark.parametrize( diff --git a/testing/test_nose.py b/testing/test_nose.py index edca0f4463e..e838e79ddd5 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -37,7 +37,7 @@ class A: def f(self): values.append(1) - call_optional(A(), "f") + call_optional(A(), "f", "A.f") assert not values @@ -47,7 +47,7 @@ def test_setup_func_not_callable() -> None: class A: f = 1 - call_optional(A(), "f") + call_optional(A(), "f", "A.f") def test_nose_setup_func(pytester: Pytester) -> None: @@ -345,7 +345,7 @@ def test_failing(): """ ) result = pytester.runpytest(p) - result.assert_outcomes(skipped=1, warnings=1) + result.assert_outcomes(skipped=1, warnings=0) def test_SkipTest_in_test(pytester: Pytester) -> None: @@ -477,3 +477,43 @@ def test_raises_baseexception_caught(): "* 1 failed, 2 passed *", ] ) + + +def test_nose_setup_skipped_if_non_callable(pytester: Pytester) -> None: + """Regression test for #9391.""" + p = pytester.makepyfile( + __init__="", + setup=""" + """, + teardown=""" + """, + test_it=""" + from . import setup, teardown + + def test_it(): + pass + """, + ) + result = pytester.runpytest(p, "-p", "nose") + assert result.ret == 0 + + +@pytest.mark.parametrize("fixture_name", ("teardown", "teardown_class")) +def test_teardown_fixture_not_called_directly(fixture_name, pytester: Pytester) -> None: + """Regression test for #10597.""" + p = pytester.makepyfile( + f""" + import pytest + + class TestHello: + + @pytest.fixture + def {fixture_name}(self): + yield + + def test_hello(self, {fixture_name}): + assert True + """ + ) + result = pytester.runpytest(p, "-p", "nose") + assert result.ret == 0 diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index 28529d04378..992f49bc53c 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -295,7 +295,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True, - universal_newlines=True, + text=True, ).stdout except (OSError, subprocess.CalledProcessError): pytest.skip("bash is not available") diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index b338519ae17..86b231f8b5e 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -1,3 +1,4 @@ +import email.message import io from typing import List from typing import Union @@ -98,7 +99,9 @@ def mocked_urlopen_fail(self, monkeypatch: MonkeyPatch): def mocked(url, data): calls.append((url, data)) - raise urllib.error.HTTPError(url, 400, "Bad request", {}, io.BytesIO()) + raise urllib.error.HTTPError( + url, 400, "Bad request", email.message.Message(), io.BytesIO() + ) monkeypatch.setattr(urllib.request, "urlopen", mocked) return calls diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 5eb153e847d..577c7749fd9 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -91,6 +91,12 @@ def path1(self, tmp_path_factory: TempPathFactory) -> Generator[Path, None, None yield path assert path.joinpath("samplefile").exists() + @pytest.fixture(autouse=True) + def preserve_sys(self): + with unittest.mock.patch.dict(sys.modules): + with unittest.mock.patch.object(sys, "path", list(sys.path)): + yield + def setuptestfs(self, path: Path) -> None: # print "setting up test fs for", repr(path) samplefile = path / "samplefile" @@ -143,6 +149,10 @@ def test_smoke_test(self, path1: Path) -> None: assert obj.x == 42 # type: ignore[attr-defined] assert obj.__name__ == "execfile" + def test_import_path_missing_file(self, path1: Path) -> None: + with pytest.raises(ImportPathMismatchError): + import_path(path1 / "sampledir", root=path1) + def test_renamed_dir_creates_mismatch( self, tmp_path: Path, monkeypatch: MonkeyPatch ) -> None: @@ -450,7 +460,6 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N class TestImportLibMode: - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_importmode_importlib_with_dataclass(self, tmp_path: Path) -> None: """Ensure that importlib mode works with a module containing dataclasses (#7856).""" fn = tmp_path.joinpath("_src/tests/test_dataclass.py") @@ -559,15 +568,20 @@ def test_module_name_from_path(self, tmp_path: Path) -> None: result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar")) assert result == "home.foo.test_foo" - def test_insert_missing_modules(self) -> None: - modules = {"src.tests.foo": ModuleType("src.tests.foo")} - insert_missing_modules(modules, "src.tests.foo") - assert sorted(modules) == ["src", "src.tests", "src.tests.foo"] + def test_insert_missing_modules( + self, monkeypatch: MonkeyPatch, tmp_path: Path + ) -> None: + monkeypatch.chdir(tmp_path) + # Use 'xxx' and 'xxy' as parent names as they are unlikely to exist and + # don't end up being imported. + modules = {"xxx.tests.foo": ModuleType("xxx.tests.foo")} + insert_missing_modules(modules, "xxx.tests.foo") + assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"] mod = ModuleType("mod", doc="My Module") - modules = {"src": mod} - insert_missing_modules(modules, "src") - assert modules == {"src": mod} + modules = {"xxy": mod} + insert_missing_modules(modules, "xxy") + assert modules == {"xxy": mod} modules = {} insert_missing_modules(modules, "") diff --git a/testing/test_pytester.py b/testing/test_pytester.py index bc6e52aba0e..62dad98589d 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -618,14 +618,9 @@ def test_linematcher_string_api() -> None: def test_pytest_addopts_before_pytester(request, monkeypatch: MonkeyPatch) -> None: - orig = os.environ.get("PYTEST_ADDOPTS", None) monkeypatch.setenv("PYTEST_ADDOPTS", "--orig-unused") - pytester: Pytester = request.getfixturevalue("pytester") + _: Pytester = request.getfixturevalue("pytester") assert "PYTEST_ADDOPTS" not in os.environ - pytester._finalize() - assert os.environ.get("PYTEST_ADDOPTS") == "--orig-unused" - monkeypatch.undo() - assert os.environ.get("PYTEST_ADDOPTS") == orig def test_run_stdin(pytester: Pytester) -> None: @@ -743,8 +738,8 @@ def test_run_result_repr() -> None: # known exit code r = pytester_mod.RunResult(1, outlines, errlines, duration=0.5) - assert ( - repr(r) == "" ) @@ -835,6 +830,8 @@ def test_with_warning(): ) result = pytester.runpytest() result.assert_outcomes(passed=1, warnings=1) + # If warnings is not passed, it is not checked at all. + result.assert_outcomes(passed=1) def test_pytester_outcomes_deselected(pytester: Pytester) -> None: @@ -849,3 +846,5 @@ def test_two(): ) result = pytester.runpytest("-k", "test_one") result.assert_outcomes(passed=1, deselected=1) + # If deselected is not passed, it is not checked at all. + result.assert_outcomes(passed=1) diff --git a/testing/test_pythonpath.py b/testing/test_python_path.py similarity index 98% rename from testing/test_pythonpath.py rename to testing/test_python_path.py index 97c439ce0c3..5ee0f55e36a 100644 --- a/testing/test_pythonpath.py +++ b/testing/test_python_path.py @@ -81,7 +81,7 @@ def test_no_ini(pytester: Pytester, file_structure) -> None: def test_clean_up(pytester: Pytester) -> None: - """Test that the pythonpath plugin cleans up after itself.""" + """Test that the plugin cleans up after itself.""" # This is tough to test behaviorly because the cleanup really runs last. # So the test make several implementation assumptions: # - Cleanup is done in pytest_unconfigure(). diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index d3f218f1660..7e0f836a634 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -1,4 +1,3 @@ -import re import warnings from typing import Optional @@ -114,13 +113,13 @@ def test_deprecated_call_preserves(self) -> None: # Type ignored because `onceregistry` and `filters` are not # documented API. onceregistry = warnings.onceregistry.copy() # type: ignore - filters = warnings.filters[:] # type: ignore + filters = warnings.filters[:] warn = warnings.warn warn_explicit = warnings.warn_explicit self.test_deprecated_call_raises() self.test_deprecated_call() assert onceregistry == warnings.onceregistry # type: ignore - assert filters == warnings.filters # type: ignore + assert filters == warnings.filters assert warn is warnings.warn assert warn_explicit is warnings.warn_explicit @@ -263,7 +262,7 @@ def test_as_contextmanager(self) -> None: with pytest.warns(RuntimeWarning): warnings.warn("user", UserWarning) excinfo.match( - r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted. " + r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted.\n" r"The list of emitted warnings is: \[UserWarning\('user',?\)\]." ) @@ -271,15 +270,15 @@ def test_as_contextmanager(self) -> None: with pytest.warns(UserWarning): warnings.warn("runtime", RuntimeWarning) excinfo.match( - r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. " - r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\]." + r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n" + r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)]." ) with pytest.raises(pytest.fail.Exception) as excinfo: with pytest.warns(UserWarning): pass excinfo.match( - r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. " + r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n" r"The list of emitted warnings is: \[\]." ) @@ -289,18 +288,14 @@ def test_as_contextmanager(self) -> None: warnings.warn("runtime", RuntimeWarning) warnings.warn("import", ImportWarning) - message_template = ( - "DID NOT WARN. No warnings of type {0} were emitted. " - "The list of emitted warnings is: {1}." - ) - excinfo.match( - re.escape( - message_template.format( - warning_classes, [each.message for each in warninfo] - ) - ) + messages = [each.message for each in warninfo] + expected_str = ( + f"DID NOT WARN. No warnings of type {warning_classes} were emitted.\n" + f"The list of emitted warnings is: {messages}." ) + assert str(excinfo.value) == expected_str + def test_record(self) -> None: with pytest.warns(UserWarning) as record: warnings.warn("user", UserWarning) diff --git a/testing/test_runner.py b/testing/test_runner.py index a34cd98f964..2e2c462d978 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -287,7 +287,7 @@ def teardown_class(cls): def test_func(): import sys - # on python2 exc_info is keept till a function exits + # on python2 exc_info is kept till a function exits # so we would end up calling test functions while # sys.exc_info would return the indexerror # from guessing the lastitem diff --git a/testing/test_session.py b/testing/test_session.py index 3ca6d390383..f73dc89ef33 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -335,6 +335,54 @@ def pytest_sessionfinish(): assert res.ret == ExitCode.NO_TESTS_COLLECTED +def test_collection_args_do_not_duplicate_modules(pytester: Pytester) -> None: + """Test that when multiple collection args are specified on the command line + for the same module, only a single Module collector is created. + + Regression test for #723, #3358. + """ + pytester.makepyfile( + **{ + "d/test_it": """ + def test_1(): pass + def test_2(): pass + """ + } + ) + + result = pytester.runpytest( + "--collect-only", + "d/test_it.py::test_1", + "d/test_it.py::test_2", + ) + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + ], + consecutive=True, + ) + + # Different, but related case. + result = pytester.runpytest( + "--collect-only", + "--keep-duplicates", + "d", + "d", + ) + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + " ", + ], + consecutive=True, + ) + + @pytest.mark.parametrize("path", ["root", "{relative}/root", "{environment}/root"]) def test_rootdir_option_arg( pytester: Pytester, monkeypatch: MonkeyPatch, path: str diff --git a/testing/test_skipping.py b/testing/test_skipping.py index a0b5cddabce..892ed85476b 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -441,10 +441,8 @@ def test_this_false(): result = pytester.runpytest(p, "-rx") result.stdout.fnmatch_lines( [ - "*test_one*test_this*", - "*NOTRUN*noway", - "*test_one*test_this_true*", - "*NOTRUN*condition:*True*", + "*test_one*test_this - reason: *NOTRUN* noway", + "*test_one*test_this_true - reason: *NOTRUN* condition: True", "*1 passed*", ] ) @@ -461,9 +459,7 @@ def setup_module(mod): """ ) result = pytester.runpytest(p, "-rx") - result.stdout.fnmatch_lines( - ["*test_one*test_this*", "*NOTRUN*hello", "*1 xfailed*"] - ) + result.stdout.fnmatch_lines(["*test_one*test_this*NOTRUN*hello", "*1 xfailed*"]) def test_xfail_xpass(self, pytester: Pytester) -> None: p = pytester.makepyfile( @@ -489,7 +485,7 @@ def test_this(): result = pytester.runpytest(p) result.stdout.fnmatch_lines(["*1 xfailed*"]) result = pytester.runpytest(p, "-rx") - result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"]) + result.stdout.fnmatch_lines(["*XFAIL*test_this*reason:*hello*"]) result = pytester.runpytest(p, "--runxfail") result.stdout.fnmatch_lines(["*1 pass*"]) @@ -507,7 +503,7 @@ def test_this(): result = pytester.runpytest(p) result.stdout.fnmatch_lines(["*1 xfailed*"]) result = pytester.runpytest(p, "-rx") - result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"]) + result.stdout.fnmatch_lines(["*XFAIL*test_this*reason:*hello*"]) result = pytester.runpytest(p, "--runxfail") result.stdout.fnmatch_lines( """ @@ -543,7 +539,7 @@ def test_this(arg): """ ) result = pytester.runpytest(p, "-rxX") - result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*NOTRUN*"]) + result.stdout.fnmatch_lines(["*XFAIL*test_this*NOTRUN*"]) def test_dynamic_xfail_set_during_funcarg_setup(self, pytester: Pytester) -> None: p = pytester.makepyfile( @@ -622,7 +618,7 @@ def test_foo(): """ ) result = pytester.runpytest(p, "-rxX") - result.stdout.fnmatch_lines(["*XFAIL*", "*unsupported feature*"]) + result.stdout.fnmatch_lines(["*XFAIL*unsupported feature*"]) assert result.ret == 0 @pytest.mark.parametrize("strict", [True, False]) @@ -1143,8 +1139,6 @@ def test_func(): pypy_version_info = getattr(sys, "pypy_version_info", None) if pypy_version_info is not None and pypy_version_info < (6,): markline = markline[5:] - elif sys.version_info[:2] >= (3, 10): - markline = markline[11:] elif sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"): markline = markline[4:] @@ -1187,7 +1181,7 @@ def test_boolean(): """ ) result = pytester.runpytest("-rsx") - result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*", "*x == 3*"]) + result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*x == 3*"]) def test_default_markers(pytester: Pytester) -> None: @@ -1299,8 +1293,7 @@ def test_func(): result = pytester.runpytest("-rxs") result.stdout.fnmatch_lines( """ - *XFAIL* - *True123* + *XFAIL*True123* *1 xfail* """ ) @@ -1446,6 +1439,27 @@ def test_pass(): ) +def test_skip_from_fixture(pytester: Pytester) -> None: + pytester.makepyfile( + **{ + "tests/test_1.py": """ + import pytest + def test_pass(arg): + pass + @pytest.fixture + def arg(): + condition = True + if condition: + pytest.skip("Fixture conditional skip") + """, + } + ) + result = pytester.runpytest("-rs", "tests/test_1.py", "--rootdir=tests") + result.stdout.fnmatch_lines( + ["SKIPPED [[]1[]] tests/test_1.py:2: Fixture conditional skip"] + ) + + def test_skip_using_reason_works_ok(pytester: Pytester) -> None: p = pytester.makepyfile( """ diff --git a/testing/test_stash.py b/testing/test_stash.py index bb294f5da35..2c9df4832e4 100644 --- a/testing/test_stash.py +++ b/testing/test_stash.py @@ -56,7 +56,7 @@ def test_stash() -> None: with pytest.raises(AttributeError): stash.foo = "nope" # type: ignore[attr-defined] - # No interaction with anoter stash. + # No interaction with another stash. stash2 = Stash() key3 = StashKey[int]() assert key2 not in stash2 diff --git a/testing/test_stepwise.py b/testing/test_stepwise.py index 63d29d6241f..2094abc4e50 100644 --- a/testing/test_stepwise.py +++ b/testing/test_stepwise.py @@ -1,6 +1,10 @@ +from pathlib import Path + import pytest +from _pytest.cacheprovider import Cache from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +from _pytest.stepwise import STEPWISE_CACHE_DIR @pytest.fixture @@ -277,4 +281,77 @@ def test_three(): def test_sw_skip_help(pytester: Pytester) -> None: result = pytester.runpytest("-h") - result.stdout.fnmatch_lines("*implicitly enables --stepwise.") + result.stdout.fnmatch_lines("*Implicitly enables --stepwise.") + + +def test_stepwise_xdist_dont_store_lastfailed(pytester: Pytester) -> None: + pytester.makefile( + ext=".ini", + pytest=f"[pytest]\ncache_dir = {pytester.path}\n", + ) + + pytester.makepyfile( + conftest=""" +import pytest + +@pytest.hookimpl(tryfirst=True) +def pytest_configure(config) -> None: + config.workerinput = True +""" + ) + pytester.makepyfile( + test_one=""" +def test_one(): + assert False +""" + ) + result = pytester.runpytest("--stepwise") + assert result.ret == pytest.ExitCode.INTERRUPTED + + stepwise_cache_file = ( + pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR + ) + assert not Path(stepwise_cache_file).exists() + + +def test_disabled_stepwise_xdist_dont_clear_cache(pytester: Pytester) -> None: + pytester.makefile( + ext=".ini", + pytest=f"[pytest]\ncache_dir = {pytester.path}\n", + ) + + stepwise_cache_file = ( + pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR + ) + stepwise_cache_dir = stepwise_cache_file.parent + stepwise_cache_dir.mkdir(exist_ok=True, parents=True) + + stepwise_cache_file_relative = f"{Cache._CACHE_PREFIX_VALUES}/{STEPWISE_CACHE_DIR}" + + expected_value = '"test_one.py::test_one"' + content = {f"{stepwise_cache_file_relative}": expected_value} + + pytester.makefile(ext="", **content) + + pytester.makepyfile( + conftest=""" +import pytest + +@pytest.hookimpl(tryfirst=True) +def pytest_configure(config) -> None: + config.workerinput = True +""" + ) + pytester.makepyfile( + test_one=""" +def test_one(): + assert True +""" + ) + result = pytester.runpytest() + assert result.ret == 0 + + assert Path(stepwise_cache_file).exists() + with stepwise_cache_file.open() as file_handle: + observed_value = file_handle.readlines() + assert [expected_value] == observed_value diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 23f597e3325..453f183236f 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -385,21 +385,55 @@ def test_9(): def test_10(): pytest.xfail("It's 🕙 o'clock") + + @pytest.mark.skip( + reason="cannot do foobar because baz is missing due to I don't know what" + ) + def test_long_skip(): + pass + + @pytest.mark.xfail( + reason="cannot do foobar because baz is missing due to I don't know what" + ) + def test_long_xfail(): + print(1 / 0) """ ) + + common_output = [ + "test_verbose_skip_reason.py::test_1 SKIPPED (123) *", + "test_verbose_skip_reason.py::test_2 XPASS (456) *", + "test_verbose_skip_reason.py::test_3 XFAIL (789) *", + "test_verbose_skip_reason.py::test_4 XFAIL *", + "test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *", + "test_verbose_skip_reason.py::test_6 XPASS *", + "test_verbose_skip_reason.py::test_7 SKIPPED *", + "test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *", + "test_verbose_skip_reason.py::test_9 XFAIL *", + "test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *", + ] + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( - [ - "test_verbose_skip_reason.py::test_1 SKIPPED (123) *", - "test_verbose_skip_reason.py::test_2 XPASS (456) *", - "test_verbose_skip_reason.py::test_3 XFAIL (789) *", - "test_verbose_skip_reason.py::test_4 XFAIL *", - "test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *", - "test_verbose_skip_reason.py::test_6 XPASS *", - "test_verbose_skip_reason.py::test_7 SKIPPED *", - "test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *", - "test_verbose_skip_reason.py::test_9 XFAIL *", - "test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *", + common_output + + [ + "test_verbose_skip_reason.py::test_long_skip SKIPPED (cannot *...) *", + "test_verbose_skip_reason.py::test_long_xfail XFAIL (cannot *...) *", + ] + ) + + result = pytester.runpytest("-vv") + result.stdout.fnmatch_lines( + common_output + + [ + ( + "test_verbose_skip_reason.py::test_long_skip SKIPPED" + " (cannot do foobar because baz is missing due to I don't know what) *" + ), + ( + "test_verbose_skip_reason.py::test_long_xfail XFAIL" + " (cannot do foobar because baz is missing due to I don't know what) *" + ), ] ) @@ -682,9 +716,7 @@ def test_three(): pass """ ) - result = pytester.runpytest( - "-Wignore::pytest.PytestRemovedIn7Warning", "-k", "test_two:", testpath - ) + result = pytester.runpytest("-k", "test_t", testpath) result.stdout.fnmatch_lines( ["collected 3 items / 1 deselected / 2 selected", "*test_deselected.py ..*"] ) @@ -751,6 +783,33 @@ def test_pass(): result.stdout.no_fnmatch_line("*= 1 deselected =*") assert result.ret == 0 + def test_selected_count_with_error(self, pytester: Pytester) -> None: + pytester.makepyfile( + test_selected_count_3=""" + def test_one(): + pass + def test_two(): + pass + def test_three(): + pass + """, + test_selected_count_error=""" + 5/0 + def test_foo(): + pass + def test_bar(): + pass + """, + ) + result = pytester.runpytest("-k", "test_t") + result.stdout.fnmatch_lines( + [ + "collected 3 items / 1 error / 1 deselected / 2 selected", + "* ERROR collecting test_selected_count_error.py *", + ] + ) + assert result.ret == ExitCode.INTERRUPTED + def test_no_skip_summary_if_failure(self, pytester: Pytester) -> None: pytester.makepyfile( """ @@ -939,6 +998,22 @@ def test_showlocals(): ] ) + def test_noshowlocals_addopts_override(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]\naddopts=--showlocals") + p1 = pytester.makepyfile( + """ + def test_noshowlocals(): + x = 3 + y = "x" * 5000 + assert 0 + """ + ) + + # Override global --showlocals for py.test via arg + result = pytester.runpytest(p1, "--no-showlocals") + result.stdout.no_fnmatch_line("x* = 3") + result.stdout.no_fnmatch_line("y* = 'xxxxxx*") + def test_showlocals_short(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -1080,7 +1155,21 @@ def test(): assert result.stdout.lines.count(expected) == 1 -def test_fail_extra_reporting(pytester: Pytester, monkeypatch) -> None: +@pytest.mark.parametrize( + ("use_ci", "expected_message"), + ( + (True, f"- AssertionError: {'this_failed'*100}"), + (False, "- AssertionError: this_failedt..."), + ), + ids=("on CI", "not on CI"), +) +def test_fail_extra_reporting( + pytester: Pytester, monkeypatch, use_ci: bool, expected_message: str +) -> None: + if use_ci: + monkeypatch.setenv("CI", "true") + else: + monkeypatch.delenv("CI", raising=False) monkeypatch.setenv("COLUMNS", "80") pytester.makepyfile("def test_this(): assert 0, 'this_failed' * 100") result = pytester.runpytest("-rN") @@ -1089,7 +1178,7 @@ def test_fail_extra_reporting(pytester: Pytester, monkeypatch) -> None: result.stdout.fnmatch_lines( [ "*test summary*", - "FAILED test_fail_extra_reporting.py::test_this - AssertionError: this_failedt...", + f"FAILED test_fail_extra_reporting.py::test_this {expected_message}", ] ) @@ -1176,14 +1265,14 @@ def test_this(): "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "", - " {kw}def{hl-reset} {function}test_this{hl-reset}():", - "> fail()", + " {kw}def{hl-reset} {function}test_this{hl-reset}():{endline}", + "> fail(){endline}", "", "{bold}{red}test_color_yes.py{reset}:5: ", "_ _ * _ _*", "", - " {kw}def{hl-reset} {function}fail{hl-reset}():", - "> {kw}assert{hl-reset} {number}0{hl-reset}", + " {kw}def{hl-reset} {function}fail{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "", "{bold}{red}test_color_yes.py{reset}:2: AssertionError", @@ -1203,9 +1292,9 @@ def test_this(): "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "{bold}{red}test_color_yes.py{reset}:5: in test_this", - " fail()", + " fail(){endline}", "{bold}{red}test_color_yes.py{reset}:2: in fail", - " {kw}assert{hl-reset} {number}0{hl-reset}", + " {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}", ] @@ -2260,7 +2349,7 @@ def test_line_with_reprcrash(monkeypatch: MonkeyPatch) -> None: def mock_get_pos(*args): return mocked_pos - monkeypatch.setattr(_pytest.terminal, "_get_pos", mock_get_pos) + monkeypatch.setattr(_pytest.terminal, "_get_node_id_with_markup", mock_get_pos) class config: pass @@ -2274,10 +2363,16 @@ class reprcrash: pass def check(msg, width, expected): + class DummyTerminalWriter: + fullwidth = width + + def markup(self, word: str, **markup: str): + return word + __tracebackhide__ = True if msg: rep.longrepr.reprcrash.message = msg # type: ignore - actual = _get_line_with_reprcrash_message(config, rep(), width) # type: ignore + actual = _get_line_with_reprcrash_message(config, rep(), DummyTerminalWriter(), {}) # type: ignore assert actual == expected if actual != f"{mocked_verbose_word} {mocked_pos}": @@ -2377,8 +2472,8 @@ def test_foo(): result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", - "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] ) @@ -2399,9 +2494,9 @@ def test_foo(): result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", " {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}", - "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}", + "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", ] ) @@ -2422,8 +2517,8 @@ def test_foo(): result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", - "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] ) diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 12bcb9361a4..d917d331a03 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1241,12 +1241,15 @@ def test_2(self): @pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) -def test_pdb_teardown_skipped( +def test_pdb_teardown_skipped_for_functions( pytester: Pytester, monkeypatch: MonkeyPatch, mark: str ) -> None: - """With --pdb, setUp and tearDown should not be called for skipped tests.""" + """ + With --pdb, setUp and tearDown should not be called for tests skipped + via a decorator (#7215). + """ tracked: List[str] = [] - monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False) + monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) pytester.makepyfile( """ @@ -1256,10 +1259,10 @@ def test_pdb_teardown_skipped( class MyTestCase(unittest.TestCase): def setUp(self): - pytest.test_pdb_teardown_skipped.append("setUp:" + self.id()) + pytest.track_pdb_teardown_skipped.append("setUp:" + self.id()) def tearDown(self): - pytest.test_pdb_teardown_skipped.append("tearDown:" + self.id()) + pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id()) {mark}("skipped for reasons") def test_1(self): @@ -1274,6 +1277,43 @@ def test_1(self): assert tracked == [] +@pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) +def test_pdb_teardown_skipped_for_classes( + pytester: Pytester, monkeypatch: MonkeyPatch, mark: str +) -> None: + """ + With --pdb, setUp and tearDown should not be called for tests skipped + via a decorator on the class (#10060). + """ + tracked: List[str] = [] + monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) + + pytester.makepyfile( + """ + import unittest + import pytest + + {mark}("skipped for reasons") + class MyTestCase(unittest.TestCase): + + def setUp(self): + pytest.track_pdb_teardown_skipped.append("setUp:" + self.id()) + + def tearDown(self): + pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id()) + + def test_1(self): + pass + + """.format( + mark=mark + ) + ) + result = pytester.runpytest_inprocess("--pdb") + result.stdout.fnmatch_lines("* 1 skipped in *") + assert tracked == [] + + def test_async_support(pytester: Pytester) -> None: pytest.importorskip("unittest.async_case") @@ -1472,3 +1512,56 @@ def test_cleanup_called_the_right_number_of_times(): passed, skipped, failed = reprec.countoutcomes() assert failed == 2 assert passed == 1 + + +def test_traceback_pruning(pytester: Pytester) -> None: + """Regression test for #9610 - doesn't crash during traceback pruning.""" + pytester.makepyfile( + """ + import unittest + + class MyTestCase(unittest.TestCase): + def __init__(self, test_method): + unittest.TestCase.__init__(self, test_method) + + class TestIt(MyTestCase): + @classmethod + def tearDownClass(cls) -> None: + assert False + + def test_it(self): + pass + """ + ) + reprec = pytester.inline_run() + passed, skipped, failed = reprec.countoutcomes() + assert passed == 1 + assert failed == 1 + assert reprec.ret == 1 + + +def test_raising_unittest_skiptest_during_collection( + pytester: Pytester, +) -> None: + pytester.makepyfile( + """ + import unittest + + class TestIt(unittest.TestCase): + def test_it(self): pass + def test_it2(self): pass + + raise unittest.SkipTest() + + class TestIt2(unittest.TestCase): + def test_it(self): pass + def test_it2(self): pass + """ + ) + reprec = pytester.inline_run() + passed, skipped, failed = reprec.countoutcomes() + assert passed == 0 + # Unittest reports one fake test for a skipped module. + assert skipped == 1 + assert failed == 0 + assert reprec.ret == ExitCode.NO_TESTS_COLLECTED diff --git a/testing/test_warning_types.py b/testing/test_warning_types.py index b49cc68f9c6..5f69439ef3e 100644 --- a/testing/test_warning_types.py +++ b/testing/test_warning_types.py @@ -36,3 +36,11 @@ def test(): ) result = pytester.runpytest() result.stdout.fnmatch_lines(["E pytest.PytestWarning: some warning"]) + + +@pytest.mark.filterwarnings("error") +def test_warn_explicit_for_annotates_errors_with_location(): + with pytest.raises(Warning, match="(?m)test\n at .*python_api.py:\\d+"): + warning_types.warn_explicit_for( + pytest.raises, warning_types.PytestWarning("test") # type: ignore + ) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index cac716680f4..7b716bb4546 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -1,4 +1,5 @@ import os +import sys import warnings from typing import List from typing import Optional @@ -239,7 +240,7 @@ def test_func(): @pytest.mark.filterwarnings("always::UserWarning") -def test_warning_captured_hook(pytester: Pytester) -> None: +def test_warning_recorded_hook(pytester: Pytester) -> None: pytester.makeconftest( """ def pytest_configure(config): @@ -276,9 +277,9 @@ def pytest_warning_recorded(self, warning_message, when, nodeid, location): expected = [ ("config warning", "config", ""), ("collect warning", "collect", ""), - ("setup warning", "runtest", "test_warning_captured_hook.py::test_func"), - ("call warning", "runtest", "test_warning_captured_hook.py::test_func"), - ("teardown warning", "runtest", "test_warning_captured_hook.py::test_func"), + ("setup warning", "runtest", "test_warning_recorded_hook.py::test_func"), + ("call warning", "runtest", "test_warning_recorded_hook.py::test_func"), + ("teardown warning", "runtest", "test_warning_recorded_hook.py::test_func"), ] for index in range(len(expected)): collected_result = collected[index] @@ -289,7 +290,7 @@ def pytest_warning_recorded(self, warning_message, when, nodeid, location): assert collected_result[2] == expected_result[2], str(collected) # NOTE: collected_result[3] is location, which differs based on the platform you are on - # thus, the best we can do here is assert the types of the paremeters match what we expect + # thus, the best we can do here is assert the types of the parameters match what we expect # and not try and preload it in the expected array if collected_result[3] is not None: assert type(collected_result[3][0]) is str, str(collected) @@ -517,6 +518,7 @@ def test_hidden_by_system(self, pytester: Pytester, monkeypatch) -> None: assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() +@pytest.mark.skip("not relevant until pytest 8.0") @pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> None: """This ensures that PytestRemovedInXWarnings raised by pytest are turned into errors. @@ -528,7 +530,7 @@ def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> No """ import warnings, pytest def test(): - warnings.warn(pytest.PytestRemovedIn7Warning("some warning")) + warnings.warn(pytest.PytestRemovedIn8Warning("some warning")) """ ) if change_default == "ini": @@ -536,12 +538,12 @@ def test(): """ [pytest] filterwarnings = - ignore::pytest.PytestRemovedIn7Warning + ignore::pytest.PytestRemovedIn8Warning """ ) args = ( - ("-Wignore::pytest.PytestRemovedIn7Warning",) + ("-Wignore::pytest.PytestRemovedIn8Warning",) if change_default == "cmdline" else () ) @@ -707,7 +709,7 @@ def test_issue4445_preparse(self, pytester: Pytester, capwarn) -> None: pytester.parseconfig("--help") # with stacklevel=2 the warning should originate from config._preparse and is - # thrown by an errorneous conftest.py + # thrown by an erroneous conftest.py assert len(capwarn.captured) == 1 warning, location = capwarn.captured.pop() file, _, func = location @@ -773,3 +775,57 @@ def test_it(): "*Unknown pytest.mark.unknown*", ] ) + + +def test_resource_warning(pytester: Pytester, monkeypatch: pytest.MonkeyPatch) -> None: + # Some platforms (notably PyPy) don't have tracemalloc. + # We choose to explicitly not skip this in case tracemalloc is not + # available, using `importorskip("tracemalloc")` for example, + # because we want to ensure the same code path does not break in those platforms. + try: + import tracemalloc # noqa + + has_tracemalloc = True + except ImportError: + has_tracemalloc = False + + # Explicitly disable PYTHONTRACEMALLOC in case pytest's test suite is running + # with it enabled. + monkeypatch.delenv("PYTHONTRACEMALLOC", raising=False) + + pytester.makepyfile( + """ + def open_file(p): + f = p.open("r") + assert p.read_text() == "hello" + + def test_resource_warning(tmp_path): + p = tmp_path.joinpath("foo.txt") + p.write_text("hello") + open_file(p) + """ + ) + result = pytester.run(sys.executable, "-Xdev", "-m", "pytest") + expected_extra = ( + [ + "*ResourceWarning* unclosed file*", + "*Enable tracemalloc to get traceback where the object was allocated*", + "*See https* for more info.", + ] + if has_tracemalloc + else [] + ) + result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"]) + + monkeypatch.setenv("PYTHONTRACEMALLOC", "20") + + result = pytester.run(sys.executable, "-Xdev", "-m", "pytest") + expected_extra = ( + [ + "*ResourceWarning* unclosed file*", + "*Object allocated at*", + ] + if has_tracemalloc + else [] + ) + result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"]) diff --git a/testing/typing_checks.py b/testing/typing_checks.py index 0a6b5ad2841..d15b3988bb5 100644 --- a/testing/typing_checks.py +++ b/testing/typing_checks.py @@ -3,6 +3,11 @@ This file is not executed, it is only checked by mypy to ensure that none of the code triggers any mypy errors. """ +import contextlib +from typing import Optional + +from typing_extensions import assert_type + import pytest @@ -22,3 +27,9 @@ def check_fixture_ids_callable() -> None: @pytest.mark.parametrize("func", [str, int], ids=lambda x: str(x.__name__)) def check_parametrize_ids_callable(func) -> None: pass + + +def check_raises_is_a_context_manager(val: bool) -> None: + with pytest.raises(RuntimeError) if val else contextlib.nullcontext() as excinfo: + pass + assert_type(excinfo, Optional[pytest.ExceptionInfo[RuntimeError]]) diff --git a/tox.ini b/tox.ini index 9d26051ebb7..663ada60caf 100644 --- a/tox.ini +++ b/tox.ini @@ -4,19 +4,24 @@ minversion = 3.20.0 distshare = {homedir}/.tox/distshare envlist = linting - py36 py37 py38 py39 py310 + py311 + py312 pypy3 - py37-{pexpect,xdist,unittestextras,numpy,pluggymain} + py37-{pexpect,xdist,unittestextras,numpy,pluggymain,pylib} doctesting plugins py37-freeze docs docs-checklinks + # checks that 3.11 native ExceptionGroup works with exceptiongroup + # not included in CI. + py311-exceptiongroup + [testenv] @@ -25,7 +30,11 @@ commands = doctesting: {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest coverage: coverage combine coverage: coverage report -m -passenv = USER USERNAME COVERAGE_* PYTEST_ADDOPTS TERM SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST +passenv = + COVERAGE_* + PYTEST_ADDOPTS + TERM + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST setenv = _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} @@ -46,9 +55,11 @@ setenv = extras = testing deps = doctesting: PyYAML + exceptiongroup: exceptiongroup>=1.0.0rc8 numpy: numpy>=1.19.4 pexpect: pexpect>=4.8.0 pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git + pylib: py>=1.8.2 unittestextras: twisted unittestextras: asynctest xdist: pytest-xdist>=2.1.0 @@ -86,13 +97,14 @@ commands = [testenv:regen] changedir = doc/en basepython = python3 -passenv = SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST +passenv = + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST deps = dataclasses PyYAML regendoc>=0.8.1 sphinx -whitelist_externals = +allowlist_externals = make commands = make regen @@ -100,9 +112,9 @@ commands = [testenv:plugins] # use latest versions of all plugins, including pre-releases pip_pre=true -# use latest pip and new dependency resolver (#7783) +# use latest pip to get new dependency resolver (#7783) download=true -install_command=python -m pip --use-feature=2020-resolver install {opts} {packages} +install_command=python -m pip install {opts} {packages} changedir = testing/plugins_integration deps = -rtesting/plugins_integration/requirements.txt setenv = @@ -130,7 +142,7 @@ commands = {envpython} tox_run.py [testenv:release] -decription = do a release, required posarg of the version number +description = do a release, required posarg of the version number basepython = python3 usedevelop = True passenv = * @@ -144,7 +156,7 @@ deps = commands = python scripts/release.py {posargs} [testenv:prepare-release-pr] -decription = prepare a release PR from a manual trigger in GitHub actions +description = prepare a release PR from a manual trigger in GitHub actions usedevelop = {[testenv:release]usedevelop} passenv = {[testenv:release]passenv} deps = {[testenv:release]deps} @@ -154,7 +166,10 @@ commands = python scripts/prepare-release-pr.py {posargs} description = create GitHub release after deployment basepython = python3 usedevelop = True -passenv = GH_RELEASE_NOTES_TOKEN GITHUB_REF GITHUB_REPOSITORY +passenv = + GH_RELEASE_NOTES_TOKEN + GITHUB_REF + GITHUB_REPOSITORY deps = github3.py pypandoc