diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 507789bf5a4..294b13743e2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,3 +9,9 @@ updates: allow: - dependency-type: direct - dependency-type: indirect +- package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + time: "03:00" + open-pull-requests-limit: 10 diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 00000000000..aa90b51e273 --- /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@v3 + 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..25280994687 --- /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@v3 + 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/prepare-release-pr.yml b/.github/workflows/prepare-release-pr.yml index 429834b3f21..76bf14d7e56 100644 --- a/.github/workflows/prepare-release-pr.yml +++ b/.github/workflows/prepare-release-pr.yml @@ -27,12 +27,12 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.8" 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..d434e6dfc69 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -11,7 +11,8 @@ on: permissions: {} jobs: - createPullRequest: + update-plugin-list: + if: github.repository_owner == 'pytest-dev' runs-on: ubuntu-latest permissions: contents: write @@ -19,12 +20,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.8 @@ -37,7 +38,7 @@ jobs: run: python scripts/update-plugin-list.py - name: Create Pull Request - uses: peter-evans/create-pull-request@2455e1596942c2902952003bbb574afbbe2ab2e6 + uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 with: commit-message: '[automated] Update plugin list' author: 'pytest bot ' 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..d672875962f 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: 23.3.0 hooks: - id: black args: [--safe, --quiet] - repo: https://github.com/asottile/blacken-docs - rev: v1.12.0 + rev: 1.13.0 hooks: - id: blacken-docs - additional_dependencies: [black==20.8b1] + additional_dependencies: [black==23.1.0] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.4.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: v2.0.2 + 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: 6.0.0 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.9.0 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.2.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 + rev: v1.10.0 hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.910-1 + rev: v1.1.1 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..1aa5265e62e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,9 +12,11 @@ Adam Uhlir Ahn Ki-Wook Akiomi Kamakura Alan Velasco +Alessio Izzo Alexander Johnson Alexander King Alexei Kozlenok +Alice Purcell Allan Feldman Aly Sivji Amir Elkess @@ -42,8 +44,10 @@ Ariel Pillemer Armin Rigo Aron Coyle Aron Curzon +Ashish Kurmi Aviral Verma Aviv Palivoda +Babak Keyvani Barney Gale Ben Gartner Ben Webb @@ -55,6 +59,7 @@ Brian Maissy Brian Okken Brianna Laugher Bruno Oliveira +Cal Jacobson Cal Leeming Carl Friedrich Bolz Carlos Jenkins @@ -62,9 +67,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 +90,8 @@ Damian Skrzypczak Daniel Grana Daniel Hahler Daniel Nuri +Daniel Sánchez Castelló +Daniel Valenzuela Zenteno Daniel Wandschneider Daniele Procida Danielle Jenkins @@ -119,11 +128,13 @@ Erik M. Bray Evan Kepner Fabien Zarifian Fabio Zadrozny +Felix Hofstätter Felix Nieuwenhuizen Feng Ma Florian Bruhin Florian Dahlitz Floris Bruynooghe +Gabriel Landau Gabriel Reis Garvit Shubham Gene Wood @@ -149,8 +160,10 @@ Ian Bicking Ian Lesperance Ilya Konstantinov Ionuț Turturică +Itxaso Aizpurua Iwan Briquemont Jaap Broekhuizen +Jake VanderPlas Jakob van Santen Jakub Mitoraj James Bourbeau @@ -163,7 +176,9 @@ Jeff Rackauckas Jeff Widman Jenni Rinker John Eddie Ayson +John Litborn John Towler +Jon Parise Jon Sonesen Jonas Obrist Jordan Guymon @@ -173,8 +188,8 @@ Joseph Hunkeler Josh Karpel Joshua Bronson Jurko Gospodnetić -Justyna Janczyszyn Justice Ndou +Justyna Janczyszyn Kale Kundert Kamran Ahmad Karl O. Pinc @@ -183,9 +198,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 @@ -208,6 +228,7 @@ Marcin Bachry Marco Gorelli Mark Abramowitz Mark Dickinson +Marko Pacak Markus Unterwaditzer Martijn Faassen Martin Altmayer @@ -221,7 +242,6 @@ Matthias Hafner Maxim Filipenko Maximilian Cosmo Sitter mbyt -Mickey Pashov Michael Aquilina Michael Birtwell Michael Droettboom @@ -230,6 +250,7 @@ Michael Krebs Michael Seifert Michal Wajszczuk Michał Zięba +Mickey Pashov Mihai Capotă Mike Hoyle (hoylemd) Mike Lundy @@ -243,9 +264,10 @@ Nicholas Murphy Niclas Olofsson Nicolas Delaby Nikolay Kondratyev -Olga Matoula +Nipunn Koorapati Oleg Pidsadnyi Oleg Sushchenko +Olga Matoula Oliver Bestwalter Omar Kohl Omer Hadari @@ -253,12 +275,15 @@ Ondřej Súkup Oscar Benjamin Parth Patel Patrick Hayes +Paul Müller +Paul Reece Pauli Virtanen Pavel Karateev Paweł Adamczak Pedro Algarvio Petter Strandmark Philipp Loose +Pierre Sassoulas Pieter Mulder Piotr Banaszkiewicz Piotr Helm @@ -268,12 +293,14 @@ Prashant Sharma Pulkit Goyal Punyashloka Biswal Quentin Pradet +q0w Ralf Schmitt -Ram Rachum Ralph Giles +Ram Rachum Ran Benita Raphael Castaneda Raphael Pierzina +Rafal Semik Raquel Alegre Ravi Chandra Robert Holt @@ -287,23 +314,27 @@ 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 Stefan Farmbauer Stefan Scherfke Stefan Zimmermann +Stefanie Molin Stefano Taschini Steffen Allner Stephan Obermann @@ -315,26 +346,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 @@ -347,8 +384,14 @@ Wouter van Ackooy Xixi Zhao Xuan Luong Xuecong Liao +Yannick Péroux 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..96db2e248a7 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,17 @@ Release announcements :maxdepth: 2 + release-7.3.0 + 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/announce/release-7.3.0.rst b/doc/en/announce/release-7.3.0.rst new file mode 100644 index 00000000000..33258dabade --- /dev/null +++ b/doc/en/announce/release-7.3.0.rst @@ -0,0 +1,130 @@ +pytest-7.3.0 +======================================= + +The pytest team is proud to announce the 7.3.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 +* Alessio Izzo +* Alex Hadley +* Alice Purcell +* Anthony Sottile +* Anton Yakutovich +* Ashish Kurmi +* Babak Keyvani +* Billy +* Brandon Chinn +* Bruno Oliveira +* Cal Jacobson +* Chanvin Xiao +* Cheuk Ting Ho +* Chris Wheeler +* Daniel Garcia Moreno +* Daniel Scheffler +* Daniel Valenzuela +* EmptyRabbit +* Ezio Melotti +* Felix Hofstätter +* Florian Best +* Florian Bruhin +* Fredrik Berndtsson +* Gabriel Landau +* Garvit Shubham +* Gergely Kalmár +* HTRafal +* Hugo van Kemenade +* Ilya Konstantinov +* Itxaso Aizpurua +* James Gerity +* Jay +* John Litborn +* Jon Parise +* Jouke Witteveen +* Kadino +* Kevin C +* Kian Eliasi +* Klaus Rettinghaus +* Kodi Arfer +* Mahesh Vashishtha +* Manuel Jacob +* Marko Pacak +* MatthewFlamm +* Miro Hrončok +* Nate Meyvis +* Neil Girdhar +* Nhieuvu1802 +* Nipunn Koorapati +* Ofek Lev +* Paul Kehrer +* Paul Müller +* Paul Reece +* Pax +* Pete Baughman +* Peyman Salehi +* Philipp A +* Pierre Sassoulas +* Prerak Patel +* Ramsey +* Ran Benita +* Robert O'Shea +* Ronny Pfannschmidt +* Rowin +* Ruth Comer +* Samuel Colvin +* Samuel Gaist +* Sandro Tosi +* Santiago Castro +* Shantanu +* Simon K +* Stefanie Molin +* Stephen Rosen +* Sviatoslav Sydorenko +* Tatiana Ovary +* Teejay +* Thierry Moisan +* Thomas Grainger +* Tim Hoffmann +* Tobias Diez +* Tony Narlock +* Vivaan Verma +* Wolfremium +* Yannick PÉROUX +* Yusuke Kadowaki +* Zac Hatfield-Dodds +* Zach OBrien +* aizpurua23a +* bitzge +* bluthej +* gresm +* holesch +* itxasos23 +* johnkangw +* q0w +* rdb +* s-padmanaban +* skhomuti +* sommersoft +* vin01 +* wim glenn +* wodny +* zx.qiu + + +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..7e9b51d002d 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:510 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 - 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 + capsysbinary -- .../_pytest/capture.py:1001 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:1029 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:1057 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" + + capsys -- .../_pytest/capture.py:973 + 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" + + doctest_namespace [session scope] -- .../_pytest/doctest.py:737 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:1360 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`. + + 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 -- ../../../..$PYTHON_SITE/_pytest/logging.py:491 + caplog -- .../_pytest/logging.py:498 Access and control log capturing. Captured logs are available through the following properties/methods:: @@ -128,59 +207,49 @@ 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. - recwarn -- ../../../..$PYTHON_SITE/_pytest/recwarn.py:29 + To undo modifications done by the fixture in a contained scope, + use :meth:`context() `. + + 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:245 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:260 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 + and old bases are removed after 3 sessions, to aid in debugging. + This behavior can be configured with :confval:`tmp_path_retention_count` and + :confval:`tmp_path_retention_policy`. + If ``--basetemp`` is used then it is cleared each session. See :ref:`base temporary directory`. The returned object is a :class:`pathlib.Path` object. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 6cec2935d9a..4fc959c6b19 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,990 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 7.3.0 (2023-04-08) +========================= + +Features +-------- + +- `#10525 `_: Test methods decorated with ``@classmethod`` can now be discovered as tests, following the same rules as normal methods. This fills the gap that static methods were discoverable as tests but not class methods. + + +- `#10755 `_: :confval:`console_output_style` now supports ``progress-even-when-capture-no`` to force the use of the progress output even when capture is disabled. This is useful in large test suites where capture may have significant performance impact. + + +- `#7431 `_: ``--log-disable`` CLI option added to disable individual loggers. + + +- `#8141 `_: Added :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy` configuration options to control how directories created by the :fixture:`tmp_path` fixture are kept. + + + +Improvements +------------ + +- `#10226 `_: If multiple errors are raised in teardown, we now re-raise an ``ExceptionGroup`` of them instead of discarding all but the last. + + +- `#10658 `_: Allow ``-p`` arguments to include spaces (eg: ``-p no:logging`` instead of + ``-pno:logging``). Mostly useful in the ``addopts`` section of the configuration + file. + + +- `#10710 `_: Added ``start`` and ``stop`` timestamps to ``TestReport`` objects. + + +- `#10727 `_: Split the report header for ``rootdir``, ``config file`` and ``testpaths`` so each has its own line. + + +- `#10840 `_: pytest should no longer crash on AST with pathological position attributes, for example testing AST produced by `Hylang __`. + + +- `#6267 `_: The full output of a test is no longer truncated if the truncation message would be longer than + the hidden text. The line number shown has also been fixed. + + + +Bug Fixes +--------- + +- `#10743 `_: The assertion rewriting mechanism now works correctly when assertion expressions contain the walrus operator. + + +- `#10765 `_: Fixed :fixture:`tmp_path` fixture always raising :class:`OSError` on ``emscripten`` platform due to missing :func:`os.getuid`. + + +- `#1904 `_: Correctly handle ``__tracebackhide__`` for chained exceptions. + + + +Improved Documentation +---------------------- + +- `#10782 `_: Fixed the minimal example in :ref:`goodpractices`: ``pip install -e .`` requires a ``version`` entry in ``pyproject.toml`` to run successfully. + + + +Trivial/Internal Changes +------------------------ + +- `#10669 `_: pytest no longer depends on the `attrs` package (don't worry, nice diffs for attrs classes are still supported). + + +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 +1046,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 +1361,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 +1470,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 +1812,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 +2259,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 +2267,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 +2371,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 +2929,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 +3006,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 +3154,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 +3330,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 +5142,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 +5531,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 +6499,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 +6514,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 +8523,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 +8835,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 +8909,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..4f7830a2791 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 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -828,7 +1052,7 @@ that are then turned into proper test methods. Example: .. code-block:: python def check(x, y): - assert x ** x == y + assert x**x == y def test_squared(): @@ -843,7 +1067,7 @@ This form of test function doesn't support fixtures properly, and users should s @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)]) def test_squared(x, y): - assert x ** x == y + assert x**x == y .. _internal classes accessed through node deprecated: 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..2451e3cab49 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -147,15 +147,15 @@ 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 - rootdir: /home/sweet/project, configfile: pytest.ini + 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 +209,16 @@ 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 - rootdir: /home/sweet/project, configfile: pytest.ini + 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,9 +291,9 @@ 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 - rootdir: /home/sweet/project, configfile: pytest.ini + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + configfile: pytest.ini collected 0 items ======================= no tests collected in 0.12s ======================== diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index ff814bd754a..cb59c4b42e1 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 @@ -145,7 +144,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E 1 E 1... E - E ...Full output truncated (7 lines hidden), use '-vv' to show + E ...Full output truncated (6 lines hidden), use '-vv' to show failure_demo.py:60: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ @@ -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 _________________ @@ -185,9 +184,8 @@ Here is a nice run of several failures and how ``pytest`` presents things: E Left contains 1 more item: E {'c': 0} E Right contains 1 more item: - E {'d': 0}... - E - E ...Full output truncated (2 lines hidden), use '-vv' to show + E {'d': 0} + E Use -v to get more diff failure_demo.py:71: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ @@ -196,16 +194,15 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_set(self): > assert {0, 10, 11, 12} == {0, 20, 21} - E AssertionError: assert {0, 10, 11, 12} == {0, 20, 21} + E assert {0, 10, 11, 12} == {0, 20, 21} E Extra items in the left set: E 10 E 11 E 12 E Extra items in the right set: E 20 - E 21... - E - E ...Full output truncated (2 lines hidden), use '-vv' to show + E 21 + E Use -v to get more diff failure_demo.py:74: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ @@ -216,7 +213,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 _________________ @@ -242,9 +239,8 @@ Here is a nice run of several failures and how ``pytest`` presents things: E which E includes foo E ? +++ - E and a... - E - E ...Full output truncated (2 lines hidden), use '-vv' to show + E and a + E tail failure_demo.py:84: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ @@ -308,9 +304,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: E ['b'] E E Drill down into differing attribute b: - E b: 'b' != 'c'... - E - E ...Full output truncated (3 lines hidden), use '-vv' to show + E b: 'b' != 'c' + E - c + E + b failure_demo.py:108: AssertionError ________________ TestSpecialisedExplanations.test_eq_attrs _________________ @@ -335,9 +331,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: E ['b'] E E Drill down into differing attribute b: - E b: 'b' != 'c'... - E - E ...Full output truncated (3 lines hidden), use '-vv' to show + E b: 'b' != 'c' + E - c + E + b failure_demo.py:120: AssertionError ______________________________ test_attribute ______________________________ @@ -674,7 +670,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list - asser... FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list_long - ... FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_dict - Asser... - FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - Assert... + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - assert... FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_longer_list FAILED failure_demo.py::TestSpecialisedExplanations::test_in_list - asser... FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_multiline diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 3b9963f09d6..fa3e68ce94a 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 @@ -899,8 +892,11 @@ here is a little example implemented via a local plugin: .. code-block:: python # content of conftest.py - + from typing import Dict import pytest + from pytest import StashKey, CollectReport + + phase_report_key = StashKey[Dict[str, CollectReport]]() @pytest.hookimpl(tryfirst=True, hookwrapper=True) @@ -909,10 +905,9 @@ here is a little example implemented via a local plugin: outcome = yield rep = outcome.get_result() - # set a report attribute for each phase of a call, which can + # store test results for each phase of a call, which can # be "setup", "call", "teardown" - - setattr(item, "rep_" + rep.when, rep) + item.stash.setdefault(phase_report_key, {})[rep.when] = rep @pytest.fixture @@ -920,11 +915,11 @@ here is a little example implemented via a local plugin: yield # request.node is an "item" because we use the default # "function" scope - if request.node.rep_setup.failed: - print("setting up a test failed!", request.node.nodeid) - elif request.node.rep_setup.passed: - if request.node.rep_call.failed: - print("executing test failed", request.node.nodeid) + report = request.node.stash[phase_report_key] + if report["setup"].failed: + print("setting up a test failed or skipped", request.node.nodeid) + elif ("call" not in report) or report["call"].failed: + print("executing test failed or skipped", request.node.nodeid) if you then have failing tests: @@ -958,13 +953,12 @@ 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 - test_module.py Esetting up a test failed! test_module.py::test_setup_fails - Fexecuting test failed test_module.py::test_call_fails + test_module.py Esetting up a test failed or skipped test_module.py::test_setup_fails + Fexecuting test failed or skipped test_module.py::test_call_fails F ================================== ERRORS ================================== @@ -1076,6 +1070,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..7331a789600 100644 --- a/doc/en/explanation/goodpractices.rst +++ b/doc/en/explanation/goodpractices.rst @@ -12,41 +12,27 @@ 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: + [project] + name = "PACKAGENAME" + version = "PACKAGEVERSION" -.. code-block:: ini - - [metadata] - name = PACKAGENAME - - [options] - packages = find: - -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() +where ``PACKAGENAME`` and ``PACKAGEVERSION`` are the name and version of your package respectively. 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. @@ -65,8 +51,8 @@ Conventions for Python test discovery * In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_. * From those files, collect test items: - * ``test`` prefixed test functions or methods outside of class - * ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method) + * ``test`` prefixed test functions or methods outside of class. + * ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method). Methods decorated with ``@staticmethod`` and ``@classmethods`` are also considered. For examples of how to customize your test discovery :doc:`/example/pythoncollection`. @@ -89,11 +75,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 +89,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 +150,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 +212,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 +271,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..8c9c4e75adf 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.3.0 .. _`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..d8517c2c8f3 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 @@ -1154,7 +1237,6 @@ If the data created by the factory requires managing, the fixture can take care @pytest.fixture def make_customer_record(): - created_records = [] def _make_customer_record(name): @@ -1194,15 +1276,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 +1414,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 +1436,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 +1468,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 +1518,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 +1589,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 +1598,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 +1690,7 @@ and declare its use in a test module via a ``usefixtures`` marker: # content of test_setenv.py import os + import pytest @@ -1686,8 +1771,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest @@ -1702,8 +1785,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 +1793,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 +1810,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest @@ -1772,8 +1851,6 @@ Given the tests file structure is: :: tests/ - __init__.py - conftest.py # content of tests/conftest.py import pytest @@ -1810,8 +1887,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..9957a9bb886 100644 --- a/doc/en/how-to/logging.rst +++ b/doc/en/how-to/logging.rst @@ -55,6 +55,13 @@ These options can also be customized through ``pytest.ini`` file: log_format = %(asctime)s %(levelname)s %(message)s log_date_format = %Y-%m-%d %H:%M:%S +Specific loggers can be disabled via ``--log-disable={logger_name}``. +This argument can be passed multiple times: + +.. code-block:: bash + + pytest --log-disable=main --log-disable=testing + Further it is possible to disable reporting of captured content (stdout, stderr and logs) on failed tests completely with: @@ -73,7 +80,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 +89,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 +166,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 +183,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 +201,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..a9504dcb32a 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`` @@ -133,10 +135,10 @@ This can be done in our test file by defining a class to represent ``r``. # this is the previous code block example import app + # custom class to be the mock return value # will override the requests.Response returned from requests.get class MockResponse: - # mock json() method always returns a specific testing dictionary @staticmethod def json(): @@ -144,7 +146,6 @@ This can be done in our test file by defining a class to represent ``r``. def test_get_json(monkeypatch): - # Any arguments may be passed and mock_get() will always return our # mocked object, which only has the .json() method. def mock_get(*args, **kwargs): @@ -179,6 +180,7 @@ This mock can be shared across tests using a ``fixture``: # app.py that includes the get_json() function import app + # custom class to be the mock return value of requests.get() class MockResponse: @staticmethod @@ -356,7 +358,6 @@ For testing purposes we can patch the ``DEFAULT_CONFIG`` dictionary to specific def test_connection(monkeypatch): - # Patch the values of DEFAULT_CONFIG to specific # testing values only for this test. monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user") @@ -381,7 +382,6 @@ You can use the :py:meth:`monkeypatch.delitem ` to remove v def test_missing_user(monkeypatch): - # patch the DEFAULT_CONFIG t be missing the 'user' key monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False) @@ -402,6 +402,7 @@ separate fixtures for each potential mock and reference them in the needed tests # app.py with the connection string function import app + # all of the mocks are moved into separated fixtures @pytest.fixture def mock_test_user(monkeypatch): @@ -423,7 +424,6 @@ separate fixtures for each potential mock and reference them in the needed tests # tests reference only the fixture mocks that are needed def test_connection(mock_test_user, mock_test_database): - expected = "User Id=test_user; Location=test_db;" result = app.create_connection_string() @@ -431,12 +431,11 @@ separate fixtures for each potential mock and reference them in the needed tests def test_missing_user(mock_missing_default_user): - with pytest.raises(KeyError): _ = 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..e8e9af0c70b 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 ____________________________ @@ -166,9 +167,9 @@ Now we can increase pytest's verbosity: E Right contains 4 more items: E {'10': 10, '20': 20, '30': 30, '40': 40} E Full diff: - E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}... - E - E ...Full output truncated (3 lines hidden), use '-vv' to show + E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40} + E ? - - - - - - - - + E + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} 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..792933dd87e 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. @@ -119,8 +131,14 @@ The default base temporary directory Temporary directories are by default created as sub-directories of 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. +``NUM`` will be incremented with each test run. +By default, entries older than 3 temporary directories will be removed. +This behavior can be configured with :confval:`tmp_path_retention_count` and +:confval:`tmp_path_retention_policy`. + +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: 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..6f321110718 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,9 +448,9 @@ 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 - rootdir: /home/sweet/project, configfile: pytest.ini + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + rootdir: /home/sweet/project + configfile: pytest.ini collected 2 items test_example.py .. [100%] 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..6f3115b19c2 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -1,10 +1,11 @@ :orphan: -.. sidebar:: Next Open Trainings +.. + .. 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 +18,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 +43,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 +77,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 +97,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 +129,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..d558ccf5707 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -11,990 +11,1272 @@ 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 1231 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-abq` Pytest integration for the ABQ universal test runner. Mar 27, 2023 N/A N/A + :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 Dec 21, 2022 N/A pytest (>=6,<8) + :pypi:`pytest-adaptavist` pytest plugin for generating test execution results within Jira Test Management (tm4j) Oct 13, 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-ads-testplan` Azure DevOps Test Case reporting for pytest tests Sep 15, 2022 N/A N/A + :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 Feb 03, 2023 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 Jan 10, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-aiomoto` pytest-aiomoto Nov 09, 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. Feb 03, 2023 N/A pytest (>=6.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 Oct 21, 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-intersection` Oct 27, 2022 N/A pytest (<5) + :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 Oct 13, 2022 N/A pytest + :pypi:`pytest-aoc` Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Dec 08, 2022 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-approvaltests-geo` Extension for ApprovalTests.Python specific to geo data verification Mar 04, 2023 5 - Production/Stable pytest + :pypi:`pytest-archon` Rule your architecture like a real developer Jan 31, 2023 5 - Production/Stable pytest (>=7.2) + :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 Oct 23, 2022 N/A pytest (>=5.0.0) + :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 Mar 19, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Feb 10, 2023 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-aviator` Aviator's Flakybot pytest plugin that automatically reruns flaky tests. Nov 04, 2022 4 - Beta 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` Pytest utilities and mocks for Azure Jan 18, 2023 3 - Alpha pytest + :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 Oct 20, 2022 5 - Production/Stable pytest (>=5.0.0) + :pypi:`pytest-bandit` A bandit plugin for pytest Feb 23, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-bandit-xayon` A bandit plugin for pytest Oct 17, 2022 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 Nov 08, 2022 6 - Mature pytest (>=6.2.0) + :pypi:`pytest-bdd-html` pytest plugin to display BDD info in HTML test report Nov 22, 2022 3 - Alpha pytest (!=6.0.0,>=5.0) + :pypi:`pytest-bdd-ng` BDD for pytest Oct 06, 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. Oct 25, 2022 5 - Production/Stable pytest (>=3.8) + :pypi:`pytest-better-datadir` A small example package Mar 13, 2023 N/A N/A + :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 Dec 28, 2022 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-black-ng` A pytest plugin to enable format checking with black Oct 20, 2022 4 - Beta pytest (>=7.0.0) + :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. Jan 04, 2023 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-boost-xml` Plugin for pytest to generate boost xml reports Nov 30, 2022 4 - Beta 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-budosystems` Budo Systems is a martial arts school management system. This module is the Budo Systems Pytest Plugin. Feb 14, 2023 3 - Alpha pytest + :pypi:`pytest-bug` Pytest plugin for marking tests as a bug Jan 29, 2023 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 Feb 26, 2023 5 - Production/Stable 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-call-checker` Small pytest utility to easily create test doubles Oct 16, 2022 4 - Beta pytest (>=7.1.3,<8.0.0) + :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. Feb 23, 2023 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-assert` 修改报错中文为英文 Oct 19, 2022 N/A 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. Feb 13, 2023 5 - Production/Stable pytest + :pypi:`pytest-checkdocs` check the README when running tests Oct 09, 2022 5 - Production/Stable pytest (>=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-check-requirements` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A + :pypi:`pytest-chic-report` A pytest plugin to send a report and printing summary of tests. Jan 31, 2023 5 - Production/Stable N/A + :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. Oct 20, 2022 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 Nov 07, 2022 N/A pytest (>=3.6) + :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-cmake` Provide CMake module for Pytest Jan 21, 2023 N/A pytest<8,>=4 + :pypi:`pytest-cmake-presets` Execute CMake Presets via pytest Dec 26, 2022 N/A pytest (>=7.2.0,<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 Nov 29, 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-codspeed` Pytest plugin to create CodSpeed benchmarks Dec 02, 2022 5 - Production/Stable pytest>=3.8 + :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-compare` pytest plugin for comparing call arguments. Mar 30, 2023 5 - Production/Stable N/A + :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 Mar 21, 2023 4 - Beta 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. 🍪 Mar 22, 2023 5 - Production/Stable pytest (>=3.9.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. Sep 28, 2022 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-coveragemarkers` Using pytest markers to track functional coverage and filtering of tests Nov 29, 2022 N/A pytest (>=7.1.2,<8.0.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 Jan 30, 2023 5 - Production/Stable pytest (>=7.0) + :pypi:`pytest-cppython` A pytest plugin that imports CPPython testing types Mar 20, 2023 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-crayons` A pytest plugin for colorful print statements Mar 19, 2023 N/A pytest + :pypi:`pytest-create` pytest-create Feb 15, 2023 1 - Planning N/A + :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 Feb 16, 2023 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 25, 2022 5 - Production/Stable pytest (>=5.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-datadir-nng` Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Nov 09, 2022 5 - Production/Stable pytest (>=7.0.0,<8.0.0) + :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 'tmp_path' containing predefined files/directories. Feb 24, 2023 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. Jan 08, 2023 5 - Production/Stable pytest + :pypi:`pytest-dataset` Plugin for loading different datasets for pytest by prefix from json or yaml files Oct 10, 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. Mar 01, 2023 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-dbx` Pytest plugin to run unit tests for dbx (Databricks CLI extensions) related code Nov 29, 2022 N/A pytest (>=7.1.3,<8.0.0) + :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-diffeo` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A + :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-difido` PyTest plugin for generating Difido reports Oct 23, 2022 4 - Beta pytest (>=4.0.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. Feb 05, 2023 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. Mar 18, 2023 4 - Beta pytest + :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 Sep 14, 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 Mar 24, 2023 3 - Alpha pytest + :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 Feb 22, 2023 3 - Alpha pytest (>=7.1.3) + :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. Sep 26, 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-draw` Pytest plugin for randomly selecting a specific number of tests Mar 21, 2023 3 - Alpha pytest + :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 Mar 27, 2023 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-ekstazi` Pytest plugin to select test using Ekstazi algorithm Sep 10, 2022 N/A pytest + :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 Mar 10, 2023 N/A pytest (>=7.0) + :pypi:`pytest-embedded-arduino` pytest embedded plugin for Arduino projects Mar 10, 2023 N/A N/A + :pypi:`pytest-embedded-idf` pytest embedded plugin for esp-idf project Mar 10, 2023 N/A N/A + :pypi:`pytest-embedded-jtag` pytest embedded plugin for testing with jtag Mar 10, 2023 N/A N/A + :pypi:`pytest-embedded-qemu` pytest embedded plugin for qemu, not target chip Mar 10, 2023 N/A N/A + :pypi:`pytest-embedded-serial` pytest embedded plugin for testing serial ports Mar 10, 2023 N/A N/A + :pypi:`pytest-embedded-serial-esp` pytest embedded plugin for testing espressif boards via serial ports Mar 10, 2023 N/A N/A + :pypi:`pytest-embrace` 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. Mar 25, 2023 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 Jan 27, 2023 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-enhanced-reports` Enhanced test reports for pytest Dec 15, 2022 N/A N/A + :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. Oct 23, 2022 5 - Production/Stable pytest>=7.1.3 + :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-examples` Pytest plugin for testing examples in docstrings and markdown files. Mar 26, 2023 4 - Beta pytest>=7 + :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 Mar 25, 2023 N/A pytest (<8,>=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-expectdir` A pytest plugin to provide initial/expected directories, and check a test transforms the initial directory to the expected one Mar 19, 2023 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-expecter` Better testing with expecter and pytest. Sep 18, 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-extra-markers` Additional pytest markers to dynamically enable/disable tests viia CLI flags Mar 05, 2023 4 - Beta pytest + :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-factor` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A 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 01, 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-screen-record` Create a video of the screen when pytest fails Jan 05, 2023 4 - Beta pytest (>=7.1.2d,<8.0.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 Jan 19, 2023 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-file-watcher` Pytest-File-Watcher is a CLI tool that watches for changes in your code and runs pytest on the changed files. Mar 23, 2023 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 Dec 12, 2022 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-classes` Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers Jan 20, 2023 4 - Beta pytest + :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-ref` Lets users reference fixtures without name matching magic. Nov 17, 2022 4 - Beta N/A + :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. Oct 26, 2022 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. Feb 10, 2023 4 - Beta pytest (~=7.2) + :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-flexreport` Apr 01, 2023 4 - Beta pytest + :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-forbid` Mar 07, 2023 N/A pytest (>=7.2.2,<8.0.0) + :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-freezer` Pytest plugin providing a fixture interface for spulec/freezegun Oct 20, 2022 N/A pytest>=3.6 + :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. Mar 15, 2023 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 Dec 19, 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-gitlabci-parallelized` Parallelize pytest across GitLab CI workers. Mar 08, 2023 N/A N/A + :pypi:`pytest-git-selector` Utility to select tests that have had its dependencies modified (as identified by git diff) Nov 17, 2022 N/A 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-grunnur` Py.Test plugin for Grunnur-based packages. Feb 05, 2023 N/A N/A + :pypi:`pytest-hammertime` Display "🔨 " instead of "." for passed pytest tests. Jul 28, 2018 N/A pytest + :pypi:`pytest-harmony` Chain tests and data with pytest Jan 17, 2023 N/A pytest (>=7.2.1,<8.0.0) + :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. Mar 08, 2023 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 Apr 01, 2023 3 - Alpha pytest (==7.2.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-hot-test` A plugin that tracks test changes Dec 10, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Jan 30, 2023 N/A pytest (>=5.0) + :pypi:`pytest-hoverfly-wrapper` Integrates the Hoverfly HTTP proxy into Pytest Feb 27, 2023 5 - Production/Stable pytest (>=3.7.0) + :pypi:`pytest-hpfeeds` Helpers for testing hpfeeds in your python project Feb 28, 2023 4 - Beta pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-html` pytest plugin for generating HTML reports Mar 05, 2023 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 Sep 12, 2022 3 - Alpha N/A + :pypi:`pytest-httptesting` http_testing framework on top of pytest Mar 15, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-httpx` Send responses to httpx. Jan 20, 2023 5 - Production/Stable pytest (<8.0,>=6.0) + :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Feb 16, 2023 N/A pytest (>=7.2.1) + :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 pytest>=7.1 + :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` Mar 09, 2023 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-inline` A pytest plugin for writing inline tests. Feb 08, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Feb 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-inmanta-extensions` Inmanta tests package Feb 09, 2023 5 - Production/Stable N/A + :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules Feb 21, 2023 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 Nov 02, 2022 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-instafail` pytest plugin to show failures instantly Mar 31, 2023 4 - Beta pytest (>=5) + :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 Nov 17, 2022 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. Mar 24, 2023 5 - Production/Stable pytest (<7.2.0,>=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. Mar 20, 2013 2 - Pre-Alpha N/A + :pypi:`pytest-ipynb` THIS PROJECT IS ABANDONED Jan 29, 2019 3 - Alpha N/A + :pypi:`pytest-isolate` Feb 20, 2023 4 - Beta pytest + :pypi:`pytest-isort` py.test plugin to check import ordering using isort Oct 31, 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-jelastic` Pytest plugin defining the necessary command-line options to pass to pytests testing a Jelastic environment. Nov 16, 2022 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-jest` A custom jest-pytest oriented Pytest reporter May 22, 2018 4 - Beta pytest (>=3.3.2) + :pypi:`pytest-jinja` A plugin to generate customizable jinja-based HTML reports in pytest Oct 04, 2022 3 - Alpha pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-jira` py.test JIRA integration plugin, using markers Apr 07, 2022 3 - Alpha N/A + :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked Dec 01, 2022 N/A pytest (~=7.2.0) + :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Mar 13, 2023 4 - Beta pytest + :pypi:`pytest-job-selection` A pytest plugin for load balancing test suites Jan 30, 2023 4 - Beta pytest (>=3.5.0) + :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 Mar 14, 2023 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 Mar 15, 2022 4 - Beta pytest (>=3.8.0) + :pypi:`pytest-jtr` pytest plugin supporting json test report output Nov 29, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Mar 30, 2023 4 - Beta pytest + :pypi:`pytest-kafka` Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest Oct 01, 2022 N/A pytest + :pypi:`pytest-kafkavents` A plugin to send pytest events to Kafka Sep 08, 2021 4 - Beta pytest + :pypi:`pytest-kasima` Display horizontal lines above and below the captured standard output for easy viewing. Jan 26, 2023 5 - Production/Stable pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-keep-together` Pytest plugin to customize test ordering by running all 'related' tests together Dec 07, 2022 5 - Production/Stable 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 Nov 30, 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-koopmans` A plugin for testing the koopmans package Nov 21, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-krtech-common` pytest krtech common library Nov 28, 2016 4 - Beta N/A + :pypi:`pytest-kubernetes` Feb 16, 2023 N/A pytest (>=7.2.1,<8.0.0) + :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-langchain` Pytest-style test runner for langchain agents Feb 26, 2023 N/A pytest + :pypi:`pytest-lark` A package for enhancing pytest Nov 20, 2022 N/A 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-lazy-fixtures` Allows you to use fixtures in @pytest.mark.parametrize. Mar 11, 2023 N/A pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-ldap` python-ldap fixtures for pytest Aug 18, 2020 N/A pytest + :pypi:`pytest-leak-finder` Find the test that's leaking before the one that fails Feb 15, 2023 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 Dec 05, 2022 N/A 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-local-badge` Generate local badges (shields) reporting your test suite status. Jan 15, 2023 N/A pytest (>=6.1.0) + :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests Oct 04, 2022 5 - Production/Stable pytest + :pypi:`pytest-localserver` pytest plugin to test server connections locally. Jan 30, 2023 4 - Beta N/A + :pypi:`pytest-localstack` Pytest plugin for AWS integration tests Oct 17, 2022 4 - Beta pytest (>=6.0.0,<7.0.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-logging-end-to-end-test-tool` Sep 23, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-logikal` Common testing environment Mar 09, 2023 5 - Production/Stable pytest (==7.2.1) + :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 Jan 14, 2023 3 - Alpha pytest + :pypi:`pytest-manual-marker` pytest marker for marking manual tests Aug 04, 2022 3 - Alpha pytest>=7 + :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 Mar 09, 2023 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 Dec 02, 2022 N/A pytest>=7.2 + :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 Nov 24, 2022 5 - Production/Stable N/A + :pypi:`pytest-metadata` pytest plugin for test session metadata Oct 30, 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-mini` A plugin to test mp Feb 06, 2023 N/A pytest (>=7.2.0,<8.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 Oct 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. Mar 03, 2023 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-mocktcp` A pytest plugin for testing TCP clients Oct 11, 2022 N/A pytest + :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. Oct 22, 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 Nov 15, 2022 4 - Beta pytest (>=6) + :pypi:`pytest-mqtt` pytest-mqtt supports testing systems based on MQTT Mar 15, 2023 4 - Beta pytest (<8) ; extra == 'test' + :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 Jan 17, 2023 N/A pytest + :pypi:`pytest-multithreading` a pytest plugin for th and concurrent testing Dec 07, 2022 N/A N/A + :pypi:`pytest-multithreading-allure` pytest_multithreading_allure Nov 25, 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 Dec 18, 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 Oct 26, 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 pytest>=6.0.0 + :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. Feb 25, 2023 N/A pytest>=7,<8 + :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Mar 27, 2023 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-netdut` "Automated software testing for switches using pytest" Jan 11, 2023 N/A pytest (>=3.5.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 Mar 06, 2023 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 Oct 20, 2022 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-oar` PyTest plugin for the OAR testing framework Mar 31, 2023 N/A pytest>=6.0.1 + :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 Nov 17, 2022 4 - Beta pytest (>=7.2.0) + :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-offline` Mar 09, 2023 1 - Planning pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-ogsm-plugin` 针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数 Mar 08, 2023 N/A 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 Mar 15, 2023 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 Sep 28, 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 Mar 10, 2023 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-order-modify` 新增run_marker 来自定义用例的执行顺序 Nov 04, 2022 N/A N/A + :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 Jan 18, 2023 N/A N/A + :pypi:`pytest-override-env-var` Pytest mark to override a value of an environment variable. Feb 25, 2023 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-parallelize-tests` pytest plugin that parallelizes test execution across multiple hosts Jan 27, 2023 4 - Beta N/A + :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 decorator for parametrizing tests with default iterables. Sep 13, 2022 5 - Production/Stable pytest + :pypi:`pytest-parametrize-suite` A simple pytest extension for creating a named test suite. Jan 19, 2023 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 Mar 28, 2023 N/A N/A + :pypi:`pytest-pg` A tiny plugin for pytest which runs PostgreSQL in Docker Sep 19, 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-pingguo-pytest-plugin` pingguo test Oct 26, 2022 4 - Beta 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 10, 2023 N/A pytest (<8.0.0,>=6.2.4) + :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-plone` Pytest plugin to test Plone addons Jan 05, 2023 3 - Alpha pytest + :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 Dec 24, 2022 5 - Production/Stable pytest (>=6.0.1) + :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 Dec 26, 2022 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 Mar 16, 2023 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-pot` A package for enhancing pytest Nov 20, 2022 N/A N/A + :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` pytest plugin for printing summary data as I want it Mar 22, 2023 5 - Production/Stable pytest>=7 + :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 27, 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-pumpkin-spice` A pytest plugin that makes your test reporting pumpkin-spiced Sep 18, 2022 4 - Beta N/A + :pypi:`pytest-purkinje` py.test plugin for purkinje test runner Oct 28, 2017 2 - Pre-Alpha N/A + :pypi:`pytest-pusher` pytest plugin for push report to minio Jan 06, 2023 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-py125` Dec 03, 2022 N/A 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 Oct 28, 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 Jan 05, 2023 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" Jan 05, 2023 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 Dec 13, 2022 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 Nov 20, 2022 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-pyspec` A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it". Mar 12, 2023 5 - Production/Stable pytest (>=7.2.1,<8.0.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-pyvista` Pytest-pyvista package Mar 19, 2023 4 - Beta pytest>=3.5.0 + :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. Feb 14, 2023 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 Oct 25, 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 05, 2022 4 - Beta pytest (>=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 Dec 03, 2022 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. Dec 13, 2022 3 - Alpha N/A + :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Mar 30, 2023 N/A N/A + :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Feb 16, 2023 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. Mar 27, 2023 5 - Production/Stable pytest (>=6.2) + :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 13, 2023 5 - Production/Stable pytest (>=6.2.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 Dec 31, 2022 5 - Production/Stable pytest (>=7) + :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 12, 2022 3 - Alpha pytest (>=4.6) + :pypi:`pytest-remote-response` Pytest plugin for capturing and mocking connection requests. Mar 27, 2023 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-reporter-html-dots` A basic HTML report for pytest using Jinja2 template engine. Jan 22, 2023 N/A 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 Mar 11, 2023 3 - Alpha pytest + :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 Mar 28, 2023 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 Sep 20, 2022 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 Mar 09, 2023 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-reserial` Pytest fixture for recording and replaying serial port traffic. Nov 29, 2022 4 - Beta pytest + :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest Feb 28, 2023 N/A pytest (~=4.6) ; python_version == "2.7" + :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-resource-usage` Pytest plugin for reporting running time and peak memory usage Nov 06, 2022 5 - Production/Stable pytest>=7.0.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 Oct 11, 2022 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-result-log` Write the execution result of the case to the log Feb 02, 2023 N/A pytest>=7.2.0 + :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-retry-class` A pytest plugin to rerun entire class on failure Mar 25, 2023 N/A pytest (>=5.3) + :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-richtrace` Nov 05, 2022 N/A pytest (>=7.2.0,<8.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 Nov 09, 2022 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 Jan 26, 2023 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-ruff` pytest plugin to check ruff requirements. Mar 22, 2023 4 - Beta N/A + :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. Nov 12, 2022 5 - Production/Stable pytest + :pypi:`pytest-runtime-types` Checks type annotations on runtime while running tests. Feb 09, 2023 N/A pytest + :pypi:`pytest-runtime-xfail` Call runtime_xfail() to mark running test as xfail. Aug 26, 2021 N/A pytest>=5.0.0 + :pypi:`pytest-ry-demo1` 测试 Mar 26, 2023 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). Oct 31, 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 Dec 15, 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. Mar 28, 2023 5 - Production/Stable N/A + :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A + :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable 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 Sep 21, 2022 5 - Production/Stable pytest (>=6.0.0,<7.0.0) + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Mar 28, 2023 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 Jan 05, 2023 N/A N/A + :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 Feb 24, 2023 3 - Alpha pytest (>=6.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-setupinfo` Displaying setup info during pytest command run Jan 23, 2023 N/A 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-share-hdf` Plugin to save test data in HDF files and retrieve them for comparison Sep 21, 2022 4 - Beta pytest (>=3.5.0) + :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 Sep 23, 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 Jan 16, 2023 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 Dec 20, 2022 5 - Production/Stable pytest (>=7.1.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. Feb 09, 2023 N/A pytest>=6.2.0 + :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-slowest-first` Sort tests by their last duration, slowest first Dec 11, 2022 4 - Beta N/A + :pypi:`pytest-slow-last` Run tests in order of execution time (faster tests first) Dec 10, 2022 4 - Beta pytest (>=3.5.0) + :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 Feb 03, 2023 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 Feb 14, 2023 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. Jan 22, 2023 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 Feb 22, 2023 N/A pytest (>5.4.0,<7.3) + :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Mar 07, 2023 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 Mar 15, 2023 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-sqlfluff` A pytest plugin to use sqlfluff to enable format checking of sql files. Dec 21, 2022 4 - Beta pytest (>=3.5.0) + :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 pytest>=6.2.0 + :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-star-track-issue` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 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 Dec 04, 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 Dec 18, 2022 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 Jan 28, 2023 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 Feb 16, 2023 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). Nov 05, 2022 3 - Alpha pytest (>=2.9) + :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-tagging` a pytest plugin to tag tests Apr 01, 2023 N/A pytest (>=7.1.3,<8.0.0) + :pypi:`pytest-takeltest` Fixtures for ansible, testinfra and molecule Feb 15, 2023 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-tcpclient` A pytest plugin for testing TCP clients Nov 16, 2022 N/A pytest (<8,>=7.1.3) + :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-telegram-notifier` Telegram notification plugin for Pytest Mar 17, 2023 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 15, 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. Feb 01, 2023 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 Dec 01, 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 Mar 25, 2023 4 - Beta pytest (<8,>=5) + :pypi:`pytest-testmon-dev` selects tests affected by changed files and methods Mar 30, 2023 4 - Beta pytest (<8,>=5) + :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 Mar 03, 2023 4 - Beta pytest (<8,>=5) + :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 pytest plugin to upload results to TestRail. Feb 10, 2023 N/A pytest (<8.0,>=7.2.0) + :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` Dec 01, 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 23, 2022 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 Apr 01, 2023 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. Oct 26, 2022 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-tmux` A pytest plugin that enables tmux driven tests Feb 15, 2023 4 - Beta 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 Oct 21, 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 Nov 01, 2022 N/A pytest (>=7.2.0) + :pypi:`pytest-trytond` Pytest plugin for the Tryton server framework Nov 04, 2022 4 - Beta 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) and HTML report for Pytest test runs Mar 30, 2023 4 - Beta N/A + :pypi:`pytest-tutorials` Mar 11, 2023 N/A N/A + :pypi:`pytest-twilio-conversations-client-mock` Aug 02, 2022 N/A N/A + :pypi:`pytest-twisted` A twisted plugin for pytest. Oct 16, 2022 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 Nov 04, 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-ui-failed-screenshot` UI自动测试失败时自动截图,并将截图加入到测试报告中 Dec 06, 2022 N/A N/A + :pypi:`pytest-ui-failed-screenshot-allure` UI自动测试失败时自动截图,并将截图加入到Allure测试报告中 Dec 06, 2022 N/A N/A + :pypi:`pytest-unflakable` Unflakable plugin for PyTest Mar 24, 2023 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 Nov 28, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-unstable` Set a test as unstable to return 0 even if it failed Sep 27, 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. Feb 02, 2023 4 - Beta pytest (>=7.0.0,<8.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-vcs` Sep 22, 2022 4 - Beta N/A + :pypi:`pytest-venv` py.test fixture for creating a virtual environment Aug 04, 2020 4 - Beta pytest + :pypi:`pytest-ver` Pytest module with Verification Protocol, Verification Report and Trace Matrix Mar 22, 2023 4 - Beta 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 Feb 25, 2023 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-vscode-pycharm-cls` A PyTest helper to enable start remote debugger on test start or failure or when pytest.set_trace is used. Feb 01, 2023 N/A pytest + :pypi:`pytest-vts` pytest plugin for automatic recording of http stubbed tests Jun 05, 2019 N/A pytest (>=2.3) + :pypi:`pytest-vulture` A pytest plugin to checks dead code with vulture Oct 12, 2022 N/A pytest (>=7.0.0) + :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` Feb 27, 2023 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 11, 2022 3 - Alpha N/A + :pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A + :pypi:`pytest-web3-data` Sep 15, 2022 4 - Beta pytest + :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 Jan 13, 2023 5 - Production/Stable pytest (>=7.0.0) + :pypi:`pytest-xdist` pytest xdist plugin for distributed testing, most importantly across multiple CPUs Mar 12, 2023 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) Mar 01, 2023 N/A pytest>=7.2.0 + :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. Jan 05, 2023 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-xskynet` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A + :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-yaml-sanmu` pytest plugin for generating test cases by yaml Mar 17, 2023 N/A pytest>=7.2.0 + :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-yaml-yoyo` http/https API run by yaml Mar 21, 2023 N/A pytest (>=7.2.0) + :pypi:`pytest-yapf` Run yapf Jul 06, 2017 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-yapf3` Validate your Python file format with yapf Mar 29, 2023 5 - Production/Stable pytest (>=7) + :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. Mar 29, 2023 N/A pytest (>=7.2.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 pytest>=5.0.0 + :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 12, 2022 5 - Production/Stable pytest (>=4.5.0) + :pypi:`pytest-zest` Zesty additions to pytest. Nov 17, 2022 N/A N/A + :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-abq` + *last release*: Mar 27, 2023, + *status*: N/A, + *requires*: N/A + + Pytest integration for the ABQ universal test runner. + + :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*: Dec 21, 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*: Oct 13, 2022, *status*: N/A, *requires*: pytest (>=5.4.0) @@ -1021,6 +1303,13 @@ This list contains 963 plugins. Pytest plugin for writing Azure Data Factory integration tests + :pypi:`pytest-ads-testplan` + *last release*: Sep 15, 2022, + *status*: N/A, + *requires*: N/A + + Azure DevOps Test Case reporting for pytest tests + :pypi:`pytest-agent` *last release*: Nov 25, 2021, *status*: N/A, @@ -1036,7 +1325,7 @@ This list contains 963 plugins. pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. :pypi:`pytest-aio` - *last release*: Oct 20, 2021, + *last release*: Feb 03, 2023, *status*: 4 - Beta, *requires*: pytest @@ -1050,19 +1339,26 @@ 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, + *last release*: Jan 10, 2023, *status*: N/A, - *requires*: pytest (>=6) + *requires*: pytest (>=7.2.0,<8.0.0) Pytest \`client\` fixture for the Aiohttp + :pypi:`pytest-aiomoto` + *last release*: Nov 09, 2022, + *status*: N/A, + *requires*: pytest (>=7.0,<8.0) + + pytest-aiomoto + :pypi:`pytest-aioresponses` *last release*: Jul 29, 2021, *status*: 4 - Beta, @@ -1092,9 +1388,9 @@ This list contains 963 plugins. :pypi:`pytest-alembic` - *last release*: Dec 02, 2021, + *last release*: Feb 03, 2023, *status*: N/A, - *requires*: pytest (>=1.0) + *requires*: pytest (>=6.0) A pytest plugin for verifying alembic migrations. @@ -1119,6 +1415,13 @@ This list contains 963 plugins. Plugin for py.test to generate allure xml reports + :pypi:`pytest-allure-collection` + *last release*: Oct 21, 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, @@ -1126,6 +1429,13 @@ This list contains 963 plugins. pytest plugin to test case doc string dls instructions + :pypi:`pytest-allure-intersection` + *last release*: Oct 27, 2022, + *status*: N/A, + *requires*: pytest (<5) + + + :pypi:`pytest-allure-spec-coverage` *last release*: Oct 26, 2021, *status*: N/A, @@ -1134,8 +1444,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 +1458,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 +1492,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 @@ -1197,25 +1514,39 @@ This list contains 963 plugins. The pytest anyio plugin is built into anyio. You don't need this package. :pypi:`pytest-anything` - *last release*: Feb 18, 2021, + *last release*: Oct 13, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest Pytest fixtures to assert anything and something :pypi:`pytest-aoc` - *last release*: Nov 23, 2021, + *last release*: Dec 08, 2022, *status*: N/A, *requires*: pytest ; extra == 'test' 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,12 +1570,26 @@ 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 + :pypi:`pytest-approvaltests-geo` + *last release*: Mar 04, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + Extension for ApprovalTests.Python specific to geo data verification + + :pypi:`pytest-archon` + *last release*: Jan 31, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.2) + + Rule your architecture like a real developer + :pypi:`pytest-argus` *last release*: Jun 24, 2021, *status*: 5 - Production/Stable, @@ -1253,9 +1598,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 +1618,20 @@ This list contains 963 plugins. test Answer Set Programming programs + :pypi:`pytest-assertcount` + *last release*: Oct 23, 2022, + *status*: N/A, + *requires*: pytest (>=5.0.0) + + 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 +1640,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 +1653,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 +1667,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 +1696,15 @@ This list contains 963 plugins. :pypi:`pytest-asyncio` - *last release*: Oct 15, 2021, + *last release*: Mar 19, 2023, *status*: 4 - Beta, - *requires*: pytest (>=5.4.0) + *requires*: pytest (>=7.0.0) - Pytest support for asyncio. + Pytest support for asyncio :pypi:`pytest-asyncio-cooperative` - *last release*: Oct 12, 2021, - *status*: 4 - Beta, + *last release*: Feb 10, 2023, + *status*: N/A, *requires*: N/A Run all your asynchronous tests cooperatively. @@ -1378,6 +1751,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 +1766,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' @@ -1413,6 +1793,13 @@ This list contains 963 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. + :pypi:`pytest-aviator` + *last release*: Nov 04, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Aviator's Flakybot pytest plugin that automatically reruns flaky tests. + :pypi:`pytest-avoidance` *last release*: May 23, 2019, *status*: 4 - Beta, @@ -1441,11 +1828,25 @@ This list contains 963 plugins. pytest plugin for axe-selenium-python - :pypi:`pytest-azurepipelines` - *last release*: Jul 23, 2020, + :pypi:`pytest-azure` + *last release*: Jan 18, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest utilities and mocks for Azure + + :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*: Oct 20, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=5.0.0) + Formatting PyTest output for Azure Pipelines UI :pypi:`pytest-bandit` @@ -1455,17 +1856,38 @@ This list contains 963 plugins. A bandit plugin for pytest + :pypi:`pytest-bandit-xayon` + *last release*: Oct 17, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + 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*: Nov 08, 2022, *status*: 6 - Mature, - *requires*: pytest (>=4.3) + *requires*: pytest (>=6.2.0) + + BDD for pytest + + :pypi:`pytest-bdd-html` + *last release*: Nov 22, 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*: Oct 06, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=5.0) BDD for pytest @@ -1512,28 +1934,35 @@ This list contains 963 plugins. Benchmark utility that plugs into pytest. :pypi:`pytest-benchmark` - *last release*: Apr 17, 2021, + *last release*: Oct 25, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.8) A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. + :pypi:`pytest-better-datadir` + *last release*: Mar 13, 2023, + *status*: N/A, + *requires*: N/A + + A small example package + :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 A BigchainDB plugin for pytest. :pypi:`pytest-bigquery-mock` - *last release*: Aug 05, 2021, + *last release*: Dec 28, 2022, *status*: N/A, *requires*: pytest (>=5.0) @@ -1553,6 +1982,13 @@ This list contains 963 plugins. Allow '--black' on older Pythons + :pypi:`pytest-black-ng` + *last release*: Oct 20, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.0.0) + + A pytest plugin to enable format checking with black + :pypi:`pytest-blame` *last release*: May 04, 2019, *status*: N/A, @@ -1561,9 +1997,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*: Jan 04, 2023, *status*: N/A, - *requires*: pytest (==6.2.5) ; extra == 'dev' + *requires*: pytest ; extra == 'dev' Blender Pytest plugin. @@ -1575,7 +2011,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 +2024,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 +2038,20 @@ This list contains 963 plugins. Local continuous test runner with pytest and watchdog. + :pypi:`pytest-boost-xml` + *last release*: Nov 30, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Plugin for pytest to generate boost xml reports + + :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 +2060,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 @@ -1651,15 +2108,22 @@ This list contains 963 plugins. \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. + :pypi:`pytest-budosystems` + *last release*: Feb 14, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Budo Systems is a martial arts school management system. This module is the Budo Systems Pytest Plugin. + :pypi:`pytest-bug` - *last release*: Jun 02, 2020, + *last release*: Jan 29, 2023, *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 +2172,9 @@ This list contains 963 plugins. pytest plugin with mechanisms for caching across test runs :pypi:`pytest-cache-assert` - *last release*: Nov 03, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=5) + *last release*: Feb 26, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=5.0.0) Cache assertion data to simplify regression testing of complex serializable data @@ -1721,6 +2185,20 @@ 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-call-checker` + *last release*: Oct 16, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.3,<8.0.0) + + Small pytest utility to easily create test doubles + :pypi:`pytest-camel-collect` *last release*: Aug 02, 2020, *status*: N/A, @@ -1749,15 +2227,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*: Feb 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -1805,6 +2283,20 @@ This list contains 963 plugins. A set of py.test fixtures for AWS Chalice + :pypi:`pytest-change-assert` + *last release*: Oct 19, 2022, + *status*: N/A, + *requires*: N/A + + 修改报错中文为英文 + + :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 +2304,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,10 +2318,17 @@ This list contains 963 plugins. A pytest fixture for changing current working directory + :pypi:`pytest-check` + *last release*: Feb 13, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + A pytest plugin that allows multiple failures per test. + :pypi:`pytest-checkdocs` - *last release*: Jul 31, 2021, + *last release*: Oct 09, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.6) ; extra == 'testing' + *requires*: pytest (>=6) ; extra == 'testing' check the README when running tests @@ -1833,10 +2339,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 +2367,27 @@ This list contains 963 plugins. pytest plugin to test Check_MK checks + :pypi:`pytest-check-requirements` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + + :pypi:`pytest-chic-report` + *last release*: Jan 31, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + A pytest plugin to send a report and printing summary of tests. + + :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, @@ -1855,12 +2396,19 @@ This list contains 963 plugins. py.test plugin for CircleCI :pypi:`pytest-circleci-parallelized` - *last release*: Mar 26, 2019, + *last release*: Oct 20, 2022, *status*: N/A, *requires*: N/A 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, @@ -1876,21 +2424,28 @@ This list contains 963 plugins. A plugin providing an alternative, colourful diff output for failing assertions. :pypi:`pytest-cldf` - *last release*: May 06, 2019, + *last release*: Nov 07, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest (>=3.6) 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 +2465,27 @@ 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-cmake` + *last release*: Jan 21, 2023, + *status*: N/A, + *requires*: pytest<8,>=4 + + Provide CMake module for Pytest + + :pypi:`pytest-cmake-presets` + *last release*: Dec 26, 2022, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + Execute CMake Presets via pytest + :pypi:`pytest-cobra` *last release*: Jun 29, 2019, *status*: 3 - Alpha, @@ -1917,12 +2493,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 +2508,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*: Nov 29, 2022, *status*: 4 - Beta, *requires*: pytest (>=4.6.0) @@ -1945,6 +2521,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, @@ -1952,6 +2535,13 @@ This list contains 963 plugins. pytest plugin to run pycodestyle + :pypi:`pytest-codspeed` + *last release*: Dec 02, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest>=3.8 + + Pytest plugin to create CodSpeed benchmarks + :pypi:`pytest-collect-formatter` *last release*: Mar 29, 2021, *status*: 5 - Production/Stable, @@ -1966,6 +2556,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,12 +2578,19 @@ 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 + :pypi:`pytest-compare` + *last release*: Mar 30, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + pytest plugin for comparing call arguments. + :pypi:`pytest-concurrent` *last release*: Jan 12, 2019, *status*: 4 - Beta, @@ -2002,14 +2606,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,8 +2627,8 @@ This list contains 963 plugins. pytest plugin with fixtures for testing consul aware apps :pypi:`pytest-container` - *last release*: Nov 19, 2021, - *status*: 3 - Alpha, + *last release*: Mar 21, 2023, + *status*: 4 - Beta, *requires*: pytest (>=3.10) Pytest fixtures for writing container based tests @@ -2044,9 +2648,9 @@ This list contains 963 plugins. A plugin to run tests written with the Contexts framework using pytest :pypi:`pytest-cookies` - *last release*: May 24, 2021, + *last release*: Mar 22, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.3.0) + *requires*: pytest (>=3.9.0) The pytest plugin for your Cookiecutter templates. 🍪 @@ -2065,7 +2669,7 @@ This list contains 963 plugins. count erros and send email :pypi:`pytest-cov` - *last release*: Oct 04, 2021, + *last release*: Sep 28, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=4.6) @@ -2092,6 +2696,13 @@ This list contains 963 plugins. Coverage dynamic context support for PyTest, including sub-processes + :pypi:`pytest-coveragemarkers` + *last release*: Nov 29, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + Using pytest markers to track functional coverage and filtering of tests + :pypi:`pytest-cov-exclude` *last release*: Apr 29, 2016, *status*: 4 - Beta, @@ -2100,12 +2711,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*: Jan 30, 2023, *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*: Mar 20, 2023, + *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, @@ -2120,6 +2745,20 @@ This list contains 963 plugins. Manages CrateDB instances during your integration tests + :pypi:`pytest-crayons` + *last release*: Mar 19, 2023, + *status*: N/A, + *requires*: pytest + + A pytest plugin for colorful print statements + + :pypi:`pytest-create` + *last release*: Feb 15, 2023, + *status*: 1 - Planning, + *requires*: N/A + + pytest-create + :pypi:`pytest-cricri` *last release*: Jan 27, 2018, *status*: N/A, @@ -2141,6 +2780,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 +2837,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*: Feb 16, 2023, + *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, @@ -2226,18 +2879,18 @@ This list contains 963 plugins. Pytest plugin for remote Databricks notebooks testing :pypi:`pytest-datadir` - *last release*: Oct 22, 2019, + *last release*: Oct 25, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.7.0) + *requires*: pytest (>=5.0) 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 +2899,20 @@ This list contains 963 plugins. Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. + :pypi:`pytest-datadir-nng` + *last release*: Nov 09, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.0.0,<8.0.0) + + 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,11 +2921,11 @@ 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*: Feb 24, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=3.6) - py.test plugin to create a 'tmpdir' containing predefined files/directories. + py.test plugin to create a 'tmp_path' containing predefined files/directories. :pypi:`pytest-datafixtures` *last release*: Dec 05, 2020, @@ -2282,12 +2949,26 @@ This list contains 963 plugins. A pytest plugin for managing an archive of test data. :pypi:`pytest-datarecorder` - *last release*: Apr 20, 2020, + *last release*: Jan 08, 2023, *status*: 5 - Production/Stable, *requires*: pytest A py.test plugin recording and comparing test output. + :pypi:`pytest-dataset` + *last release*: Oct 10, 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 +3004,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*: Mar 01, 2023, + *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, @@ -2330,6 +3025,13 @@ This list contains 963 plugins. D-BUS notifications for pytest results. + :pypi:`pytest-dbx` + *last release*: Nov 29, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.3,<8.0.0) + + Pytest plugin to run unit tests for dbx (Databricks CLI extensions) related code + :pypi:`pytest-deadfixtures` *last release*: Jul 23, 2020, *status*: 5 - Production/Stable, @@ -2400,6 +3102,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 +3137,27 @@ This list contains 963 plugins. A simple plugin to use with pytest + :pypi:`pytest-diffeo` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + + :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-difido` + *last release*: Oct 23, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=4.0.0) + + PyTest plugin for generating Difido reports + :pypi:`pytest-disable` *last release*: Sep 10, 2015, *status*: 4 - Beta, @@ -2443,14 +3173,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*: Feb 05, 2023, + *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) @@ -2464,9 +3194,9 @@ This list contains 963 plugins. A Django plugin for pytest. :pypi:`pytest-djangoapp` - *last release*: Aug 04, 2021, + *last release*: Mar 18, 2023, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest Nice pytest plugin to help you with Django pluggable application testing. @@ -2498,6 +3228,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 +3250,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 +3264,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 +3313,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 +3326,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 +3341,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*: Sep 14, 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 +3383,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*: Mar 24, 2023, *status*: 3 - Alpha, - *requires*: N/A + *requires*: pytest 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 +3425,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*: Feb 22, 2023, + *status*: 3 - Alpha, + *requires*: pytest (>=7.1.3) + + 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 +3488,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*: Sep 26, 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, @@ -2748,12 +3513,19 @@ This list contains 963 plugins. *status*: 4 - Beta, *requires*: pytest (>=5.0.0) - A py.test plugin that parses environment files before running tests + A py.test plugin that parses environment files before running tests + + :pypi:`pytest-draw` + *last release*: Mar 21, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest plugin for randomly selecting a specific number of 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 +3544,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 +3564,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, @@ -2800,7 +3579,7 @@ This list contains 963 plugins. A pytest plugin to rerun tests dynamically based off of test outcome and output. :pypi:`pytest-dynamodb` - *last release*: Jun 03, 2021, + *last release*: Mar 27, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -2841,6 +3620,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, @@ -2855,10 +3641,17 @@ This list contains 963 plugins. pytest plugin with mechanisms for echoing environment variables, package version and generic attributes + :pypi:`pytest-ekstazi` + *last release*: Sep 10, 2022, + *status*: N/A, + *requires*: pytest + + Pytest plugin to select test using Ekstazi algorithm + :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 +3662,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 +3684,61 @@ This list contains 963 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Nov 29, 2021, + *last release*: Mar 10, 2023, *status*: N/A, - *requires*: pytest (>=6.2.0) + *requires*: pytest (>=7.0) pytest embedded plugin + :pypi:`pytest-embedded-arduino` + *last release*: Mar 10, 2023, + *status*: N/A, + *requires*: N/A + + pytest embedded plugin for Arduino projects + :pypi:`pytest-embedded-idf` - *last release*: Nov 29, 2021, + *last release*: Mar 10, 2023, *status*: N/A, *requires*: N/A pytest embedded plugin for esp-idf project :pypi:`pytest-embedded-jtag` - *last release*: Nov 29, 2021, + *last release*: Mar 10, 2023, *status*: N/A, *requires*: N/A pytest embedded plugin for testing with jtag :pypi:`pytest-embedded-qemu` - *last release*: Nov 29, 2021, + *last release*: Mar 10, 2023, *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*: Mar 10, 2023, *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*: Mar 10, 2023, *status*: N/A, *requires*: N/A pytest embedded plugin for testing espressif boards via serial ports + :pypi:`pytest-embrace` + *last release*: Mar 25, 2023, + *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 +3747,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*: Jan 27, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=6) ; extra == 'testing' @@ -2967,6 +3774,13 @@ This list contains 963 plugins. set your encoding and logger + :pypi:`pytest-enhanced-reports` + *last release*: Dec 15, 2022, + *status*: N/A, + *requires*: N/A + + Enhanced test reports for pytest + :pypi:`pytest-enhancements` *last release*: Oct 30, 2019, *status*: 4 - Beta, @@ -2975,9 +3789,9 @@ This list contains 963 plugins. Improvements for pytest (rejected upstream) :pypi:`pytest-env` - *last release*: Jun 16, 2017, - *status*: 4 - Beta, - *requires*: N/A + *last release*: Oct 23, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.1.3 py.test plugin that allows you to add environment variables. @@ -3045,7 +3859,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) @@ -3058,8 +3872,15 @@ This list contains 963 plugins. Applies eventlet monkey-patch as a pytest plugin. + :pypi:`pytest-examples` + *last release*: Mar 26, 2023, + *status*: 4 - Beta, + *requires*: pytest>=7 + + Pytest plugin for testing examples in docstrings and markdown files. + :pypi:`pytest-excel` - *last release*: Oct 06, 2020, + *last release*: Jan 31, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -3080,12 +3901,19 @@ This list contains 963 plugins. Walk your code through exception script to check it's resiliency to failures. :pypi:`pytest-executable` - *last release*: Nov 10, 2021, - *status*: 4 - Beta, - *requires*: pytest (<6.3,>=4.3) + *last release*: Mar 25, 2023, + *status*: N/A, + *requires*: pytest (<8,>=4.3) 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, @@ -3093,8 +3921,15 @@ This list contains 963 plugins. py.test plugin to store test expectations and mark tests based on them + :pypi:`pytest-expectdir` + *last release*: Mar 19, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=5.0) + + A pytest plugin to provide initial/expected directories, and check a test transforms the initial directory to the expected one + :pypi:`pytest-expecter` - *last release*: Jul 08, 2020, + *last release*: Sep 18, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -3107,6 +3942,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 +3957,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, @@ -3135,6 +3984,13 @@ This list contains 963 plugins. A pytest plugin to get durations on a per-function basis and per module basis. + :pypi:`pytest-extra-markers` + *last release*: Mar 05, 2023, + *status*: 4 - Beta, + *requires*: pytest + + Additional pytest markers to dynamically enable/disable tests viia CLI flags + :pypi:`pytest-fabric` *last release*: Sep 12, 2018, *status*: 5 - Production/Stable, @@ -3142,6 +3998,13 @@ This list contains 963 plugins. Provides test utilities to run fabric task tests by using docker containers + :pypi:`pytest-factor` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + :pypi:`pytest-factory` *last release*: Sep 06, 2020, *status*: 3 - Alpha, @@ -3150,9 +4013,9 @@ This list contains 963 plugins. Use factories for test setup with py.test :pypi:`pytest-factoryboy` - *last release*: Dec 30, 2020, + *last release*: Dec 01, 2022, *status*: 6 - Mature, - *requires*: pytest (>=4.6) + *requires*: pytest (>=5.0.0) Factory Boy support for pytest. @@ -3164,12 +4027,19 @@ 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 + :pypi:`pytest-failed-screen-record` + *last release*: Jan 05, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.2d,<8.0.0) + + Create a video of the screen when pytest fails + :pypi:`pytest-failed-screenshot` *last release*: Apr 21, 2021, *status*: N/A, @@ -3184,6 +4054,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 +4096,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, @@ -3227,7 +4111,7 @@ This list contains 963 plugins. Use SCM and coverage to run only needed tests :pypi:`pytest-fast-first` - *last release*: Apr 02, 2021, + *last release*: Jan 19, 2023, *status*: 3 - Alpha, *requires*: pytest @@ -3275,6 +4159,13 @@ This list contains 963 plugins. A pytest plugin that runs marked tests when files change. + :pypi:`pytest-file-watcher` + *last release*: Mar 23, 2023, + *status*: N/A, + *requires*: pytest + + Pytest-File-Watcher is a CLI tool that watches for changes in your code and runs pytest on the changed files. + :pypi:`pytest-filter-case` *last release*: Nov 05, 2020, *status*: N/A, @@ -3283,16 +4174,16 @@ This list contains 963 plugins. run test cases filter by mark :pypi:`pytest-filter-subpackage` - *last release*: Jan 09, 2020, + *last release*: Dec 12, 2022, *status*: 3 - Alpha, *requires*: pytest (>=3.0) 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 @@ -3310,6 +4201,13 @@ This list contains 963 plugins. pytest plugin to manipulate firefox + :pypi:`pytest-fixture-classes` + *last release*: Jan 20, 2023, + *status*: 4 - Beta, + *requires*: pytest + + Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers + :pypi:`pytest-fixture-config` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -3332,12 +4230,26 @@ 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-ref` + *last release*: Nov 17, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Lets users reference fixtures without name matching magic. + + :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,21 +4272,28 @@ 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, + *last release*: Oct 26, 2022, *status*: 4 - Beta, *requires*: pytest (>=2.7.1) @@ -3401,8 +4320,15 @@ This list contains 963 plugins. A set of py.test fixtures to test Flask applications. + :pypi:`pytest-flask-ligand` + *last release*: Feb 10, 2023, + *status*: 4 - Beta, + *requires*: pytest (~=7.2) + + 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 +4341,20 @@ This list contains 963 plugins. Run tests in transactions using pytest, Flask, and SQLalchemy. + :pypi:`pytest-flexreport` + *last release*: Apr 01, 2023, + *status*: 4 - Beta, + *requires*: pytest + + + + :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, @@ -3429,6 +4369,13 @@ This list contains 963 plugins. A pytest plugin that alerts user of failed test cases with screen notifications + :pypi:`pytest-forbid` + *last release*: Mar 07, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.2,<8.0.0) + + + :pypi:`pytest-forcefail` *last release*: May 15, 2018, *status*: 4 - Beta, @@ -3457,6 +4404,13 @@ This list contains 963 plugins. Wrap tests with fixtures in freeze_time + :pypi:`pytest-freezer` + *last release*: Oct 20, 2022, + *status*: N/A, + *requires*: pytest>=3.6 + + Pytest plugin providing a fixture interface for spulec/freezegun + :pypi:`pytest-freeze-reqs` *last release*: Apr 29, 2021, *status*: N/A, @@ -3465,7 +4419,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 +4453,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 +4495,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 +4510,7 @@ This list contains 963 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Nov 30, 2021, + *last release*: Mar 15, 2023, *status*: N/A, *requires*: N/A @@ -3570,12 +4545,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*: Dec 19, 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, @@ -3583,8 +4565,22 @@ This list contains 963 plugins. py.test plugin to ignore the same files as git + :pypi:`pytest-gitlabci-parallelized` + *last release*: Mar 08, 2023, + *status*: N/A, + *requires*: N/A + + Parallelize pytest across GitLab CI workers. + + :pypi:`pytest-git-selector` + *last release*: Nov 17, 2022, + *status*: N/A, + *requires*: N/A + + Utility to select tests that have had its dependencies modified (as identified by git diff) + :pypi:`pytest-glamor-allure` - *last release*: Nov 26, 2021, + *last release*: Jul 22, 2022, *status*: 4 - Beta, *requires*: pytest @@ -3598,12 +4594,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, @@ -3632,6 +4635,13 @@ This list contains 963 plugins. pytest plugin for grpc + :pypi:`pytest-grunnur` + *last release*: Feb 05, 2023, + *status*: N/A, + *requires*: N/A + + Py.Test plugin for Grunnur-based packages. + :pypi:`pytest-hammertime` *last release*: Jul 28, 2018, *status*: N/A, @@ -3639,8 +4649,15 @@ This list contains 963 plugins. Display "🔨 " instead of "." for passed pytest tests. + :pypi:`pytest-harmony` + *last release*: Jan 17, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.1,<8.0.0) + + Chain tests and data with pytest + :pypi:`pytest-harvest` - *last release*: Apr 01, 2021, + *last release*: Jun 10, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -3654,9 +4671,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*: Mar 08, 2023, *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 +4692,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 +4727,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*: Apr 01, 2023, *status*: 3 - Alpha, - *requires*: pytest (==6.2.5) + *requires*: pytest (==7.2.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, @@ -3723,29 +4747,36 @@ This list contains 963 plugins. Report on tests that honor constraints, and guard against regressions + :pypi:`pytest-hot-test` + *last release*: Dec 10, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A plugin that tracks test changes + :pypi:`pytest-hoverfly` - *last release*: Jul 12, 2021, + *last release*: Jan 30, 2023, *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, - *requires*: N/A + *last release*: Feb 27, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=3.7.0) Integrates the Hoverfly HTTP proxy into Pytest :pypi:`pytest-hpfeeds` - *last release*: Aug 27, 2021, + *last release*: Feb 28, 2023, *status*: 4 - Beta, *requires*: pytest (>=6.2.4,<7.0.0) Helpers for testing hpfeeds in your python project :pypi:`pytest-html` - *last release*: Dec 13, 2020, + *last release*: Mar 05, 2023, *status*: 5 - Production/Stable, *requires*: pytest (!=6.0.0,>=5.0) @@ -3758,6 +4789,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 +4811,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 +4839,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,23 +4860,30 @@ This list contains 963 plugins. A thin wrapper of HTTPretty for pytest :pypi:`pytest-httpserver` - *last release*: Oct 18, 2021, + *last release*: Sep 12, 2022, *status*: 3 - Alpha, - *requires*: pytest ; extra == 'dev' + *requires*: N/A pytest-httpserver is a httpserver for pytest + :pypi:`pytest-httptesting` + *last release*: Mar 15, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + http_testing framework on top of pytest + :pypi:`pytest-httpx` - *last release*: Nov 16, 2021, + *last release*: Jan 20, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (==6.*) + *requires*: pytest (<8.0,>=6.0) Send responses to httpx. :pypi:`pytest-httpx-blockage` - *last release*: Nov 16, 2021, + *last release*: Feb 16, 2023, *status*: N/A, - *requires*: pytest (>=6.2.5) + *requires*: pytest (>=7.2.1) Disable httpx requests during a test run @@ -3850,14 +4909,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*: pytest>=7.1 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 +4929,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 +4951,7 @@ This list contains 963 plugins. ignore failures from flaky tests (pytest plugin) :pypi:`pytest-image-diff` - *last release*: Jul 28, 2021, + *last release*: Mar 09, 2023, *status*: 3 - Alpha, *requires*: pytest @@ -3927,26 +4993,47 @@ 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-inline` + *last release*: Feb 08, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.0.0) + + A pytest plugin for writing inline tests. + :pypi:`pytest-inmanta` - *last release*: Aug 17, 2021, + *last release*: Feb 23, 2023, *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*: Feb 09, 2023, *status*: 5 - Production/Stable, *requires*: N/A Inmanta tests package + :pypi:`pytest-inmanta-lsm` + *last release*: Feb 21, 2023, + *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,16 +5042,16 @@ This list contains 963 plugins. A simple image diff plugin for pytest :pypi:`pytest-insta` - *last release*: Apr 07, 2021, + *last release*: Nov 02, 2022, *status*: N/A, - *requires*: pytest (>=6.0.2,<7.0.0) + *requires*: pytest (>=7.2.0,<8.0.0) A practical snapshot testing plugin for pytest :pypi:`pytest-instafail` - *last release*: Jun 14, 2020, + *last release*: Mar 31, 2023, *status*: 4 - Beta, - *requires*: pytest (>=2.9) + *requires*: pytest (>=5) pytest plugin to show failures instantly @@ -3976,7 +5063,7 @@ This list contains 963 plugins. pytest plugin to instrument tests :pypi:`pytest-integration` - *last release*: Apr 16, 2020, + *last release*: Nov 17, 2022, *status*: N/A, *requires*: N/A @@ -4004,9 +5091,9 @@ 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*: Mar 24, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (<7,>=6) + *requires*: pytest (<7.2.0,>=6) Pytest fixtures for Invenio. @@ -4018,7 +5105,7 @@ This list contains 963 plugins. Run tests covering a specific file or changeset :pypi:`pytest-ipdb` - *last release*: Sep 02, 2014, + *last release*: Mar 20, 2013, *status*: 2 - Pre-Alpha, *requires*: N/A @@ -4031,13 +5118,27 @@ This list contains 963 plugins. THIS PROJECT IS ABANDONED + :pypi:`pytest-isolate` + *last release*: Feb 20, 2023, + *status*: 4 - Beta, + *requires*: pytest + + + :pypi:`pytest-isort` - *last release*: Apr 27, 2021, + *last release*: Oct 31, 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, @@ -4050,7 +5151,14 @@ This list contains 963 plugins. *status*: 3 - Alpha, *requires*: N/A - Nicer list and iterable assertion messages for pytest + 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, @@ -4059,6 +5167,13 @@ This list contains 963 plugins. Run jasmine tests from your pytest test suite + :pypi:`pytest-jelastic` + *last release*: Nov 16, 2022, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + Pytest plugin defining the necessary command-line options to pass to pytests testing a Jelastic environment. + :pypi:`pytest-jest` *last release*: May 22, 2018, *status*: 4 - Beta, @@ -4066,20 +5181,41 @@ This list contains 963 plugins. A custom jest-pytest oriented Pytest reporter + :pypi:`pytest-jinja` + *last release*: Oct 04, 2022, + *status*: 3 - Alpha, + *requires*: pytest (>=6.2.5,<7.0.0) + + A plugin to generate customizable jinja-based HTML reports in pytest + :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-xfail` + *last release*: Dec 01, 2022, + *status*: N/A, + *requires*: pytest (~=7.2.0) + + Plugin skips (xfail) tests if unresolved Jira issue(s) linked + :pypi:`pytest-jira-xray` - *last release*: Nov 28, 2021, - *status*: 3 - Alpha, + *last release*: Mar 13, 2023, + *status*: 4 - Beta, *requires*: pytest pytest plugin to integrate tests with JIRA XRAY + :pypi:`pytest-job-selection` + *last release*: Jan 30, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A pytest plugin for load balancing test suites + :pypi:`pytest-jobserver` *last release*: May 15, 2019, *status*: 5 - Production/Stable, @@ -4101,6 +5237,13 @@ This list contains 963 plugins. Generate JSON test reports + :pypi:`pytest-json-fixtures` + *last release*: Mar 14, 2023, + *status*: 4 - Beta, + *requires*: N/A + + JSON output for the --fixtures flag + :pypi:`pytest-jsonlint` *last release*: Aug 04, 2016, *status*: N/A, @@ -4109,14 +5252,28 @@ 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) A pytest plugin to report test results as JSON files + :pypi:`pytest-jtr` + *last release*: Nov 29, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + pytest plugin supporting json test report output + + :pypi:`pytest-jupyter` + *last release*: Mar 30, 2023, + *status*: 4 - Beta, + *requires*: pytest + + A pytest plugin for testing Jupyter libraries and extensions. + :pypi:`pytest-kafka` - *last release*: Aug 24, 2021, + *last release*: Oct 01, 2022, *status*: N/A, *requires*: pytest @@ -4129,8 +5286,29 @@ This list contains 963 plugins. A plugin to send pytest events to Kafka + :pypi:`pytest-kasima` + *last release*: Jan 26, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.2.1,<8.0.0) + + Display horizontal lines above and below the captured standard output for easy viewing. + + :pypi:`pytest-keep-together` + *last release*: Dec 07, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest plugin to customize test ordering by running all 'related' tests together + + :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*: Nov 30, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -4157,6 +5335,13 @@ This list contains 963 plugins. Run Konira DSL tests with py.test + :pypi:`pytest-koopmans` + *last release*: Nov 21, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A plugin for testing the koopmans package + :pypi:`pytest-krtech-common` *last release*: Nov 28, 2016, *status*: 4 - Beta, @@ -4164,6 +5349,13 @@ This list contains 963 plugins. pytest krtech common library + :pypi:`pytest-kubernetes` + *last release*: Feb 16, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.1,<8.0.0) + + + :pypi:`pytest-kwparametrize` *last release*: Jan 22, 2021, *status*: N/A, @@ -4172,9 +5364,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 +5377,27 @@ This list contains 963 plugins. + :pypi:`pytest-langchain` + *last release*: Feb 26, 2023, + *status*: N/A, + *requires*: pytest + + Pytest-style test runner for langchain agents + + :pypi:`pytest-lark` + *last release*: Nov 20, 2022, + *status*: N/A, + *requires*: N/A + + A package for enhancing pytest + + :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, @@ -4199,6 +5412,13 @@ This list contains 963 plugins. It helps to use fixtures in pytest.mark.parametrize + :pypi:`pytest-lazy-fixtures` + *last release*: Mar 11, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.1,<8.0.0) + + Allows you to use fixtures in @pytest.mark.parametrize. + :pypi:`pytest-ldap` *last release*: Aug 18, 2020, *status*: N/A, @@ -4206,6 +5426,13 @@ This list contains 963 plugins. python-ldap fixtures for pytest + :pypi:`pytest-leak-finder` + *last release*: Feb 15, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Find the test that's leaking before the one that fails + :pypi:`pytest-leaks` *last release*: Nov 27, 2019, *status*: 1 - Planning, @@ -4228,7 +5455,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 +5489,13 @@ This list contains 963 plugins. Profile code executed by pytest + :pypi:`pytest-line-profiler-apn` + *last release*: Dec 05, 2022, + *status*: N/A, + *requires*: pytest (>=3.5.0) + + Profile code executed by pytest + :pypi:`pytest-lisa` *last release*: Jan 21, 2021, *status*: 3 - Alpha, @@ -4290,29 +5524,36 @@ This list contains 963 plugins. Live results for pytest + :pypi:`pytest-local-badge` + *last release*: Jan 15, 2023, + *status*: N/A, + *requires*: pytest (>=6.1.0) + + Generate local badges (shields) reporting your test suite status. + :pypi:`pytest-localftpserver` - *last release*: Aug 25, 2021, + *last release*: Oct 04, 2022, *status*: 5 - Production/Stable, *requires*: pytest A PyTest plugin which provides an FTP fixture for your tests :pypi:`pytest-localserver` - *last release*: Nov 19, 2021, + *last release*: Jan 30, 2023, *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, + *last release*: Oct 17, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.3.0) + *requires*: pytest (>=6.0.0,<7.0.0) 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 @@ -4367,6 +5608,20 @@ This list contains 963 plugins. Configures logging and allows tweaking the log level with a py.test flag + :pypi:`pytest-logging-end-to-end-test-tool` + *last release*: Sep 23, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + + + :pypi:`pytest-logikal` + *last release*: Mar 09, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (==7.2.1) + + Common testing environment + :pypi:`pytest-log-report` *last release*: Dec 26, 2019, *status*: N/A, @@ -4374,13 +5629,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*: Jan 14, 2023, + *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*: pytest>=7 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 +5671,13 @@ This list contains 963 plugins. Test your markdown docs with pytest + :pypi:`pytest-markdown-docs` + *last release*: Mar 09, 2023, + *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 +5714,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 +5741,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 +5763,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 +5776,13 @@ This list contains 963 plugins. Estimates memory consumption of test functions + :pypi:`pytest-memray` + *last release*: Dec 02, 2022, + *status*: N/A, + *requires*: pytest>=7.2 + + A simple plugin to use with pytest + :pypi:`pytest-menu` *last release*: Oct 04, 2017, *status*: 3 - Alpha, @@ -4493,24 +5797,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*: Nov 24, 2022, *status*: 5 - Production/Stable, *requires*: N/A Pytest to Slack reporting plugin :pypi:`pytest-metadata` - *last release*: Nov 27, 2020, + *last release*: Oct 30, 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,12 +5840,19 @@ 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 + :pypi:`pytest-mini` + *last release*: Feb 06, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + A plugin to test mp + :pypi:`pytest-missing-fixtures` *last release*: Oct 14, 2020, *status*: 4 - Beta, @@ -4557,7 +5875,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*: Oct 05, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) @@ -4571,7 +5889,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 +5917,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*: Mar 03, 2023, *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 +5937,20 @@ This list contains 963 plugins. A set of fixtures to test your requests to HTTP/UDP servers + :pypi:`pytest-mocktcp` + *last release*: Oct 11, 2022, + *status*: N/A, + *requires*: pytest + + A pytest plugin for testing TCP clients + + :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 +5966,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 +5987,7 @@ This list contains 963 plugins. pytest plugin for MongoDB fixtures :pypi:`pytest-monitor` - *last release*: Aug 24, 2021, + *last release*: Oct 22, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -4697,32 +6029,32 @@ 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*: Nov 15, 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, - *status*: N/A, - *requires*: pytest + :pypi:`pytest-mqtt` + *last release*: Mar 15, 2023, + *status*: 4 - Beta, + *requires*: pytest (<8) ; extra == 'test' - Pytest-плагин, реализует возможность мульти проверок и мягких проверок + pytest-mqtt supports testing systems based on MQTT :pypi:`pytest-multihost` *last release*: Apr 07, 2020, @@ -4732,19 +6064,26 @@ This list contains 963 plugins. Utility for writing multi-host tests for pytest :pypi:`pytest-multilog` - *last release*: Jun 10, 2021, + *last release*: Jan 17, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest Multi-process logs handling and other helpers for pytest :pypi:`pytest-multithreading` - *last release*: Aug 12, 2021, + *last release*: Dec 07, 2022, *status*: N/A, - *requires*: pytest (>=3.6) + *requires*: N/A a pytest plugin for th and concurrent testing + :pypi:`pytest-multithreading-allure` + *last release*: Nov 25, 2022, + *status*: N/A, + *requires*: N/A + + pytest_multithreading_allure + :pypi:`pytest-mutagen` *last release*: Jul 24, 2020, *status*: N/A, @@ -4753,9 +6092,9 @@ This list contains 963 plugins. Add the mutation testing feature to pytest :pypi:`pytest-mypy` - *last release*: Mar 21, 2021, + *last release*: Dec 18, 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 +6106,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*: Oct 26, 2022, + *status*: 4 - Beta, *requires*: pytest (>=6.0.0) pytest plugin for writing tests for mypy plugins @@ -4776,21 +6115,21 @@ This list contains 963 plugins. :pypi:`pytest-mypy-plugins-shim` *last release*: Apr 12, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=6.0.0 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*: Feb 25, 2023, *status*: N/A, - *requires*: pytest + *requires*: pytest>=7,<8 Pytest plugin to check mypy output. :pypi:`pytest-mysql` - *last release*: Nov 22, 2021, + *last release*: Mar 27, 2023, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: pytest (>=6.2) MySQL process and client fixtures for pytest @@ -4802,12 +6141,19 @@ 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. + :pypi:`pytest-netdut` + *last release*: Jan 11, 2023, + *status*: N/A, + *requires*: pytest (>=3.5.0) + + "Automated software testing for switches using pytest" + :pypi:`pytest-network` *last release*: May 07, 2020, *status*: N/A, @@ -4815,6 +6161,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 +6190,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 +6203,13 @@ This list contains 963 plugins. pytest ngs fixtures + :pypi:`pytest-nhsd-apim` + *last release*: Mar 06, 2023, + *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 +6252,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 +6288,26 @@ This list contains 963 plugins. A PyTest Reporter to send test runs to Notion.so :pypi:`pytest-nunit` - *last release*: Aug 04, 2020, - *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *last release*: Oct 20, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=4.6.0) A pytest plugin for generating NUnit3 test result XML output + :pypi:`pytest-oar` + *last release*: Mar 31, 2023, + *status*: N/A, + *requires*: pytest>=6.0.1 + + PyTest plugin for the OAR testing framework + + :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 +6316,9 @@ This list contains 963 plugins. pytest results data-base and HTML reporter :pypi:`pytest-odoo` - *last release*: Nov 04, 2021, + *last release*: Nov 17, 2022, *status*: 4 - Beta, - *requires*: pytest (>=2.9) + *requires*: pytest (>=7.2.0) py.test plugin to run Odoo tests @@ -4969,6 +6336,20 @@ This list contains 963 plugins. pytest plugin to test OpenERP modules + :pypi:`pytest-offline` + *last release*: Mar 09, 2023, + *status*: 1 - Planning, + *requires*: pytest (>=7.0.0,<8.0.0) + + + + :pypi:`pytest-ogsm-plugin` + *last release*: Mar 08, 2023, + *status*: N/A, + *requires*: N/A + + 针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数 + :pypi:`pytest-ok` *last release*: Apr 01, 2019, *status*: 4 - Beta, @@ -4977,9 +6358,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 +6378,24 @@ This list contains 963 plugins. Pytest plugin for detecting inadvertent open file handles + :pypi:`pytest-opentelemetry` + *last release*: Mar 15, 2023, + *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*: Sep 28, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest Fixtures for Operators @@ -5033,9 +6421,9 @@ This list contains 963 plugins. A pytest plugin for orchestrating tests :pypi:`pytest-order` - *last release*: May 30, 2021, + *last release*: Mar 10, 2023, *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 @@ -5046,6 +6434,13 @@ This list contains 963 plugins. pytest plugin to run your tests in a specific order + :pypi:`pytest-order-modify` + *last release*: Nov 04, 2022, + *status*: N/A, + *requires*: N/A + + 新增run_marker 来自定义用例的执行顺序 + :pypi:`pytest-osxnotify` *last release*: May 15, 2015, *status*: N/A, @@ -5054,12 +6449,26 @@ This list contains 963 plugins. OS X notifications for py.test results. :pypi:`pytest-otel` - *last release*: Dec 03, 2021, + *last release*: Jan 18, 2023, *status*: N/A, *requires*: N/A pytest-otel report OpenTelemetry traces about test executed + :pypi:`pytest-override-env-var` + *last release*: Feb 25, 2023, + *status*: N/A, + *requires*: N/A + + Pytest mark to override a value of an environment variable. + + :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, @@ -5088,6 +6497,13 @@ This list contains 963 plugins. a pytest plugin for parallel and concurrent testing + :pypi:`pytest-parallelize-tests` + *last release*: Jan 27, 2023, + *status*: 4 - Beta, + *requires*: N/A + + pytest plugin that parallelizes test execution across multiple hosts + :pypi:`pytest-param` *last release*: Sep 11, 2016, *status*: 4 - Beta, @@ -5103,25 +6519,32 @@ 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. :pypi:`pytest-parametrized` - *last release*: Oct 19, 2020, + *last release*: Sep 13, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest decorator for parametrizing tests with default iterables. + + :pypi:`pytest-parametrize-suite` + *last release*: Jan 19, 2023, *status*: 5 - Production/Stable, *requires*: pytest - Pytest plugin for parametrizing tests with default iterables. + A simple pytest extension for creating a named test suite. :pypi:`pytest-parawtf` *last release*: Dec 03, 2018, @@ -5194,9 +6617,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 +6631,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*: Mar 28, 2023, *status*: N/A, *requires*: N/A Pytest tool for persistent objects + :pypi:`pytest-pg` + *last release*: Sep 19, 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 +6652,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. @@ -5256,6 +6686,13 @@ This list contains 963 plugins. Slice in your test base thanks to powerful markers. + :pypi:`pytest-pingguo-pytest-plugin` + *last release*: Oct 26, 2022, + *status*: 4 - Beta, + *requires*: N/A + + pingguo test + :pypi:`pytest-pings` *last release*: Jun 29, 2019, *status*: 3 - Alpha, @@ -5306,9 +6743,9 @@ This list contains 963 plugins. Pytest plugin for reading playbooks. :pypi:`pytest-playwright` - *last release*: Oct 28, 2021, + *last release*: Mar 10, 2023, *status*: N/A, - *requires*: pytest + *requires*: pytest (<8.0.0,>=6.2.4) A pytest wrapper with fixtures for Playwright to automate web browsers @@ -5326,6 +6763,20 @@ 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-plone` + *last release*: Jan 05, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest plugin to test Plone addons + :pypi:`pytest-plt` *last release*: Aug 17, 2020, *status*: 5 - Production/Stable, @@ -5341,9 +6792,9 @@ This list contains 963 plugins. A plugin to help developing and testing other plugins :pypi:`pytest-plus` - *last release*: Mar 19, 2020, + *last release*: Dec 24, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.50) + *requires*: pytest (>=6.0.1) PyTest Plus Plugin :: extends pytest functionality @@ -5355,7 +6806,7 @@ This list contains 963 plugins. :pypi:`pytest-pointers` - *last release*: Oct 14, 2021, + *last release*: Dec 26, 2022, *status*: N/A, *requires*: N/A @@ -5404,7 +6855,7 @@ This list contains 963 plugins. Visualize your failed tests with poo :pypi:`pytest-pop` - *last release*: Aug 19, 2021, + *last release*: Mar 16, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -5425,12 +6876,19 @@ 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. + :pypi:`pytest-pot` + *last release*: Nov 20, 2022, + *status*: N/A, + *requires*: N/A + + A package for enhancing pytest + :pypi:`pytest-power` *last release*: Dec 31, 2020, *status*: N/A, @@ -5438,8 +6896,22 @@ 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` + *last release*: Mar 22, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest>=7 + + pytest plugin for printing summary data as I want it + :pypi:`pytest-pretty-terminal` - *last release*: Nov 24, 2021, + *last release*: Jan 31, 2022, *status*: N/A, *requires*: pytest (>=3.4.1) @@ -5453,12 +6925,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 +6946,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 +6959,13 @@ This list contains 963 plugins. Report test pass / failures to a Prometheus PushGateway + :pypi:`pytest-prometheus-pushgateway` + *last release*: Sep 27, 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 +6988,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) @@ -5515,6 +7001,13 @@ This list contains 963 plugins. Pytest PuDB debugger integration + :pypi:`pytest-pumpkin-spice` + *last release*: Sep 18, 2022, + *status*: 4 - Beta, + *requires*: N/A + + A pytest plugin that makes your test reporting pumpkin-spiced + :pypi:`pytest-purkinje` *last release*: Oct 28, 2017, *status*: 2 - Pre-Alpha, @@ -5522,6 +7015,20 @@ This list contains 963 plugins. py.test plugin for purkinje test runner + :pypi:`pytest-pusher` + *last release*: Jan 06, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=3.6) + + pytest plugin for push report to minio + + :pypi:`pytest-py125` + *last release*: Dec 03, 2022, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-pycharm` *last release*: Aug 13, 2020, *status*: 5 - Production/Stable, @@ -5530,7 +7037,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*: Oct 28, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -5544,19 +7051,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*: Jan 05, 2023, *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*: Jan 05, 2023, + *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 +7093,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, @@ -5586,7 +7107,7 @@ This list contains 963 plugins. Pytest fixture "q" for pyq :pypi:`pytest-pyramid` - *last release*: Oct 15, 2021, + *last release*: Dec 13, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -5600,12 +7121,19 @@ This list contains 963 plugins. Pyramid server fixture for py.test :pypi:`pytest-pyright` - *last release*: Aug 16, 2021, + *last release*: Nov 20, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest (>=7.0.0) Pytest plugin for type checking code with Pyright + :pypi:`pytest-pyspec` + *last release*: Mar 12, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.2.1,<8.0.0) + + A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it". + :pypi:`pytest-pytestrail` *last release*: Aug 27, 2020, *status*: 4 - Beta, @@ -5614,9 +7142,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. @@ -5627,6 +7155,13 @@ This list contains 963 plugins. pytest plugin for a better developer experience when working with the PyTorch test suite + :pypi:`pytest-pyvista` + *last release*: Mar 19, 2023, + *status*: 4 - Beta, + *requires*: pytest>=3.5.0 + + Pytest-pyvista package + :pypi:`pytest-qasync` *last release*: Jul 12, 2021, *status*: 4 - Beta, @@ -5635,14 +7170,14 @@ This list contains 963 plugins. Pytest support for qasync. :pypi:`pytest-qatouch` - *last release*: Jun 26, 2021, + *last release*: Feb 14, 2023, *status*: 4 - Beta, *requires*: pytest (>=6.2.0) 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 +7198,7 @@ This list contains 963 plugins. pytest plugin to generate test result QR codes :pypi:`pytest-qt` - *last release*: Jun 13, 2021, + *last release*: Oct 25, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.0.0) @@ -5684,21 +7219,21 @@ This list contains 963 plugins. A plugin for pytest to manage expected test failures :pypi:`pytest-quickcheck` - *last release*: Nov 15, 2020, + *last release*: Nov 05, 2022, *status*: 4 - Beta, - *requires*: pytest (<6.0.0,>=4.0) + *requires*: pytest (>=4.0) 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 +7246,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 +7275,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 +7289,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 @@ -5768,28 +7310,35 @@ This list contains 963 plugins. Randomise the order in which pytest tests are run with some control over the randomness :pypi:`pytest-random-order` - *last release*: Nov 30, 2018, + *last release*: Dec 03, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.0.0) 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*: Dec 13, 2022, *status*: 3 - Alpha, *requires*: N/A Pytest fixtures for REANA. + :pypi:`pytest-recorder` + *last release*: Mar 30, 2023, + *status*: N/A, + *requires*: N/A + + Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. + :pypi:`pytest-recording` - *last release*: Jul 08, 2021, + *last release*: Feb 16, 2023, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) @@ -5803,14 +7352,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*: Mar 27, 2023, *status*: 5 - Production/Stable, - *requires*: pytest + *requires*: pytest (>=6.2) 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 +7386,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 13, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.5.0) + *requires*: pytest (>=6.2.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 @@ -5859,9 +7415,9 @@ This list contains 963 plugins. a pytest plugin that sorts tests using "before" and "after" markers :pypi:`pytest-relaxed` - *last release*: Jun 14, 2019, + *last release*: Dec 31, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (<5,>=3) + *requires*: pytest (>=7) Relaxed test discovery/organization for pytest @@ -5873,14 +7429,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 12, 2022, *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*: Mar 27, 2023, *status*: 4 - Beta, *requires*: pytest (>=4.6) @@ -5915,7 +7471,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 @@ -5942,6 +7498,13 @@ This list contains 963 plugins. A basic HTML report template for Pytest + :pypi:`pytest-reporter-html-dots` + *last release*: Jan 22, 2023, + *status*: N/A, + *requires*: N/A + + A basic HTML report for pytest using Jinja2 template engine. + :pypi:`pytest-reportinfra` *last release*: Aug 11, 2019, *status*: 3 - Alpha, @@ -5957,9 +7520,9 @@ This list contains 963 plugins. A plugin to report summarized results in a table format :pypi:`pytest-reportlog` - *last release*: Dec 11, 2020, + *last release*: Mar 11, 2023, *status*: 3 - Alpha, - *requires*: pytest (>=5.2) + *requires*: pytest Replacement for the --resultlog option, focused in simplicity and extensibility @@ -5978,7 +7541,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*: Mar 28, 2023, *status*: N/A, *requires*: pytest (>=3.8.0) @@ -5998,8 +7561,29 @@ 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, + *last release*: Sep 20, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=4.6) @@ -6013,18 +7597,32 @@ This list contains 963 plugins. Re-run only changed files in specified branch :pypi:`pytest-rerunfailures` - *last release*: Sep 17, 2021, + *last release*: Mar 09, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=5.3) 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-reserial` + *last release*: Nov 29, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Pytest fixture for recording and replaying serial port traffic. + :pypi:`pytest-resilient-circuits` - *last release*: Nov 15, 2021, + *last release*: Feb 28, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest (~=4.6) ; python_version == "2.7" - Resilient Circuits fixtures for PyTest. + Resilient Circuits fixtures for PyTest :pypi:`pytest-resource` *last release*: Nov 14, 2018, @@ -6040,27 +7638,48 @@ This list contains 963 plugins. Provides path for uniform access to test resources in isolated directory + :pypi:`pytest-resource-usage` + *last release*: Nov 06, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.0.0 + + Pytest plugin for reporting running time and peak memory usage + :pypi:`pytest-responsemock` - *last release*: Oct 10, 2020, + *last release*: Mar 10, 2022, *status*: 5 - Production/Stable, *requires*: N/A Simplified requests calls mocking for pytest :pypi:`pytest-responses` - *last release*: Apr 26, 2021, + *last release*: Oct 11, 2022, *status*: N/A, *requires*: pytest (>=2.5) 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 Pytest plugin to restrict the test types allowed + :pypi:`pytest-result-log` + *last release*: Feb 02, 2023, + *status*: N/A, + *requires*: pytest>=7.2.0 + + Write the execution result of the case to the log + :pypi:`pytest-rethinkdb` *last release*: Jul 24, 2016, *status*: 4 - Beta, @@ -6068,13 +7687,48 @@ 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-retry-class` + *last release*: Mar 25, 2023, + *status*: N/A, + *requires*: pytest (>=5.3) + + A pytest plugin to rerun entire class on failure + :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-richtrace` + *last release*: Nov 05, 2022, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + + :pypi:`pytest-ringo` *last release*: Sep 27, 2017, *status*: 3 - Alpha, @@ -6082,6 +7736,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, @@ -6090,7 +7751,7 @@ This list contains 963 plugins. Fixtures for seeding tests and making randomness reproducible :pypi:`pytest-roast` - *last release*: Jul 29, 2021, + *last release*: Nov 09, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -6118,14 +7779,14 @@ This list contains 963 plugins. Extend py.test for RPC OpenStack testing. :pypi:`pytest-rst` - *last release*: Sep 21, 2021, + *last release*: Jan 26, 2023, *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 @@ -6138,6 +7799,13 @@ This list contains 963 plugins. Coverage-based regression test selection (RTS) plugin for pytest + :pypi:`pytest-ruff` + *last release*: Mar 22, 2023, + *status*: 4 - Beta, + *requires*: N/A + + pytest plugin to check ruff requirements. + :pypi:`pytest-run-changed` *last release*: Apr 02, 2021, *status*: 3 - Alpha, @@ -6153,19 +7821,47 @@ 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*: Nov 12, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest Plugin for running and testing subprocesses. + + :pypi:`pytest-runtime-types` + *last release*: Feb 09, 2023, + *status*: N/A, + *requires*: pytest + + Checks type annotations on runtime while running tests. + :pypi:`pytest-runtime-xfail` *last release*: Aug 26, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=5.0.0 Call runtime_xfail() to mark running test as xfail. + :pypi:`pytest-ry-demo1` + *last release*: Mar 26, 2023, + *status*: N/A, + *requires*: N/A + + 测试 + + :pypi:`pytest-saccharin` + *last release*: Oct 31, 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 +7877,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*: Dec 15, 2022, *status*: 4 - Beta, *requires*: pytest (>=6.0.0) @@ -6223,7 +7919,7 @@ This list contains 963 plugins. :pypi:`pytest-sbase` - *last release*: Dec 03, 2021, + *last release*: Mar 28, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -6236,8 +7932,15 @@ This list contains 963 plugins. pytest plugin for test scenarios + :pypi:`pytest-schedule` + *last release*: Jan 07, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + The job of test scheduling for humans. + :pypi:`pytest-schema` - *last release*: Aug 31, 2020, + *last release*: Mar 14, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.0) @@ -6258,21 +7961,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*: Sep 21, 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*: Mar 28, 2023, *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 @@ -6293,9 +7996,9 @@ This list contains 963 plugins. Send pytest execution result email :pypi:`pytest-sentry` - *last release*: Apr 21, 2021, + *last release*: Jan 05, 2023, *status*: N/A, - *requires*: pytest + *requires*: N/A A pytest plugin to send testrun information to Sentry.io @@ -6307,12 +8010,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*: Feb 24, 2023, + *status*: 3 - Alpha, + *requires*: pytest (>=6.2) + + pytest servers + :pypi:`pytest-services` *last release*: Oct 30, 2020, *status*: 6 - Mature, @@ -6341,6 +8051,13 @@ This list contains 963 plugins. pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. + :pypi:`pytest-setupinfo` + *last release*: Jan 23, 2023, + *status*: N/A, + *requires*: N/A + + Displaying setup info during pytest command run + :pypi:`pytest-sftpserver` *last release*: Sep 16, 2019, *status*: 4 - Beta, @@ -6355,13 +8072,34 @@ This list contains 963 plugins. + :pypi:`pytest-share-hdf` + *last release*: Sep 21, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Plugin to save test data in HDF files and retrieve them for comparison + + :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*: Sep 23, 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, @@ -6370,7 +8108,7 @@ This list contains 963 plugins. Versatile ZODB abstraction layer - pytest fixtures :pypi:`pytest-sherlock` - *last release*: Nov 18, 2021, + *last release*: Jan 16, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.1) @@ -6419,9 +8157,9 @@ 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, - *requires*: pytest (>=6.0.0) + *last release*: Dec 20, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.1.0) Pytest Salt Plugin @@ -6440,9 +8178,9 @@ 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*: Feb 09, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest>=6.2.0 A pytest plugin to skip \`@pytest.mark.slow\` tests by default. @@ -6460,6 +8198,20 @@ This list contains 963 plugins. A pytest plugin to skip \`@pytest.mark.slow\` tests by default. + :pypi:`pytest-slowest-first` + *last release*: Dec 11, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Sort tests by their last duration, slowest first + + :pypi:`pytest-slow-last` + *last release*: Dec 10, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Run tests in order of execution time (faster tests first) + :pypi:`pytest-smartcollect` *last release*: Oct 04, 2018, *status*: N/A, @@ -6474,6 +8226,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 +8255,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 +8268,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*: Feb 03, 2023, *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 +8296,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 +8317,13 @@ This list contains 963 plugins. A simple plugin to first execute tests that historically failed more + :pypi:`pytest-sosu` + *last release*: Feb 14, 2023, + *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 +8352,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*: Jan 22, 2023, + *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 +8423,14 @@ This list contains 963 plugins. :pypi:`pytest-splunk-addon` - *last release*: Nov 29, 2021, + *last release*: Feb 22, 2023, *status*: N/A, - *requires*: pytest (>5.4.0,<6.3) + *requires*: pytest (>5.4.0,<7.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 07, 2023, *status*: N/A, *requires*: N/A @@ -6649,6 +8457,13 @@ This list contains 963 plugins. pytest plugin with sqlalchemy related fixtures + :pypi:`pytest-sqlalchemy-mock` + *last release*: Mar 15, 2023, + *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,10 +8471,24 @@ This list contains 963 plugins. Yet another SQL-testing framework for BigQuery provided by pytest plugin + :pypi:`pytest-sqlfluff` + *last release*: Dec 21, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A pytest plugin to use sqlfluff to enable format checking of sql files. + + :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, - *requires*: N/A + *requires*: pytest>=6.2.0 Add paths to sys.path @@ -6677,6 +8506,13 @@ This list contains 963 plugins. Start pytest run from a given point + :pypi:`pytest-star-track-issue` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + :pypi:`pytest-statsd` *last release*: Nov 30, 2018, *status*: 5 - Production/Stable, @@ -6705,6 +8541,13 @@ This list contains 963 plugins. Run a test suite one failing test at a time. + :pypi:`pytest-stf` + *last release*: Dec 04, 2022, + *status*: N/A, + *requires*: pytest (>=5.0) + + pytest plugin for openSTF + :pypi:`pytest-stoq` *last release*: Feb 09, 2021, *status*: 4 - Beta, @@ -6720,7 +8563,7 @@ This list contains 963 plugins. A Pytest plugin that allows you to loop tests for a user defined amount of time. :pypi:`pytest-structlog` - *last release*: Sep 21, 2021, + *last release*: Dec 18, 2022, *status*: N/A, *requires*: pytest @@ -6755,23 +8598,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*: Jan 28, 2023, *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*: Feb 16, 2023, *status*: 4 - Beta, - *requires*: pytest (>=5.3.0) + *requires*: pytest (>=7.0) unittest subTest() support and subtests fixture @@ -6783,26 +8626,12 @@ 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*: Nov 05, 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). - :pypi:`pytest-sugar-bugfix159` - *last release*: Nov 07, 2018, - *status*: 5 - Production/Stable, - *requires*: pytest (!=3.7.3,>=3.5); extra == 'testing' - - Workaround for https://github.com/Frozenball/pytest-sugar/issues/159 - - :pypi:`pytest-super-check` - *last release*: Aug 12, 2021, - *status*: 5 - Production/Stable, - *requires*: pytest - - Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc. - :pypi:`pytest-svn` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -6817,8 +8646,29 @@ 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-tagging` + *last release*: Apr 01, 2023, + *status*: N/A, + *requires*: pytest (>=7.1.3,<8.0.0) + + a pytest plugin to tag tests + :pypi:`pytest-takeltest` - *last release*: Oct 13, 2021, + *last release*: Feb 15, 2023, *status*: N/A, *requires*: N/A @@ -6859,6 +8709,13 @@ This list contains 963 plugins. tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used + :pypi:`pytest-tcpclient` + *last release*: Nov 16, 2022, + *status*: N/A, + *requires*: pytest (<8,>=7.1.3) + + A pytest plugin for testing TCP clients + :pypi:`pytest-teamcity-logblock` *last release*: May 15, 2018, *status*: 4 - Beta, @@ -6873,6 +8730,13 @@ This list contains 963 plugins. Pytest to Telegram reporting plugin + :pypi:`pytest-telegram-notifier` + *last release*: Mar 17, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Telegram notification plugin for Pytest + :pypi:`pytest-tempdir` *last release*: Oct 11, 2019, *status*: 4 - Beta, @@ -6880,8 +8744,15 @@ This list contains 963 plugins. Predictable and repeatable tempdir support. + :pypi:`pytest-terra-fixt` + *last release*: Sep 15, 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 +8780,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*: Feb 01, 2023, + *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 +8808,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*: Dec 01, 2022, *status*: 5 - Production/Stable, *requires*: pytest (!=3.0.2) @@ -6944,9 +8822,30 @@ This list contains 963 plugins. pytest reporting plugin for testlink :pypi:`pytest-testmon` - *last release*: Oct 22, 2021, + *last release*: Mar 25, 2023, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (<8,>=5) + + selects tests affected by changed files and methods + + :pypi:`pytest-testmon-dev` + *last release*: Mar 30, 2023, + *status*: 4 - Beta, + *requires*: pytest (<8,>=5) + + 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*: Mar 03, 2023, + *status*: 4 - Beta, + *requires*: pytest (<8,>=5) selects tests affected by changed files and methods @@ -6957,6 +8856,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, @@ -6965,21 +8871,14 @@ This list contains 963 plugins. pytest plugin for creating TestRail runs and adding results :pypi:`pytest-testrail2` - *last release*: Nov 17, 2020, - *status*: N/A, - *requires*: pytest (>=5) - - A small example package - - :pypi:`pytest-testrail-api` - *last release*: Nov 30, 2021, + *last release*: Feb 10, 2023, *status*: N/A, - *requires*: pytest (>=5.5) + *requires*: pytest (<8.0,>=7.2.0) - Плагин Pytest, для интеграции с TestRail + A pytest plugin to upload results to TestRail. :pypi:`pytest-testrail-api-client` - *last release*: Dec 03, 2021, + *last release*: Dec 14, 2021, *status*: N/A, *requires*: pytest @@ -7006,10 +8905,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 +8934,14 @@ This list contains 963 plugins. :pypi:`pytest-testreport` - *last release*: Nov 12, 2021, + *last release*: Dec 01, 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 +8962,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 23, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.0) @@ -7069,6 +8982,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 +8997,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 +9018,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 +9045,13 @@ This list contains 963 plugins. Pytest plugin to add a timestamp prefix to the pytest output + :pypi:`pytest-timestamps` + *last release*: Apr 01, 2023, + *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, @@ -7140,7 +9067,7 @@ This list contains 963 plugins. Better fixtures management. Various helpers :pypi:`pytest-tldr` - *last release*: Mar 12, 2021, + *last release*: Oct 26, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) @@ -7153,13 +9080,41 @@ 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 this is a vue-element ui report for pytest + :pypi:`pytest-tmux` + *last release*: Feb 15, 2023, + *status*: 4 - Beta, + *requires*: N/A + + A pytest plugin that enables tmux driven tests + :pypi:`pytest-todo` *last release*: May 23, 2019, *status*: 4 - Beta, @@ -7188,6 +9143,13 @@ This list contains 963 plugins. Numerous useful plugins for pytest. + :pypi:`pytest-tools` + *last release*: Oct 21, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Pytest tools + :pypi:`pytest-tornado` *last release*: Jun 17, 2020, *status*: 5 - Production/Stable, @@ -7216,6 +9178,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, @@ -7259,12 +9228,19 @@ This list contains 963 plugins. py.test plugin for using the same _trial_temp working directory as trial :pypi:`pytest-trio` - *last release*: Oct 16, 2020, + *last release*: Nov 01, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest (>=7.2.0) Pytest plugin for trio + :pypi:`pytest-trytond` + *last release*: Nov 04, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=5) + + Pytest plugin for the Tryton server framework + :pypi:`pytest-tspwplib` *last release*: Jan 08, 2021, *status*: 4 - Beta, @@ -7272,6 +9248,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,15 +9262,50 @@ This list contains 963 plugins. Test Class Base + :pypi:`pytest-tui` + *last release*: Mar 30, 2023, + *status*: 4 - Beta, + *requires*: N/A + + Text User Interface (TUI) and HTML report for Pytest test runs + + :pypi:`pytest-tutorials` + *last release*: Mar 11, 2023, + *status*: N/A, + *requires*: N/A + + + + :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, + *last release*: Oct 16, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=2.3) 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*: Nov 04, 2022, *status*: 4 - Beta, *requires*: N/A @@ -7314,6 +9332,27 @@ This list contains 963 plugins. Text User Interface for running python tests + :pypi:`pytest-ui-failed-screenshot` + *last release*: Dec 06, 2022, + *status*: N/A, + *requires*: N/A + + UI自动测试失败时自动截图,并将截图加入到测试报告中 + + :pypi:`pytest-ui-failed-screenshot-allure` + *last release*: Dec 06, 2022, + *status*: N/A, + *requires*: N/A + + UI自动测试失败时自动截图,并将截图加入到Allure测试报告中 + + :pypi:`pytest-unflakable` + *last release*: Mar 24, 2023, + *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 +9375,19 @@ This list contains 963 plugins. Run only unmarked tests :pypi:`pytest-unordered` - *last release*: Mar 28, 2021, + *last release*: Nov 28, 2022, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=6.0.0) Test equality of unordered collections in pytest + :pypi:`pytest-unstable` + *last release*: Sep 27, 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, @@ -7350,9 +9396,9 @@ This list contains 963 plugins. pytest-upload-report is a plugin for pytest that upload your test report for test results. :pypi:`pytest-utils` - *last release*: Dec 04, 2021, + *last release*: Feb 02, 2023, *status*: 4 - Beta, - *requires*: pytest (>=6.2.5,<7.0.0) + *requires*: pytest (>=7.0.0,<8.0.0) Some helpers for pytest. @@ -7371,14 +9417,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 +9438,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. @@ -7405,6 +9451,13 @@ This list contains 963 plugins. Test from HTTP interactions to dataframe processed. + :pypi:`pytest-vcs` + *last release*: Sep 22, 2022, + *status*: 4 - Beta, + *requires*: N/A + + + :pypi:`pytest-venv` *last release*: Aug 04, 2020, *status*: 4 - Beta, @@ -7413,11 +9466,11 @@ This list contains 963 plugins. py.test fixture for creating a virtual environment :pypi:`pytest-ver` - *last release*: Aug 30, 2021, - *status*: 2 - Pre-Alpha, + *last release*: Mar 22, 2023, + *status*: 4 - Beta, *requires*: N/A - Pytest module with Verification Report + Pytest module with Verification Protocol, Verification Report and Trace Matrix :pypi:`pytest-verbose-parametrize` *last release*: May 28, 2019, @@ -7440,6 +9493,13 @@ This list contains 963 plugins. Virtualenv fixture for py.test + :pypi:`pytest-vnc` + *last release*: Feb 25, 2023, + *status*: N/A, + *requires*: pytest + + VNC client for Pytest + :pypi:`pytest-voluptuous` *last release*: Jun 09, 2020, *status*: N/A, @@ -7454,6 +9514,13 @@ This list contains 963 plugins. A pytest plugin to easily enable debugging tests within Visual Studio Code + :pypi:`pytest-vscode-pycharm-cls` + *last release*: Feb 01, 2023, + *status*: N/A, + *requires*: pytest + + A PyTest helper to enable start remote debugger on test start or failure or when pytest.set_trace is used. + :pypi:`pytest-vts` *last release*: Jun 05, 2019, *status*: N/A, @@ -7461,6 +9528,13 @@ This list contains 963 plugins. pytest plugin for automatic recording of http stubbed tests + :pypi:`pytest-vulture` + *last release*: Oct 12, 2022, + *status*: N/A, + *requires*: pytest (>=7.0.0) + + A pytest plugin to checks dead code with vulture + :pypi:`pytest-vw` *last release*: Oct 07, 2015, *status*: 4 - Beta, @@ -7482,6 +9556,13 @@ This list contains 963 plugins. Pytest plugin for testing whatsapp bots with end to end tests + :pypi:`pytest-wake` + *last release*: Feb 27, 2023, + *status*: N/A, + *requires*: pytest + + + :pypi:`pytest-watch` *last release*: May 20, 2018, *status*: N/A, @@ -7490,7 +9571,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 11, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -7503,6 +9584,13 @@ This list contains 963 plugins. Pytest plugin for testing WDL workflows. + :pypi:`pytest-web3-data` + *last release*: Sep 15, 2022, + *status*: 4 - Beta, + *requires*: pytest + + + :pypi:`pytest-webdriver` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -7545,6 +9633,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, @@ -7553,18 +9648,18 @@ This list contains 963 plugins. pytest with docker helpers. :pypi:`pytest-workflow` - *last release*: Dec 03, 2021, + *last release*: Jan 13, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.4.0) + *requires*: pytest (>=7.0.0) A pytest plugin for configuring workflow/pipeline tests using YAML files :pypi:`pytest-xdist` - *last release*: Sep 21, 2021, + *last release*: Mar 12, 2023, *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 + pytest xdist plugin for distributed testing, most importantly across multiple CPUs :pypi:`pytest-xdist-debug-for-graingert` *last release*: Jul 24, 2019, @@ -7608,6 +9703,13 @@ This list contains 963 plugins. Extended logging for test and decorators + :pypi:`pytest-xlsx` + *last release*: Mar 01, 2023, + *status*: N/A, + *requires*: pytest>=7.2.0 + + pytest plugin for generating test cases by xlsx(excel) + :pypi:`pytest-xpara` *last release*: Oct 30, 2017, *status*: 3 - Alpha, @@ -7616,7 +9718,7 @@ This list contains 963 plugins. An extended parametrizing plugin of pytest. :pypi:`pytest-xprocess` - *last release*: Jul 28, 2021, + *last release*: Jan 05, 2023, *status*: 4 - Beta, *requires*: pytest (>=2.8) @@ -7637,12 +9739,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-xskynet` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + :pypi:`pytest-xvfb` *last release*: Jun 09, 2020, *status*: 4 - Beta, @@ -7657,6 +9766,13 @@ This list contains 963 plugins. This plugin is used to load yaml output to your test using pytest framework. + :pypi:`pytest-yaml-sanmu` + *last release*: Mar 17, 2023, + *status*: N/A, + *requires*: pytest>=7.2.0 + + pytest plugin for generating test cases by yaml + :pypi:`pytest-yamltree` *last release*: Mar 02, 2020, *status*: 4 - Beta, @@ -7671,6 +9787,13 @@ This list contains 963 plugins. Run tests against wsgi apps defined in yaml + :pypi:`pytest-yaml-yoyo` + *last release*: Mar 21, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0) + + http/https API run by yaml + :pypi:`pytest-yapf` *last release*: Jul 06, 2017, *status*: 4 - Beta, @@ -7679,9 +9802,9 @@ This list contains 963 plugins. Run yapf :pypi:`pytest-yapf3` - *last release*: Aug 03, 2020, + *last release*: Mar 29, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.4) + *requires*: pytest (>=7) Validate your Python file format with yapf @@ -7692,10 +9815,17 @@ This list contains 963 plugins. PyTest plugin to run tests concurrently, each \`yield\` switch context to other one + :pypi:`pytest-yls` + *last release*: Mar 29, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.2,<8.0.0) + + Pytest plugin to test the YLS as a whole. + :pypi:`pytest-yuk` *last release*: Mar 26, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=5.0.0 Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. @@ -7714,15 +9844,29 @@ This list contains 963 plugins. OWASP ZAP plugin for py.test. :pypi:`pytest-zebrunner` - *last release*: Dec 02, 2021, + *last release*: Dec 12, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=4.5.0) Pytest connector for Zebrunner reporting + :pypi:`pytest-zest` + *last release*: Nov 17, 2022, + *status*: N/A, + *requires*: N/A + + Zesty additions to pytest. + :pypi:`pytest-zigzag` *last release*: Feb 27, 2019, *status*: 4 - Beta, *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..963e666ade3 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() - +**Tutorial**: :ref:`captures` -.. fixture:: record_property - -record_property -~~~~~~~~~~~~~~~~~~~ - -**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`` @@ -1205,6 +1220,7 @@ passed multiple times. The expected format is ``name=value``. For example:: * ``classic``: classic pytest output. * ``progress``: like classic pytest output, but with a progress indicator. + * ``progress-even-when-capture-no``: allows the use of the progress indicator even when ``capture=no``. * ``count``: like progress, but shows progress as the number of tests completed instead of a percent. The default is ``progress``, but you can fallback to ``classic`` if you prefer or @@ -1455,7 +1471,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 +1718,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. @@ -1714,6 +1732,40 @@ passed multiple times. The expected format is ``name=value``. For example:: directories when executing from the root directory. +.. confval:: tmp_path_retention_count + + + + How many sessions should we keep the `tmp_path` directories, + according to `tmp_path_retention_policy`. + + .. code-block:: ini + + [pytest] + tmp_path_retention_count = 3 + + Default: 3 + + +.. confval:: tmp_path_retention_policy + + + + Controls which directories created by the `tmp_path` fixture are kept around, + based on test outcome. + + * `all`: retains directories for all tests, regardless of the outcome. + * `failed`: retains directories only for tests with outcome `error` or `failed`. + * `none`: directories are always removed after each test ends, regardless of the outcome. + + .. code-block:: ini + + [pytest] + tmp_path_retention_policy = "all" + + Default: all + + .. confval:: usefixtures List of fixtures that will be applied to all test functions; this is semantically the same to apply @@ -1754,8 +1806,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 +1821,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 +1915,132 @@ 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. + --log-disable=LOGGER_DISABLE + Disable a logger by name. Can be passed multipe + times. - [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found: + [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml 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" | "progress-even-when-capture-no" (forces + progress even when capture=no) + xfail_strict (bool): Default for the strict parameter of xfail markers when not given explicitly (default: False) + tmp_path_retention_count (string): + How many sessions should we keep the `tmp_path` + directories, according to + `tmp_path_retention_policy`. + tmp_path_retention_policy (string): + Controls which directories created by the `tmp_path` + fixture are kept around, based on test outcome. + (all/failed/none) 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 +2055,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/scripts/update-plugin-list.py b/scripts/update-plugin-list.py index c034c72420b..34d1c8bb639 100644 --- a/scripts/update-plugin-list.py +++ b/scripts/update-plugin-list.py @@ -78,11 +78,23 @@ def iter_plugins(): requires = "N/A" if info["requires_dist"]: for requirement in info["requires_dist"]: - if requirement == "pytest" or "pytest " in requirement: + if re.match(r"pytest(?![-.\w])", requirement): requires = requirement break + + def version_sort_key(version_string): + """ + Return the sort key for the given version string + returned by the API. + """ + try: + return packaging.version.parse(version_string) + except packaging.version.InvalidVersion: + # Use a hard-coded pre-release version. + return packaging.version.Version("0.0.0alpha") + releases = response.json()["releases"] - for release in sorted(releases, key=packaging.version.parse, reverse=True): + for release in sorted(releases, key=version_sort_key, reverse=True): if releases[release]: release_date = datetime.date.fromisoformat( releases[release][-1]["upload_time_iso_8601"].split("T")[0] @@ -90,7 +102,9 @@ def iter_plugins(): last_release = release_date.strftime("%b %d, %Y") break name = f':pypi:`{info["name"]}`' - summary = escape_rst(info["summary"].replace("\n", "")) + summary = "" + if info["summary"]: + summary = escape_rst(info["summary"].replace("\n", "")) yield { "name": name, "summary": summary.strip(), @@ -122,7 +136,7 @@ def main(): reference_dir = pathlib.Path("doc", "en", "reference") plugin_list = reference_dir / "plugin_list.rst" - with plugin_list.open("w") as f: + with plugin_list.open("w", encoding="UTF-8") as f: f.write(FILE_HEAD) f.write(f"This list contains {len(plugins)} plugins.\n\n") f.write(".. only:: not latex\n\n") diff --git a/setup.cfg b/setup.cfg index 26a5d2e63e5..56dadae7bf5 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,21 @@ 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 = @@ -67,6 +67,7 @@ console_scripts = [options.extras_require] testing = argcomplete + attrs>=19.2.0 hypothesis>=3.56 mock nose @@ -95,7 +96,6 @@ mypy_path = src check_untyped_defs = True disallow_any_generics = True ignore_missing_imports = True -no_implicit_optional = True show_error_codes = True strict_equality = True warn_redundant_casts = True diff --git a/src/_pytest/_argcomplete.py b/src/_pytest/_argcomplete.py index 41d9d9407c7..6a8083770ae 100644 --- a/src/_pytest/_argcomplete.py +++ b/src/_pytest/_argcomplete.py @@ -78,15 +78,15 @@ def __init__(self, directories: bool = True) -> None: def __call__(self, prefix: str, **kwargs: Any) -> List[str]: # Only called on non option completions. - if os.path.sep in prefix[1:]: - prefix_dir = len(os.path.dirname(prefix) + os.path.sep) + if os.sep in prefix[1:]: + prefix_dir = len(os.path.dirname(prefix) + os.sep) else: prefix_dir = 0 completion = [] globbed = [] if "*" not in prefix and "?" not in prefix: # We are on unix, otherwise no bash. - if not prefix or prefix[-1] == os.path.sep: + if not prefix or prefix[-1] == os.sep: globbed.extend(glob(prefix + ".*")) prefix += "*" globbed.extend(glob(prefix)) @@ -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..032b83beb02 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -1,5 +1,7 @@ import ast +import dataclasses import inspect +import os import re import sys import traceback @@ -31,7 +33,6 @@ from typing import Union from weakref import ref -import attr import pluggy import _pytest @@ -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: @@ -405,13 +411,13 @@ def filter( """ return Traceback(filter(fn, self), self._excinfo) - def getcrashentry(self) -> TracebackEntry: + def getcrashentry(self) -> Optional[TracebackEntry]: """Return last non-hidden traceback entry that lead to the exception of a traceback.""" for i in range(-1, -len(self) - 1, -1): entry = self[i] if not entry.ishidden(): return entry - return self[-1] + return None def recursionindex(self) -> Optional[int]: """Return the index of the frame/TracebackEntry where recursion originates if @@ -439,7 +445,7 @@ def recursionindex(self) -> Optional[int]: @final -@attr.s(repr=False, init=False, auto_attribs=True) +@dataclasses.dataclass class ExceptionInfo(Generic[E]): """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" @@ -596,11 +602,13 @@ def errisinstance( """ return isinstance(self.value, exc) - def _getreprcrash(self) -> "ReprFileLocation": + def _getreprcrash(self) -> Optional["ReprFileLocation"]: exconly = self.exconly(tryshort=True) entry = self.traceback.getcrashentry() - path, lineno = entry.frame.code.raw.co_filename, entry.lineno - return ReprFileLocation(path, lineno + 1, exconly) + if entry: + path, lineno = entry.frame.code.raw.co_filename, entry.lineno + return ReprFileLocation(path, lineno + 1, exconly) + return None def getrepr( self, @@ -643,12 +651,12 @@ def getrepr( """ if style == "native": return ReprExceptionInfo( - ReprTracebackNative( + reprtraceback=ReprTracebackNative( traceback.format_exception( self.type, self.value, self.traceback[0]._rawentry ) ), - self._getreprcrash(), + reprcrash=self._getreprcrash(), ) fmt = FormattedExcinfo( @@ -669,15 +677,16 @@ 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 -@attr.s(auto_attribs=True) +@dataclasses.dataclass class FormattedExcinfo: """Presenting information about failing Functions and Generators.""" @@ -692,8 +701,8 @@ class FormattedExcinfo: funcargs: bool = False truncate_locals: bool = True chain: bool = True - astcache: Dict[Union[str, Path], ast.AST] = attr.ib( - factory=dict, init=False, repr=False + astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field( + default_factory=dict, init=False, repr=False ) def _getindent(self, source: "Source") -> int: @@ -734,11 +743,13 @@ def get_source( ) -> List[str]: """Return formatted and marked up source lines.""" lines = [] - if source is None or line_index >= len(source.lines): + if source is not None and line_index < 0: + line_index += len(source) + if source is None or line_index >= len(source.lines) or line_index < 0: + # `line_index` could still be outside `range(len(source.lines))` if + # we're processing AST with pathological position attributes. source = Source("???") line_index = 0 - if line_index < 0: - line_index += len(source) space_prefix = " " if short: lines.append(space_prefix + source.lines[line_index].strip()) @@ -895,7 +906,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,10 +931,29 @@ 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_) - reprcrash: Optional[ReprFileLocation] = ( - excinfo_._getreprcrash() if self.style != "value" else None - ) + # 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_) + + # will be None if all traceback entries are hidden + reprcrash: Optional[ReprFileLocation] = excinfo_._getreprcrash() + if reprcrash: + if self.style == "value": + repr_chain += [(reprtraceback, None, descr)] + else: + repr_chain += [(reprtraceback, reprcrash, descr)] else: # Fallback to native repr if the exception doesn't have a traceback: # ExceptionInfo objects require a full traceback to work. @@ -931,8 +961,8 @@ def repr_excinfo( traceback.format_exception(type(e), e, None) ) reprcrash = None + repr_chain += [(reprtraceback, reprcrash, descr)] - repr_chain += [(reprtraceback, reprcrash, descr)] if e.__cause__ is not None and self.chain: e = e.__cause__ excinfo_ = ( @@ -957,7 +987,7 @@ def repr_excinfo( return ExceptionChainRepr(repr_chain) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class TerminalRepr: def __str__(self) -> str: # FYI this is called from pytest-xdist's serialization of exception @@ -975,14 +1005,14 @@ def toterminal(self, tw: TerminalWriter) -> None: # This class is abstract -- only subclasses are instantiated. -@attr.s(eq=False) +@dataclasses.dataclass(eq=False) class ExceptionRepr(TerminalRepr): # Provided by subclasses. - reprcrash: Optional["ReprFileLocation"] reprtraceback: "ReprTraceback" - - def __attrs_post_init__(self) -> None: - self.sections: List[Tuple[str, str, str]] = [] + reprcrash: Optional["ReprFileLocation"] + sections: List[Tuple[str, str, str]] = dataclasses.field( + init=False, default_factory=list + ) def addsection(self, name: str, content: str, sep: str = "-") -> None: self.sections.append((name, content, sep)) @@ -993,16 +1023,23 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line(content) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ExceptionChainRepr(ExceptionRepr): chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] - def __attrs_post_init__(self) -> None: - super().__attrs_post_init__() + def __init__( + self, + chain: Sequence[ + Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] + ], + ) -> None: # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain. - self.reprtraceback = self.chain[-1][0] - self.reprcrash = self.chain[-1][1] + super().__init__( + reprtraceback=chain[-1][0], + reprcrash=chain[-1][1], + ) + self.chain = chain def toterminal(self, tw: TerminalWriter) -> None: for element in self.chain: @@ -1013,17 +1050,17 @@ def toterminal(self, tw: TerminalWriter) -> None: super().toterminal(tw) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprExceptionInfo(ExceptionRepr): reprtraceback: "ReprTraceback" - reprcrash: "ReprFileLocation" + reprcrash: Optional["ReprFileLocation"] def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) super().toterminal(tw) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprTraceback(TerminalRepr): reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] extraline: Optional[str] @@ -1052,12 +1089,12 @@ def toterminal(self, tw: TerminalWriter) -> None: class ReprTracebackNative(ReprTraceback): def __init__(self, tblines: Sequence[str]) -> None: - self.style = "native" self.reprentries = [ReprEntryNative(tblines)] self.extraline = None + self.style = "native" -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprEntryNative(TerminalRepr): lines: Sequence[str] @@ -1067,7 +1104,7 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines)) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprEntry(TerminalRepr): lines: Sequence[str] reprfuncargs: Optional["ReprFuncArgs"] @@ -1147,12 +1184,15 @@ def __str__(self) -> str: ) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprFileLocation(TerminalRepr): - path: str = attr.ib(converter=str) + path: str lineno: int message: str + def __post_init__(self) -> None: + self.path = str(self.path) + def toterminal(self, tw: TerminalWriter) -> None: # Filename and lineno output for each entry, using an output format # that most editors understand. @@ -1164,7 +1204,7 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line(f":{self.lineno}: {msg}") -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprLocals(TerminalRepr): lines: Sequence[str] @@ -1173,7 +1213,7 @@ def toterminal(self, tw: TerminalWriter, indent="") -> None: tw.line(indent + line) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprFuncArgs(TerminalRepr): args: Sequence[Tuple[str, object]] 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..fb64830f814 --- /dev/null +++ b/src/_pytest/_py/path.py @@ -0,0 +1,1475 @@ +"""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 cast +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 = cast(Callable[[Any], Any], sorted) if sort else (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) # type:ignore[attr-defined] + 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) # type:ignore[attr-defined] + 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] # type:ignore[attr-defined] + return user + + +def getgroupid(group): + import grp + + if not isinstance(group, int): + group = grp.getgrnam(group)[2] # type:ignore[attr-defined] + 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.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.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..8b182347052 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -44,9 +44,13 @@ if TYPE_CHECKING: from _pytest.assertion import AssertionState +if sys.version_info >= (3, 8): + namedExpr = ast.NamedExpr +else: + namedExpr = ast.Expr -assertstate_key = StashKey["AssertionState"]() +assertstate_key = StashKey["AssertionState"]() # pytest caches rewritten pycs in pycache dirs PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}" @@ -100,9 +104,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) @@ -183,7 +184,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: for initial_path in self.session._initialpaths: # Make something as c:/projects/my_project/path.py -> # ['c:', 'projects', 'my_project', 'path.py'] - parts = str(initial_path).split(os.path.sep) + parts = str(initial_path).split(os.sep) # add 'path' to basenames to be checked. self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0]) @@ -193,7 +194,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 +277,21 @@ 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): + 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) -> importlib.abc.TraversableResources: # type: ignore - from types import SimpleNamespace - from importlib.readers import FileReader + 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 +302,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 +312,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 +360,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 +495,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 @@ -658,8 +639,12 @@ class AssertionRewriter(ast.NodeVisitor): .push_format_context() and .pop_format_context() which allows to build another %-formatted string while already building one. - This state is reset on every new assert statement visited and used - by the other visitors. + :variables_overwrite: A dict filled with references to variables + that change value within an assert. This happens when a variable is + reassigned with the walrus operator + + This state, except the variables_overwrite, is reset on every new assert + statement visited and used by the other visitors. """ def __init__( @@ -675,6 +660,7 @@ def __init__( else: self.enable_assertion_pass_hook = False self.source = source + self.variables_overwrite: Dict[str, str] = {} def run(self, mod: ast.Module) -> None: """Find all assert statements in *mod* and rewrite them.""" @@ -689,7 +675,7 @@ def run(self, mod: ast.Module) -> None: if doc is not None and self.is_rewrite_disabled(doc): return pos = 0 - lineno = 1 + item = None for item in mod.body: if ( expect_docstring @@ -960,6 +946,18 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: ast.copy_location(node, assert_) return self.statements + def visit_NamedExpr(self, name: namedExpr) -> Tuple[namedExpr, str]: + # This method handles the 'walrus operator' repr of the target + # name if it's a local variable or _should_repr_global_name() + # thinks it's acceptable. + locs = ast.Call(self.builtin("locals"), [], []) + target_id = name.target.id # type: ignore[attr-defined] + inlocs = ast.Compare(ast.Str(target_id), [ast.In()], [locs]) + dorepr = self.helper("_should_repr_global_name", name) + test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) + expr = ast.IfExp(test, self.display(name), ast.Str(target_id)) + return name, self.explanation_param(expr) + def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: # Display the repr of the name if it's a local variable or # _should_repr_global_name() thinks it's acceptable. @@ -986,6 +984,20 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: # cond is set in a prior loop iteration below self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa self.expl_stmts = fail_inner + # Check if the left operand is a namedExpr and the value has already been visited + if ( + isinstance(v, ast.Compare) + and isinstance(v.left, namedExpr) + and v.left.target.id + in [ + ast_expr.id + for ast_expr in boolop.values[:i] + if hasattr(ast_expr, "id") + ] + ): + pytest_temp = self.variable() + self.variables_overwrite[v.left.target.id] = pytest_temp + v.left.target.id = pytest_temp self.push_format_context() res, expl = self.visit(v) body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) @@ -1061,6 +1073,9 @@ def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: self.push_format_context() + # We first check if we have overwritten a variable in the previous assert + if isinstance(comp.left, ast.Name) and comp.left.id in self.variables_overwrite: + comp.left.id = self.variables_overwrite[comp.left.id] left_res, left_expl = self.visit(comp.left) if isinstance(comp.left, (ast.Compare, ast.BoolOp)): left_expl = f"({left_expl})" @@ -1072,6 +1087,13 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: syms = [] results = [left_res] for i, op, next_operand in it: + if ( + isinstance(next_operand, namedExpr) + and isinstance(left_res, ast.Name) + and next_operand.target.id == left_res.id + ): + next_operand.target.id = self.variable() + self.variables_overwrite[left_res.id] = next_operand.target.id next_res, next_expl = self.visit(next_operand) if isinstance(next_operand, (ast.Compare, ast.BoolOp)): next_expl = f"({next_expl})" @@ -1095,6 +1117,7 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: res: ast.expr = ast.BoolOp(ast.And(), load_names) else: res = load_names[0] + return res, self.explanation_param(self.pop_format_context(expl_call)) diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index ce148dca095..dfd6f65d281 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -38,9 +38,9 @@ def _truncate_explanation( """Truncate given list of strings that makes up the assertion explanation. Truncates to either 8 lines, or 640 characters - whichever the input reaches - first. The remaining lines will be replaced by a usage message. + first, taking the truncation explanation into account. The remaining lines + will be replaced by a usage message. """ - if max_lines is None: max_lines = DEFAULT_MAX_LINES if max_chars is None: @@ -48,35 +48,56 @@ def _truncate_explanation( # Check if truncation required input_char_count = len("".join(input_lines)) - if len(input_lines) <= max_lines and input_char_count <= max_chars: + # The length of the truncation explanation depends on the number of lines + # removed but is at least 68 characters: + # The real value is + # 64 (for the base message: + # '...\n...Full output truncated (1 line hidden), use '-vv' to show")' + # ) + # + 1 (for plural) + # + int(math.log10(len(input_lines) - max_lines)) (number of hidden line, at least 1) + # + 3 for the '...' added to the truncated line + # But if there's more than 100 lines it's very likely that we're going to + # truncate, so we don't need the exact value using log10. + tolerable_max_chars = ( + max_chars + 70 # 64 + 1 (for plural) + 2 (for '99') + 3 for '...' + ) + # The truncation explanation add two lines to the output + tolerable_max_lines = max_lines + 2 + if ( + len(input_lines) <= tolerable_max_lines + and input_char_count <= tolerable_max_chars + ): return input_lines - - # Truncate first to max_lines, and then truncate to max_chars if max_chars - # is exceeded. + # Truncate first to max_lines, and then truncate to max_chars if necessary truncated_explanation = input_lines[:max_lines] - truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars) - - # Add ellipsis to final line - truncated_explanation[-1] = truncated_explanation[-1] + "..." + truncated_char = True + # We reevaluate the need to truncate chars following removal of some lines + if len("".join(truncated_explanation)) > tolerable_max_chars: + truncated_explanation = _truncate_by_char_count( + truncated_explanation, max_chars + ) + else: + truncated_char = False - # Append useful message to explanation truncated_line_count = len(input_lines) - len(truncated_explanation) - truncated_line_count += 1 # Account for the part-truncated final line - msg = "...Full output truncated" - if truncated_line_count == 1: - msg += f" ({truncated_line_count} line hidden)" + if truncated_explanation[-1]: + # Add ellipsis and take into account part-truncated final line + truncated_explanation[-1] = truncated_explanation[-1] + "..." + if truncated_char: + # It's possible that we did not remove any char from this line + truncated_line_count += 1 else: - msg += f" ({truncated_line_count} lines hidden)" - msg += f", {USAGE_MSG}" - truncated_explanation.extend(["", str(msg)]) - return truncated_explanation + # Add proper ellipsis when we were able to fit a full line exactly + truncated_explanation[-1] = "..." + return truncated_explanation + [ + "", + f"...Full output truncated ({truncated_line_count} line" + f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}", + ] def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]: - # Check if truncation required - if len("".join(input_lines)) <= max_chars: - return input_lines - # Find point at which input length exceeds total allowed length iterated_char_count = 0 for iterated_index, input_line in enumerate(input_lines): 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..719b32f7e0e 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -1,6 +1,7 @@ """Implementation of the cache provider.""" # This plugin was not named "cache" to avoid conflicts with the external # pytest-cache version. +import dataclasses import json import os from pathlib import Path @@ -12,8 +13,6 @@ from typing import Set from typing import Union -import attr - from .pathlib import resolve_from_str from .pathlib import rm_rf from .reports import CollectReport @@ -32,7 +31,6 @@ from _pytest.python import Package from _pytest.reports import TestReport - README_CONTENT = """\ # pytest cache directory # @@ -53,10 +51,12 @@ @final -@attr.s(init=False, auto_attribs=True) +@dataclasses.dataclass class Cache: - _cachedir: Path = attr.ib(repr=False) - _config: Config = attr.ib(repr=False) + """Instance of the `cache` fixture.""" + + _cachedir: Path = dataclasses.field(repr=False) + _config: Config = dataclasses.field(repr=False) # Sub-directory under cache-dir for directories created by `mkdir()`. _CACHE_PREFIX_DIRS = "d" @@ -157,7 +157,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 +184,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 +196,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 +440,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 +448,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 +457,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 +466,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 +474,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 +487,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..275322cc335 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -1,19 +1,26 @@ """Per-test stdout/stderr capturing mechanism.""" +import abc +import collections import contextlib -import functools import io import os import sys from io import UnsupportedOperation from tempfile import TemporaryFile +from types import TracebackType from typing import Any from typing import AnyStr +from typing import BinaryIO from typing import Generator from typing import Generic +from typing import Iterable from typing import Iterator +from typing import List +from typing import NamedTuple from typing import Optional from typing import TextIO from typing import Tuple +from typing import Type from typing import TYPE_CHECKING from typing import Union @@ -29,6 +36,7 @@ from _pytest.nodes import Item if TYPE_CHECKING: + from typing_extensions import Final from typing_extensions import Literal _CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] @@ -42,14 +50,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 +76,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 +120,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 +136,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) @@ -185,53 +193,151 @@ def write(self, s: str) -> int: return self._other.write(s) -class DontReadFromInput: - encoding = None +class DontReadFromInput(TextIO): + @property + def encoding(self) -> str: + return sys.__stdin__.encoding - def read(self, *args): + def read(self, size: int = -1) -> str: raise OSError( "pytest: reading from stdin while output is captured! Consider using `-s`." ) readline = read - readlines = read - __next__ = read - def __iter__(self): + def __next__(self) -> str: + return self.readline() + + def readlines(self, hint: Optional[int] = -1) -> List[str]: + raise OSError( + "pytest: reading from stdin while output is captured! Consider using `-s`." + ) + + def __iter__(self) -> Iterator[str]: return 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 - @property - def buffer(self): + def readable(self) -> bool: + return False + + def seek(self, offset: int, whence: int = 0) -> 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: Optional[int] = None) -> int: + raise UnsupportedOperation("cannont truncate stdin") + + def write(self, data: str) -> int: + raise UnsupportedOperation("cannot write to stdin") + + def writelines(self, lines: Iterable[str]) -> None: + raise UnsupportedOperation("Cannot write to stdin") + + def writable(self) -> bool: + return False + + def __enter__(self) -> "DontReadFromInput": return self + def __exit__( + self, + type: Optional[Type[BaseException]], + value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + pass + + @property + def buffer(self) -> BinaryIO: + # The str/bytes doesn't actually matter in this type, so OK to fake. + return self # type: ignore[return-value] + # Capture classes. +class CaptureBase(abc.ABC, Generic[AnyStr]): + EMPTY_BUFFER: AnyStr + + @abc.abstractmethod + def __init__(self, fd: int) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def start(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def done(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def suspend(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def resume(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def writeorg(self, data: AnyStr) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def snap(self) -> AnyStr: + raise NotImplementedError() + + patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} -class NoCapture: - EMPTY_BUFFER = None - __init__ = start = done = suspend = resume = lambda *args: None +class NoCapture(CaptureBase[str]): + EMPTY_BUFFER = "" + def __init__(self, fd: int) -> None: + pass -class SysCaptureBinary: + def start(self) -> None: + pass - EMPTY_BUFFER = b"" + def done(self) -> None: + pass - def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None: + def suspend(self) -> None: + pass + + def resume(self) -> None: + pass + + def snap(self) -> str: + return "" + + def writeorg(self, data: str) -> None: + pass + + +class SysCaptureBase(CaptureBase[AnyStr]): + def __init__( + self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False + ) -> None: name = patchsysdict[fd] - self._old = getattr(sys, name) + self._old: TextIO = getattr(sys, name) self.name = name if tmpfile is None: if name == "stdin": @@ -271,14 +377,6 @@ def start(self) -> None: setattr(sys, self.name, self.tmpfile) self._state = "started" - def snap(self): - self._assert_state("snap", ("started", "suspended")) - self.tmpfile.seek(0) - res = self.tmpfile.buffer.read() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res - def done(self) -> None: self._assert_state("done", ("initialized", "started", "suspended", "done")) if self._state == "done": @@ -300,36 +398,43 @@ def resume(self) -> None: setattr(sys, self.name, self.tmpfile) self._state = "started" - def writeorg(self, data) -> None: + +class SysCaptureBinary(SysCaptureBase[bytes]): + EMPTY_BUFFER = b"" + + def snap(self) -> bytes: + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.buffer.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + + def writeorg(self, data: bytes) -> None: self._assert_state("writeorg", ("started", "suspended")) self._old.flush() self._old.buffer.write(data) self._old.buffer.flush() -class SysCapture(SysCaptureBinary): - EMPTY_BUFFER = "" # type: ignore[assignment] +class SysCapture(SysCaptureBase[str]): + EMPTY_BUFFER = "" - def snap(self): + def snap(self) -> str: + self._assert_state("snap", ("started", "suspended")) + assert isinstance(self.tmpfile, CaptureIO) res = self.tmpfile.getvalue() self.tmpfile.seek(0) self.tmpfile.truncate() return res - def writeorg(self, data): + def writeorg(self, data: str) -> None: self._assert_state("writeorg", ("started", "suspended")) self._old.write(data) self._old.flush() -class FDCaptureBinary: - """Capture IO to/from a given OS-level file descriptor. - - snap() produces `bytes`. - """ - - EMPTY_BUFFER = b"" - +class FDCaptureBase(CaptureBase[AnyStr]): def __init__(self, targetfd: int) -> None: self.targetfd = targetfd @@ -354,8 +459,8 @@ def __init__(self, targetfd: int) -> None: self.targetfd_save = os.dup(targetfd) if targetfd == 0: - self.tmpfile = open(os.devnull) - self.syscapture = SysCapture(targetfd) + self.tmpfile = open(os.devnull, encoding="utf-8") + self.syscapture: CaptureBase[str] = SysCapture(targetfd) else: self.tmpfile = EncodedFile( TemporaryFile(buffering=0), @@ -367,7 +472,7 @@ def __init__(self, targetfd: int) -> None: if targetfd in patchsysdict: self.syscapture = SysCapture(targetfd, self.tmpfile) else: - self.syscapture = NoCapture() + self.syscapture = NoCapture(targetfd) self._state = "initialized" @@ -394,14 +499,6 @@ def start(self) -> None: self.syscapture.start() self._state = "started" - def snap(self): - self._assert_state("snap", ("started", "suspended")) - self.tmpfile.seek(0) - res = self.tmpfile.buffer.read() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res - def done(self) -> None: """Stop capturing, restore streams, return original capture file, seeked to position zero.""" @@ -434,22 +531,38 @@ def resume(self) -> None: os.dup2(self.tmpfile.fileno(), self.targetfd) self._state = "started" - def writeorg(self, data): + +class FDCaptureBinary(FDCaptureBase[bytes]): + """Capture IO to/from a given OS-level file descriptor. + + snap() produces `bytes`. + """ + + EMPTY_BUFFER = b"" + + def snap(self) -> bytes: + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.buffer.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + + def writeorg(self, data: bytes) -> None: """Write to original file descriptor.""" self._assert_state("writeorg", ("started", "suspended")) os.write(self.targetfd_save, data) -class FDCapture(FDCaptureBinary): +class FDCapture(FDCaptureBase[str]): """Capture IO to/from a given OS-level file descriptor. snap() produces text. """ - # Ignore type because it doesn't match the type in the superclass (bytes). - EMPTY_BUFFER = "" # type: ignore + EMPTY_BUFFER = "" - def snap(self): + def snap(self) -> str: self._assert_state("snap", ("started", "suspended")) self.tmpfile.seek(0) res = self.tmpfile.read() @@ -457,77 +570,49 @@ def snap(self): self.tmpfile.truncate() return res - def writeorg(self, data): + def writeorg(self, data: str) -> None: """Write to original file descriptor.""" - super().writeorg(data.encode("utf-8")) # XXX use encoding of original stream + self._assert_state("writeorg", ("started", "suspended")) + # XXX use encoding of original stream + os.write(self.targetfd_save, data.encode("utf-8")) # MultiCapture -# This class was a namedtuple, but due to mypy limitation[0] it could not be -# made generic, so was replaced by a regular class which tries to emulate the -# pertinent parts of a namedtuple. If the mypy limitation is ever lifted, can -# make it a namedtuple again. -# [0]: https://github.com/python/mypy/issues/685 -@final -@functools.total_ordering -class CaptureResult(Generic[AnyStr]): - """The result of :method:`CaptureFixture.readouterr`.""" - - __slots__ = ("out", "err") +# Generic NamedTuple only supported since Python 3.11. +if sys.version_info >= (3, 11) or TYPE_CHECKING: - def __init__(self, out: AnyStr, err: AnyStr) -> None: - self.out: AnyStr = out - self.err: AnyStr = err + @final + class CaptureResult(NamedTuple, Generic[AnyStr]): + """The result of :method:`CaptureFixture.readouterr`.""" - def __len__(self) -> int: - return 2 + out: AnyStr + err: AnyStr - def __iter__(self) -> Iterator[AnyStr]: - return iter((self.out, self.err)) +else: - def __getitem__(self, item: int) -> AnyStr: - return tuple(self)[item] - - def _replace( - self, *, out: Optional[AnyStr] = None, err: Optional[AnyStr] = None - ) -> "CaptureResult[AnyStr]": - return CaptureResult( - out=self.out if out is None else out, err=self.err if err is None else err - ) + class CaptureResult( + collections.namedtuple("CaptureResult", ["out", "err"]), Generic[AnyStr] + ): + """The result of :method:`CaptureFixture.readouterr`.""" - def count(self, value: AnyStr) -> int: - return tuple(self).count(value) - - def index(self, value) -> int: - return tuple(self).index(value) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, (CaptureResult, tuple)): - return NotImplemented - return tuple(self) == tuple(other) - - def __hash__(self) -> int: - return hash(tuple(self)) - - def __lt__(self, other: object) -> bool: - if not isinstance(other, (CaptureResult, tuple)): - return NotImplemented - return tuple(self) < tuple(other) - - def __repr__(self) -> str: - return f"CaptureResult(out={self.out!r}, err={self.err!r})" + __slots__ = () class MultiCapture(Generic[AnyStr]): _state = None _in_suspended = False - def __init__(self, in_, out, err) -> None: - self.in_ = in_ - self.out = out - self.err = err + def __init__( + self, + in_: Optional[CaptureBase[AnyStr]], + out: Optional[CaptureBase[AnyStr]], + err: Optional[CaptureBase[AnyStr]], + ) -> None: + self.in_: Optional[CaptureBase[AnyStr]] = in_ + self.out: Optional[CaptureBase[AnyStr]] = out + self.err: Optional[CaptureBase[AnyStr]] = err def __repr__(self) -> str: return "".format( @@ -551,8 +636,10 @@ def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]: """Pop current snapshot out/err capture and flush to orig streams.""" out, err = self.readouterr() if out: + assert self.out is not None self.out.writeorg(out) if err: + assert self.err is not None self.err.writeorg(err) return out, err @@ -573,6 +660,7 @@ def resume_capturing(self) -> None: if self.err: self.err.resume() if self._in_suspended: + assert self.in_ is not None self.in_.resume() self._in_suspended = False @@ -595,7 +683,8 @@ def is_started(self) -> bool: def readouterr(self) -> CaptureResult[AnyStr]: out = self.out.snap() if self.out else "" err = self.err.snap() if self.err else "" - return CaptureResult(out, err) + # TODO: This type error is real, need to fix. + return CaptureResult(out, err) # type: ignore[arg-type] def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]: @@ -635,7 +724,7 @@ class CaptureManager: """ def __init__(self, method: "_CaptureMethod") -> None: - self._method = method + self._method: Final = method self._global_capturing: Optional[MultiCapture[str]] = None self._capture_fixture: Optional[CaptureFixture[Any]] = None @@ -804,14 +893,18 @@ class CaptureFixture(Generic[AnyStr]): :fixture:`capfd` and :fixture:`capfdbinary` fixtures.""" def __init__( - self, captureclass, request: SubRequest, *, _ispytest: bool = False + self, + captureclass: Type[CaptureBase[AnyStr]], + request: SubRequest, + *, + _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self.captureclass = captureclass + self.captureclass: Type[CaptureBase[AnyStr]] = captureclass self.request = request self._capture: Optional[MultiCapture[AnyStr]] = None - self._captured_out = self.captureclass.EMPTY_BUFFER - self._captured_err = self.captureclass.EMPTY_BUFFER + self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER + self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER def _start(self) -> None: if self._capture is None: @@ -866,7 +959,9 @@ def _is_started(self) -> bool: @contextlib.contextmanager def disabled(self) -> Generator[None, None, None]: """Temporarily disable capturing while inside the ``with`` block.""" - capmanager = self.request.config.pluginmanager.getplugin("capturemanager") + capmanager: CaptureManager = self.request.config.pluginmanager.getplugin( + "capturemanager" + ) with capmanager.global_and_fixture_disabled(): yield @@ -876,14 +971,25 @@ 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) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(SysCapture, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture @@ -893,14 +999,25 @@ 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) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(SysCaptureBinary, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture @@ -910,14 +1027,25 @@ 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) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(FDCapture, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture @@ -927,14 +1055,26 @@ 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) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(FDCaptureBinary, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 7703dee8c5a..352211de8aa 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -1,27 +1,35 @@ """Python version compatibility code.""" +from __future__ import annotations + +import dataclasses import enum import functools 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 Optional -from typing import Tuple +from typing import NoReturn from typing import TYPE_CHECKING from typing import TypeVar -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 @@ -37,7 +45,7 @@ # fmt: on -def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: +def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: """Internal wrapper to prepare lazy proxies for legacy_path instances""" return LEGACY_PATH(path) @@ -47,13 +55,15 @@ def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: # https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions class NotSetType(enum.Enum): token = 0 -NOTSET: "Final" = NotSetType.token # noqa: E305 +NOTSET: Final = NotSetType.token # noqa: E305 # 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: @@ -83,7 +93,7 @@ def is_async_function(func: object) -> bool: return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) -def getlocation(function, curdir: Optional[str] = None) -> str: +def getlocation(function, curdir: str | None = None) -> str: function = get_real_func(function) fn = Path(inspect.getfile(function)) lineno = function.__code__.co_firstlineno @@ -121,8 +131,8 @@ def getfuncargnames( *, name: str = "", is_method: bool = False, - cls: Optional[type] = None, -) -> Tuple[str, ...]: + cls: type | None = None, +) -> tuple[str, ...]: """Return the names of a function's mandatory arguments. Should return the names of all function arguments that: @@ -186,18 +196,7 @@ 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, ...]: +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 # because they had default values. @@ -228,7 +227,7 @@ def _bytes_to_ascii(val: bytes) -> str: return val.decode("ascii", "backslashreplace") -def ascii_escaped(val: Union[bytes, str]) -> str: +def ascii_escaped(val: bytes | str) -> str: r"""If val is pure ASCII, return it as an str, otherwise, escape bytes objects into a sequence of escaped bytes: @@ -252,7 +251,7 @@ def ascii_escaped(val: Union[bytes, str]) -> str: return _translate_non_printable(ret) -@attr.s +@dataclasses.dataclass class _PytestWrapper: """Dummy wrapper around a function object for internal use only. @@ -261,7 +260,7 @@ class _PytestWrapper: decorator to issue warnings when the fixture function is called directly. """ - obj = attr.ib() + obj: Any def get_real_func(obj): @@ -355,8 +354,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]): __slots__ = ("func", "__doc__") @@ -367,12 +364,12 @@ def __init__(self, func: Callable[[_S], _T]) -> None: @overload def __get__( - self, instance: None, owner: Optional[Type[_S]] = ... - ) -> "cached_property[_S, _T]": + self, instance: None, owner: type[_S] | None = ... + ) -> cached_property[_S, _T]: ... @overload - def __get__(self, instance: _S, owner: Optional[Type[_S]] = ...) -> _T: + def __get__(self, instance: _S, owner: type[_S] | None = ...) -> _T: ... def __get__(self, instance, owner=None): @@ -382,6 +379,18 @@ def __get__(self, instance, owner=None): return value +def get_user_id() -> int | None: + """Return the current user id, or None if we cannot get it reliably on the current platform.""" + # win32 does not have a getuid() function. + # On Emscripten, getuid() is a stub that always returns 0. + if sys.platform in ("win32", "emscripten"): + return None + # getuid shouldn't fail, but cpython defines such a case. + # Let's hope for the best. + uid = os.getuid() + return uid if uid != -1 else None + + # Perform exhaustiveness checking. # # Consider this example: @@ -413,5 +422,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..720f3953153 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1,9 +1,10 @@ """Command line options, ini-file and conftest.py processing.""" import argparse import collections.abc -import contextlib import copy +import dataclasses import enum +import glob import inspect import os import re @@ -14,6 +15,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 @@ -33,7 +35,6 @@ from typing import TYPE_CHECKING from typing import Union -import attr from pluggy import HookimplMarker from pluggy import HookspecMarker from pluggy import PluginManager @@ -58,9 +59,9 @@ 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: - from _pytest._code.code import _TracebackStyle from _pytest.terminal import TerminalReporter from .argparsing import Argument @@ -255,7 +256,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 +311,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 +334,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 +388,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 +447,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 +511,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 +558,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 +579,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 +622,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 +640,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}") @@ -655,6 +696,7 @@ def consider_preparse( parg = opt[2:] else: continue + parg = parg.strip() if exclude_only and not parg.startswith("no:"): continue self.consider_pluginarg(parg) @@ -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: @@ -843,10 +886,6 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: yield from _iter_rewritable_modules(new_package_files) -def _args_converter(args: Iterable[str]) -> Tuple[str, ...]: - return tuple(args) - - @final class Config: """Access to configuration values, pluginmanager and plugin hooks. @@ -860,7 +899,7 @@ class Config: """ @final - @attr.s(frozen=True, auto_attribs=True) + @dataclasses.dataclass(frozen=True) class InvocationParams: """Holds parameters passed during :func:`pytest.main`. @@ -876,13 +915,37 @@ class InvocationParams: Plugins accessing ``InvocationParams`` must be aware of that. """ - args: Tuple[str, ...] = attr.ib(converter=_args_converter) + args: Tuple[str, ...] """The command-line arguments as passed to :func:`pytest.main`.""" plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] """Extra plugins, might be `None`.""" dir: Path """The directory from which :func:`pytest.main` was invoked.""" + def __init__( + self, + *, + args: Iterable[str], + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]], + dir: Path, + ) -> None: + object.__setattr__(self, "args", tuple(args)) + object.__setattr__(self, "plugins", plugins) + object.__setattr__(self, "dir", dir) + + 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 +1005,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 +1035,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: @@ -1001,7 +1066,6 @@ def pytest_cmdline_parse( try: self.parse(args) except UsageError: - # Handle --version and --help here in a minimal fashion. # This gets done via helpconfig normally, but its # pytest_cmdline_main is not called in case of errors. @@ -1085,11 +1149,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 +1345,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 +1356,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 +1404,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 +1503,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 +1649,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 +1691,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..5bd922a4a87 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 """ @@ -43,7 +43,6 @@ def __getattr__(self, key, _wraps=functools.wraps): @_wraps(hook) def fixed_hook(**kw): - path_value: Optional[Path] = kw.pop(path_var, None) fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None) if fspath_value is not None: 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..455ad62cc9c 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 @@ -528,7 +531,6 @@ def _find( if _is_mocked(obj): return with _patch_unwrap_mock_aware(): - # Type ignored because this is a private function. super()._find( # type:ignore[misc] tests, obj, name, module, source_lines, globs, seen @@ -542,10 +544,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 +662,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 +733,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..007245b241c 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1,3 +1,4 @@ +import dataclasses import functools import inspect import os @@ -18,8 +19,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 @@ -28,8 +29,6 @@ from typing import TypeVar from typing import Union -import attr - import _pytest from _pytest import nodes from _pytest._code import getfslineno @@ -47,17 +46,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 +68,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 @@ -104,7 +102,7 @@ ] -@attr.s(frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class PseudoFixtureDef(Generic[FixtureValue]): cached_result: "_FixtureCachedResult[FixtureValue]" _scope: Scope @@ -225,15 +223,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,47 +345,14 @@ 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 -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class FuncFixtureInfo: + __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") + # Original function argument names. argnames: Tuple[str, ...] # Argnames that function immediately requires. These include argnames + @@ -449,6 +409,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 +497,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 +515,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 +531,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 +558,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 +616,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 +666,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 +775,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 +883,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 +949,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 +961,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 +1037,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 +1072,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 +1131,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 +1142,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): @@ -1180,20 +1182,21 @@ def result(*args, **kwargs): @final -@attr.s(frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class FixtureFunctionMarker: scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" - params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter) + params: Optional[Tuple[object, ...]] autouse: bool = False - ids: Union[ - Tuple[Union[None, str, float, int, bool], ...], - Callable[[Any], Optional[object]], - ] = attr.ib( - default=None, - converter=_ensure_immutable_ids, - ) + ids: Optional[ + Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] + ] = None name: Optional[str] = None + _ispytest: dataclasses.InitVar[bool] = False + + def __post_init__(self, _ispytest: bool) -> None: + check_ispytest(_ispytest) + def __call__(self, function: FixtureFunction) -> FixtureFunction: if inspect.isclass(function): raise ValueError("class fixtures not supported (maybe in the future)") @@ -1228,10 +1231,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 +1239,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 +1302,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. @@ -1322,10 +1316,11 @@ def fixture( """ fixture_marker = FixtureFunctionMarker( scope=scope, - params=params, + params=tuple(params) if params is not None else None, autouse=autouse, - ids=ids, + ids=None if ids is None else ids if callable(ids) else tuple(ids), name=name, + _ispytest=True, ) # Direct decoration. @@ -1381,7 +1376,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..6b6718a7083 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`.", ) @@ -163,7 +164,8 @@ def showhelp(config: Config) -> None: tw.write(config._parser.optparser.format_help()) tw.line() tw.line( - "[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:" + "[pytest] ini-options in the first " + "pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:" ) tw.line() @@ -203,12 +205,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..143ec190c2d 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,10 +502,12 @@ 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 location: A tuple of ``(filename, lineno, testname)``. + :param nodeid: Full node ID of the item. + :param location: A tuple of ``(filename, lineno, testname)`` + where ``filename`` is a file path relative to ``config.rootpath`` + and ``lineno`` is 0-based. """ @@ -479,10 +516,12 @@ 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 location: A tuple of ``(filename, lineno, testname)``. + :param nodeid: Full node ID of the item. + :param location: A tuple of ``(filename, lineno, testname)`` + where ``filename`` is a file path relative to ``config.rootpath`` + and ``lineno`` is 0-based. """ @@ -493,6 +532,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 +542,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 +556,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 +573,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 +586,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 +596,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 +609,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 +626,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 +647,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 +665,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 +675,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 +702,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 +730,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. """ @@ -673,14 +742,14 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No # ------------------------------------------------------------------------- -def pytest_report_header( +def pytest_report_header( # type:ignore[empty-body] config: "Config", start_path: Path, startdir: "LEGACY_PATH" ) -> 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:: @@ -702,7 +771,7 @@ def pytest_report_header( """ -def pytest_report_collectionfinish( +def pytest_report_collectionfinish( # type:ignore[empty-body] config: "Config", start_path: Path, startdir: "LEGACY_PATH", @@ -715,9 +784,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:: @@ -735,7 +804,7 @@ def pytest_report_collectionfinish( @hookspec(firstresult=True) -def pytest_report_teststatus( +def pytest_report_teststatus( # type:ignore[empty-body] report: Union["CollectReport", "TestReport"], config: "Config" ) -> Tuple[str, str, Union[str, Mapping[str, bool]]]: """Return result-category, shortletter and verbose word for status @@ -756,6 +825,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 +838,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 +856,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. @@ -849,7 +884,9 @@ def pytest_warning_recorded( # ------------------------------------------------------------------------- -def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]: +def pytest_markeval_namespace( # type:ignore[empty-body] + config: "Config", +) -> Dict[str, Any]: """Called when constructing the globals dictionary used for evaluating string conditions in xfail/skipif markers. @@ -860,7 +897,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 +915,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 +938,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 +962,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 +973,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..af1d0c07e3c 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -1,4 +1,5 @@ """Add backward compatibility support for the legacy py path type.""" +import dataclasses import shlex import subprocess from pathlib import Path @@ -7,16 +8,28 @@ from typing import TYPE_CHECKING from typing import Union -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. @@ -262,41 +268,45 @@ def testdir(pytester: pytest.Pytester) -> Testdir: @final -@attr.s(init=False, auto_attribs=True) +@dataclasses.dataclass 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..4e3d12475d1 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", @@ -290,6 +297,13 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs): default=None, help="Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.", ) + group.addoption( + "--log-disable", + action="append", + default=[], + dest="logger_disable", + help="Disable a logger by name. Can be passed multipe times.", + ) _HandlerType = TypeVar("_HandlerType", bound=logging.Handler) @@ -322,11 +336,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 +353,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 +391,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 +451,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 +460,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 +479,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 @@ -586,6 +601,15 @@ def __init__(self, config: Config) -> None: get_option_ini(config, "log_auto_indent"), ) self.log_cli_handler.setFormatter(log_cli_formatter) + self._disable_loggers(loggers_to_disable=config.option.logger_disable) + + def _disable_loggers(self, loggers_to_disable: List[str]) -> None: + if not loggers_to_disable: + return + + for name in loggers_to_disable: + logger = logging.getLogger(name) + logger.disabled = True def _create_formatter(self, log_format, log_date_format, auto_indent): # Color option doesn't exist if terminal plugin is disabled. @@ -621,20 +645,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 +773,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..5f8ac46895a 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -1,5 +1,6 @@ """Core implementation of the testing process: init, session, runtest loop.""" import argparse +import dataclasses import fnmatch import functools import importlib @@ -12,7 +13,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 @@ -20,11 +20,10 @@ from typing import TYPE_CHECKING from typing import Union -import attr - 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 +50,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 +66,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 +101,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 +150,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 +181,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 +196,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 +210,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 +222,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.)" ), ) @@ -440,8 +441,10 @@ class Failed(Exception): """Signals a stop as failed test run.""" -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class _bestrelpath_cache(Dict[Path, str]): + __slots__ = ("path",) + path: Path def __missing__(self, path: Path) -> str: @@ -595,18 +598,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 +648,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 +697,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 +878,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..de46b4c8a75 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -1,5 +1,5 @@ """Generic mechanism for marking and selecting python functions.""" -import warnings +import dataclasses from typing import AbstractSet from typing import Collection from typing import List @@ -7,8 +7,6 @@ from typing import TYPE_CHECKING from typing import Union -import attr - from .expression import Expression from .expression import ParseError from .structures import EMPTY_PARAMETERSET_OPTION @@ -23,8 +21,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 +61,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 +75,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 +95,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 +105,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) @@ -133,7 +129,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: return None -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class KeywordMatcher: """A matcher for keywords. @@ -148,6 +144,8 @@ class KeywordMatcher: any item, as well as names directly assigned to test functions. """ + __slots__ = ("_names",) + _names: AbstractSet[str] @classmethod @@ -189,27 +187,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: @@ -217,13 +202,15 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None: items[:] = remaining -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class MarkMatcher: """A matcher for markers which are present. Tries to match on any marker names, attached to the given colitem. """ + __slots__ = ("own_mark_names",) + own_mark_names: AbstractSet[str] @classmethod diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 9d57e944b92..f82a81d44c5 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -15,20 +15,16 @@ - or/and/not evaluate according to the usual boolean semantics. """ import ast +import dataclasses import enum import re import types 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__ = [ @@ -47,8 +43,9 @@ class TokenType(enum.Enum): EOF = "end of input" -@attr.s(frozen=True, slots=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Token: + __slots__ = ("type", "value", "pos") type: TokenType value: str pos: int @@ -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..8dbff1dc93a 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -1,4 +1,5 @@ import collections.abc +import dataclasses import inspect import warnings from typing import Any @@ -20,8 +21,6 @@ from typing import TypeVar from typing import Union -import attr - from .._code import getfslineno from ..compat import ascii_escaped from ..compat import final @@ -72,16 +71,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 +125,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 +149,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 @@ -196,8 +190,10 @@ def _for_parametrize( @final -@attr.s(frozen=True, init=False, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Mark: + """A pytest mark.""" + #: Name of the mark. name: str #: Positional arguments of the mark decorator. @@ -206,9 +202,11 @@ class Mark: kwargs: Mapping[str, Any] #: Source Mark for ids with parametrize Marks. - _param_ids_from: Optional["Mark"] = attr.ib(default=None, repr=False) + _param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False) #: Resolved/generated ids with parametrize Marks. - _param_ids_generated: Optional[Sequence[str]] = attr.ib(default=None, repr=False) + _param_ids_generated: Optional[Sequence[str]] = dataclasses.field( + default=None, repr=False + ) def __init__( self, @@ -266,7 +264,7 @@ def combined_with(self, other: "Mark") -> "Mark": Markable = TypeVar("Markable", bound=Union[Callable[..., object], type]) -@attr.s(init=False, auto_attribs=True) +@dataclasses.dataclass class MarkDecorator: """A decorator for applying a mark on test functions and classes. @@ -360,12 +358,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 +414,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 +423,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 +441,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 +460,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..c74740dbc8a 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 @@ -499,7 +511,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i * "obj": a Python object that the node wraps. * "fspath": just a path - :rtype: A tuple of (str|Path, int) with filename and line number. + :rtype: A tuple of (str|Path, int) with filename and 0-based line number. """ # See Item.location. location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None) @@ -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. @@ -726,7 +755,7 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str Returns a tuple with three elements: - The path of the test (default ``self.path``) - - The line number of the test (default ``None``) + - The 0-based line number of the test (default ``None``) - A name of the test to be shown (default ``""``) .. seealso:: :ref:`non-python tests` @@ -735,6 +764,11 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str @cached_property def location(self) -> Tuple[str, Optional[int], str]: + """ + Returns a tuple of ``(relfspath, lineno, testname)`` for this item + where ``relfspath`` is file path relative to ``config.rootpath`` + and lineno is a 0-based line number. + """ location = self.reportinfo() path = absolutepath(os.fspath(location[0])) relfspath = self.session._node_location_to_relpath(path) 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..1be97dda4ea 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: @@ -221,7 +223,6 @@ def _resolve_msg_to_reason( """ __tracebackhide__ = True if msg is not None: - if reason: from pytest import UsageError @@ -238,11 +239,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 +262,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..9f9463d8862 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -335,15 +335,26 @@ def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]: yield path +def cleanup_dead_symlink(root: Path): + for left_dir in root.iterdir(): + if left_dir.is_symlink(): + if not left_dir.resolve().exists(): + left_dir.unlink() + + def cleanup_numbered_dir( root: Path, prefix: str, keep: int, consider_lock_dead_if_created_before: float ) -> None: """Cleanup for lock driven numbered directories.""" + if not root.exists(): + return for path in cleanup_candidates(root, prefix, keep): try_cleanup(path, consider_lock_dead_if_created_before) for path in root.glob("garbage-*"): try_cleanup(path, consider_lock_dead_if_created_before) + cleanup_dead_symlink(root) + def make_numbered_dir_with_cleanup( root: Path, @@ -357,8 +368,10 @@ def make_numbered_dir_with_cleanup( for i in range(10): try: p = make_numbered_dir(root, prefix, mode) - lock_path = create_cleanup_lock(p) - register_cleanup_lock_removal(lock_path) + # Only lock the current dir when keep is not 0 + if keep != 0: + lock_path = create_cleanup_lock(p) + register_cleanup_lock_removal(lock_path) except Exception as exc: e = exc else: @@ -464,14 +477,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,10 +552,13 @@ 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"): - module_file = module_file[: -(len(os.path.sep + "__init__.py"))] + if module_file.endswith(os.sep + "__init__.py"): + module_file = module_file[: -(len(os.sep + "__init__.py"))] try: is_same = _is_same(str(path), module_file) @@ -562,7 +578,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 +616,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..d04b6fa4ded 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1,4 +1,5 @@ """Python test discovery, setup and run of test functions.""" +import dataclasses import enum import fnmatch import inspect @@ -27,8 +28,6 @@ from typing import TYPE_CHECKING from typing import Union -import attr - import _pytest from _pytest import fixtures from _pytest import nodes @@ -59,6 +58,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 +77,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 +97,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 +105,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 +194,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 +233,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 +260,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 +292,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 +312,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 +352,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 @@ -374,8 +402,8 @@ def classnamefilter(self, name: str) -> bool: def istestfunction(self, obj: object, name: str) -> bool: if self.funcnamefilter(name) or self.isnosetest(obj): - if isinstance(obj, staticmethod): - # staticmethods need to be unwrapped. + if isinstance(obj, (staticmethod, classmethod)): + # staticmethods and classmethods need to be unwrapped. obj = safe_getattr(obj, "__func__", False) return callable(obj) and fixtures.getfixturemarker(obj) is None else: @@ -409,7 +437,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 +545,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 @@ -755,7 +789,8 @@ def _call_with_optional_argument(func, arg) -> None: def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]: """Return the attribute from the given object to be used as a setup/teardown - xunit-style function, but only if not marked as a fixture to avoid calling it twice.""" + xunit-style function, but only if not marked as a fixture to avoid calling it twice. + """ for name in names: meth: Optional[object] = getattr(obj, name, None) if meth is not None and fixtures.getfixturemarker(meth) is None: @@ -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) @@ -907,7 +956,170 @@ def hasnew(obj: object) -> bool: @final -@attr.s(frozen=True, slots=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) +class IdMaker: + """Make IDs for a parametrization.""" + + __slots__ = ( + "argnames", + "parametersets", + "idfn", + "ids", + "config", + "nodeid", + "func_name", + ) + + # 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 +@dataclasses.dataclass(frozen=True) class CallSpec2: """A planned parameterized invocation of a test function. @@ -918,18 +1130,18 @@ class CallSpec2: # arg name -> arg value which will be passed to the parametrized test # function (direct parameterization). - funcargs: Dict[str, object] = attr.Factory(dict) + funcargs: Dict[str, object] = dataclasses.field(default_factory=dict) # arg name -> arg value which will be passed to a fixture of the same name # (indirect parametrization). - params: Dict[str, object] = attr.Factory(dict) + params: Dict[str, object] = dataclasses.field(default_factory=dict) # arg name -> arg index. - indices: Dict[str, int] = attr.Factory(dict) + indices: Dict[str, int] = dataclasses.field(default_factory=dict) # Used for sorting parametrized resources. - _arg2scope: Dict[str, Scope] = attr.Factory(dict) + _arg2scope: Dict[str, Scope] = dataclasses.field(default_factory=dict) # Parts which will be added to the item's name in `[..]` separated by "-". - _idlist: List[str] = attr.Factory(list) + _idlist: List[str] = dataclasses.field(default_factory=list) # Marks which will be applied to the item. - marks: List[Mark] = attr.Factory(list) + marks: List[Mark] = dataclasses.field(default_factory=list) def setmulti( self, @@ -961,9 +1173,9 @@ def setmulti( return CallSpec2( funcargs=funcargs, params=params, - arg2scope=arg2scope, indices=indices, - idlist=[*self._idlist, id], + _arg2scope=arg2scope, + _idlist=[*self._idlist, id], marks=[*self.marks, *normalize_mark_list(marks)], ) @@ -980,7 +1192,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 +1236,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 +1307,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 +1339,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 +1352,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 +1367,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 +1399,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 +1424,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 +1551,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 +1722,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 +1743,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 +1780,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..b03a251abaf 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 expected exception type, or a tuple if one of multiple possible + exception types are expected. + :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,11 +917,17 @@ 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,) + expected_exceptions: Tuple[Type[E], ...] = (expected_exception,) else: - excepted_exceptions = expected_exception - for exc in excepted_exceptions: + expected_exceptions = expected_exception + for exc in expected_exceptions: if not isinstance(exc, type) or not issubclass(exc, BaseException): msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ @@ -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..c0a76f92b59 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -1,3 +1,4 @@ +import dataclasses import os from io import StringIO from pprint import pprint @@ -7,6 +8,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 @@ -14,8 +17,6 @@ from typing import TypeVar from typing import Union -import attr - from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionRepr @@ -35,7 +36,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 +228,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 +254,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 @@ -262,6 +262,8 @@ def __init__( when: "Literal['setup', 'call', 'teardown']", sections: Iterable[Tuple[str, str]] = (), duration: float = 0, + start: float = 0, + stop: float = 0, user_properties: Optional[Iterable[Tuple[str, object]]] = None, **extra, ) -> None: @@ -271,11 +273,13 @@ def __init__( #: A (filesystempath, lineno, domaininfo) tuple indicating the #: actual location of a test item - it might be different from the #: collected one e.g. if a method is inherited from a different module. + #: The filesystempath may be relative to ``config.rootdir``. + #: The line number is 0-based. self.location: Tuple[str, Optional[int], str] = location #: 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 +301,12 @@ def __init__( self.sections = list(sections) #: Time it took to run just the test. - self.duration = duration + self.duration: float = duration + + #: The system time when the call started, in seconds since the epoch. + self.start: float = start + #: The system time when the call ended, in seconds since the epoch. + self.stop: float = stop self.__dict__.update(extra) @@ -308,11 +317,17 @@ 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" duration = call.duration + start = call.start + stop = call.stop keywords = {x: 1 for x in item.keywords} excinfo = call.excinfo sections = [] @@ -332,6 +347,10 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": elif isinstance(excinfo.value, skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() + if r is None: + raise ValueError( + "There should always be a traceback entry for skipping a test." + ) if excinfo.value._use_item_location: path, line = item.reportinfo()[:2] assert line is not None @@ -357,6 +376,8 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": when, sections, duration, + start, + stop, user_properties=item.user_properties, ) @@ -402,7 +423,9 @@ def __init__( self.__dict__.update(extra) @property - def location(self): + def location( # type:ignore[override] + self, + ) -> Optional[Tuple[str, Optional[int], str]]: return (self.fspath, None, self.fspath) def __repr__(self) -> str: @@ -454,15 +477,15 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: def serialize_repr_entry( entry: Union[ReprEntry, ReprEntryNative] ) -> Dict[str, Any]: - data = attr.asdict(entry) + data = dataclasses.asdict(entry) for key, value in data.items(): if hasattr(value, "__dict__"): - data[key] = attr.asdict(value) + data[key] = dataclasses.asdict(value) entry_data = {"type": type(entry).__name__, "data": data} return entry_data def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: - result = attr.asdict(reprtraceback) + result = dataclasses.asdict(reprtraceback) result["reprentries"] = [ serialize_repr_entry(x) for x in reprtraceback.reprentries ] @@ -472,7 +495,7 @@ def serialize_repr_crash( reprcrash: Optional[ReprFileLocation], ) -> Optional[Dict[str, Any]]: if reprcrash is not None: - return attr.asdict(reprcrash) + return dataclasses.asdict(reprcrash) else: return None @@ -568,7 +591,6 @@ def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): and "reprcrash" in reportdict["longrepr"] and "reprtraceback" in reportdict["longrepr"] ): - reprtraceback = deserialize_repr_traceback( reportdict["longrepr"]["reprtraceback"] ) @@ -589,7 +611,10 @@ def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): ExceptionChainRepr, ReprExceptionInfo ] = ExceptionChainRepr(chain) else: - exception_info = ReprExceptionInfo(reprtraceback, reprcrash) + exception_info = ReprExceptionInfo( + reprtraceback=reprtraceback, + reprcrash=reprcrash, + ) for section in reportdict["longrepr"]["sections"]: exception_info.addsection(*section) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index e43dd2dc818..f861c05a451 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -1,8 +1,8 @@ """Basic collect and runtest protocol implementations.""" import bdb +import dataclasses import os import sys -import warnings from typing import Callable from typing import cast from typing import Dict @@ -15,8 +15,6 @@ from typing import TypeVar from typing import Union -import attr - from .reports import BaseReport from .reports import CollectErrorRepr from .reports import CollectReport @@ -28,7 +26,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 @@ -37,6 +34,9 @@ from _pytest.outcomes import Skipped from _pytest.outcomes import TEST_OUTCOME +if sys.version_info[:2] < (3, 11): + from exceptiongroup import BaseExceptionGroup + if TYPE_CHECKING: from typing_extensions import Literal @@ -48,14 +48,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 +63,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.", ) @@ -266,7 +267,7 @@ def call_runtest_hook( @final -@attr.s(repr=False, init=False, auto_attribs=True) +@dataclasses.dataclass class CallInfo(Generic[TResult]): """Result/Exception info of a function invocation.""" @@ -379,11 +380,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_) @@ -518,22 +514,29 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: stack is torn down. """ needed_collectors = nextitem and nextitem.listchain() or [] - exc = None + exceptions: List[BaseException] = [] while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break node, (finalizers, _) = self.stack.popitem() + these_exceptions = [] while finalizers: fin = finalizers.pop() try: fin() except TEST_OUTCOME as e: - # XXX Only first exception will be seen by user, - # ideally all should be reported. - if exc is None: - exc = e - if exc: - raise exc + these_exceptions.append(e) + + if len(these_exceptions) == 1: + exceptions.extend(these_exceptions) + elif these_exceptions: + msg = f"errors while tearing down {node!r}" + exceptions.append(BaseExceptionGroup(msg, these_exceptions[::-1])) + + if len(exceptions) == 1: + raise exceptions[0] + elif exceptions: + raise BaseExceptionGroup("errors during test teardown", exceptions[::-1]) if nextitem is None: assert not self.stack 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..26ce73758a0 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -1,4 +1,5 @@ """Support for skip/xfail functions and markers.""" +import dataclasses import os import platform import sys @@ -9,8 +10,6 @@ from typing import Tuple from typing import Type -import attr - from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config.argparsing import Parser @@ -31,12 +30,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", @@ -157,7 +156,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, return result, reason -@attr.s(slots=True, frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Skip: """The result of evaluate_skip_marks().""" @@ -192,10 +191,12 @@ def evaluate_skip_marks(item: Item) -> Optional[Skip]: return None -@attr.s(slots=True, frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Xfail: """The result of evaluate_xfail_marks().""" + __slots__ = ("reason", "run", "strict", "raises") + reason: str run: bool strict: 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..dfc0fa98e18 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -3,6 +3,7 @@ This is a good source for looking at the various reporting hooks. """ import argparse +import dataclasses import datetime import inspect import platform @@ -27,7 +28,6 @@ from typing import TYPE_CHECKING from typing import Union -import attr import pluggy import _pytest._version @@ -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,21 @@ 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" | "progress-even-when-capture-no" (forces ' + "progress even when capture=no)", default="progress", ) @@ -277,7 +288,7 @@ def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: return outcome, letter, outcome.upper() -@attr.s(auto_attribs=True) +@dataclasses.dataclass class WarningReport: """Simple structure to hold warnings information captured by ``pytest_warning_recorded``. @@ -336,14 +347,19 @@ def __init__(self, config: Config, file: Optional[TextIO] = None) -> None: def _determine_show_progress_info(self) -> "Literal['progress', 'count', False]": """Return whether we should display progress information based on the current config.""" - # do not show progress if we are not capturing output (#3038) - if self.config.getoption("capture", "no") == "no": + # do not show progress if we are not capturing output (#3038) unless explicitly + # overridden by progress-even-when-capture-no + if ( + self.config.getoption("capture", "no") == "no" + and self.config.getini("console_output_style") + != "progress-even-when-capture-no" + ): return False # do not show progress if we are showing fixture setup/teardown if self.config.getoption("setupshow", False): return False cfg: str = self.config.getini("console_output_style") - if cfg == "progress": + if cfg == "progress" or cfg == "progress-even-when-capture-no": return "progress" elif cfg == "count": return "count" @@ -542,15 +558,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 +679,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 +690,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) @@ -717,16 +739,14 @@ def _write_report_lines_from_hooks( self.write_line(line) def pytest_report_header(self, config: Config) -> List[str]: - line = "rootdir: %s" % config.rootpath + result = [f"rootdir: {config.rootpath}"] if config.inipath: - line += ", configfile: " + bestrelpath(config.rootpath, config.inipath) + result.append("configfile: " + bestrelpath(config.rootpath, config.inipath)) - testpaths: List[str] = config.getini("testpaths") - if config.invocation_params.dir == config.rootpath and config.args == testpaths: - line += ", testpaths: {}".format(", ".join(testpaths)) - - result = [line] + if config.args_source == Config.ArgsSource.TESTPATHS: + testpaths: List[str] = config.getini("testpaths") + result.append("testpaths: {}".format(", ".join(testpaths))) plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: @@ -1068,33 +1088,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 +1132,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 +1162,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 +1276,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 +1312,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 +1328,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..5f347665f9a 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -1,40 +1,66 @@ """Support for providing temporary directories to test functions.""" +import dataclasses import os import re -import sys import tempfile from pathlib import Path +from shutil import rmtree +from typing import Any +from typing import Dict +from typing import Generator from typing import Optional +from typing import TYPE_CHECKING +from typing import Union -import attr +from _pytest.nodes import Item +from _pytest.reports import CollectReport +from _pytest.stash import StashKey + +if TYPE_CHECKING: + from typing_extensions import Literal + + RetentionType = Literal["all", "failed", "none"] + + +from _pytest.config.argparsing import Parser from .pathlib import LOCK_TIMEOUT from .pathlib import make_numbered_dir from .pathlib import make_numbered_dir_with_cleanup from .pathlib import rm_rf -from _pytest.compat import final +from .pathlib import cleanup_dead_symlink +from _pytest.compat import final, get_user_id from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import hookimpl from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.monkeypatch import MonkeyPatch +tmppath_result_key = StashKey[Dict[str, bool]]() + @final -@attr.s(init=False) +@dataclasses.dataclass class TempPathFactory: """Factory for temporary directories under the common base temp directory. The base directory can be configured using the ``--basetemp`` option. """ - _given_basetemp = attr.ib(type=Optional[Path]) - _trace = attr.ib() - _basetemp = attr.ib(type=Optional[Path]) + _given_basetemp: Optional[Path] + # pluggy TagTracerSub, not currently exposed, so Any. + _trace: Any + _basetemp: Optional[Path] + _retention_count: int + _retention_policy: "RetentionType" def __init__( self, given_basetemp: Optional[Path], + retention_count: int, + retention_policy: "RetentionType", trace, basetemp: Optional[Path] = None, *, @@ -49,6 +75,8 @@ def __init__( # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012). self._given_basetemp = Path(os.path.abspath(str(given_basetemp))) self._trace = trace + self._retention_count = retention_count + self._retention_policy = retention_policy self._basetemp = basetemp @classmethod @@ -63,9 +91,23 @@ def from_config( :meta private: """ check_ispytest(_ispytest) + count = int(config.getini("tmp_path_retention_count")) + if count < 0: + raise ValueError( + f"tmp_path_retention_count must be >= 0. Current input: {count}." + ) + + policy = config.getini("tmp_path_retention_policy") + if policy not in ("all", "failed", "none"): + raise ValueError( + f"tmp_path_retention_policy must be either all, failed, none. Current intput: {policy}." + ) + return cls( given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir"), + retention_count=count, + retention_policy=policy, _ispytest=True, ) @@ -100,7 +142,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 @@ -129,23 +175,23 @@ def getbasetemp(self) -> Path: # Also, to keep things private, fixup any world-readable temp # rootdir's permissions. Historically 0o755 was used, so we can't # just error out on this, at least for a while. - if sys.platform != "win32": - uid = os.getuid() + uid = get_user_id() + if uid is not None: rootdir_stat = rootdir.stat() - # getuid shouldn't fail, but cpython defines such a case. - # Let's hope for the best. - if uid != -1: - if rootdir_stat.st_uid != uid: - raise OSError( - f"The temporary directory {rootdir} is not owned by the current user. " - "Fix this and try again." - ) - if (rootdir_stat.st_mode & 0o077) != 0: - os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) + if rootdir_stat.st_uid != uid: + raise OSError( + f"The temporary directory {rootdir} is not owned by the current user. " + "Fix this and try again." + ) + if (rootdir_stat.st_mode & 0o077) != 0: + os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) + keep = self._retention_count + if self._retention_policy == "none": + keep = 0 basetemp = make_numbered_dir_with_cleanup( prefix="pytest-", root=rootdir, - keep=3, + keep=keep, lock_timeout=LOCK_TIMEOUT, mode=0o700, ) @@ -158,9 +204,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 @@ -179,6 +226,21 @@ def pytest_configure(config: Config) -> None: mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False) +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "tmp_path_retention_count", + help="How many sessions should we keep the `tmp_path` directories, according to `tmp_path_retention_policy`.", + default=3, + ) + + parser.addini( + "tmp_path_retention_policy", + help="Controls which directories created by the `tmp_path` fixture are kept around, based on test outcome. " + "(all/failed/none)", + default="all", + ) + + @fixture(scope="session") def tmp_path_factory(request: FixtureRequest) -> TempPathFactory: """Return a :class:`pytest.TempPathFactory` instance for the test session.""" @@ -195,17 +257,69 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: @fixture -def tmp_path(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path: +def tmp_path( + request: FixtureRequest, tmp_path_factory: TempPathFactory +) -> Generator[Path, None, None]: """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 + and old bases are removed after 3 sessions, to aid in debugging. + This behavior can be configured with :confval:`tmp_path_retention_count` and + :confval:`tmp_path_retention_policy`. + If ``--basetemp`` is used then it is cleared each session. See :ref:`base temporary directory`. The returned object is a :class:`pathlib.Path` object. """ - return _mk_tmp(request, tmp_path_factory) + path = _mk_tmp(request, tmp_path_factory) + yield path + + # Remove the tmpdir if the policy is "failed" and the test passed. + tmp_path_factory: TempPathFactory = request.session.config._tmp_path_factory # type: ignore + policy = tmp_path_factory._retention_policy + result_dict = request.node.stash[tmppath_result_key] + + if policy == "failed" and result_dict.get("call", True): + # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, + # permissions, etc, in which case we ignore it. + rmtree(path, ignore_errors=True) + + del request.node.stash[tmppath_result_key] + + # remove dead symlink + basetemp = tmp_path_factory._basetemp + if basetemp is None: + return + cleanup_dead_symlink(basetemp) + + +def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): + """After each session, remove base directory if all the tests passed, + the policy is "failed", and the basetemp is not specified by a user. + """ + tmp_path_factory: TempPathFactory = session.config._tmp_path_factory + if tmp_path_factory._basetemp is None: + return + policy = tmp_path_factory._retention_policy + if ( + exitstatus == 0 + and policy == "failed" + and tmp_path_factory._given_basetemp is None + ): + passed_dir = tmp_path_factory._basetemp + if passed_dir.exists(): + # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, + # permissions, etc, in which case we ignore it. + rmtree(passed_dir, ignore_errors=True) + + +@hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item: Item, call): + outcome = yield + result: CollectReport = outcome.get_result() + + empty: Dict[str, bool] = {} + item.stash.setdefault(tmppath_result_key, empty)[result.when] = result.passed 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..86fa9a07e0c 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -1,10 +1,12 @@ +import dataclasses +import inspect +import warnings +from types import FunctionType from typing import Any from typing import Generic from typing import Type from typing import TypeVar -import attr - from _pytest.compat import final @@ -48,16 +50,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 +82,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 @@ -129,7 +129,7 @@ class PytestUnhandledThreadExceptionWarning(PytestWarning): @final -@attr.s(auto_attribs=True) +@dataclasses.dataclass class UnformattedWarning(Generic[_W]): """A warning meant to be formatted during runtime. @@ -143,3 +143,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..b463d769d4c --- /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 # type:ignore[unreachable] + 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 # type:ignore[attr-defined] + from grp import getgrgid # type:ignore[attr-defined] + + 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..6b421dde641 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1,9 +1,8 @@ +import dataclasses import os import sys import types -import attr - import pytest from _pytest.compat import importlib_metadata from _pytest.config import ExitCode @@ -115,11 +114,11 @@ def test_early_load_setuptools_name( loaded = [] - @attr.s + @dataclasses.dataclass class DummyEntryPoint: - name = attr.ib() - module = attr.ib() - group = "pytest11" + name: str + module: str + group: str = "pytest11" def load(self): __import__(self.module) @@ -132,10 +131,10 @@ def load(self): DummyEntryPoint("mycov", "mycov_module"), ] - @attr.s + @dataclasses.dataclass class DummyDist: - entry_points = attr.ib() - files = () + entry_points: object + files: object = () def my_dists(): return (DummyDist(entry_points),) @@ -186,8 +185,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 +693,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( [ @@ -873,7 +878,6 @@ def test_calls(self, pytester: Pytester, mock_timing) -> None: ) def test_calls_show_2(self, pytester: Pytester, mock_timing) -> None: - pytester.makepyfile(self.source) result = pytester.runpytest_inprocess("--durations=2") assert result.ret == 0 @@ -1038,14 +1042,14 @@ def test_fixture_values_leak(pytester: Pytester) -> None: """ pytester.makepyfile( """ - import attr + import dataclasses import gc import pytest import weakref - @attr.s - class SomeObj(object): - name = attr.ib() + @dataclasses.dataclass + class SomeObj: + name: str fix_of_test1_ref = None session_ref = None @@ -1238,8 +1242,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 +1283,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 +1297,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..918c972762b 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -294,6 +294,7 @@ def f(): excinfo = pytest.raises(ValueError, f) tb = excinfo.traceback entry = tb.getcrashentry() + assert entry is not None co = _pytest._code.Code.from_function(h) assert entry.frame.code.path == co.path assert entry.lineno == co.firstlineno + 1 @@ -311,10 +312,7 @@ def f(): excinfo = pytest.raises(ValueError, f) tb = excinfo.traceback entry = tb.getcrashentry() - co = _pytest._code.Code.from_function(g) - assert entry.frame.code.path == co.path - assert entry.lineno == co.firstlineno + 2 - assert entry.frame.code.name == "g" + assert entry is None def test_excinfo_exconly(): @@ -420,18 +418,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: @@ -461,6 +461,24 @@ def f(x): assert lines[0] == "| def f(x):" assert lines[1] == " pass" + def test_repr_source_out_of_bounds(self): + pr = FormattedExcinfo() + source = _pytest._code.Source( + """\ + def f(x): + pass + """ + ).strip() + pr.flow_marker = "|" # type: ignore[misc] + + lines = pr.get_source(source, 100) + assert len(lines) == 1 + assert lines[0] == "| ???" + + lines = pr.get_source(source, -100) + assert len(lines) == 1 + assert lines[0] == "| ???" + def test_repr_source_excinfo(self) -> None: """Check if indentation is right.""" try: @@ -1468,3 +1486,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..a83552fd256 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,3 +1,4 @@ +import dataclasses import re import sys from typing import List @@ -157,6 +158,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()} @@ -191,20 +193,18 @@ def mock_timing(monkeypatch: MonkeyPatch): Time is static, and only advances through `sleep` calls, thus tests might sleep over large numbers and obtain accurate time() calls at the end, making tests reliable and instant. """ - import attr - @attr.s + @dataclasses.dataclass class MockTiming: + _current_time: float = 1590150050.0 - _current_time = attr.ib(default=1590150050.0) - - def sleep(self, seconds): + def sleep(self, seconds: float) -> None: self._current_time += seconds - def time(self): + def time(self) -> float: return self._current_time - def patch(self): + def patch(self) -> None: from _pytest import timing monkeypatch.setattr(timing, "sleep", self.sleep) 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/logging/test_reporting.py b/testing/logging/test_reporting.py index 323ff7b2446..3cd8599b417 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -1165,3 +1165,72 @@ def test_log_file_cli_subdirectories_are_successfully_created( result = pytester.runpytest("--log-file=foo/bar/logf.log") assert "logf.log" in os.listdir(expected) assert result.ret == ExitCode.OK + + +def test_disable_loggers(testdir): + testdir.makepyfile( + """ + import logging + import os + disabled_log = logging.getLogger('disabled') + test_log = logging.getLogger('test') + def test_logger_propagation(caplog): + with caplog.at_level(logging.DEBUG): + disabled_log.warning("no log; no stderr") + test_log.debug("Visible text!") + assert caplog.record_tuples == [('test', 10, 'Visible text!')] + """ + ) + result = testdir.runpytest("--log-disable=disabled", "-s") + assert result.ret == ExitCode.OK + assert not result.stderr.lines + + +def test_disable_loggers_does_not_propagate(testdir): + testdir.makepyfile( + """ + import logging + import os + + parent_logger = logging.getLogger("parent") + child_logger = parent_logger.getChild("child") + + def test_logger_propagation_to_parent(caplog): + with caplog.at_level(logging.DEBUG): + parent_logger.warning("some parent logger message") + child_logger.warning("some child logger message") + assert len(caplog.record_tuples) == 1 + assert caplog.record_tuples[0][0] == "parent" + assert caplog.record_tuples[0][2] == "some parent logger message" + """ + ) + + result = testdir.runpytest("--log-disable=parent.child", "-s") + assert result.ret == ExitCode.OK + assert not result.stderr.lines + + +def test_log_disabling_works_with_log_cli(testdir): + testdir.makepyfile( + """ + import logging + disabled_log = logging.getLogger('disabled') + test_log = logging.getLogger('test') + + def test_log_cli_works(caplog): + test_log.info("Visible text!") + disabled_log.warning("This string will be suppressed.") + """ + ) + result = testdir.runpytest( + "--log-cli-level=DEBUG", + "--log-disable=disabled", + ) + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines( + "INFO test:test_log_disabling_works_with_log_cli.py:6 Visible text!" + ) + result.stdout.no_fnmatch_line( + "WARNING disabled:test_log_disabling_works_with_log_cli.py:7 This string will be suppressed." + ) + assert not result.stderr.lines 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..3a6765f65ce 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.2 +django==4.1.7 +pytest-asyncio==0.21.0 +pytest-bdd==6.1.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-rerunfailures==10.2 -pytest-sugar==0.9.4 +pytest-html==3.2.0 +pytest-mock==3.10.0 +pytest-rerunfailures==11.1.2 +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..054c14a39e4 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 @@ -490,7 +416,7 @@ def test_class(cls): pass def test_static(): pass """ ) - assert len(items) == 3 + assert len(items) == 4 assert isinstance(items[0], Function) assert items[0].name == "test_func" assert items[0].instance is None @@ -498,6 +424,6 @@ def test_static(): pass assert items[1].name == "test_method" assert items[1].instance is not None assert items[1].instance.__class__.__name__ == "TestIt" - assert isinstance(items[2], Function) - assert items[2].name == "test_static" - assert items[2].instance is None + assert isinstance(items[3], Function) + assert items[3].name == "test_static" + assert items[3].instance is None diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index fc0082eb6b9..c1cc9c3d3bb 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1,3 +1,4 @@ +import dataclasses import itertools import re import sys @@ -12,7 +13,6 @@ from typing import Tuple from typing import Union -import attr import hypothesis from hypothesis import strategies @@ -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 @@ -40,14 +39,14 @@ class FuncFixtureInfoMock: def __init__(self, names): self.names_closure = names - @attr.s + @dataclasses.dataclass class DefinitionMock(python.FunctionDefinition): - obj = attr.ib() - _nodeid = attr.ib() + _nodeid: str + obj: object names = getfuncargnames(func) fixtureinfo: Any = FuncFixtureInfoMock(names) - definition: Any = DefinitionMock._create(func, "mock::nodeid") + definition: Any = DefinitionMock._create(obj=func, _nodeid="mock::nodeid") return python.Metafunc(definition, fixtureinfo, config, _ispytest=True) def test_no_funcargs(self) -> None: @@ -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] @@ -141,9 +140,9 @@ def test_find_parametrized_scope(self) -> None: """Unit test for _find_parametrized_scope (#3941).""" from _pytest.python import _find_parametrized_scope - @attr.s + @dataclasses.dataclass class DummyFixtureDef: - _scope = attr.ib() + _scope: Scope fixtures_defs = cast( Dict[str, Sequence[fixtures.FixtureDef[object]]], @@ -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..473ae44d98d 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) @@ -808,14 +807,13 @@ def test_dataclasses(self, pytester: Pytester) -> None: "E ['field_b']", "E ", "E Drill down into differing attribute field_b:", - "E field_b: 'b' != 'c'...", - "E ", - "E ...Full output truncated (3 lines hidden), use '-vv' to show", + "E field_b: 'b' != 'c'", + "E - c", + "E + b", ], 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) @@ -829,12 +827,11 @@ def test_recursive_dataclasses(self, pytester: Pytester) -> None: "E Drill down into differing attribute g:", "E g: S(a=10, b='ten') != S(a=20, b='xxx')...", "E ", - "E ...Full output truncated (52 lines hidden), use '-vv' to show", + "E ...Full output truncated (51 lines hidden), use '-vv' to show", ], 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", ] @@ -1151,30 +1188,55 @@ def test_doesnt_truncate_at_when_input_is_5_lines_and_LT_max_chars(self) -> None def test_truncates_at_8_lines_when_given_list_of_empty_strings(self) -> None: expl = ["" for x in range(50)] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) + assert len(result) != len(expl) assert result != expl assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG assert "Full output truncated" in result[-1] - assert "43 lines hidden" in result[-1] + assert "42 lines hidden" in result[-1] last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1] assert last_line_before_trunc_msg.endswith("...") def test_truncates_at_8_lines_when_first_8_lines_are_LT_max_chars(self) -> None: - expl = ["a" for x in range(100)] + total_lines = 100 + expl = ["a" for x in range(total_lines)] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80) assert result != expl assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG assert "Full output truncated" in result[-1] - assert "93 lines hidden" in result[-1] + assert f"{total_lines - 8} lines hidden" in result[-1] last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1] assert last_line_before_trunc_msg.endswith("...") + def test_truncates_at_8_lines_when_there_is_one_line_to_remove(self) -> None: + """The number of line in the result is 9, the same number as if we truncated.""" + expl = ["a" for x in range(9)] + result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80) + assert result == expl + assert "truncated" not in result[-1] + + def test_truncates_edgecase_when_truncation_message_makes_the_result_longer_for_chars( + self, + ) -> None: + line = "a" * 10 + expl = [line, line] + result = truncate._truncate_explanation(expl, max_lines=10, max_chars=10) + assert result == [line, line] + + def test_truncates_edgecase_when_truncation_message_makes_the_result_longer_for_lines( + self, + ) -> None: + line = "a" * 10 + expl = [line, line] + result = truncate._truncate_explanation(expl, max_lines=1, max_chars=100) + assert result == [line, line] + def test_truncates_at_8_lines_when_first_8_lines_are_EQ_max_chars(self) -> None: - expl = ["a" * 80 for x in range(16)] + expl = [chr(97 + x) * 80 for x in range(16)] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80) assert result != expl - assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG + assert len(result) == 16 - 8 + self.LINES_IN_TRUNCATION_MSG assert "Full output truncated" in result[-1] - assert "9 lines hidden" in result[-1] + assert "8 lines hidden" in result[-1] last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1] assert last_line_before_trunc_msg.endswith("...") @@ -1203,7 +1265,7 @@ def test_full_output_truncated(self, monkeypatch, pytester: Pytester) -> None: line_count = 7 line_len = 100 - expected_truncated_lines = 2 + expected_truncated_lines = 1 pytester.makepyfile( r""" def test_many_lines(): @@ -1224,7 +1286,7 @@ def test_many_lines(): "*+ 1*", "*+ 3*", "*+ 5*", - "*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines, + "*truncated (%d line hidden)*use*-vv*" % expected_truncated_lines, ] ) @@ -1616,7 +1678,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 +1689,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 +1737,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..8d944140307 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,178 @@ def test_simple_failure(): @pytest.mark.skipif( - sys.maxsize <= (2 ** 31 - 1), reason="Causes OverflowError on 32bit systems" + sys.version_info < (3, 8), reason="walrus operator not available in py<38" +) +class TestIssue10743: + def test_assertion_walrus_operator(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def my_func(before, after): + return before == after + + def change_value(value): + return value.lower() + + def test_walrus_conversion(): + a = "Hello" + assert not my_func(a, a := change_value(a)) + assert a == "hello" + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_dont_rewrite(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + 'PYTEST_DONT_REWRITE' + def my_func(before, after): + return before == after + + def change_value(value): + return value.lower() + + def test_walrus_conversion_dont_rewrite(): + a = "Hello" + assert not my_func(a, a := change_value(a)) + assert a == "hello" + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_inline_walrus_operator(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def my_func(before, after): + return before == after + + def test_walrus_conversion_inline(): + a = "Hello" + assert not my_func(a, a := a.lower()) + assert a == "hello" + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_inline_walrus_operator_reverse(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def my_func(before, after): + return before == after + + def test_walrus_conversion_reverse(): + a = "Hello" + assert my_func(a := a.lower(), a) + assert a == 'hello' + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_no_variable_name_conflict( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_conversion_no_conflict(): + a = "Hello" + assert a == (b := a.lower()) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"]) + + def test_assertion_walrus_operator_true_assertion_and_changes_variable_value( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_conversion_succeed(): + a = "Hello" + assert a != (a := a.lower()) + assert a == 'hello' + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_fail_assertion(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def test_walrus_conversion_fails(): + a = "Hello" + assert a == (a := a.lower()) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"]) + + def test_assertion_walrus_operator_boolean_composite( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_boolean_value(): + a = True + assert a and True and ((a := False) is False) and (a is False) and ((a := None) is None) + assert a is None + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_compare_boolean_fails( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_boolean_value(): + a = True + assert not (a and ((a := False) is False)) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*assert not (True and False is False)"]) + + def test_assertion_walrus_operator_boolean_none_fails( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_boolean_value(): + a = True + assert not (a and ((a := None) is None)) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*assert not (True and None is None)"]) + + def test_assertion_walrus_operator_value_changes_cleared_after_each_test( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_value(): + a = True + assert (a := None) is None + + def test_walrus_operator_not_override_value(): + a = True + assert a is True + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + +@pytest.mark.skipif( + 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 +1456,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 +1500,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 +1521,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 +1575,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..2f8517f9962 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -494,7 +494,6 @@ def test_a2(): pass def test_lastfailed_collectfailure( self, pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: - pytester.makepyfile( test_maybe=""" import os @@ -773,7 +772,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 +1248,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..5d6ef64ef71 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -890,14 +890,26 @@ def test_dontreadfrominput() -> None: from _pytest.capture import DontReadFromInput f = DontReadFromInput() - assert f.buffer is f + assert f.buffer is f # type: ignore[comparison-overlap] assert not f.isatty() pytest.raises(OSError, f.read) pytest.raises(OSError, f.readlines) 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() + assert isinstance(f.encoding, str) f.close() # just for completeness + with f: + pass def test_captureresult() -> None: @@ -1040,6 +1052,7 @@ def test_simple_resume_suspend(self) -> None: ) ) # Should not crash with missing "_old". + assert isinstance(cap.syscapture, capture.SysCapture) assert repr(cap.syscapture) == ( " _state='done' tmpfile={!r}>".format( cap.syscapture.tmpfile @@ -1340,6 +1353,7 @@ def test_capsys_results_accessible_by_attribute(capsys: CaptureFixture[str]) -> def test_fdcapture_tmpfile_remains_the_same() -> None: cap = StdCaptureFD(out=False, err=True) + assert isinstance(cap.err, capture.FDCapture) try: cap.start_capturing() capfile = cap.err.tmpfile @@ -1433,19 +1447,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..d907244d551 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 @@ -723,6 +735,20 @@ def testmethod_two(self, arg0): assert s.endswith("test_example_items1.testone") print(s) + def test_classmethod_is_discovered(self, pytester: Pytester) -> None: + """Test that classmethods are discovered""" + p = pytester.makepyfile( + """ + class TestCase: + @classmethod + def test_classmethod(cls) -> None: + pass + """ + ) + items, reprec = pytester.inline_genitems(p) + ids = [x.getmodpath() for x in items] # type: ignore[attr-defined] + assert ids == ["TestCase.test_classmethod"] + def test_class_and_functions_discovery_using_glob(self, pytester: Pytester) -> None: """Test that Python_classes and Python_functions config options work as prefixes and glob-like patterns (#600).""" @@ -874,6 +900,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 +1207,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 +1218,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 +1241,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 +1525,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..6754cd15bf4 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1,3 +1,4 @@ +import dataclasses import os import re import sys @@ -10,8 +11,6 @@ from typing import Type from typing import Union -import attr - import _pytest._code import pytest from _pytest.compat import importlib_metadata @@ -75,7 +74,7 @@ def test_setupcfg_uses_toolpytest_with_pytest(self, pytester: Pytester) -> None: % p1.name, ) result = pytester.runpytest() - result.stdout.fnmatch_lines(["*, configfile: setup.cfg, *", "* 1 passed in *"]) + result.stdout.fnmatch_lines(["configfile: setup.cfg", "* 1 passed in *"]) assert result.ret == 0 def test_append_parse_args( @@ -112,21 +111,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 +167,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 +400,7 @@ def pytest_configure(config): pytest.param( """ [some_other_header] - required_plugins = wont be triggered + required_plugins = won't be triggered [pytest] """, "1.5", @@ -408,11 +422,11 @@ def test_missing_required_plugins( This test installs a mock "myplugin-1.5" which is used in the parametrized test cases. """ - @attr.s + @dataclasses.dataclass class DummyEntryPoint: - name = attr.ib() - module = attr.ib() - group = "pytest11" + name: str + module: str + group: str = "pytest11" def load(self): __import__(self.module) @@ -422,11 +436,11 @@ def load(self): DummyEntryPoint("myplugin1", "myplugin1_module"), ] - @attr.s + @dataclasses.dataclass class DummyDist: - entry_points = attr.ib() - files = () - version = plugin_version + entry_points: object + files: object = () + version: str = plugin_version @property def metadata(self): @@ -807,7 +821,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 +841,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 +1281,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: @@ -1785,6 +1808,10 @@ def test_config_does_not_load_blocked_plugin_from_args(pytester: Pytester) -> No result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"]) assert result.ret == ExitCode.USAGE_ERROR + result = pytester.runpytest(str(p), "-p no:capture", "-s") + result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"]) + assert result.ret == ExitCode.USAGE_ERROR + def test_invocation_args(pytester: Pytester) -> None: """Ensure that Config.invocation_* arguments are correctly defined""" @@ -1839,8 +1866,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 +2128,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..d2944fa2bcf 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") @@ -1207,7 +1236,6 @@ def my_config_context(): class TestDoctestAutoUseFixtures: - SCOPES = ["module", "session", "class", "function"] def test_doctest_module_session_fixture(self, pytester: Pytester): @@ -1350,7 +1378,6 @@ def auto(request): class TestDoctestNamespaceFixture: - SCOPES = ["module", "session", "class", "function"] @pytest.mark.parametrize("scope", SCOPES) 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..90804c61902 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -253,7 +253,6 @@ def test_junit_duration_report( duration_report: str, run_and_parse: RunAndParse, ) -> None: - # mock LogXML.node_reporter so it always sets a known duration to each test report object original_node_reporter = LogXML.node_reporter @@ -603,7 +602,6 @@ def test_func(arg1): node.assert_attr(failures=3, tests=3) for index, char in enumerate("<&'"): - tnode = node.find_nth_by_tag("testcase", index) tnode.assert_attr( classname="test_failure_escape", name="test_func[%s]" % char @@ -1625,6 +1623,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..3d09ef4263a 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 # type:ignore[truthy-function] 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..481d7a606b0 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") @@ -509,10 +518,10 @@ def test_importmode_importlib_with_pickle_separate_modules( fn1.write_text( dedent( """ - import attr + import dataclasses import pickle - @attr.s(auto_attribs=True) + @dataclasses.dataclass class Data: x: int = 42 """ @@ -524,10 +533,10 @@ class Data: fn2.write_text( dedent( """ - import attr + import dataclasses import pickle - @attr.s(auto_attribs=True) + @dataclasses.dataclass class Data: x: str = "" """ @@ -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_reports.py b/testing/test_reports.py index 31b6cf1afc6..e101b51dacb 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -6,6 +6,7 @@ from _pytest._code.code import ExceptionRepr from _pytest.config import Config from _pytest.pytester import Pytester +from _pytest.python_api import approx from _pytest.reports import CollectReport from _pytest.reports import TestReport @@ -415,6 +416,26 @@ def test_report_prevent_ConftestImportFailure_hiding_exception( result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"]) result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*") + def test_report_timestamps_match_duration(self, pytester: Pytester, mock_timing): + reprec = pytester.inline_runsource( + """ + import pytest + from _pytest import timing + @pytest.fixture + def fixture_(): + timing.sleep(5) + yield + timing.sleep(5) + def test_1(fixture_): timing.sleep(10) + """ + ) + reports = reprec.getreports("pytest_runtest_logreport") + assert len(reports) == 3 + for report in reports: + data = report._to_json() + loaded_report = TestReport._from_json(data) + assert loaded_report.stop - loaded_report.start == approx(report.duration) + class TestHooks: """Test that the hooks are working correctly for plugins""" diff --git a/testing/test_runner.py b/testing/test_runner.py index a34cd98f964..de3e18184c7 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -2,6 +2,7 @@ import os import sys import types +from functools import partial from pathlib import Path from typing import Dict from typing import List @@ -19,6 +20,9 @@ from _pytest.outcomes import OutcomeException from _pytest.pytester import Pytester +if sys.version_info[:2] < (3, 11): + from exceptiongroup import ExceptionGroup + class TestSetupState: def test_setup(self, pytester: Pytester) -> None: @@ -77,8 +81,6 @@ def fin3(): assert r == ["fin3", "fin1"] def test_teardown_multiple_fail(self, pytester: Pytester) -> None: - # Ensure the first exception is the one which is re-raised. - # Ideally both would be reported however. def fin1(): raise Exception("oops1") @@ -90,9 +92,14 @@ def fin2(): ss.setup(item) ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) - with pytest.raises(Exception) as err: + with pytest.raises(ExceptionGroup) as err: ss.teardown_exact(None) - assert err.value.args == ("oops2",) + + # Note that finalizers are run LIFO, but because FIFO is more intuitive for + # users we reverse the order of messages, and see the error from fin1 first. + err1, err2 = err.value.exceptions + assert err1.args == ("oops1",) + assert err2.args == ("oops2",) def test_teardown_multiple_scopes_one_fails(self, pytester: Pytester) -> None: module_teardown = [] @@ -113,6 +120,25 @@ def fin_module(): ss.teardown_exact(None) assert module_teardown == ["fin_module"] + def test_teardown_multiple_scopes_several_fail(self, pytester) -> None: + def raiser(exc): + raise exc + + item = pytester.getitem("def test_func(): pass") + mod = item.listchain()[-2] + ss = item.session._setupstate + ss.setup(item) + ss.addfinalizer(partial(raiser, KeyError("from module scope")), mod) + ss.addfinalizer(partial(raiser, TypeError("from function scope 1")), item) + ss.addfinalizer(partial(raiser, ValueError("from function scope 2")), item) + + with pytest.raises(ExceptionGroup, match="errors during test teardown") as e: + ss.teardown_exact(None) + mod, func = e.value.exceptions + assert isinstance(mod, KeyError) + assert isinstance(func.exceptions[0], TypeError) # type: ignore + assert isinstance(func.exceptions[1], ValueError) # type: ignore + class BaseFunctionalTests: def test_passfunction(self, pytester: Pytester) -> None: @@ -287,7 +313,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 @@ -447,6 +473,7 @@ class TestClass(object): assert not rep.skipped assert rep.passed locinfo = rep.location + assert locinfo is not None assert locinfo[0] == col.path.name assert not locinfo[1] assert locinfo[2] == col.path.name @@ -880,6 +907,7 @@ def test_fix(foo): def test_store_except_info_on_error() -> None: """Test that upon test failure, the exception info is stored on sys.last_traceback and friends.""" + # Simulate item that might raise a specific exception, depending on `raise_error` class var class ItemMightRaise: nodeid = "item_that_raises" 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..fe325b72d29 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( """ @@ -850,7 +909,7 @@ def test_header(self, pytester: Pytester) -> None: # with configfile pytester.makeini("""[pytest]""") result = pytester.runpytest() - result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"]) + result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"]) # with testpaths option, and not passing anything in the command-line pytester.makeini( @@ -861,12 +920,12 @@ def test_header(self, pytester: Pytester) -> None: ) result = pytester.runpytest() result.stdout.fnmatch_lines( - ["rootdir: *test_header0, configfile: tox.ini, testpaths: tests, gui"] + ["rootdir: *test_header0", "configfile: tox.ini", "testpaths: tests, gui"] ) # with testpaths option, passing directory in command-line: do not show testpaths then result = pytester.runpytest("tests") - result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"]) + result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"]) def test_header_absolute_testpath( self, pytester: Pytester, monkeypatch: MonkeyPatch @@ -885,9 +944,9 @@ def test_header_absolute_testpath( result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "rootdir: *absolute_testpath0, configfile: pyproject.toml, testpaths: {}".format( - tests - ) + "rootdir: *absolute_testpath0", + "configfile: pyproject.toml", + f"testpaths: {tests}", ] ) @@ -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}", ] @@ -2124,6 +2213,24 @@ def test_capture_no(self, many_tests_files, pytester: Pytester) -> None: output = pytester.runpytest("--capture=no") output.stdout.no_fnmatch_line("*%]*") + def test_capture_no_progress_enabled( + self, many_tests_files, pytester: Pytester + ) -> None: + pytester.makeini( + """ + [pytest] + console_output_style = progress-even-when-capture-no + """ + ) + output = pytester.runpytest("-s") + output.stdout.re_match_lines( + [ + r"test_bar.py \.{10} \s+ \[ 50%\]", + r"test_foo.py \.{5} \s+ \[ 75%\]", + r"test_foobar.py \.{5} \s+ \[100%\]", + ] + ) + class TestProgressWithTeardown: """Ensure we show the correct percentages for tests that fail during teardown (#3088)""" @@ -2260,7 +2367,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 +2381,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 +2490,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 +2512,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 +2535,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_tmpdir.py b/testing/test_tmpdir.py index 4f7c5384700..fcb0775dd5f 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -1,3 +1,4 @@ +import dataclasses import os import stat import sys @@ -6,8 +7,7 @@ from typing import Callable from typing import cast from typing import List - -import attr +from typing import Union import pytest from _pytest import pathlib @@ -31,9 +31,9 @@ def test_tmp_path_fixture(pytester: Pytester) -> None: results.stdout.fnmatch_lines(["*1 passed*"]) -@attr.s +@dataclasses.dataclass class FakeConfig: - basetemp = attr.ib() + basetemp: Union[str, Path] @property def trace(self): @@ -42,13 +42,21 @@ def trace(self): def get(self, key): return lambda *k: None + def getini(self, name): + if name == "tmp_path_retention_count": + return 3 + elif name == "tmp_path_retention_policy": + return "all" + else: + assert False + @property def option(self): return self class TestTmpPathHandler: - def test_mktemp(self, tmp_path): + def test_mktemp(self, tmp_path: Path) -> None: config = cast(Config, FakeConfig(tmp_path)) t = TempPathFactory.from_config(config, _ispytest=True) tmp = t.mktemp("world") @@ -59,7 +67,9 @@ def test_mktemp(self, tmp_path): assert str(tmp2.relative_to(t.getbasetemp())).startswith("this") assert tmp2 != tmp - def test_tmppath_relative_basetemp_absolute(self, tmp_path, monkeypatch): + def test_tmppath_relative_basetemp_absolute( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: """#4425""" monkeypatch.chdir(tmp_path) config = cast(Config, FakeConfig("hello")) @@ -84,6 +94,136 @@ def test_1(tmp_path): assert mytemp.exists() assert not mytemp.joinpath("hello").exists() + def test_policy_failed_removes_only_passed_dir(self, pytester: Pytester) -> None: + p = pytester.makepyfile( + """ + def test_1(tmp_path): + assert 0 == 0 + def test_2(tmp_path): + assert 0 == 1 + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "failed" + """ + ) + + pytester.inline_run(p) + root = pytester._test_tmproot + + for child in root.iterdir(): + base_dir = list( + filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir()) + ) + assert len(base_dir) == 1 + test_dir = list( + filter( + lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir() + ) + ) + # Check only the failed one remains + assert len(test_dir) == 1 + assert test_dir[0].name == "test_20" + + def test_policy_failed_removes_basedir_when_all_passed( + self, pytester: Pytester + ) -> None: + p = pytester.makepyfile( + """ + def test_1(tmp_path): + assert 0 == 0 + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "failed" + """ + ) + + pytester.inline_run(p) + root = pytester._test_tmproot + for child in root.iterdir(): + # This symlink will be deleted by cleanup_numbered_dir **after** + # the test finishes because it's triggered by atexit. + # So it has to be ignored here. + base_dir = filter(lambda x: not x.is_symlink(), child.iterdir()) + # Check the base dir itself is gone + assert len(list(base_dir)) == 0 + + # issue #10502 + def test_policy_failed_removes_dir_when_skipped_from_fixture( + self, pytester: Pytester + ) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.fixture + def fixt(tmp_path): + pytest.skip() + + def test_fixt(fixt): + pass + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "failed" + """ + ) + + pytester.inline_run(p) + + # Check if the whole directory is removed + root = pytester._test_tmproot + for child in root.iterdir(): + base_dir = list( + filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir()) + ) + assert len(base_dir) == 0 + + # issue #10502 + def test_policy_all_keeps_dir_when_skipped_from_fixture( + self, pytester: Pytester + ) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.fixture + def fixt(tmp_path): + pytest.skip() + + def test_fixt(fixt): + pass + """ + ) + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "all" + """ + ) + pytester.inline_run(p) + + # Check if the whole directory is kept + root = pytester._test_tmproot + for child in root.iterdir(): + base_dir = list( + filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir()) + ) + assert len(base_dir) == 1 + test_dir = list( + filter( + lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir() + ) + ) + assert len(test_dir) == 1 + testdata = [ ("mypath", True), @@ -275,12 +415,12 @@ def test_lock_register_cleanup_removal(self, tmp_path: Path) -> None: assert not lock.exists() - def _do_cleanup(self, tmp_path: Path) -> None: + def _do_cleanup(self, tmp_path: Path, keep: int = 2) -> None: self.test_make(tmp_path) cleanup_numbered_dir( root=tmp_path, prefix=self.PREFIX, - keep=2, + keep=keep, consider_lock_dead_if_created_before=0, ) @@ -289,6 +429,11 @@ def test_cleanup_keep(self, tmp_path): a, b = (x for x in tmp_path.iterdir() if not x.is_symlink()) print(a, b) + def test_cleanup_keep_0(self, tmp_path: Path): + self._do_cleanup(tmp_path, 0) + dir_num = len(list(tmp_path.iterdir())) + assert dir_num == 0 + def test_cleanup_locked(self, tmp_path): p = make_numbered_dir(root=tmp_path, prefix=self.PREFIX) @@ -446,7 +591,7 @@ def test_tmp_path_factory_create_directory_with_safe_permissions( """Verify that pytest creates directories under /tmp with private permissions.""" # Use the test's tmp_path as the system temproot (/tmp). monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path)) - tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True) + tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # No world-readable permissions. @@ -466,14 +611,14 @@ def test_tmp_path_factory_fixes_up_world_readable_permissions( """ # Use the test's tmp_path as the system temproot (/tmp). monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path)) - tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True) + tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # Before - simulate bad perms. os.chmod(basetemp.parent, 0o777) assert (basetemp.parent.stat().st_mode & 0o077) != 0 - tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True) + tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # After - fixed. diff --git a/testing/test_tracebackhide.py b/testing/test_tracebackhide.py new file mode 100644 index 00000000000..88f9c4fc00e --- /dev/null +++ b/testing/test_tracebackhide.py @@ -0,0 +1,25 @@ +def test_tbh_chained(testdir): + """Ensure chained exceptions whose frames contain "__tracebackhide__" are not shown (#1904).""" + p = testdir.makepyfile( + """ + import pytest + + def f1(): + __tracebackhide__ = True + try: + return f1.meh + except AttributeError: + pytest.fail("fail") + + @pytest.fixture + def fix(): + f1() + + + def test(fix): + pass + """ + ) + result = testdir.runpytest(str(p)) + assert "'function' object has no attribute 'meh'" not in result.stdout.str() + assert result.ret == 1 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