Compare commits

..

66 Commits

Author SHA1 Message Date
James Frost
40d58e0ce7 Add html_baseurl to sphinx conf.py (#12364) (#12391)
This is used to set the <link rel="canonical" href="X"> tag that points to the canonical version of the webpage. Including this indicates to search engines which version to include in their indexes, and should prevent older versions showing up.

Fixes #12363
2024-05-30 08:08:19 -03:00
github-actions[bot]
549190651e [7.4.x] doc: Remove sold out training (#11824)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2024-01-16 14:07:26 +00:00
github-actions[bot]
cf72d1a40e [7.4.x] doc: Update training dates and add pytest sprint (#11820)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2024-01-15 20:18:31 +00:00
Ran Benita
18dcd9d38d Merge pull request #11752 from pytest-dev/release-7.4.4
Prepare release 7.4.4
2023-12-31 14:10:58 +02:00
pytest bot
33f694f4b3 Prepare release version 7.4.4 2023-12-31 10:17:27 +00:00
Ran Benita
76c107c463 Merge pull request #11751 from bluetech/backport-11143-to-7.4.x
(cherry picked from commit 084d756ae6)

[ran: adapted to 7.4.x, fixed changelog issue number]
2023-12-31 12:12:34 +02:00
github-actions[bot]
531d76daa4 [7.4.x] Improve reporting from __iter__ exceptions (#11749) 2023-12-31 12:11:02 +02:00
Zac Hatfield-Dodds
a0f58fa9e7 Merge pull request #11143 from tushar-deepsource/patch-1
(cherry picked from commit 084d756ae6)

[ran: adapted to 7.4.x, fixed changelog issue number]
2023-12-31 11:53:19 +02:00
github-actions[bot]
b1f3387d42 [7.4.x] #11091: documentation should use hypthonated properties (#11750) 2023-12-31 11:50:38 +02:00
Ran Benita
2cdd619bf4 Merge pull request #11747 from pytest-dev/backport-11711-to-7.4.x
[7.4.x] nodes: fix tracebacks from collection errors are not getting pruned
2023-12-31 11:15:21 +02:00
Ran Benita
d06c05bd23 [7.4.x] nodes: fix tracebacks from collection errors are not getting pruned 2023-12-31 08:14:55 +00:00
github-actions[bot]
5582bfcddf [7.4.x] Improves clarity in Sphinx documentation for function signature. (#11702)
Co-authored-by: Arthur Richard <arthur.richard2299@gmail.com>
2023-12-14 08:19:40 -03:00
github-actions[bot]
13024efd7a [7.4.x] Fix for operation on closed file in faulthandler teardown (#11631)
Co-authored-by: Simon Blanchard <bnomis@gmail.com>
2023-11-22 20:25:29 +00:00
github-actions[bot]
a40dacf657 [7.4.x] XFAIL TestLocalPath.test_make_numbered_dir_multiprocess_safe (#11616)
Co-authored-by: Miro Hrončok <miro@hroncok.cz>
2023-11-14 15:08:28 +00:00
Ran Benita
3550906ead Merge pull request #11570 from pytest-dev/backport-11567-to-7.4.x
[7.4.x] doc/reference: fix sidebar TOC depth
2023-10-30 09:26:36 +02:00
Ran Benita
bd068705b1 [7.4.x] doc/reference: fix sidebar TOC depth 2023-10-30 07:02:22 +00:00
Bruno Oliveira
ec5bd27cc7 Update build-and-inspect-python-package action (#11561) (#11562)
This should fix the action for Python 3.12.

Ref: hynek/build-and-inspect-python-package#72
(cherry picked from commit 247436819a)
2023-10-27 10:36:51 -03:00
Bruno Oliveira
a32feda04c Fix tag name generated by deploy workflow (#11550) (#11554)
Traditionally pytest publishes tags in the form `X.Y.Z`, however the deploy workflow (copied from somewhere else) published tags in the form `vX.Y.Z`.

This is the root cause of #11548, because it tried to publish the release notes for tag `X.Y.Z` (which did not exist).

Fix #11548

(cherry picked from commit c1728948ac)
2023-10-25 13:28:22 -03:00
github-actions[bot]
f8070ffb9b [7.4.x] Add deploy instructions using the command-line (#11553)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2023-10-25 13:37:56 +00:00
Bruno Oliveira
53df6164b4 Merge pull request #11546 from pytest-dev/release-7.4.3
Prepare release 7.4.3
2023-10-24 16:38:09 -03:00
Bruno Oliveira
2390610696 Tweak changelog.rst 2023-10-24 15:45:08 -03:00
pytest bot
a0714aa007 Prepare release version 7.4.3 2023-10-24 18:43:16 +00:00
github-actions[bot]
44ad1c9811 [7.4.x] fix #10447 - consider marks in reverse mro order to give base classes priority (#11545)
Co-authored-by: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
2023-10-24 15:04:13 +00:00
github-actions[bot]
5dc77253d4 [7.4.x] Ensure logging tests always cleanup after themselves (#11541)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2023-10-23 14:28:04 +00:00
github-actions[bot]
a517827318 [7.4.x] Configure ReadTheDocs to fail on warnings (#11540)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2023-10-23 13:23:18 +00:00
github-actions[bot]
21fe071d79 [7.4.x] fix for ValueError raised in faulthandler teardown code (#11455)
Co-authored-by: Simon Blanchard <bnomis@gmail.com>
2023-09-20 12:41:01 +00:00
Bruno Oliveira
f8bb8572fe Force terminal width when running tests (#11425) (#11432)
Related to #11423

(cherry picked from commit 241f2a890e)
2023-09-11 09:48:22 -03:00
github-actions[bot]
1944dc06d3 [7.4.x] Fix --import-mode=importlib when root contains __init__.py file (#11426)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2023-09-10 13:27:53 +00:00
Bruno Oliveira
946634c84c Merge pull request #11419 from nicoddemus/backport-11414-to-7.4.x
[7.4.x] Fix assert rewriting with assignment expressions (#11414)
2023-09-09 10:08:41 -03:00
github-actions[bot]
d849a3ed64 [7.4.x] fix: closes #11343's [attr-defined] type errors (#11421)
Co-authored-by: Warren Markham <rabbitsinwarrens@gmail.com>
2023-09-09 13:02:31 +00:00
Bruno Oliveira
721a0881fb Skip test_assertion_walrus_different_test_cases on Python 3.7 2023-09-09 09:43:15 -03:00
Marc Mueller
5341b9cd67 Fix assert rewriting with assignment expressions (#11414)
Fixes #11239

(cherry picked from commit 7259e8db98)
2023-09-09 09:13:10 -03:00
Bruno Oliveira
c39bdf6190 Adjustments to the release process (#11410) (#11415)
As discussed in #11408:

* Improve documentation for the release process.
* Fix the description for the PRs created by the `prepare release pr` workflow.
* Fix pushing tag in the `deploy` workflow.

(cherry picked from commit e5c81fa41a)
2023-09-08 08:42:55 -03:00
Bruno Oliveira
b0c4775a28 Merge pull request #11408 from pytest-dev/release-7.4.2
Prepare release 7.4.2
2023-09-07 15:47:56 -03:00
pytest bot
45f34dfb8d Prepare release version 7.4.2 2023-09-07 17:21:49 +00:00
Bruno Oliveira
e4f022f0d8 Merge pull request #11406 from nicoddemus/backport-11404-to-7.4.x
[7.4.x] Fix crash when passing a very long cmdline argument (#11404)
2023-09-07 14:14:40 -03:00
Bruno Oliveira
63b0c6f75f Use _pytest.pathlib.safe_exists in get_dirs_from_args
Related to #11394
2023-09-07 13:50:02 -03:00
Bruno Oliveira
884b911a9c Fix crash when passing a very long cmdline argument (#11404)
Fixes #11394

(cherry picked from commit 28ccf476b9)
2023-09-07 12:54:41 -03:00
github-actions[bot]
6e49a74089 [7.4.x] Fix doctest collection of functools.cached_property objects. (#11403)
Co-authored-by: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
2023-09-07 13:33:12 +00:00
github-actions[bot]
79c2012d40 [7.4.x] doc: Remove done training (#11400)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2023-09-06 13:50:00 +00:00
github-actions[bot]
de69883e3a [7.4.x] improve plugin list disclaimer (#11398)
Co-authored-by: Stefaan Lippens <soxofaan@users.noreply.github.com>
2023-09-06 11:02:29 +00:00
github-actions[bot]
1de00e9830 [7.4.x] Fix import_path for packages (#11395)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2023-09-05 23:07:48 +00:00
Bruno Oliveira
7f5d9b9df4 Fix user_properties not saved to XML if fixture errors during teardown (#11382)
Move handling of user_properties to `finalize()`.

Previously if a fixture failed during teardown, `pytest_runtest_logreport` would not be called with "teardown", resulting in the user properties not being saved on the JUnit XML file.

Fixes: #11367
(cherry picked from commit 917ce9aa01)

Co-authored-by: Israel Fruchter <israel.fruchter@gmail.com>
2023-09-03 15:01:56 -03:00
Bruno Oliveira
82eb86f707 Merge pull request #11377 from pytest-dev/release-7.4.1
Prepare release 7.4.1
2023-09-02 12:41:32 -03:00
Bruno Oliveira
0319a0d4fd Checkout source code during deploy
We need the checked out repository in order to push the tag.
2023-09-02 12:38:39 -03:00
Bruno Oliveira
7855a72d2c Improve CI workflow
* Build the package only once, and test on all platforms.
* Deploy is now triggered manually via an Action, which is then responsible for tagging the repository after the package has been uploaded successfully.
* Drop 'docs': we nowadays rely on readthedocs preview PR builds.
2023-09-02 08:46:22 -03:00
pytest bot
7a0a0e8b08 Prepare release version 7.4.1 2023-09-02 11:03:06 +00:00
github-actions[bot]
fbcfd3a52e [7.4.x] Update CONTRIBUTING.rst (#11371)
Co-authored-by: Sourabh Beniwal <sourabhbeniwal@outlook.com>
2023-08-30 08:57:49 -03:00
github-actions[bot]
b170081788 [7.4.x] Issue 11354 fixing docs for lfnf (#11364)
Co-authored-by: Sean Patrick Malloy <spmalloy@ucdavis.edu>
2023-08-29 00:40:49 +00:00
Ran Benita
7a5f2feefb [7.4.x] Fixes for typed pluggy (#11355)
Since version 1.3 pluggy added typing, which requires some fixes to
please mypy.
2023-08-26 22:15:32 +00:00
github-actions[bot]
69140717d4 [7.4.x] Improve duplicate values documentation (#11296)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2023-08-08 14:44:03 +03:00
Bruno Oliveira
5c7c3f6329 Merge pull request #11294 from The-Compiler/pluggy-py38
ci: Use Python 3.8 to test latest pluggy
2023-08-07 08:13:03 -03:00
Florian Bruhin
ba40975bb7 ci: Use Python 3.8 to test latest pluggy
Pluggy dropped Python 3.7 support.
Also see 165fbbd12a

Fixes #11293
2023-08-07 12:08:56 +02:00
github-actions[bot]
e3fe7286f8 [7.4.x] doc: Link pytest.main to how-to guide (#11290)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2023-08-07 10:47:37 +02:00
github-actions[bot]
34c73944e1 [7.4.x] doc: update information about assertion messages (#11286)
Co-authored-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-08-07 10:47:12 +02:00
github-actions[bot]
350122abb2 [7.4.x] Remove ep2023 training (#11242)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2023-07-22 18:40:56 +00:00
github-actions[bot]
06ff7ca13b [7.4.x] Clarify docs for pytest.main default behavior (#11188)
Co-authored-by: antosikv <79337398+antosikv@users.noreply.github.com>
2023-07-09 15:54:59 +00:00
github-actions[bot]
6dfe498c77 [7.4.x] doc: fix EncodingWarnings in examples (#11182)
Co-authored-by: Ran Benita <ran@unusedvar.com>
2023-07-08 19:17:21 +00:00
github-actions[bot]
a566b78730 [7.4.x] reference: improve the node types docs a bit (#11181)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2023-07-08 19:07:55 +00:00
github-actions[bot]
511adf85be [7.4.x] Fix error assertion handling in approx when None in dict comparison (#11180)
Co-authored-by: Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>
2023-07-08 18:37:35 +00:00
github-actions[bot]
c71b5df734 [7.4.x] Add child modules as attributes of parent modules. (#11163)
* [7.4.x] Add child modules as attributes of parent modules.

* Update 10337.bugfix.rst

---------

Co-authored-by: akhilramkee <31619526+akhilramkee@users.noreply.github.com>
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2023-07-08 15:11:26 -03:00
github-actions[bot]
d53951836d [7.4.x] Update open trainings (#11172)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2023-07-04 21:49:24 +00:00
github-actions[bot]
a4d7254d18 [7.4.x] Fix duplicated imports with importlib mode and doctest-modules (#11164)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2023-07-03 16:33:47 +00:00
Bruno Oliveira
b6c55787fe Switch to deploy environment and configure for pypi oidc (#10925) (#11162)
Closes #10871
Closes #10870

Co-authored-by: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
2023-07-03 16:17:02 +00:00
Ran Benita
fb03d1388b Merge pull request #11131 from pytest-dev/release-7.4.0
Prepare release 7.4.0
2023-06-23 14:18:43 +03:00
pytest bot
d9bf9dbec1 Prepare release version 7.4.0
[ran: made some fixups]
2023-06-23 14:03:58 +03:00
299 changed files with 8325 additions and 14681 deletions

View File

@@ -23,11 +23,6 @@ afc607cfd81458d4e4f3b1f3cf8cc931b933907e
5f95dce95602921a70bfbc7d8de2f7712c5e4505
# ran pyupgrade-docs again
75d0b899bbb56d6849e9d69d83a9426ed3f43f8b
# move argument parser to own file
c9df77cbd6a365dcb73c39618e4842711817e871
# Replace reorder-python-imports by isort due to black incompatibility (#11896)
8b54596639f41dfac070030ef20394b9001fe63c
# Run blacken-docs with black's 2024's style
4546d5445aaefe6a03957db028c263521dfb5c4b
# Migration to ruff / ruff format
4588653b2497ed25976b7aaff225b889fb476756

View File

@@ -22,7 +22,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
persist-credentials: true

View File

@@ -20,13 +20,13 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
persist-credentials: false
- name: Build and Check Package
uses: hynek/build-and-inspect-python-package@v2.0.1
uses: hynek/build-and-inspect-python-package@v1.5.3
deploy:
if: github.repository == 'pytest-dev/pytest'
@@ -38,16 +38,16 @@ jobs:
id-token: write
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Download Package
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: Packages
path: dist
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@v1.8.11
uses: pypa/gh-action-pypi-publish@v1.8.5
- name: Push tag
run: |
@@ -67,35 +67,24 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
persist-credentials: false
- name: Download Package
uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v4
with:
python-version: "3.11"
python-version: "3.10"
- name: Install tox
run: |
python -m pip install --upgrade pip
pip install --upgrade tox
- name: Generate release notes
- name: Publish GitHub release notes
env:
GH_RELEASE_NOTES_TOKEN: ${{ github.token }}
run: |
sudo apt-get install pandoc
tox -e generate-gh-release-notes -- ${{ github.event.inputs.version }} scripts/latest-release-notes.md
- name: Publish GitHub Release
uses: softprops/action-gh-release@v1
with:
body_path: scripts/latest-release-notes.md
files: dist/*
tag_name: ${{ github.event.inputs.version }}
tox -e publish-gh-release-notes

View File

@@ -27,12 +27,12 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v4
with:
python-version: "3.8"

View File

@@ -10,7 +10,7 @@ jobs:
permissions:
issues: write
steps:
- uses: actions/stale@v9
- uses: actions/stale@v8
with:
debug-only: false
days-before-issue-stale: 14

View File

@@ -30,12 +30,12 @@ jobs:
package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
persist-credentials: false
- name: Build and Check Package
uses: hynek/build-and-inspect-python-package@v2.0.1
uses: hynek/build-and-inspect-python-package@v1.5.3
build:
needs: [package]
@@ -49,6 +49,7 @@ jobs:
fail-fast: false
matrix:
name: [
"windows-py37",
"windows-py38",
"windows-py38-pluggy",
"windows-py39",
@@ -56,16 +57,17 @@ jobs:
"windows-py311",
"windows-py312",
"ubuntu-py37",
"ubuntu-py37-freeze",
"ubuntu-py38",
"ubuntu-py38-pluggy",
"ubuntu-py38-freeze",
"ubuntu-py39",
"ubuntu-py310",
"ubuntu-py311",
"ubuntu-py312",
"ubuntu-pypy3",
"macos-py38",
"macos-py37",
"macos-py39",
"macos-py310",
"macos-py312",
@@ -75,6 +77,10 @@ jobs:
]
include:
- name: "windows-py37"
python: "3.7"
os: windows-latest
tox_env: "py37-numpy"
- name: "windows-py38"
python: "3.8"
os: windows-latest
@@ -101,19 +107,23 @@ jobs:
os: windows-latest
tox_env: "py312"
- name: "ubuntu-py37"
python: "3.7"
os: ubuntu-latest
tox_env: "py37-lsof-numpy-pexpect"
use_coverage: true
- name: "ubuntu-py37-freeze"
python: "3.7"
os: ubuntu-latest
tox_env: "py37-freeze"
- name: "ubuntu-py38"
python: "3.8"
os: ubuntu-latest
tox_env: "py38-lsof-numpy-pexpect"
use_coverage: true
tox_env: "py38-xdist"
- name: "ubuntu-py38-pluggy"
python: "3.8"
os: ubuntu-latest
tox_env: "py38-pluggymain-pylib-xdist"
- name: "ubuntu-py38-freeze"
python: "3.8"
os: ubuntu-latest
tox_env: "py38-freeze"
- name: "ubuntu-py39"
python: "3.9"
os: ubuntu-latest
@@ -133,14 +143,14 @@ jobs:
tox_env: "py312"
use_coverage: true
- name: "ubuntu-pypy3"
python: "pypy-3.8"
python: "pypy-3.7"
os: ubuntu-latest
tox_env: "pypy3-xdist"
- name: "macos-py38"
python: "3.8"
- name: "macos-py37"
python: "3.7"
os: macos-latest
tox_env: "py38-xdist"
tox_env: "py37-xdist"
- name: "macos-py39"
python: "3.9"
os: macos-latest
@@ -156,30 +166,30 @@ jobs:
tox_env: "py312-xdist"
- name: "plugins"
python: "3.12"
python: "3.9"
os: ubuntu-latest
tox_env: "plugins"
- name: "doctesting"
python: "3.8"
python: "3.7"
os: ubuntu-latest
tox_env: "doctesting"
use_coverage: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
persist-credentials: false
- name: Download Package
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: Packages
path: dist
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
check-latest: ${{ endsWith(matrix.python, '-dev') }}
@@ -205,7 +215,7 @@ jobs:
- name: Upload coverage to Codecov
if: "matrix.use_coverage"
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v3
continue-on-error: true
with:
fail_ci_if_error: true

View File

@@ -20,33 +20,25 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v4
with:
python-version: "3.11"
cache: pip
- name: requests-cache
uses: actions/cache@v4
with:
path: ~/.cache/pytest-plugin-list/
key: plugins-http-cache-${{ github.run_id }} # Can use time based key as well
restore-keys: plugins-http-cache-
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install packaging requests tabulate[widechars] tqdm requests-cache platformdirs
pip install packaging requests tabulate[widechars] tqdm
- name: Update Plugin List
run: python scripts/update-plugin-list.py
- name: Create Pull Request
uses: peter-evans/create-pull-request@b1ddad2c994a25fbc81a28b3ec0e368bb2021c50
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38
with:
commit-message: '[automated] Update plugin list'
author: 'pytest bot <pytestbot@users.noreply.github.com>'

View File

@@ -1,12 +1,16 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.2.2"
hooks:
- id: ruff
args: ["--fix"]
- id: ruff-format
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
args: [--safe, --quiet]
- repo: https://github.com/asottile/blacken-docs
rev: 1.14.0
hooks:
- id: blacken-docs
additional_dependencies: [black==23.1.0]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -16,38 +20,56 @@ repos:
- id: debug-statements
exclude: _pytest/(debugging|hookspec).py
language_version: python3
- repo: https://github.com/adamchainz/blacken-docs
rev: 1.16.0
- repo: https://github.com/PyCQA/autoflake
rev: v2.1.1
hooks:
- id: blacken-docs
additional_dependencies: [black==24.1.1]
- 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: 6.0.0
hooks:
- id: flake8
language_version: python3
additional_dependencies:
- flake8-typing-imports==1.12.0
- flake8-docstrings==1.5.0
- repo: https://github.com/asottile/reorder-python-imports
rev: v3.10.0
hooks:
- id: reorder-python-imports
args: ['--application-directories=.:src', --py37-plus]
- repo: https://github.com/asottile/pyupgrade
rev: v3.7.0
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.3.0
hooks:
- id: setup-cfg-fmt
args: ["--max-py-version=3.12", "--include-version-classifiers"]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-use-type-annotations
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
rev: v1.3.0
hooks:
- id: mypy
files: ^(src/|testing/|scripts/)
files: ^(src/|testing/)
args: []
additional_dependencies:
- iniconfig>=1.1.0
- attrs>=19.2.0
- pluggy>=1.4.0
- packaging
- tomli
- types-pkg_resources
- types-tabulate
# for mypy running on python>=3.11 since exceptiongroup is only a dependency
# on <3.11
- exceptiongroup>=1.0.0rc8
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "1.7.0"
hooks:
- id: pyproject-fmt
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
additional_dependencies: ["tox>=4.9"]
- repo: local
hooks:
- id: rst

29
AUTHORS
View File

@@ -11,7 +11,6 @@ Adam Johnson
Adam Stewart
Adam Uhlir
Ahn Ki-Wook
Akhilesh Ramakrishnan
Akiomi Kamakura
Alan Velasco
Alessio Izzo
@@ -54,14 +53,10 @@ Aviral Verma
Aviv Palivoda
Babak Keyvani
Barney Gale
Ben Brown
Ben Gartner
Ben Leith
Ben Webb
Benjamin Peterson
Benjamin Schubert
Bernard Pratz
Bo Wu
Bob Ippolito
Brian Dorsey
Brian Larsen
@@ -94,7 +89,6 @@ Christopher Dignam
Christopher Gilling
Claire Cecil
Claudio Madotto
Clément M.T. Robert
CrazyMerlyn
Cristian Vera
Cyrus Maden
@@ -128,8 +122,6 @@ Edison Gustavo Muenz
Edoardo Batini
Edson Tadeu M. Manoel
Eduardo Schettino
Edward Haigh
Eero Vaher
Eli Boyarski
Elizaveta Shashkova
Éloi Rivard
@@ -138,21 +130,17 @@ Eric Hunsberger
Eric Liu
Eric Siegerman
Erik Aronesty
Erik Hasse
Erik M. Bray
Evan Kepner
Evgeny Seliverstov
Fabian Sturm
Fabien Zarifian
Fabio Zadrozny
faph
Felix Hofstätter
Felix Nieuwenhuizen
Feng Ma
Florian Bruhin
Florian Dahlitz
Floris Bruynooghe
Fraser Stark
Gabriel Landau
Gabriel Reis
Garvit Shubham
@@ -188,6 +176,7 @@ Jake VanderPlas
Jakob van Santen
Jakub Mitoraj
James Bourbeau
James Frost
Jan Balster
Janne Vanhala
Jason R. Coombs
@@ -196,7 +185,6 @@ Javier Romero
Jeff Rackauckas
Jeff Widman
Jenni Rinker
Jens Tröger
John Eddie Ayson
John Litborn
John Towler
@@ -249,7 +237,6 @@ Marc Mueller
Marc Schlaich
Marcelo Duarte Trevisani
Marcin Bachry
Marc Bresson
Marco Gorelli
Mark Abramowitz
Mark Dickinson
@@ -274,20 +261,15 @@ Michael Goerz
Michael Krebs
Michael Seifert
Michal Wajszczuk
Michał Górny
Michał Zięba
Mickey Pashov
Mihai Capotă
Mihail Milushev
Mike Hoyle (hoylemd)
Mike Lundy
Milan Lesnek
Miro Hrončok
mrbean-bremen
Nathaniel Compton
Nathaniel Waisbrot
Ned Batchelder
Neil Martin
Neven Mundar
Nicholas Devenish
Nicholas Murphy
@@ -305,7 +287,6 @@ Ondřej Súkup
Oscar Benjamin
Parth Patel
Patrick Hayes
Patrick Lannigan
Paul Müller
Paul Reece
Pauli Virtanen
@@ -334,7 +315,6 @@ Raphael Pierzina
Rafal Semik
Raquel Alegre
Ravi Chandra
Reagan Lee
Robert Holt
Roberto Aldera
Roberto Polli
@@ -345,25 +325,20 @@ Ronny Pfannschmidt
Ross Lawley
Ruaridh Williamson
Russel Winder
Russell Martin
Ryan Puddephatt
Ryan Wooden
Sadra Barikbin
Saiprasad Kale
Samuel Colvin
Samuel Dion-Girardeau
Samuel Searles-Bryant
Samuel Therrien (Avasam)
Samuele Pedroni
Sanket Duthade
Sankt Petersbug
Saravanan Padmanaban
Sean Malloy
Segev Finer
Serhii Mozghovyi
Seth Junot
Shantanu Jain
Sharad Nair
Shubham Adep
Simon Blanchard
Simon Gomizelj
@@ -385,7 +360,6 @@ Tadek Teleżyński
Takafumi Arakaki
Taneli Hukkinen
Tanvi Mehta
Tanya Agarwal
Tarcisio Fischer
Tareq Alayan
Tatiana Ovary
@@ -420,7 +394,6 @@ Vivaan Verma
Vlad Dragos
Vlad Radziuk
Vladyslav Rachek
Volodymyr Kochetkov
Volodymyr Piskun
Wei Lin
Wil Cooley

View File

@@ -197,12 +197,11 @@ Short version
~~~~~~~~~~~~~
#. Fork the repository.
#. Fetch tags from upstream if necessary (if you cloned only main `git fetch --tags https://github.com/pytest-dev/pytest`).
#. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed.
#. Follow `PEP-8 <https://www.python.org/dev/peps/pep-0008/>`_ for naming.
#. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting.
#. Tests are run using ``tox``::
tox -e linting,py39
tox -e linting,py37
The test environments above are usually enough to cover most cases locally.
@@ -237,7 +236,6 @@ Here is a simple overview, with pytest-specific bits:
$ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
$ cd pytest
$ git fetch --tags https://github.com/pytest-dev/pytest
# now, create your own branch off "main":
$ git checkout -b your-bugfix-branch-name main
@@ -274,24 +272,24 @@ Here is a simple overview, with pytest-specific bits:
#. Run all the tests
You need to have Python 3.8 or later available in your system. Now
You need to have Python 3.7 available in your system. Now
running tests is as simple as issuing this command::
$ tox -e linting,py39
$ tox -e linting,py37
This command will run tests via the "tox" tool against Python 3.9
This command will run tests via the "tox" tool against Python 3.7
and also perform "lint" coding-style checks.
#. You can now edit your local working copy and run the tests again as necessary. Please follow `PEP-8 <https://www.python.org/dev/peps/pep-0008/>`_ for naming.
#. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 for naming.
You can pass different options to ``tox``. For example, to run tests on Python 3.9 and pass options to pytest
You can pass different options to ``tox``. For example, to run tests on Python 3.7 and pass options to pytest
(e.g. enter pdb on failure) to pytest you can do::
$ tox -e py39 -- --pdb
$ tox -e py37 -- --pdb
Or to only run tests in a particular test module on Python 3.9::
Or to only run tests in a particular test module on Python 3.7::
$ tox -e py39 -- testing/test_config.py
$ tox -e py37 -- testing/test_config.py
When committing, ``pre-commit`` will re-format the files if necessary.

View File

@@ -20,13 +20,16 @@
:target: https://codecov.io/gh/pytest-dev/pytest
:alt: Code coverage Status
.. image:: https://github.com/pytest-dev/pytest/actions/workflows/test.yml/badge.svg
.. 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
:alt: pre-commit.ci status
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
:target: https://www.codetriage.com/pytest-dev/pytest
@@ -94,12 +97,12 @@ Features
- `Modular fixtures <https://docs.pytest.org/en/stable/explanation/fixtures.html>`_ for
managing small or parametrized long-lived test resources
- Can run `unittest <https://docs.pytest.org/en/stable/how-to/unittest.html>`_ (or trial)
test suites out of the box
- Can run `unittest <https://docs.pytest.org/en/stable/how-to/unittest.html>`_ (or trial),
`nose <https://docs.pytest.org/en/stable/how-to/nose.html>`_ test suites out of the box
- Python 3.8+ or PyPy3
- Python 3.7+ or PyPy3
- Rich plugin architecture, with over 1300+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community
- Rich plugin architecture, with over 850+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community
Documentation

View File

@@ -23,6 +23,7 @@ members of the `contributors team`_ interested in receiving funding.
The current list of contributors receiving funding are:
* `@asottile`_
* `@nicoddemus`_
* `@The-Compiler`_
@@ -54,5 +55,6 @@ funds. Just drop a line to one of the `@pytest-dev/tidelift-admins`_ or use the
.. _`@pytest-dev/tidelift-admins`: https://github.com/orgs/pytest-dev/teams/tidelift-admins/members
.. _`agreement`: https://tidelift.com/docs/lifting/agreement
.. _`@asottile`: https://github.com/asottile
.. _`@nicoddemus`: https://github.com/nicoddemus
.. _`@The-Compiler`: https://github.com/The-Compiler

View File

@@ -1,12 +1,10 @@
import sys
if __name__ == "__main__":
import cProfile
import pytest # NOQA
import pstats
import pytest # noqa: F401
script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
p = pstats.Stats("prof")

View File

@@ -4,7 +4,6 @@
# FastFilesCompleter 0.7383 1.0760
import timeit
imports = [
"from argcomplete.completers import FilesCompleter as completer",
"from _pytest._argcomplete import FastFilesCompleter as completer",

View File

@@ -1,6 +1,5 @@
import pytest
SKIP = True

View File

@@ -1,6 +1,5 @@
from unittest import TestCase # noqa: F401
for i in range(15000):
exec(
f"""

1
changelog/12363.doc.rst Normal file
View File

@@ -0,0 +1 @@
The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results.

View File

@@ -5,10 +5,11 @@
<div id="searchbox" style="display: none" role="search">
<div class="searchformwrapper">
<form class="search" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
<input type="text" name="q" aria-labelledby="searchlabel"
placeholder="Search"/>
<input type="submit" value="{{ _('Go') }}" />
</form>
</div>
</div>
<script>document.getElementById('searchbox').style.display = "block"</script>
<script type="text/javascript">$('#searchbox').show(0);</script>
{%- endif %}

View File

@@ -44,7 +44,7 @@ Partner projects, sign up here! (by 22 March)
What does it mean to "adopt pytest"?
-----------------------------------------
There can be many different definitions of "success". Pytest can run many unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right?
There can be many different definitions of "success". Pytest can run many nose_ and unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right?
Progressive success might look like:
@@ -62,6 +62,7 @@ Progressive success might look like:
It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies.
.. _nose: nose.html
.. _unittest: unittest.html
.. _assert: assert.html
.. _pycmd: https://bitbucket.org/hpk42/pycmd/overview

View File

@@ -6,12 +6,6 @@ Release announcements
:maxdepth: 2
release-8.1.0
release-8.0.2
release-8.0.1
release-8.0.0
release-8.0.0rc2
release-8.0.0rc1
release-7.4.4
release-7.4.3
release-7.4.2

View File

@@ -1,26 +0,0 @@
pytest-8.0.0
=======================================
The pytest team is proud to announce the 8.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:
* Bruno Oliveira
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -1,82 +0,0 @@
pytest-8.0.0rc1
=======================================
The pytest team is proud to announce the 8.0.0rc1 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:
* Akhilesh Ramakrishnan
* Aleksandr Brodin
* Anthony Sottile
* Arthur Richard
* Avasam
* Benjamin Schubert
* Bruno Oliveira
* Carsten Grohmann
* Cheukting
* Chris Mahoney
* Christoph Anton Mitterer
* DetachHead
* Erik Hasse
* Florian Bruhin
* Fraser Stark
* Ha Pam
* Hugo van Kemenade
* Isaac Virshup
* Israel Fruchter
* Jens Tröger
* Jon Parise
* Kenny Y
* Lesnek
* Marc Mueller
* Michał Górny
* Mihail Milushev
* Milan Lesnek
* Miro Hrončok
* Patrick Lannigan
* Ran Benita
* Reagan Lee
* Ronny Pfannschmidt
* Sadra Barikbin
* Sean Malloy
* Sean Patrick Malloy
* Sharad Nair
* Simon Blanchard
* Sourabh Beniwal
* Stefaan Lippens
* Tanya Agarwal
* Thomas Grainger
* Tom Mortimer-Jones
* Tushar Sadhwani
* Tyler Smart
* Uday Kumar
* Warren Markham
* WarrenTheRabbit
* Zac Hatfield-Dodds
* Ziad Kermadi
* akhilramkee
* antosikv
* bowugit
* mickeypash
* neilmartin2000
* pomponchik
* ryanpudd
* touilleWoman
* ubaumann
Happy testing,
The pytest Development Team

View File

@@ -1,32 +0,0 @@
pytest-8.0.0rc2
=======================================
The pytest team is proud to announce the 8.0.0rc2 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==8.0.0rc2
Users are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/release-8.0.0rc2/changelog.html
Thanks to all the contributors to this release:
* Ben Brown
* Bruno Oliveira
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -1,21 +0,0 @@
pytest-8.0.1
=======================================
pytest 8.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:
* Bruno Oliveira
* Clément Robert
* Pierre Sassoulas
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -1,18 +0,0 @@
pytest-8.0.2
=======================================
pytest 8.0.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:
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -1,54 +0,0 @@
pytest-8.1.0
=======================================
The pytest team is proud to announce the 8.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:
* Ben Brown
* Ben Leith
* Bruno Oliveira
* Clément Robert
* Dave Hall
* Dương Quốc Khánh
* Eero Vaher
* Eric Larson
* Fabian Sturm
* Faisal Fawad
* Florian Bruhin
* Franck Charras
* Joachim B Haga
* John Litborn
* Loïc Estève
* Marc Bresson
* Patrick Lannigan
* Pierre Sassoulas
* Ran Benita
* Reagan Lee
* Ronny Pfannschmidt
* Russell Martin
* clee2000
* donghui
* faph
* jakkdl
* mrbean-bremen
* robotherapist
* whysage
* woutdenolf
Happy testing,
The pytest Development Team

View File

@@ -22,7 +22,7 @@ b) transitional: the old and new API don't conflict
We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationWarning`).
A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationwarning`).
When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g. `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
@@ -87,7 +87,6 @@ Released pytest versions support all Python versions that are actively maintaine
============== ===================
pytest version min. Python version
============== ===================
8.0+ 3.8+
7.1+ 3.7+
6.2 - 7.0 3.6+
5.0 - 6.1 3.5+

View File

@@ -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-8.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 -- .../_pytest/cacheprovider.py:527
cache -- .../_pytest/cacheprovider.py:532
Return a cache object that can persist state between testing sessions.
cache.get(key, default)
@@ -33,7 +33,7 @@ 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.
capsysbinary -- .../_pytest/capture.py:1008
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()``
@@ -43,6 +43,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
Example:
.. code-block:: python
def test_output(capsysbinary):
@@ -50,7 +51,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
captured = capsysbinary.readouterr()
assert captured.out == b"hello\n"
capfd -- .../_pytest/capture.py:1035
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
@@ -60,6 +61,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
Example:
.. code-block:: python
def test_system_echo(capfd):
@@ -67,7 +69,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
captured = capfd.readouterr()
assert captured.out == "hello\n"
capfdbinary -- .../_pytest/capture.py:1062
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
@@ -77,6 +79,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
Example:
.. code-block:: python
def test_system_echo(capfdbinary):
@@ -84,7 +87,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
captured = capfdbinary.readouterr()
assert captured.out == b"hello\n"
capsys -- .../_pytest/capture.py:981
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
@@ -94,6 +97,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
Example:
.. code-block:: python
def test_output(capsys):
@@ -101,7 +105,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
captured = capsys.readouterr()
assert captured.out == "hello\n"
doctest_namespace [session scope] -- .../_pytest/doctest.py:737
doctest_namespace [session scope] -- .../_pytest/doctest.py:757
Fixture that returns a :py:class:`dict` that will be injected into the
namespace of doctests.
@@ -115,7 +119,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
For more details: :ref:`doctest_namespace`.
pytestconfig [session scope] -- .../_pytest/fixtures.py:1346
pytestconfig [session scope] -- .../_pytest/fixtures.py:1353
Session-scoped fixture that returns the session's :class:`pytest.Config`
object.
@@ -125,7 +129,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
if pytestconfig.getoption("verbose") > 0:
...
record_property -- .../_pytest/junitxml.py:283
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
@@ -139,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 -- .../_pytest/junitxml.py:306
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] -- .../_pytest/junitxml.py:344
record_testsuite_property [session scope] -- .../_pytest/junitxml.py:343
Record a new ``<property>`` tag as child of the root ``<testsuite>``.
This is suitable to writing global information regarding the entire test
@@ -170,18 +174,18 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
`pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See
:issue:`7767` for details.
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:317
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:302
Return a :class:`pytest.TempdirFactory` instance for the test session.
tmpdir -- .../_pytest/legacypath.py:324
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:`temporary directory location and retention`.
``--basetemp`` is used then it is cleared each session. See :ref:`base
temporary directory`.
The returned object is a `legacy_path`_ object.
@@ -192,7 +196,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
caplog -- .../_pytest/logging.py:601
caplog -- .../_pytest/logging.py:570
Access and control log capturing.
Captured logs are available through the following properties/methods::
@@ -203,7 +207,7 @@ 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 -- .../_pytest/monkeypatch.py:32
monkeypatch -- .../_pytest/monkeypatch.py:30
A convenient fixture for monkey-patching.
The fixture provides these methods to modify objects, dictionaries, or
@@ -227,16 +231,16 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
To undo modifications done by the fixture in a contained scope,
use :meth:`context() <pytest.MonkeyPatch.context>`.
recwarn -- .../_pytest/recwarn.py:31
recwarn -- .../_pytest/recwarn.py:30
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information
on warning categories.
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:241
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:245
Return a :class:`pytest.TempPathFactory` instance for the test session.
tmp_path -- .../_pytest/tmpdir.py:256
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.
@@ -245,8 +249,8 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
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:`temporary directory location and retention`.
If ``--basetemp`` is used then it is cleared each session. See :ref:`base
temporary directory`.
The returned object is a :class:`pathlib.Path` object.

View File

@@ -28,557 +28,6 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 8.1.0 (2024-03-03)
=========================
Features
--------
- `#11475 <https://github.com/pytest-dev/pytest/issues/11475>`_: Added the new :confval:`consider_namespace_packages` configuration option, defaulting to ``False``.
If set to ``True``, pytest will attempt to identify modules that are part of `namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages>`__ when importing modules.
- `#11653 <https://github.com/pytest-dev/pytest/issues/11653>`_: Added the new :confval:`verbosity_test_cases` configuration option for fine-grained control of test execution verbosity.
See :ref:`Fine-grained verbosity <pytest.fine_grained_verbosity>` for more details.
Improvements
------------
- `#10865 <https://github.com/pytest-dev/pytest/issues/10865>`_: :func:`pytest.warns` now validates that :func:`warnings.warn` was called with a `str` or a `Warning`.
Currently in Python it is possible to use other types, however this causes an exception when :func:`warnings.filterwarnings` is used to filter those warnings (see `CPython #103577 <https://github.com/python/cpython/issues/103577>`__ for a discussion).
While this can be considered a bug in CPython, we decided to put guards in pytest as the error message produced without this check in place is confusing.
- `#11311 <https://github.com/pytest-dev/pytest/issues/11311>`_: When using ``--override-ini`` for paths in invocations without a configuration file defined, the current working directory is used
as the relative directory.
Previoulsy this would raise an :class:`AssertionError`.
- `#11475 <https://github.com/pytest-dev/pytest/issues/11475>`_: :ref:`--import-mode=importlib <import-mode-importlib>` now tries to import modules using the standard import mechanism (but still without changing :py:data:`sys.path`), falling back to importing modules directly only if that fails.
This means that installed packages will be imported under their canonical name if possible first, for example ``app.core.models``, instead of having the module name always be derived from their path (for example ``.env310.lib.site_packages.app.core.models``).
- `#11801 <https://github.com/pytest-dev/pytest/issues/11801>`_: Added the :func:`iter_parents() <_pytest.nodes.Node.iter_parents>` helper method on nodes.
It is similar to :func:`listchain <_pytest.nodes.Node.listchain>`, but goes from bottom to top, and returns an iterator, not a list.
- `#11850 <https://github.com/pytest-dev/pytest/issues/11850>`_: Added support for :data:`sys.last_exc` for post-mortem debugging on Python>=3.12.
- `#11962 <https://github.com/pytest-dev/pytest/issues/11962>`_: In case no other suitable candidates for configuration file are found, a ``pyproject.toml`` (even without a ``[tool.pytest.ini_options]`` table) will be considered as the configuration file and define the ``rootdir``.
- `#11978 <https://github.com/pytest-dev/pytest/issues/11978>`_: Add ``--log-file-mode`` option to the logging plugin, enabling appending to log-files. This option accepts either ``"w"`` or ``"a"`` and defaults to ``"w"``.
Previously, the mode was hard-coded to be ``"w"`` which truncates the file before logging.
- `#12047 <https://github.com/pytest-dev/pytest/issues/12047>`_: When multiple finalizers of a fixture raise an exception, now all exceptions are reported as an exception group.
Previously, only the first exception was reported.
Bug Fixes
---------
- `#11904 <https://github.com/pytest-dev/pytest/issues/11904>`_: Fixed a regression in pytest 8.0.0 that would cause test collection to fail due to permission errors when using ``--pyargs``.
This change improves the collection tree for tests specified using ``--pyargs``, see :pull:`12043` for a comparison with pytest 8.0 and <8.
- `#12011 <https://github.com/pytest-dev/pytest/issues/12011>`_: Fixed a regression in 8.0.1 whereby ``setup_module`` xunit-style fixtures are not executed when ``--doctest-modules`` is passed.
- `#12014 <https://github.com/pytest-dev/pytest/issues/12014>`_: Fix the ``stacklevel`` used when warning about marks used on fixtures.
- `#12039 <https://github.com/pytest-dev/pytest/issues/12039>`_: Fixed a regression in ``8.0.2`` where tests created using :fixture:`tmp_path` have been collected multiple times in CI under Windows.
Improved Documentation
----------------------
- `#11790 <https://github.com/pytest-dev/pytest/issues/11790>`_: Documented the retention of temporary directories created using the ``tmp_path`` fixture in more detail.
Trivial/Internal Changes
------------------------
- `#11785 <https://github.com/pytest-dev/pytest/issues/11785>`_: Some changes were made to private functions which may affect plugins which access them:
- ``FixtureManager._getautousenames()`` now takes a ``Node`` itself instead of the nodeid.
- ``FixtureManager.getfixturedefs()`` now takes the ``Node`` itself instead of the nodeid.
- The ``_pytest.nodes.iterparentnodeids()`` function is removed without replacement.
Prefer to traverse the node hierarchy itself instead.
If you really need to, copy the function from the previous pytest release.
pytest 8.0.2 (2024-02-24)
=========================
Bug Fixes
---------
- `#11895 <https://github.com/pytest-dev/pytest/issues/11895>`_: Fix collection on Windows where initial paths contain the short version of a path (for example ``c:\PROGRA~1\tests``).
- `#11953 <https://github.com/pytest-dev/pytest/issues/11953>`_: Fix an ``IndexError`` crash raising from ``getstatementrange_ast``.
- `#12021 <https://github.com/pytest-dev/pytest/issues/12021>`_: Reverted a fix to `--maxfail` handling in pytest 8.0.0 because it caused a regression in pytest-xdist whereby session fixture teardowns may get executed multiple times when the max-fails is reached.
pytest 8.0.1 (2024-02-16)
=========================
Bug Fixes
---------
- `#11875 <https://github.com/pytest-dev/pytest/issues/11875>`_: Correctly handle errors from :func:`getpass.getuser` in Python 3.13.
- `#11879 <https://github.com/pytest-dev/pytest/issues/11879>`_: Fix an edge case where ``ExceptionInfo._stringify_exception`` could crash :func:`pytest.raises`.
- `#11906 <https://github.com/pytest-dev/pytest/issues/11906>`_: Fix regression with :func:`pytest.warns` using custom warning subclasses which have more than one parameter in their `__init__`.
- `#11907 <https://github.com/pytest-dev/pytest/issues/11907>`_: Fix a regression in pytest 8.0.0 whereby calling :func:`pytest.skip` and similar control-flow exceptions within a :func:`pytest.warns()` block would get suppressed instead of propagating.
- `#11929 <https://github.com/pytest-dev/pytest/issues/11929>`_: Fix a regression in pytest 8.0.0 whereby autouse fixtures defined in a module get ignored by the doctests in the module.
- `#11937 <https://github.com/pytest-dev/pytest/issues/11937>`_: Fix a regression in pytest 8.0.0 whereby items would be collected in reverse order in some circumstances.
pytest 8.0.0 (2024-01-27)
=========================
Bug Fixes
---------
- `#11842 <https://github.com/pytest-dev/pytest/issues/11842>`_: Properly escape the ``reason`` of a :ref:`skip <pytest.mark.skip ref>` mark when writing JUnit XML files.
- `#11861 <https://github.com/pytest-dev/pytest/issues/11861>`_: Avoid microsecond exceeds ``1_000_000`` when using ``log-date-format`` with ``%f`` specifier, which might cause the test suite to crash.
pytest 8.0.0rc2 (2024-01-17)
============================
Improvements
------------
- `#11233 <https://github.com/pytest-dev/pytest/issues/11233>`_: Improvements to ``-r`` for xfailures and xpasses:
* Report tracebacks for xfailures when ``-rx`` is set.
* Report captured output for xpasses when ``-rX`` is set.
* For xpasses, add ``-`` in summary between test name and reason, to match how xfail is displayed.
- `#11825 <https://github.com/pytest-dev/pytest/issues/11825>`_: The :hook:`pytest_plugin_registered` hook has a new ``plugin_name`` parameter containing the name by which ``plugin`` is registered.
Bug Fixes
---------
- `#11706 <https://github.com/pytest-dev/pytest/issues/11706>`_: Fix reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`.
NOTE: This change was reverted in pytest 8.0.2 to fix a `regression <https://github.com/pytest-dev/pytest-xdist/issues/1024>`_ it caused in pytest-xdist.
- `#11758 <https://github.com/pytest-dev/pytest/issues/11758>`_: Fixed ``IndexError: string index out of range`` crash in ``if highlighted[-1] == "\n" and source[-1] != "\n"``.
This bug was introduced in pytest 8.0.0rc1.
- `#9765 <https://github.com/pytest-dev/pytest/issues/9765>`_, `#11816 <https://github.com/pytest-dev/pytest/issues/11816>`_: Fixed a frustrating bug that afflicted some users with the only error being ``assert mod not in mods``. The issue was caused by the fact that ``str(Path(mod))`` and ``mod.__file__`` don't necessarily produce the same string, and was being erroneously used interchangably in some places in the code.
This fix also broke the internal API of ``PytestPluginManager.consider_conftest`` by introducing a new parameter -- we mention this in case it is being used by external code, even if marked as *private*.
pytest 8.0.0rc1 (2023-12-30)
============================
Breaking Changes
----------------
Old Deprecations Are Now Errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- `#7363 <https://github.com/pytest-dev/pytest/issues/7363>`_: **PytestRemovedIn8Warning deprecation warnings are now errors by default.**
Following our plan to remove deprecated features with as little disruption as
possible, all warnings of type ``PytestRemovedIn8Warning`` now generate errors
instead of warning messages by default.
**The affected features will be effectively removed in pytest 8.1**, so please consult the
:ref:`deprecations` section in the docs for directions on how to update existing code.
In the pytest ``8.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.PytestRemovedIn8Warning
But this will stop working when pytest ``8.1`` is released.
**If you have concerns** about the removal of a specific feature, please add a
comment to :issue:`7363`.
Version Compatibility
^^^^^^^^^^^^^^^^^^^^^
- `#11151 <https://github.com/pytest-dev/pytest/issues/11151>`_: Dropped support for Python 3.7, which `reached end-of-life on 2023-06-27 <https://devguide.python.org/versions/>`__.
- ``pluggy>=1.3.0`` is now required.
Collection Changes
^^^^^^^^^^^^^^^^^^
In this version we've made several breaking changes to pytest's collection phase,
particularly around how filesystem directories and Python packages are collected,
fixing deficiencies and allowing for cleanups and improvements to pytest's internals.
A deprecation period for these changes was not possible.
- `#7777 <https://github.com/pytest-dev/pytest/issues/7777>`_: Files and directories are now collected in alphabetical order jointly, unless changed by a plugin.
Previously, files were collected before directories.
See below for an example.
- `#8976 <https://github.com/pytest-dev/pytest/issues/8976>`_: Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
(unless :confval:`python_files` was changed to allow `__init__.py` file).
To collect the entire package, specify just the directory: `pytest pkg`.
- `#11137 <https://github.com/pytest-dev/pytest/issues/11137>`_: :class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File`.
The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file.
Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module),
the module being the `__init__.py` file.
This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details).
The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file.
Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist,
if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files).
- `#7777 <https://github.com/pytest-dev/pytest/issues/7777>`_: Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass.
This is analogous to the existing :class:`pytest.File` for file nodes.
Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`.
A ``Package`` represents a filesystem directory which is a Python package,
i.e. contains an ``__init__.py`` file.
:class:`pytest.Package` now only collects files in its own directory; previously it collected recursively.
Sub-directories are collected as their own collector nodes, which then collect themselves, thus creating a collection tree which mirrors the filesystem hierarchy.
Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`.
This node represents a filesystem directory, which is not a :class:`pytest.Package`,
that is, does not contain an ``__init__.py`` file.
Similarly to ``Package``, it only collects the files in its own directory.
:class:`pytest.Session` now only collects the initial arguments, without recursing into directories.
This work is now done by the :func:`recursive expansion process <pytest.Collector.collect>` of directory collector nodes.
:attr:`session.name <pytest.Session.name>` is now ``""``; previously it was the rootdir directory name.
This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`.
The collection tree now contains directories/packages up to the :ref:`rootdir <rootdir>`,
for initial arguments that are found within the rootdir.
For files outside the rootdir, only the immediate directory/package is collected --
note however that collecting from outside the rootdir is discouraged.
As an example, given the following filesystem tree::
myroot/
pytest.ini
top/
├── aaa
│ └── test_aaa.py
├── test_a.py
├── test_b
│ ├── __init__.py
│ └── test_b.py
├── test_c.py
└── zzz
├── __init__.py
└── test_zzz.py
the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity,
is now the following::
<Session>
<Dir myroot>
<Dir top>
<Dir aaa>
<Module test_aaa.py>
<Function test_it>
<Module test_a.py>
<Function test_it>
<Package test_b>
<Module test_b.py>
<Function test_it>
<Module test_c.py>
<Function test_it>
<Package zzz>
<Module test_zzz.py>
<Function test_it>
Previously, it was::
<Session>
<Module top/test_a.py>
<Function test_it>
<Module top/test_c.py>
<Function test_it>
<Module top/aaa/test_aaa.py>
<Function test_it>
<Package test_b>
<Module test_b.py>
<Function test_it>
<Package zzz>
<Module test_zzz.py>
<Function test_it>
Code/plugins which rely on a specific shape of the collection tree might need to update.
- `#11676 <https://github.com/pytest-dev/pytest/issues/11676>`_: The classes :class:`~_pytest.nodes.Node`, :class:`~pytest.Collector`, :class:`~pytest.Item`, :class:`~pytest.File`, :class:`~_pytest.nodes.FSCollector` are now marked abstract (see :mod:`abc`).
We do not expect this change to affect users and plugin authors, it will only cause errors when the code is already wrong or problematic.
Other breaking changes
^^^^^^^^^^^^^^^^^^^^^^
These are breaking changes where deprecation was not possible.
- `#11282 <https://github.com/pytest-dev/pytest/issues/11282>`_: Sanitized the handling of the ``default`` parameter when defining configuration options.
Previously if ``default`` was not supplied for :meth:`parser.addini <pytest.Parser.addini>` and the configuration option value was not defined in a test session, then calls to :func:`config.getini <pytest.Config.getini>` returned an *empty list* or an *empty string* depending on whether ``type`` was supplied or not respectively, which is clearly incorrect. Also, ``None`` was not honored even if ``default=None`` was used explicitly while defining the option.
Now the behavior of :meth:`parser.addini <pytest.Parser.addini>` is as follows:
* If ``default`` is NOT passed but ``type`` is provided, then a type-specific default will be returned. For example ``type=bool`` will return ``False``, ``type=str`` will return ``""``, etc.
* If ``default=None`` is passed and the option is not defined in a test session, then ``None`` will be returned, regardless of the ``type``.
* If neither ``default`` nor ``type`` are provided, assume ``type=str`` and return ``""`` as default (this is as per previous behavior).
The team decided to not introduce a deprecation period for this change, as doing so would be complicated both in terms of communicating this to the community as well as implementing it, and also because the team believes this change should not break existing plugins except in rare cases.
- `#11667 <https://github.com/pytest-dev/pytest/issues/11667>`_: pytest's ``setup.py`` file is removed.
If you relied on this file, e.g. to install pytest using ``setup.py install``,
please see `Why you shouldn't invoke setup.py directly <https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html#summary>`_ for alternatives.
- `#9288 <https://github.com/pytest-dev/pytest/issues/9288>`_: :func:`~pytest.warns` now re-emits unmatched warnings when the context
closes -- previously it would consume all warnings, hiding those that were not
matched by the function.
While this is a new feature, we announce it as a breaking change
because many test suites are configured to error-out on warnings, and will
therefore fail on the newly-re-emitted warnings.
- The internal ``FixtureManager.getfixtureclosure`` method has changed. Plugins which use this method or
which subclass ``FixtureManager`` and overwrite that method will need to adapt to the change.
Deprecations
------------
- `#10465 <https://github.com/pytest-dev/pytest/issues/10465>`_: Test functions returning a value other than ``None`` will now issue a :class:`pytest.PytestWarning` instead of ``pytest.PytestRemovedIn8Warning``, meaning this will stay a warning instead of becoming an error in the future.
- `#3664 <https://github.com/pytest-dev/pytest/issues/3664>`_: Applying a mark to a fixture function now issues a warning: marks in fixtures never had any effect, but it is a common user error to apply a mark to a fixture (for example ``usefixtures``) and expect it to work.
This will become an error in pytest 9.0.
Features and Improvements
-------------------------
Improved Diffs
^^^^^^^^^^^^^^
These changes improve the diffs that pytest prints when an assertion fails.
Note that syntax highlighting requires the ``pygments`` package.
- `#11520 <https://github.com/pytest-dev/pytest/issues/11520>`_: The very verbose (``-vv``) diff output is now colored as a diff instead of a big chunk of red.
Python code in error reports is now syntax-highlighted as Python.
The sections in the error reports are now better separated.
- `#1531 <https://github.com/pytest-dev/pytest/issues/1531>`_: The very verbose diff (``-vv``) for every standard library container type is improved. The indentation is now consistent and the markers are on their own separate lines, which should reduce the diffs shown to users.
Previously, the standard Python pretty printer was used to generate the output, which puts opening and closing
markers on the same line as the first/last entry, in addition to not having consistent indentation.
- `#10617 <https://github.com/pytest-dev/pytest/issues/10617>`_: Added more comprehensive set assertion rewrites for comparisons other than equality ``==``, with
the following operations now providing better failure messages: ``!=``, ``<=``, ``>=``, ``<``, and ``>``.
Separate Control For Assertion Verbosity
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- `#11387 <https://github.com/pytest-dev/pytest/issues/11387>`_: Added the new :confval:`verbosity_assertions` configuration option for fine-grained control of failed assertions verbosity.
If you've ever wished that pytest always show you full diffs, but without making everything else verbose, this is for you.
See :ref:`Fine-grained verbosity <pytest.fine_grained_verbosity>` for more details.
For plugin authors, :attr:`config.get_verbosity <pytest.Config.get_verbosity>` can be used to retrieve the verbosity level for a specific verbosity type.
Additional Support For Exception Groups and ``__notes__``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
These changes improve pytest's support for exception groups.
- `#10441 <https://github.com/pytest-dev/pytest/issues/10441>`_: Added :func:`ExceptionInfo.group_contains() <pytest.ExceptionInfo.group_contains>`, an assertion helper that tests if an :class:`ExceptionGroup` contains a matching exception.
See :ref:`assert-matching-exception-groups` for an example.
- `#11227 <https://github.com/pytest-dev/pytest/issues/11227>`_: Allow :func:`pytest.raises` ``match`` argument to match against `PEP-678 <https://peps.python.org/pep-0678/>` ``__notes__``.
Custom Directory collectors
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- `#7777 <https://github.com/pytest-dev/pytest/issues/7777>`_: Added a new hook :hook:`pytest_collect_directory`,
which is called by filesystem-traversing collector nodes,
such as :class:`pytest.Session`, :class:`pytest.Dir` and :class:`pytest.Package`,
to create a collector node for a sub-directory.
It is expected to return a subclass of :class:`pytest.Directory`.
This hook allows plugins to :ref:`customize the collection of directories <custom directory collectors>`.
"New-style" Hook Wrappers
^^^^^^^^^^^^^^^^^^^^^^^^^
- `#11122 <https://github.com/pytest-dev/pytest/issues/11122>`_: pytest now uses "new-style" hook wrappers internally, available since pluggy 1.2.0.
See `pluggy's 1.2.0 changelog <https://pluggy.readthedocs.io/en/latest/changelog.html#pluggy-1-2-0-2023-06-21>`_ and the :ref:`updated docs <hookwrapper>` for details.
Plugins which want to use new-style wrappers can do so if they require ``pytest>=8``.
Other Improvements
^^^^^^^^^^^^^^^^^^
- `#11216 <https://github.com/pytest-dev/pytest/issues/11216>`_: If a test is skipped from inside an :ref:`xunit setup fixture <classic xunit>`, the test summary now shows the test location instead of the fixture location.
- `#11314 <https://github.com/pytest-dev/pytest/issues/11314>`_: Logging to a file using the ``--log-file`` option will use ``--log-level``, ``--log-format`` and ``--log-date-format`` as fallback
if ``--log-file-level``, ``--log-file-format`` and ``--log-file-date-format`` are not provided respectively.
- `#11610 <https://github.com/pytest-dev/pytest/issues/11610>`_: Added the :func:`LogCaptureFixture.filtering() <pytest.LogCaptureFixture.filtering>` context manager which
adds a given :class:`logging.Filter` object to the :fixture:`caplog` fixture.
- `#11447 <https://github.com/pytest-dev/pytest/issues/11447>`_: :func:`pytest.deprecated_call` now also considers warnings of type :class:`FutureWarning`.
- `#11600 <https://github.com/pytest-dev/pytest/issues/11600>`_: Improved the documentation and type signature for :func:`pytest.mark.xfail <pytest.mark.xfail>`'s ``condition`` param to use ``False`` as the default value.
- `#7469 <https://github.com/pytest-dev/pytest/issues/7469>`_: :class:`~pytest.FixtureDef` is now exported as ``pytest.FixtureDef`` for typing purposes.
- `#11353 <https://github.com/pytest-dev/pytest/issues/11353>`_: Added typing to :class:`~pytest.PytestPluginManager`.
Bug Fixes
---------
- `#10701 <https://github.com/pytest-dev/pytest/issues/10701>`_: :meth:`pytest.WarningsRecorder.pop` will return the most-closely-matched warning in the list,
rather than the first warning which is an instance of the requested type.
- `#11255 <https://github.com/pytest-dev/pytest/issues/11255>`_: Fixed crash on `parametrize(..., scope="package")` without a package present.
- `#11277 <https://github.com/pytest-dev/pytest/issues/11277>`_: Fixed a bug that when there are multiple fixtures for an indirect parameter,
the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope.
- `#11456 <https://github.com/pytest-dev/pytest/issues/11456>`_: Parametrized tests now *really do* ensure that the ids given to each input are unique - for
example, ``a, a, a0`` now results in ``a1, a2, a0`` instead of the previous (buggy) ``a0, a1, a0``.
This necessarily means changing nodeids where these were previously colliding, and for
readability adds an underscore when non-unique ids end in a number.
- `#11563 <https://github.com/pytest-dev/pytest/issues/11563>`_: Fixed a crash when using an empty string for the same parametrized value more than once.
- `#11712 <https://github.com/pytest-dev/pytest/issues/11712>`_: Fixed handling ``NO_COLOR`` and ``FORCE_COLOR`` to ignore an empty value.
- `#9036 <https://github.com/pytest-dev/pytest/issues/9036>`_: ``pytest.warns`` and similar functions now capture warnings when an exception is raised inside a ``with`` block.
Improved Documentation
----------------------
- `#11011 <https://github.com/pytest-dev/pytest/issues/11011>`_: Added a warning about modifying the root logger during tests when using ``caplog``.
- `#11065 <https://github.com/pytest-dev/pytest/issues/11065>`_: Use ``pytestconfig`` instead of ``request.config`` in cache example to be consistent with the API documentation.
Trivial/Internal Changes
------------------------
- `#11208 <https://github.com/pytest-dev/pytest/issues/11208>`_: The (internal) ``FixtureDef.cached_result`` type has changed.
Now the third item ``cached_result[2]``, when set, is an exception instance instead of an exception triplet.
- `#11218 <https://github.com/pytest-dev/pytest/issues/11218>`_: (This entry is meant to assist plugins which access private pytest internals to instantiate ``FixtureRequest`` objects.)
:class:`~pytest.FixtureRequest` is now an abstract class which can't be instantiated directly.
A new concrete ``TopRequest`` subclass of ``FixtureRequest`` has been added for the ``request`` fixture in test functions,
as counterpart to the existing ``SubRequest`` subclass for the ``request`` fixture in fixture functions.
- `#11315 <https://github.com/pytest-dev/pytest/issues/11315>`_: The :fixture:`pytester` fixture now uses the :fixture:`monkeypatch` fixture to manage the current working directory.
If you use ``pytester`` in combination with :func:`monkeypatch.undo() <pytest.MonkeyPatch.undo>`, the CWD might get restored.
Use :func:`monkeypatch.context() <pytest.MonkeyPatch.context>` instead.
- `#11333 <https://github.com/pytest-dev/pytest/issues/11333>`_: Corrected the spelling of ``Config.ArgsSource.INVOCATION_DIR``.
The previous spelling ``INCOVATION_DIR`` remains as an alias.
- `#11638 <https://github.com/pytest-dev/pytest/issues/11638>`_: Fixed the selftests to pass correctly if ``FORCE_COLOR``, ``NO_COLOR`` or ``PY_COLORS`` is set in the calling environment.
pytest 7.4.4 (2023-12-31)
=========================
@@ -989,7 +438,7 @@ Improvements
- `#8508 <https://github.com/pytest-dev/pytest/issues/8508>`_: Introduce multiline display for warning matching via :py:func:`pytest.warns` and
enhance match comparison for :py:func:`pytest.ExceptionInfo.match` as returned by :py:func:`pytest.raises`.
enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`.
- `#8646 <https://github.com/pytest-dev/pytest/issues/8646>`_: Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing
@@ -998,7 +447,7 @@ Improvements
- `#9741 <https://github.com/pytest-dev/pytest/issues/9741>`_: On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML.
`tomli` is no longer a dependency on Python 3.11.
:mod:`tomli` is no longer a dependency on Python 3.11.
- `#9742 <https://github.com/pytest-dev/pytest/issues/9742>`_: Display assertion message without escaped newline characters with ``-vv``.
@@ -1033,7 +482,7 @@ Bug Fixes
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:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` instead.
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 <https://github.com/pytest-dev/pytest/issues/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``.
@@ -1286,7 +735,7 @@ Bug Fixes
- `#9355 <https://github.com/pytest-dev/pytest/issues/9355>`_: Fixed error message prints function decorators when using assert in Python 3.8 and above.
- `#9396 <https://github.com/pytest-dev/pytest/issues/9396>`_: Ensure `pytest.Config.inifile` is available during the :hook:`pytest_cmdline_main` hook (regression during ``7.0.0rc1``).
- `#9396 <https://github.com/pytest-dev/pytest/issues/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``).
@@ -1431,13 +880,13 @@ Deprecations
- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.
- `#8447 <https://github.com/pytest-dev/pytest/issues/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.
- `#8447 <https://github.com/pytest-dev/pytest/issues/8447>`_: Defining a custom pytest node type which is both an :class:`pytest.Item <Item>` and a :class:`pytest.Collector <Collector>` (e.g. :class:`pytest.File <File>`) now issues a warning.
It was never sanely supported and triggers hard to debug errors.
See :ref:`the deprecation note <diamond-inheritance-deprecated>` for full details.
- `#8592 <https://github.com/pytest-dev/pytest/issues/8592>`_: ``pytest_cmdline_preparse`` has been officially deprecated. It will be removed in a future release. Use :hook:`pytest_load_initial_conftests` instead.
- `#8592 <https://github.com/pytest-dev/pytest/issues/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 <cmdline-preparse-deprecated>` for full details.
@@ -1473,7 +922,7 @@ Features
- `#7132 <https://github.com/pytest-dev/pytest/issues/7132>`_: Added two environment variables :envvar:`PYTEST_THEME` and :envvar:`PYTEST_THEME_MODE` to let the users customize the pygments theme used.
- `#7259 <https://github.com/pytest-dev/pytest/issues/7259>`_: Added :meth:`cache.mkdir() <pytest.Cache.mkdir>`, which is similar to the existing ``cache.makedir()``,
- `#7259 <https://github.com/pytest-dev/pytest/issues/7259>`_: Added :meth:`cache.mkdir() <pytest.Cache.mkdir>`, which is similar to the existing :meth:`cache.makedir() <pytest.Cache.makedir>`,
but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``.
Added a ``paths`` type to :meth:`parser.addini() <pytest.Parser.addini>`,
@@ -1499,7 +948,7 @@ Features
- ``pytest.HookRecorder`` for the :class:`HookRecorder <pytest.HookRecorder>` type returned from :class:`~pytest.Pytester`.
- ``pytest.RecordedHookCall`` for the :class:`RecordedHookCall <pytest.HookRecorder>` type returned from :class:`~pytest.HookRecorder`.
- ``pytest.RunResult`` for the :class:`RunResult <pytest.RunResult>` type returned from :class:`~pytest.Pytester`.
- ``pytest.LineMatcher`` for the :class:`LineMatcher <pytest.LineMatcher>` type used in :class:`~pytest.RunResult` and others.
- ``pytest.LineMatcher`` for the :class:`LineMatcher <pytest.RunResult>` type used in :class:`~pytest.RunResult` and others.
- ``pytest.TestReport`` for the :class:`TestReport <pytest.TestReport>` type used in various hooks.
- ``pytest.CollectReport`` for the :class:`CollectReport <pytest.CollectReport>` type used in various hooks.
@@ -1532,7 +981,7 @@ Features
- `#8251 <https://github.com/pytest-dev/pytest/issues/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.
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
@@ -1564,7 +1013,7 @@ Features
See :ref:`plugin-stash` for details.
- `#8953 <https://github.com/pytest-dev/pytest/issues/8953>`_: :class:`~pytest.RunResult` method :meth:`~pytest.RunResult.assert_outcomes` now accepts a
- `#8953 <https://github.com/pytest-dev/pytest/issues/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.
@@ -1576,7 +1025,7 @@ Features
used.
- `#9113 <https://github.com/pytest-dev/pytest/issues/9113>`_: :class:`~pytest.RunResult` method :meth:`~pytest.RunResult.assert_outcomes` now accepts a
- `#9113 <https://github.com/pytest-dev/pytest/issues/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.
@@ -1589,7 +1038,7 @@ Improvements
- `#7480 <https://github.com/pytest-dev/pytest/issues/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:`~pytest.PytestDeprecationWarning` directly.
instead of :class:`PytestDeprecationWarning` directly.
See :ref:`backwards-compatibility` for more details.
@@ -1628,7 +1077,7 @@ Improvements
- `#8803 <https://github.com/pytest-dev/pytest/issues/8803>`_: It is now possible to add colors to custom log levels on cli log.
By using ``add_color_level`` from a :hook:`pytest_configure` hook, colors can be added::
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')
@@ -1693,7 +1142,7 @@ Bug Fixes
- `#8503 <https://github.com/pytest-dev/pytest/issues/8503>`_: :meth:`pytest.MonkeyPatch.syspath_prepend` no longer fails when
``setuptools`` is not installed.
It now only calls ``pkg_resources.fixup_namespace_packages`` if
It now only calls :func:`pkg_resources.fixup_namespace_packages` if
``pkg_resources`` was previously imported, because it is not needed otherwise.
@@ -1920,7 +1369,7 @@ Features
This is part of the movement to use :class:`pathlib.Path` objects internally, in order to remove the dependency to ``py`` in the future.
Internally, the old ``pytest.Testdir`` is now a thin wrapper around :class:`~pytest.Pytester`, preserving the old interface.
Internally, the old :class:`Testdir <_pytest.pytester.Testdir>` is now a thin wrapper around :class:`Pytester <_pytest.pytester.Pytester>`, preserving the old interface.
- :issue:`7695`: A new hook was added, `pytest_markeval_namespace` which should return a dictionary.
@@ -1958,7 +1407,7 @@ Features
Improvements
------------
- :issue:`1265`: Added an ``__str__`` implementation to the :class:`~pytest.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method.
- :issue:`1265`: Added an ``__str__`` implementation to the :class:`~pytest.pytester.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method.
- :issue:`2044`: Verbose mode now shows the reason that a test was skipped in the test's terminal line after the "SKIPPED", "XFAIL" or "XPASS".
@@ -2022,7 +1471,7 @@ Bug Fixes
- :issue:`7911`: Directories created by by :fixture:`tmp_path` and :fixture:`tmpdir` are now considered stale after 3 days without modification (previous value was 3 hours) to avoid deleting directories still in use in long running test suites.
- :issue:`7913`: Fixed a crash or hang in :meth:`pytester.spawn <pytest.Pytester.spawn>` when the :mod:`readline` module is involved.
- :issue:`7913`: Fixed a crash or hang in :meth:`pytester.spawn <_pytest.pytester.Pytester.spawn>` when the :mod:`readline` module is involved.
- :issue:`7951`: Fixed handling of recursive symlinks when collecting tests.
@@ -2139,7 +1588,7 @@ Deprecations
if you use this and want a replacement.
- :issue:`7255`: The ``pytest_warning_captured`` hook is deprecated in favor
- :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.
@@ -2167,8 +1616,8 @@ Improvements
- :issue:`7572`: When a plugin listed in ``required_plugins`` is missing or an unknown config key is used with ``--strict-config``, a simple error message is now shown instead of a stacktrace.
- :issue:`7685`: Added two new attributes :attr:`rootpath <pytest.Config.rootpath>` and :attr:`inipath <pytest.Config.inipath>` to :class:`~pytest.Config`.
These attributes are :class:`pathlib.Path` versions of the existing ``rootdir`` and ``inifile`` attributes,
- :issue:`7685`: Added two new attributes :attr:`rootpath <_pytest.config.Config.rootpath>` and :attr:`inipath <_pytest.config.Config.inipath>` to :class:`Config <_pytest.config.Config>`.
These attributes are :class:`pathlib.Path` versions of the existing :attr:`rootdir <_pytest.config.Config.rootdir>` and :attr:`inifile <_pytest.config.Config.inifile>` attributes,
and should be preferred over them when possible.
@@ -2239,7 +1688,7 @@ Trivial/Internal Changes
- :issue:`7587`: The dependency on the ``more-itertools`` package has been removed.
- :issue:`7631`: The result type of :meth:`capfd.readouterr() <pytest.CaptureFixture.readouterr>` (and similar) is no longer a namedtuple,
- :issue:`7631`: The result type of :meth:`capfd.readouterr() <_pytest.capture.CaptureFixture.readouterr>` (and similar) is no longer a namedtuple,
but should behave like one in all respects. This was done for technical reasons.
@@ -2617,10 +2066,10 @@ Improvements
- :issue:`7128`: `pytest --version` now displays just the pytest version, while `pytest --version --version` displays more verbose information including plugins. This is more consistent with how other tools show `--version`.
- :issue:`7133`: :meth:`caplog.set_level() <pytest.LogCaptureFixture.set_level>` will now override any :confval:`log_level` set via the CLI or configuration file.
- :issue:`7133`: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` will now override any :confval:`log_level` set via the CLI or configuration file.
- :issue:`7159`: :meth:`caplog.set_level() <pytest.LogCaptureFixture.set_level>` and :meth:`caplog.at_level() <pytest.LogCaptureFixture.at_level>` no longer affect
- :issue:`7159`: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` and :meth:`caplog.at_level() <_pytest.logging.LogCaptureFixture.at_level>` no longer affect
the level of logs that are shown in the *Captured log report* report section.
@@ -2715,7 +2164,7 @@ Bug Fixes
parameter when Python is called with the ``-bb`` flag.
- :issue:`7143`: Fix :meth:`pytest.File.from_parent <_pytest.nodes.Node.from_parent>` so it forwards extra keyword arguments to the constructor.
- :issue:`7143`: Fix :meth:`pytest.File.from_parent` so it forwards extra keyword arguments to the constructor.
- :issue:`7145`: Classes with broken ``__getattribute__`` methods are displayed correctly during failures.
@@ -2966,7 +2415,7 @@ Improvements
- :issue:`6384`: Make `--showlocals` work also with `--tb=short`.
- :issue:`6653`: Add support for matching lines consecutively with :class:`~pytest.LineMatcher`'s :func:`~pytest.LineMatcher.fnmatch_lines` and :func:`~pytest.LineMatcher.re_match_lines`.
- :issue:`6653`: Add support for matching lines consecutively with :attr:`LineMatcher <_pytest.pytester.LineMatcher>`'s :func:`~_pytest.pytester.LineMatcher.fnmatch_lines` and :func:`~_pytest.pytester.LineMatcher.re_match_lines`.
- :issue:`6658`: Code is now highlighted in tracebacks when ``pygments`` is installed.
@@ -3034,7 +2483,7 @@ Bug Fixes
- :issue:`6597`: Fix node ids which contain a parametrized empty-string variable.
- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's ``testdir.runpytest`` etc.
- :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 :hook:`pytest_sessionfinish` hook. This includes quitting from a debugger.
@@ -3100,7 +2549,7 @@ Bug Fixes
``multiprocessing`` module.
- :issue:`6436`: :class:`~pytest.FixtureDef` objects now properly register their finalizers with autouse and
- :issue:`6436`: :class:`FixtureDef <_pytest.fixtures.FixtureDef>` objects now properly register their finalizers with autouse and
parameterized fixtures that execute before them in the fixture stack so they are torn
down at the right times, and in the right order.
@@ -3156,7 +2605,7 @@ Improvements
Bug Fixes
---------
- :issue:`5914`: pytester: fix :py:func:`~pytest.LineMatcher.no_fnmatch_line` when used after positive matching.
- :issue:`5914`: pytester: fix :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` when used after positive matching.
- :issue:`6082`: Fix line detection for doctest samples inside :py:class:`python:property` docstrings, as a workaround to :bpo:`17446`.
@@ -3220,8 +2669,8 @@ Features
rather than implicitly.
- :issue:`5914`: :fixture:`testdir` learned two new functions, :py:func:`~pytest.LineMatcher.no_fnmatch_line` and
:py:func:`~pytest.LineMatcher.no_re_match_line`.
- :issue:`5914`: :fixture:`testdir` learned two new functions, :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` and
:py:func:`~_pytest.pytester.LineMatcher.no_re_match_line`.
The functions are used to ensure the captured text *does not* match the given
pattern.
@@ -7073,7 +6522,7 @@ Changes
* fix :issue:`2013`: turn RecordedWarning into ``namedtuple``,
to give it a comprehensible repr while preventing unwarranted modification.
* fix :issue:`2208`: ensure an iteration limit for ``_pytest.compat.get_real_func``.
* fix :issue:`2208`: ensure an iteration limit for _pytest.compat.get_real_func.
Thanks :user:`RonnyPfannschmidt` for the report and PR.
* Hooks are now verified after collection is complete, rather than right after loading installed plugins. This

View File

@@ -15,15 +15,16 @@
#
# The full version, including alpha/beta/rc tags.
# The short X.Y version.
import ast
import os
import shutil
import sys
from textwrap import dedent
from typing import List
from typing import TYPE_CHECKING
from _pytest import __version__ as version
if TYPE_CHECKING:
import sphinx.application
@@ -170,49 +171,6 @@ extlinks = {
}
nitpicky = True
nitpick_ignore = [
# TODO (fix in pluggy?)
("py:class", "HookCaller"),
("py:class", "HookspecMarker"),
("py:exc", "PluginValidationError"),
# Might want to expose/TODO (https://github.com/pytest-dev/pytest/issues/7469)
("py:class", "ExceptionRepr"),
("py:class", "Exit"),
("py:class", "SubRequest"),
("py:class", "SubRequest"),
("py:class", "TerminalReporter"),
("py:class", "_pytest._code.code.TerminalRepr"),
("py:class", "_pytest.fixtures.FixtureFunctionMarker"),
("py:class", "_pytest.logging.LogCaptureHandler"),
("py:class", "_pytest.mark.structures.ParameterSet"),
# Intentionally undocumented/private
("py:class", "_pytest._code.code.Traceback"),
("py:class", "_pytest._py.path.LocalPath"),
("py:class", "_pytest.capture.CaptureResult"),
("py:class", "_pytest.compat.NotSetType"),
("py:class", "_pytest.python.PyCollector"),
("py:class", "_pytest.python.PyobjMixin"),
("py:class", "_pytest.python_api.RaisesContext"),
("py:class", "_pytest.recwarn.WarningsChecker"),
("py:class", "_pytest.reports.BaseReport"),
# Undocumented third parties
("py:class", "_tracing.TagTracerSub"),
("py:class", "warnings.WarningMessage"),
# Undocumented type aliases
("py:class", "_PluggyPlugin"),
# TypeVars
("py:class", "_pytest._code.code.E"),
("py:class", "_pytest.fixtures.FixtureFunction"),
("py:class", "_pytest.nodes._NodeType"),
("py:class", "_pytest.python_api.E"),
("py:class", "_pytest.recwarn.T"),
("py:class", "_pytest.runner.TResult"),
("py:obj", "_pytest.fixtures.FixtureValue"),
("py:obj", "_pytest.stash.T"),
]
# -- Options for HTML output ---------------------------------------------------
sys.path.append(os.path.abspath("_themes"))
@@ -315,6 +273,9 @@ html_show_sourcelink = False
# Output file base name for HTML help builder.
htmlhelp_basename = "pytestdoc"
# The base URL which points to the root of the HTML documentation. It is used
# to indicate the location of document using the canonical link relation (#12363).
html_baseurl = "https://docs.pytest.org/en/stable/"
# -- Options for LaTeX output --------------------------------------------------
@@ -441,9 +402,8 @@ intersphinx_mapping = {
def configure_logging(app: "sphinx.application.Sphinx") -> None:
"""Configure Sphinx's WarningHandler to handle (expected) missing include."""
import logging
import sphinx.util.logging
import logging
class WarnLogFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
@@ -494,6 +454,25 @@ def setup(app: "sphinx.application.Sphinx") -> None:
configure_logging(app)
# Make Sphinx mark classes with "final" when decorated with @final.
# We need this because we import final from pytest._compat, not from
# typing (for Python < 3.8 compat), so Sphinx doesn't detect it.
# To keep things simple we accept any `@final` decorator.
# Ref: https://github.com/pytest-dev/pytest/pull/7780
import sphinx.pycode.ast
import sphinx.pycode.parser
original_is_final = sphinx.pycode.parser.VariableCommentPicker.is_final
def patched_is_final(self, decorators: List[ast.expr]) -> bool:
if original_is_final(self, decorators):
return True
return any(
sphinx.pycode.ast.unparse(decorator) == "final" for decorator in decorators
)
sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final
# legacypath.py monkey-patches pytest.Testdir in. Import the file so
# that autodoc can discover references to it.
import _pytest.legacypath # noqa: F401

View File

@@ -44,6 +44,7 @@ How-to guides
how-to/existingtestsuite
how-to/unittest
how-to/nose
how-to/xunit_setup
how-to/bash-completion

View File

@@ -19,6 +19,170 @@ Below is a complete list of all pytest features which are considered deprecated.
:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
.. _nose-deprecation:
Support for tests written for nose
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.2
Support for running tests written for `nose <https://nose.readthedocs.io/en/latest/>`__ 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 <with-setup-nose>`_ 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:
The ``pytest.Instance`` collector
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 7.0
The ``pytest.Instance`` collector type has been removed.
Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`.
Now :class:`~pytest.Class` collects the test methods directly.
Most plugins which reference ``Instance`` do so in order to ignore or skip it,
using a check such as ``if isinstance(node, Instance): return``.
Such plugins should simply remove consideration of ``Instance`` on pytest>=7.
However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``,
and importing it emits a deprecation warning. This will be removed in pytest 8.
.. _node-ctor-fspath-deprecation:
``fspath`` argument for Node constructors replaced with ``pathlib.Path``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
is now deprecated.
Plugins which construct nodes should pass the ``path`` argument, of type
:class:`pathlib.Path`, instead of the ``fspath`` argument.
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 <uncooperative-constructors-deprecated>` 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
hooks, :ref:`outlined below <legacy-path-hooks-deprecated>` (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).
Due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo`
which still is expected to return a ``py.path.local`` object, nodes still have
both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes,
no matter what argument was used in the constructor. We expect to deprecate the
``fspath`` attribute in a future release.
.. _legacy-path-hooks-deprecated:
Configuring hook specs/impls using markers
@@ -33,11 +197,13 @@ have been available since years and should be used instead.
.. code-block:: python
@pytest.mark.tryfirst
def pytest_runtest_call(): ...
def pytest_runtest_call():
...
# or
def pytest_runtest_call(): ...
def pytest_runtest_call():
...
pytest_runtest_call.tryfirst = True
@@ -47,7 +213,8 @@ should be changed to:
.. code-block:: python
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_call(): ...
def pytest_runtest_call():
...
Changed ``hookimpl`` attributes:
@@ -62,6 +229,31 @@ Changed ``hookwrapper`` attributes:
* ``historic``
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` 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.
.. note::
The name of the :class:`~_pytest.nodes.Node` arguments and attributes,
:ref:`outlined above <node-ctor-fspath-deprecation>` (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).
Directly constructing internal classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -81,6 +273,62 @@ Directly constructing the following classes is now deprecated:
These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8.
.. _cmdline-preparse-deprecated:
Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
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.
.. code-block:: python
def test_fail_example():
# old
pytest.fail(msg="foo")
# new
pytest.fail(reason="bar")
def test_skip_example():
# old
pytest.skip(msg="foo")
# new
pytest.skip(reason="bar")
def test_exit_example():
# old
pytest.exit(msg="foo")
# new
pytest.exit(reason="bar")
Implementing the ``pytest_cmdline_preparse`` hook
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
Implementing the :hook:`pytest_cmdline_preparse` hook has been officially deprecated.
Implement the :hook:`pytest_load_initial_conftests` hook instead.
.. code-block:: python
def pytest_cmdline_preparse(config: Config, args: List[str]) -> None:
...
# becomes:
def pytest_load_initial_conftests(
early_config: Config, parser: Parser, args: List[str]
) -> None:
...
.. _diamond-inheritance-deprecated:
Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item`
@@ -88,7 +336,7 @@ Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item`
.. deprecated:: 7.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.
Defining a custom pytest node type which is both an :class:`pytest.Item <Item>` and a :class:`pytest.Collector <Collector>` (e.g. :class:`pytest.File <File>`) now issues a warning.
It was never sanely supported and triggers hard to debug errors.
Some plugins providing linting/code analysis have been using this as a hack.
@@ -100,8 +348,8 @@ Instead, a separate collector node should be used, which collects the item. See
.. _uncooperative-constructors-deprecated:
Constructors of custom :class:`~_pytest.nodes.Node` subclasses should take ``**kwargs``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Constructors of custom :class:`pytest.Node` subclasses should take ``**kwargs``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
@@ -132,22 +380,28 @@ conflicts (such as :class:`pytest.File` now taking ``path`` instead of
``fspath``, as :ref:`outlined above <node-ctor-fspath-deprecation>`), a
deprecation warning is now raised.
Applying a mark to a fixture function
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Backward compatibilities in ``Parser.addoption``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.4
.. deprecated:: 2.4
Applying a mark to a fixture function never had any effect, but it is a common user error.
Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
scheduled for removal in pytest 8 (deprecated since pytest 2.4.0):
.. code-block:: python
- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead.
- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.
@pytest.mark.usefixtures("clean_database")
@pytest.fixture
def user() -> User: ...
Users expected in this case that the ``usefixtures`` mark would have its intended effect of using the ``clean_database`` fixture when ``user`` was invoked, when in fact it has no effect at all.
Using ``pytest.warns(None)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions.
.. deprecated:: 7.0
:func:`pytest.warns(None) <pytest.warns>` 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)``.
See :ref:`warns use cases` for examples.
Returning non-None value in test functions
@@ -190,6 +444,19 @@ The proper fix is to change the `return` to an `assert`:
assert foo(a, b) == result
The ``--strict`` command-line option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 6.2
The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which
better conveys what the option does.
We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing
flag for all strictness related options (``--strict-markers`` and ``--strict-config``
at the moment, more might be introduced in the future).
The ``yield_fixture`` function/decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -200,415 +467,12 @@ The ``yield_fixture`` function/decorator
It has been so for a very long time, so can be search/replaced safely.
Removed Features and Breaking Changes
-------------------------------------
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.
Some breaking changes which could not be deprecated are also listed.
.. _node-ctor-fspath-deprecation:
``fspath`` argument for Node constructors replaced with ``pathlib.Path``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
is now deprecated.
Plugins which construct nodes should pass the ``path`` argument, of type
:class:`pathlib.Path`, instead of the ``fspath`` argument.
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 <uncooperative-constructors-deprecated>` 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
hooks, :ref:`outlined below <legacy-path-hooks-deprecated>` (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).
Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`
which still is expected to return a ``py.path.local`` object, nodes still have
both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes,
no matter what argument was used in the constructor. We expect to deprecate the
``fspath`` attribute in a future release.
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
.. versionremoved:: 8.0
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` 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.
.. note::
The name of the :class:`~_pytest.nodes.Node` arguments and attributes,
:ref:`outlined above <node-ctor-fspath-deprecation>` (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).
.. _nose-deprecation:
Support for tests written for nose
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.2
.. versionremoved:: 8.0
Support for running tests written for `nose <https://nose.readthedocs.io/en/latest/>`__ 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 <with-setup-nose>`_ 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
The ``compat_co_firstlineno`` attribute
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Nose inspects this attribute on function objects to allow overriding the function's inferred line number.
Pytest no longer respects this attribute.
Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
.. versionremoved:: 8.0
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.
.. code-block:: python
def test_fail_example():
# old
pytest.fail(msg="foo")
# new
pytest.fail(reason="bar")
def test_skip_example():
# old
pytest.skip(msg="foo")
# new
pytest.skip(reason="bar")
def test_exit_example():
# old
pytest.exit(msg="foo")
# new
pytest.exit(reason="bar")
.. _instance-collector-deprecation:
The ``pytest.Instance`` collector
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 7.0
The ``pytest.Instance`` collector type has been removed.
Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`.
Now :class:`~pytest.Class` collects the test methods directly.
Most plugins which reference ``Instance`` do so in order to ignore or skip it,
using a check such as ``if isinstance(node, Instance): return``.
Such plugins should simply remove consideration of ``Instance`` on pytest>=7.
However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``,
and importing it emits a deprecation warning. This was removed in pytest 8.
Using ``pytest.warns(None)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
.. versionremoved:: 8.0
:func:`pytest.warns(None) <pytest.warns>` 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)``.
See :ref:`warns use cases` for examples.
Backward compatibilities in ``Parser.addoption``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 2.4
.. versionremoved:: 8.0
Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
removed 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.
The ``--strict`` command-line option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 6.2
.. versionremoved:: 8.0
The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which
better conveys what the option does.
We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing
flag for all strictness related options (``--strict-markers`` and ``--strict-config``
at the moment, more might be introduced in the future).
.. _cmdline-preparse-deprecated:
Implementing the ``pytest_cmdline_preparse`` hook
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
.. versionremoved:: 8.0
Implementing the ``pytest_cmdline_preparse`` hook has been officially deprecated.
Implement the :hook:`pytest_load_initial_conftests` hook instead.
.. code-block:: python
def pytest_cmdline_preparse(config: Config, args: List[str]) -> None: ...
# becomes:
def pytest_load_initial_conftests(
early_config: Config, parser: Parser, args: List[str]
) -> None: ...
Collection changes in pytest 8
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass.
This is analogous to the existing :class:`pytest.File` for file nodes.
Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`.
A ``Package`` represents a filesystem directory which is a Python package,
i.e. contains an ``__init__.py`` file.
:class:`pytest.Package` now only collects files in its own directory; previously it collected recursively.
Sub-directories are collected as sub-collector nodes, thus creating a collection tree which mirrors the filesystem hierarchy.
:attr:`session.name <pytest.Session.name>` is now ``""``; previously it was the rootdir directory name.
This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`.
Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`.
This node represents a filesystem directory, which is not a :class:`pytest.Package`,
i.e. does not contain an ``__init__.py`` file.
Similarly to ``Package``, it only collects the files in its own directory,
while collecting sub-directories as sub-collector nodes.
Files and directories are now collected in alphabetical order jointly, unless changed by a plugin.
Previously, files were collected before directories.
The collection tree now contains directories/packages up to the :ref:`rootdir <rootdir>`,
for initial arguments that are found within the rootdir.
For files outside the rootdir, only the immediate directory/package is collected --
note however that collecting from outside the rootdir is discouraged.
As an example, given the following filesystem tree::
myroot/
pytest.ini
top/
├── aaa
│ └── test_aaa.py
├── test_a.py
├── test_b
│ ├── __init__.py
│ └── test_b.py
├── test_c.py
└── zzz
├── __init__.py
└── test_zzz.py
the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity,
is now the following::
<Session>
<Dir myroot>
<Dir top>
<Dir aaa>
<Module test_aaa.py>
<Function test_it>
<Module test_a.py>
<Function test_it>
<Package test_b>
<Module test_b.py>
<Function test_it>
<Module test_c.py>
<Function test_it>
<Package zzz>
<Module test_zzz.py>
<Function test_it>
Previously, it was::
<Session>
<Module top/test_a.py>
<Function test_it>
<Module top/test_c.py>
<Function test_it>
<Module top/aaa/test_aaa.py>
<Function test_it>
<Package test_b>
<Module test_b.py>
<Function test_it>
<Package zzz>
<Module test_zzz.py>
<Function test_it>
Code/plugins which rely on a specific shape of the collection tree might need to update.
:class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionchanged:: 8.0
The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file.
Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module),
the module being the `__init__.py` file.
This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details).
The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file.
Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist,
if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files).
Collecting ``__init__.py`` files no longer collects package
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 8.0
Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
(unless :confval:`python_files` was changed to allow `__init__.py` file).
To collect the entire package, specify just the directory: `pytest pkg`.
The ``pytest.collect`` module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -909,7 +773,8 @@ Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated
(50, 500),
],
)
def test_foo(a, b): ...
def test_foo(a, b):
...
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
call.
@@ -932,7 +797,8 @@ To update the code, use ``pytest.param``:
(50, 500),
],
)
def test_foo(a, b): ...
def test_foo(a, b):
...
.. _pytest_funcarg__ prefix deprecated:
@@ -1083,13 +949,15 @@ This is just a matter of renaming the fixture as the API is the same:
.. code-block:: python
def test_foo(record_xml_property): ...
def test_foo(record_xml_property):
...
Change to:
.. code-block:: python
def test_foo(record_property): ...
def test_foo(record_property):
...
.. _passing command-line string to pytest.main deprecated:
@@ -1251,7 +1119,8 @@ Example of usage:
.. code-block:: python
class MySymbol: ...
class MySymbol:
...
def pytest_namespace():

View File

@@ -172,7 +172,7 @@ class TestRaises:
raise ValueError("demo error")
def test_tupleerror(self):
a, b = [1] # noqa: F841
a, b = [1] # NOQA
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
items = [1, 2, 3]
@@ -180,7 +180,7 @@ class TestRaises:
a, b = items.pop()
def test_some_error(self):
if namenotexi: # noqa: F821
if namenotexi: # NOQA
pass
def func1(self):

View File

@@ -2,7 +2,6 @@ import os.path
import pytest
mydir = os.path.dirname(__file__)

View File

@@ -1,7 +1,6 @@
import os.path
import shutil
failure_demo = os.path.join(os.path.dirname(__file__), "failure_demo.py")
pytest_plugins = ("pytester",)

View File

@@ -1 +1 @@
collect_ignore = ["nonpython", "customdirectory"]
collect_ignore = ["nonpython"]

View File

@@ -1,77 +0,0 @@
.. _`custom directory collectors`:
Using a custom directory collector
====================================================
By default, pytest collects directories using :class:`pytest.Package`, for directories with ``__init__.py`` files,
and :class:`pytest.Dir` for other directories.
If you want to customize how a directory is collected, you can write your own :class:`pytest.Directory` collector,
and use :hook:`pytest_collect_directory` to hook it up.
.. _`directory manifest plugin`:
A basic example for a directory manifest file
--------------------------------------------------------------
Suppose you want to customize how collection is done on a per-directory basis.
Here is an example ``conftest.py`` plugin that allows directories to contain a ``manifest.json`` file,
which defines how the collection should be done for the directory.
In this example, only a simple list of files is supported,
however you can imagine adding other keys, such as exclusions and globs.
.. include:: customdirectory/conftest.py
:literal:
You can create a ``manifest.json`` file and some test files:
.. include:: customdirectory/tests/manifest.json
:literal:
.. include:: customdirectory/tests/test_first.py
:literal:
.. include:: customdirectory/tests/test_second.py
:literal:
.. include:: customdirectory/tests/test_third.py
:literal:
An you can now execute the test specification:
.. code-block:: pytest
customdirectory $ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project/customdirectory
configfile: pytest.ini
collected 2 items
tests/test_first.py . [ 50%]
tests/test_second.py . [100%]
============================ 2 passed in 0.12s =============================
.. regendoc:wipe
Notice how ``test_three.py`` was not executed, because it is not listed in the manifest.
You can verify that your custom collector appears in the collection tree:
.. code-block:: pytest
customdirectory $ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project/customdirectory
configfile: pytest.ini
collected 2 items
<Dir customdirectory>
<ManifestDirectory tests>
<Module test_first.py>
<Function test_1>
<Module test_second.py>
<Function test_2>
======================== 2 tests collected in 0.12s ========================

View File

@@ -1,28 +0,0 @@
# content of conftest.py
import json
import pytest
class ManifestDirectory(pytest.Directory):
def collect(self):
# The standard pytest behavior is to loop over all `test_*.py` files and
# call `pytest_collect_file` on each file. This collector instead reads
# the `manifest.json` file and only calls `pytest_collect_file` for the
# files defined there.
manifest_path = self.path / "manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
ihook = self.ihook
for file in manifest["files"]:
yield from ihook.pytest_collect_file(
file_path=self.path / file, parent=self
)
@pytest.hookimpl
def pytest_collect_directory(path, parent):
# Use our custom collector for directories containing a `mainfest.json` file.
if path.joinpath("manifest.json").is_file():
return ManifestDirectory.from_parent(parent=parent, path=path)
# Otherwise fallback to the standard behavior.
return None

View File

@@ -1,6 +0,0 @@
{
"files": [
"test_first.py",
"test_second.py"
]
}

View File

@@ -1,3 +0,0 @@
# content of test_first.py
def test_1():
pass

View File

@@ -1,3 +0,0 @@
# content of test_second.py
def test_2():
pass

View File

@@ -1,3 +0,0 @@
# content of test_third.py
def test_3():
pass

View File

@@ -18,6 +18,7 @@ For basic examples, see
- :ref:`Fixtures <fixtures>` for basic fixture/setup examples
- :ref:`parametrize` for basic test function parametrization
- :ref:`unittest` for basic unittest integration
- :ref:`noseintegration` for basic nosetests integration
The following examples aim at various use cases you might encounter.
@@ -31,4 +32,3 @@ The following examples aim at various use cases you might encounter.
special
pythoncollection
nonpython
customdirectory

View File

@@ -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-8.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-8.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-8.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-8.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-8.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-8.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-8.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-8.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
@@ -397,7 +397,7 @@ the test needs:
$ pytest -E stage2
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -411,7 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -604,7 +604,7 @@ 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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 4 items
@@ -620,7 +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-8.x.y, pluggy-1.x.y
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
@@ -683,7 +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-8.x.y, pluggy-1.x.y
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
@@ -709,7 +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-8.x.y, pluggy-1.x.y
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

View File

@@ -1,14 +1,14 @@
"""Module containing a parametrized tests testing cross-python serialization
via the pickle module."""
"""
module containing a parametrized tests testing cross-python
serialization via the pickle module.
"""
import shutil
import subprocess
import textwrap
import pytest
pythonlist = ["python3.9", "python3.10", "python3.11"]
pythonlist = ["python3.5", "python3.6", "python3.7"]
@pytest.fixture(params=pythonlist)
@@ -33,33 +33,37 @@ class Python:
dumpfile = self.picklefile.with_name("dump.py")
dumpfile.write_text(
textwrap.dedent(
rf"""
r"""
import pickle
f = open({str(self.picklefile)!r}, 'wb')
s = pickle.dump({obj!r}, f, protocol=2)
f = open({!r}, 'wb')
s = pickle.dump({!r}, f, protocol=2)
f.close()
"""
""".format(
str(self.picklefile), obj
)
)
)
subprocess.run((self.pythonpath, str(dumpfile)), check=True)
subprocess.check_call((self.pythonpath, str(dumpfile)))
def load_and_is_true(self, expression):
loadfile = self.picklefile.with_name("load.py")
loadfile.write_text(
textwrap.dedent(
rf"""
r"""
import pickle
f = open({str(self.picklefile)!r}, 'rb')
f = open({!r}, 'rb')
obj = pickle.load(f)
f.close()
res = eval({expression!r})
res = eval({!r})
if not res:
raise SystemExit(1)
"""
""".format(
str(self.picklefile), expression
)
)
)
print(loadfile)
subprocess.run((self.pythonpath, str(loadfile)), check=True)
subprocess.check_call((self.pythonpath, str(loadfile)))
@pytest.mark.parametrize("obj", [42, {}, {1: 3}])

View File

@@ -28,7 +28,7 @@ now execute the test specification:
nonpython $ pytest test_simple.yaml
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project/nonpython
collected 2 items
@@ -64,7 +64,7 @@ consulted when reporting in ``verbose`` mode:
nonpython $ pytest -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.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
@@ -90,7 +90,7 @@ interesting to just look at the collection tree:
nonpython $ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project/nonpython
collected 2 items

View File

@@ -4,6 +4,8 @@
Parametrizing tests
=================================================
.. currentmodule:: _pytest.python
``pytest`` allows to easily parametrize test functions.
For basic docs, see :ref:`parametrize-basics`.
@@ -158,20 +160,19 @@ 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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 8 items
<Dir parametrize.rst-195>
<Module test_time.py>
<Function test_timedistance_v0[a0-b0-expected0]>
<Function test_timedistance_v0[a1-b1-expected1]>
<Function test_timedistance_v1[forward]>
<Function test_timedistance_v1[backward]>
<Function test_timedistance_v2[20011212-20011211-expected0]>
<Function test_timedistance_v2[20011211-20011212-expected1]>
<Function test_timedistance_v3[forward]>
<Function test_timedistance_v3[backward]>
<Module test_time.py>
<Function test_timedistance_v0[a0-b0-expected0]>
<Function test_timedistance_v0[a1-b1-expected1]>
<Function test_timedistance_v1[forward]>
<Function test_timedistance_v1[backward]>
<Function test_timedistance_v2[20011212-20011211-expected0]>
<Function test_timedistance_v2[20011211-20011212-expected1]>
<Function test_timedistance_v3[forward]>
<Function test_timedistance_v3[backward]>
======================== 8 tests collected in 0.12s ========================
@@ -184,7 +185,7 @@ A quick port of "testscenarios"
Here is a quick port to run tests configured with :pypi:`testscenarios`,
an add-on from Robert Collins for the standard unittest framework. We
only have to work a bit to construct the correct arguments for pytest's
:py:func:`Metafunc.parametrize <pytest.Metafunc.parametrize>`:
:py:func:`Metafunc.parametrize`:
.. code-block:: python
@@ -221,7 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 4 items
@@ -235,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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 4 items
<Dir parametrize.rst-195>
<Module test_scenarios.py>
<Class TestSampleWithScenarios>
<Function test_demo1[basic]>
<Function test_demo2[basic]>
<Function test_demo1[advanced]>
<Function test_demo2[advanced]>
<Module test_scenarios.py>
<Class TestSampleWithScenarios>
<Function test_demo1[basic]>
<Function test_demo2[basic]>
<Function test_demo1[advanced]>
<Function test_demo2[advanced]>
======================== 4 tests collected in 0.12s ========================
@@ -314,14 +314,13 @@ 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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
<Dir parametrize.rst-195>
<Module test_backends.py>
<Function test_db_initialized[d1]>
<Function test_db_initialized[d2]>
<Module test_backends.py>
<Function test_db_initialized[d1]>
<Function test_db_initialized[d2]>
======================== 2 tests collected in 0.12s ========================
@@ -413,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-8.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
@@ -484,8 +483,8 @@ argument sets to use for each test function. Let's run it:
FAILED test_parametrize.py::TestClass::test_equals[1-2] - assert 1 == 2
1 failed, 2 passed in 0.12s
Parametrization with multiple fixtures
--------------------------------------
Indirect parametrization with multiple fixtures
--------------------------------------------------------------
Here is a stripped down real-life example of using parametrized
testing for testing serialization of objects between different python
@@ -503,14 +502,15 @@ Running it results in some skips if we don't have all the python interpreters in
.. code-block:: pytest
. $ pytest -rs -q multipython.py
ssssssssssss...ssssssssssss [100%]
sssssssssssssssssssssssssss [100%]
========================= short test summary info ==========================
SKIPPED [12] multipython.py:65: 'python3.9' not found
SKIPPED [12] multipython.py:65: 'python3.11' not found
3 passed, 24 skipped in 0.12s
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
Parametrization of optional implementations/imports
---------------------------------------------------
Indirect parametrization of optional implementations/imports
--------------------------------------------------------------------
If you want to compare the outcomes of several implementations of a given
API, you can write test functions that receive the already imported implementations
@@ -567,7 +567,7 @@ If you run this with reporting for skips enabled:
$ pytest -rs test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
@@ -628,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-8.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
@@ -657,16 +657,13 @@ 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.
``contextlib.nullcontext`` can be used to test cases that are not expected to
raise exceptions but that should result in some value. The value is given as the
``enter_result`` parameter, which will be available as the ``with`` statements
target (``e`` in the example below).
It may be helpful to use ``nullcontext`` as a complement to ``raises``.
For example:
.. code-block:: python
from contextlib import nullcontext
from contextlib import nullcontext as does_not_raise
import pytest
@@ -674,17 +671,16 @@ For example:
@pytest.mark.parametrize(
"example_input,expectation",
[
(3, nullcontext(2)),
(2, nullcontext(3)),
(1, nullcontext(6)),
(3, does_not_raise()),
(2, does_not_raise()),
(1, does_not_raise()),
(0, pytest.raises(ZeroDivisionError)),
],
)
def test_division(example_input, expectation):
"""Test how much I know division."""
with expectation as e:
assert (6 / example_input) == e
with expectation:
assert (6 / example_input) is not None
In the example above, the first three test cases should run without any
exceptions, while the fourth should raise a``ZeroDivisionError`` exception,
which is expected by pytest.
In the example above, the first three test cases should run unexceptionally,
while the fourth should raise ``ZeroDivisionError``.

View File

@@ -147,16 +147,15 @@ The test collection would look like this:
$ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
configfile: pytest.ini
collected 2 items
<Dir pythoncollection.rst-196>
<Module check_myapp.py>
<Class CheckMyApp>
<Function simple_check>
<Function complex_check>
<Module check_myapp.py>
<Class CheckMyApp>
<Function simple_check>
<Function complex_check>
======================== 2 tests collected in 0.12s ========================
@@ -210,18 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
configfile: pytest.ini
collected 3 items
<Dir pythoncollection.rst-196>
<Dir CWD>
<Module pythoncollection.py>
<Function test_function>
<Class TestClass>
<Function test_method>
<Function test_anothermethod>
<Module CWD/pythoncollection.py>
<Function test_function>
<Class TestClass>
<Function test_method>
<Function test_anothermethod>
======================== 3 tests collected in 0.12s ========================
@@ -294,7 +291,7 @@ file will be left out:
$ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
configfile: pytest.ini
collected 0 items

View File

@@ -9,7 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project/assertion
collected 44 items
@@ -80,7 +80,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_eq_text(self):
> assert "spam" == "eggs"
E AssertionError: assert 'spam' == 'eggs'
E
E - eggs
E + spam
@@ -92,7 +91,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_eq_similar_text(self):
> assert "foo 1 bar" == "foo 2 bar"
E AssertionError: assert 'foo 1 bar' == 'foo 2 bar'
E
E - foo 2 bar
E ? ^
E + foo 1 bar
@@ -106,7 +104,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_eq_multiline_text(self):
> assert "foo\nspam\nbar" == "foo\neggs\nbar"
E AssertionError: assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
E
E foo
E - eggs
E + spam
@@ -122,7 +119,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
b = "1" * 100 + "b" + "2" * 100
> assert a == b
E AssertionError: assert '111111111111...2222222222222' == '111111111111...2222222222222'
E
E Skipping 90 identical leading characters in diff, use -v to show
E Skipping 91 identical trailing characters in diff, use -v to show
E - 1111111111b222222222
@@ -140,15 +136,15 @@ Here is a nice run of several failures and how ``pytest`` presents things:
b = "1\n" * 100 + "b" + "2\n" * 100
> assert a == b
E AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n...n2\n2\n2\n2\n'
E
E Skipping 190 identical leading characters in diff, use -v to show
E Skipping 191 identical trailing characters in diff, use -v to show
E 1
E 1
E 1
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 _________________
@@ -158,7 +154,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_eq_list(self):
> assert [0, 1, 2] == [0, 1, 3]
E assert [0, 1, 2] == [0, 1, 3]
E
E At index 2 diff: 2 != 3
E Use -v to get more diff
@@ -172,7 +167,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
b = [0] * 100 + [2] + [3] * 100
> assert a == b
E assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...]
E
E At index 100 diff: 1 != 2
E Use -v to get more diff
@@ -184,7 +178,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_eq_dict(self):
> assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0}
E AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
E
E Omitting 1 identical items, use -vv to show
E Differing items:
E {'b': 1} != {'b': 2}
@@ -202,7 +195,6 @@ 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 assert {0, 10, 11, 12} == {0, 20, 21}
E
E Extra items in the left set:
E 10
E 11
@@ -220,7 +212,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_eq_longer_list(self):
> assert [1, 2] == [1, 2, 3]
E assert [1, 2] == [1, 2, 3]
E
E Right contains one more item: 3
E Use -v to get more diff
@@ -242,7 +233,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
text = "some multiline\ntext\nwhich\nincludes foo\nand a\ntail"
> assert "foo" not in text
E AssertionError: assert 'foo' not in 'some multil...nand a\ntail'
E
E 'foo' is contained here:
E some multiline
E text
@@ -261,7 +251,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
text = "single foo line"
> assert "foo" not in text
E AssertionError: assert 'foo' not in 'single foo line'
E
E 'foo' is contained here:
E single foo line
E ? +++
@@ -275,7 +264,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
text = "head " * 50 + "foo " + "tail " * 20
> assert "foo" not in text
E AssertionError: assert 'foo' not in 'head head h...l tail tail '
E
E 'foo' is contained here:
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
E ? +++
@@ -289,7 +277,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
text = "head " * 50 + "f" * 70 + "tail " * 20
> assert "f" * 70 not in text
E AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head h...l tail tail '
E
E 'ffffffffffffffffff...fffffffffffffffffff' is contained here:
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -445,7 +432,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
self = <failure_demo.TestRaises object at 0xdeadbeef0020>
def test_tupleerror(self):
> a, b = [1] # noqa: F841
> a, b = [1] # NOQA
E ValueError: not enough values to unpack (expected 2, got 1)
failure_demo.py:175: ValueError
@@ -467,7 +454,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
self = <failure_demo.TestRaises object at 0xdeadbeef0022>
def test_some_error(self):
> if namenotexi: # noqa: F821
> if namenotexi: # NOQA
E NameError: name 'namenotexi' is not defined
failure_demo.py:183: NameError

View File

@@ -168,7 +168,7 @@ Now we'll get feedback on a bad argument:
If you need to provide more detailed error messages, you can use the
``type`` parameter and raise :exc:`pytest.UsageError`:
``type`` parameter and raise ``pytest.UsageError``:
.. code-block:: python
@@ -232,7 +232,7 @@ directory with the above conftest.py:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 0 items
@@ -296,7 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
@@ -312,7 +312,7 @@ Or run it including the ``slow`` marked test:
$ pytest --runslow
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
@@ -456,7 +456,7 @@ which will add the string to the test header accordingly:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
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
@@ -484,7 +484,7 @@ which will add info only when run with "--v":
$ pytest -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.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?
@@ -499,7 +499,7 @@ and nothing when run plainly:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 0 items
@@ -538,7 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items
@@ -644,7 +644,7 @@ If we run this:
$ pytest -rx
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 4 items
@@ -660,31 +660,6 @@ If we run this:
E assert 0
test_step.py:11: AssertionError
================================ XFAILURES =================================
______________________ TestUserHandling.test_deletion ______________________
item = <Function test_deletion>
def pytest_runtest_setup(item):
if "incremental" in item.keywords:
# retrieve the class name of the test
cls_name = str(item.cls)
# check if a previous test has failed for this class
if cls_name in _test_failed_incremental:
# retrieve the index of the test (if parametrize is used in combination with incremental)
parametrize_index = (
tuple(item.callspec.indices.values())
if hasattr(item, "callspec")
else ()
)
# retrieve the name of the first test function to fail for this class name and index
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(f"previous test failed ({test_name})")
E _pytest.outcomes.XFailed: previous test failed (test_modification)
conftest.py:47: XFailed
========================= short test summary info ==========================
XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification)
================== 1 failed, 2 passed, 1 xfailed in 0.12s ==================
@@ -751,14 +726,14 @@ We can run this:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 7 items
a/test_db.py F [ 14%]
a/test_db2.py F [ 28%]
b/test_error.py E [ 42%]
test_step.py .Fx. [100%]
test_step.py .Fx. [ 57%]
a/test_db.py F [ 71%]
a/test_db2.py F [ 85%]
b/test_error.py E [100%]
================================== ERRORS ==================================
_______________________ ERROR at setup of test_root ________________________
@@ -770,39 +745,39 @@ We can run this:
/home/sweet/project/b/test_error.py:1
================================= FAILURES =================================
_________________________________ test_a1 __________________________________
db = <conftest.DB object at 0xdeadbeef0002>
def test_a1(db):
> assert 0, db # to show value
E AssertionError: <conftest.DB object at 0xdeadbeef0002>
E assert 0
a/test_db.py:2: AssertionError
_________________________________ test_a2 __________________________________
db = <conftest.DB object at 0xdeadbeef0002>
def test_a2(db):
> assert 0, db # to show value
E AssertionError: <conftest.DB object at 0xdeadbeef0002>
E assert 0
a/test_db2.py:2: AssertionError
____________________ TestUserHandling.test_modification ____________________
self = <test_step.TestUserHandling object at 0xdeadbeef0003>
self = <test_step.TestUserHandling object at 0xdeadbeef0002>
def test_modification(self):
> assert 0
E assert 0
test_step.py:11: AssertionError
_________________________________ test_a1 __________________________________
db = <conftest.DB object at 0xdeadbeef0003>
def test_a1(db):
> assert 0, db # to show value
E AssertionError: <conftest.DB object at 0xdeadbeef0003>
E assert 0
a/test_db.py:2: AssertionError
_________________________________ test_a2 __________________________________
db = <conftest.DB object at 0xdeadbeef0003>
def test_a2(db):
> assert 0, db # to show value
E AssertionError: <conftest.DB object at 0xdeadbeef0003>
E assert 0
a/test_db2.py:2: AssertionError
========================= short test summary info ==========================
FAILED test_step.py::TestUserHandling::test_modification - assert 0
FAILED a/test_db.py::test_a1 - AssertionError: <conftest.DB object at 0x7...
FAILED a/test_db2.py::test_a2 - AssertionError: <conftest.DB object at 0x...
FAILED test_step.py::TestUserHandling::test_modification - assert 0
ERROR b/test_error.py::test_root
============= 3 failed, 2 passed, 1 xfailed, 1 error in 0.12s ==============
@@ -833,10 +808,11 @@ case we just write some information out to a ``failures`` file:
import pytest
@pytest.hookimpl(wrapper=True, tryfirst=True)
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
# execute all other hooks to obtain the report object
rep = yield
outcome = yield
rep = outcome.get_result()
# we only look at actual failing test calls, not setup/teardown
if rep.when == "call" and rep.failed:
@@ -850,8 +826,6 @@ case we just write some information out to a ``failures`` file:
f.write(rep.nodeid + extra + "\n")
return rep
if you then have failing tests:
@@ -871,7 +845,7 @@ and run them:
$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
@@ -925,17 +899,16 @@ here is a little example implemented via a local plugin:
phase_report_key = StashKey[Dict[str, CollectReport]]()
@pytest.hookimpl(wrapper=True, tryfirst=True)
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
# execute all other hooks to obtain the report object
rep = yield
outcome = yield
rep = outcome.get_result()
# store test results for each phase of a call, which can
# be "setup", "call", "teardown"
item.stash.setdefault(phase_report_key, {})[rep.when] = rep
return rep
@pytest.fixture
def something(request):
@@ -980,7 +953,7 @@ and run it:
$ pytest -s test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items

View File

@@ -1,6 +1,5 @@
import pytest
xfail = pytest.mark.xfail

View File

@@ -34,7 +34,7 @@ a function/method call.
**Assert** is where we look at that resulting state and check if it looks how
we'd expect after the dust has settled. It's where we gather evidence to say the
behavior does or does not align with what we expect. The ``assert`` in our test
behavior does or does not aligns with what we expect. The ``assert`` in our test
is where we take that measurement/observation and apply our judgement to it. If
something should be green, we'd say ``assert thing == "green"``.

View File

@@ -85,7 +85,7 @@ style of setup/teardown functions:
In addition, pytest continues to support :ref:`xunitsetup`. You can mix
both styles, moving incrementally from classic to new style, as you
prefer. You can also start out from existing :ref:`unittest.TestCase
style <unittest.TestCase>`.
style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
@@ -162,7 +162,7 @@ A note about fixture cleanup
----------------------------
pytest does not do any special processing for :data:`SIGTERM <signal.SIGTERM>` and
``SIGQUIT`` signals (:data:`SIGINT <signal.SIGINT>` is handled naturally
:data:`SIGQUIT <signal.SIGQUIT>` signals (:data:`SIGINT <signal.SIGINT>` is handled naturally
by the Python runtime via :class:`KeyboardInterrupt`), so fixtures that manage external resources which are important
to be cleared when the Python process is terminated (by those signals) might leak resources.

View File

@@ -60,10 +60,8 @@ Within Python modules, ``pytest`` also discovers tests using the standard
:ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
.. _`test layout`:
Choosing a test layout
----------------------
Choosing a test layout / import rules
-------------------------------------
``pytest`` supports two common test layouts:

View File

@@ -10,27 +10,19 @@ Import modes
pytest as a testing framework needs to import test modules and ``conftest.py`` files for execution.
Importing files in Python is a non-trivial processes, so aspects of the
Importing files in Python (at least until recently) is a non-trivial processes, often requiring
changing :data:`sys.path`. Some aspects of the
import process can be controlled through the ``--import-mode`` command-line flag, which can assume
these values:
.. _`import-mode-prepend`:
* ``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:`importlib.import_module <importlib.import_module>` function.
of :py:data:`sys.path` if not already there, and then imported with the :func:`importlib.import_module <importlib.import_module>` function.
It is highly recommended to arrange your test modules as packages by adding ``__init__.py`` files to your directories
containing tests. This will make the tests part of a proper Python package, allowing pytest to resolve their full
name (for example ``tests.core.test_core`` for ``test_core.py`` inside the ``tests.core`` package).
If the test directory tree is not arranged as packages, then each test file needs to have a unique name
compared to the other test files, otherwise pytest will raise an error if it finds two tests with the same name.
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.
This is the classic mechanism, dating back from the time Python 2 was still supported.
.. _`import-mode-append`:
* ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already
there, and imported with :func:`importlib.import_module <importlib.import_module>`.
@@ -46,78 +38,32 @@ these values:
the tests will run against the installed version
of ``pkg_under_test`` when ``--import-mode=append`` is used whereas
with ``prepend`` they would pick up the local version. This kind of confusion is why
we advocate for using :ref:`src-layouts <src-layout>`.
we advocate for using :ref:`src <src-layout>` layouts.
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.
.. _`import-mode-importlib`:
* ``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`.
* ``importlib``: this mode uses more fine control mechanisms provided by :mod:`importlib` to import test modules, without changing :py:data:`sys.path`.
For this reason this doesn't require test module names to be unique.
Advantages of this mode:
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`.
* pytest will not change :py:data:`sys.path` at all.
* Test module names do not need to be unique -- pytest will generate a unique name automatically based on the ``rootdir``.
Disadvantages:
* Test modules can't import each other.
* Testing utility modules in the tests directories (for example a ``tests.helpers`` module containing test-related functions/classes)
are not importable. The recommendation in this case it to place testing utility modules together with the application/library
code, for example ``app.testing.helpers``.
Important: by "test utility modules" we mean functions/classes which are imported by
other tests directly; this does not include fixtures, which should be placed in ``conftest.py`` files, along
with the test modules, and are discovered automatically by pytest.
It works like this:
1. Given a certain module path, for example ``tests/core/test_models.py``, derives a canonical name
like ``tests.core.test_models`` and tries to import it.
For non-test modules this will work if they are accessible via :py:data:`sys.path`, so
for example ``.env/lib/site-packages/app/core.py`` will be importable as ``app.core``.
This is happens when plugins import non-test modules (for example doctesting).
If this step succeeds, the module is returned.
For test modules, unless they are reachable from :py:data:`sys.path`, this step will fail.
2. If the previous step fails, we import the module directly using ``importlib`` facilities, which lets us import it without
changing :py:data:`sys.path`.
Because Python requires the module to also be available in :py:data:`sys.modules`, pytest derives a unique name for it based
on its relative location from the ``rootdir``, and adds the module to :py:data:`sys.modules`.
For example, ``tests/core/test_models.py`` will end up being imported as the module ``tests.core.test_models``.
.. versionadded:: 6.0
.. note::
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.
.. note::
By default, pytest will not attempt to resolve namespace packages automatically, but that can
be changed via the :confval:`consider_namespace_packages` configuration variable.
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.
The :confval:`consider_namespace_packages` configuration variable.
:ref:`test layout`.
``prepend`` and ``append`` import modes scenarios
-------------------------------------------------
Here's a list of scenarios when using ``prepend`` or ``append`` import modes where pytest needs to
change :py:data:`sys.path` in order to import test modules or ``conftest.py`` files, and the issues users
change ``sys.path`` in order to import test modules or ``conftest.py`` files, and the issues users
might encounter because of that.
Test modules / ``conftest.py`` files inside packages
@@ -146,7 +92,7 @@ pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a packa
there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the
last folder which still contains an ``__init__.py`` file in order to find the package *root* (in
this case ``foo/``). To load the module, it will insert ``root/`` to the front of
:py:data:`sys.path` (if not there already) in order to load
``sys.path`` (if not there already) in order to load
``test_foo.py`` as the *module* ``foo.bar.tests.test_foo``.
The same logic applies to the ``conftest.py`` file: it will be imported as ``foo.conftest`` module.
@@ -176,8 +122,8 @@ When executing:
pytest will find ``foo/bar/tests/test_foo.py`` and realize it is NOT part of a package given that
there's no ``__init__.py`` file in the same folder. It will then add ``root/foo/bar/tests`` to
:py:data:`sys.path` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done
with the ``conftest.py`` file by adding ``root/foo`` to :py:data:`sys.path` to import it as ``conftest``.
``sys.path`` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done
with the ``conftest.py`` file by adding ``root/foo`` to ``sys.path`` to import it as ``conftest``.
For this reason this layout cannot have test modules with the same name, as they all will be
imported in the global import namespace.
@@ -190,7 +136,7 @@ Invoking ``pytest`` versus ``python -m pytest``
-----------------------------------------------
Running pytest with ``pytest [...]`` instead of ``python -m pytest [...]`` yields nearly
equivalent behaviour, except that the latter will add the current directory to :py:data:`sys.path`, which
equivalent behaviour, except that the latter will add the current directory to ``sys.path``, which
is standard ``python`` behavior.
See also :ref:`invoke-python`.

View File

@@ -11,6 +11,8 @@ funcarg mechanism, see :ref:`historical funcargs and pytest.funcargs`.
If you are new to pytest, then you can simply ignore this
section and read the other sections.
.. currentmodule:: _pytest
Shortcomings of the previous ``pytest_funcarg__`` mechanism
--------------------------------------------------------------
@@ -44,7 +46,7 @@ There are several limitations and difficulties with this approach:
2. parametrizing the "db" resource is not straight forward:
you need to apply a "parametrize" decorator or implement a
:hook:`pytest_generate_tests` hook
:py:func:`~hookspec.pytest_generate_tests` hook
calling :py:func:`~pytest.Metafunc.parametrize` which
performs parametrization at the places where the resource
is used. Moreover, you need to modify the factory to use an
@@ -92,14 +94,15 @@ Direct parametrization of funcarg resource factories
Previously, funcarg factories could not directly cause parametrization.
You needed to specify a ``@parametrize`` decorator on your test function
or implement a :hook:`pytest_generate_tests` hook to perform
or implement a ``pytest_generate_tests`` hook to perform
parametrization, i.e. calling a test multiple times with different value
sets. pytest-2.3 introduces a decorator for use on the factory itself:
.. code-block:: python
@pytest.fixture(params=["mysql", "pg"])
def db(request): ... # use request.param
def db(request):
... # use request.param
Here the factory will be invoked twice (with the respective "mysql"
and "pg" values set as ``request.param`` attributes) and all of
@@ -140,7 +143,8 @@ argument:
.. code-block:: python
@pytest.fixture()
def db(request): ...
def db(request):
...
The name under which the funcarg resource can be requested is ``db``.
@@ -149,7 +153,8 @@ aka:
.. code-block:: python
def pytest_funcarg__db(request): ...
def pytest_funcarg__db(request):
...
But it is then not possible to define scoping and parametrization.

View File

@@ -9,7 +9,7 @@ Get Started
Install ``pytest``
----------------------------------------
``pytest`` requires: Python 3.8+ 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 8.1.0
pytest 7.4.4
.. _`simpletest`:
@@ -47,7 +47,7 @@ The test
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -97,30 +97,6 @@ Use the :ref:`raises <assertraises>` helper to assert that some code raises an e
with pytest.raises(SystemExit):
f()
You can also use the context provided by :ref:`raises <assertraises>` to
assert that an expected exception is part of a raised :class:`ExceptionGroup`:
.. code-block:: python
# content of test_exceptiongroup.py
import pytest
def f():
raise ExceptionGroup(
"Group message",
[
RuntimeError(),
],
)
def test_exception_in_group():
with pytest.raises(ExceptionGroup) as excinfo:
f()
assert excinfo.group_contains(RuntimeError)
assert not excinfo.group_contains(TypeError)
Execute the test function with “quiet” reporting mode:
.. code-block:: pytest

View File

@@ -112,7 +112,7 @@ More details can be found in the :pull:`original PR <3317>`.
.. note::
in a future major release of pytest we will introduce class based markers,
at which point markers will no longer be limited to instances of :py:class:`~pytest.Mark`.
at which point markers will no longer be limited to instances of :py:class:`~_pytest.mark.Mark`.
cache plugin integrated into the core
@@ -227,7 +227,8 @@ to use strings:
@pytest.mark.skipif("sys.version_info >= (3,3)")
def test_function(): ...
def test_function():
...
During test function setup the skipif condition is evaluated by calling
``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains
@@ -261,7 +262,8 @@ configuration value which you might have added:
.. code-block:: python
@pytest.mark.skipif("not config.getvalue('db')")
def test_function(): ...
def test_function():
...
The equivalent with "boolean conditions" is:

View File

@@ -29,7 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -98,27 +98,6 @@ and if you need to have access to the actual exception info you may use:
the actual exception raised. The main attributes of interest are
``.type``, ``.value`` and ``.traceback``.
Note that ``pytest.raises`` will match the exception type or any subclasses (like the standard ``except`` statement).
If you want to check if a block of code is raising an exact exception type, you need to check that explicitly:
.. code-block:: python
def test_foo_not_implemented():
def foo():
raise NotImplementedError
with pytest.raises(RuntimeError) as excinfo:
foo()
assert excinfo.type is RuntimeError
The :func:`pytest.raises` call will succeed, even though the function raises :class:`NotImplementedError`, because
:class:`NotImplementedError` is a subclass of :class:`RuntimeError`; however the following `assert` statement will
catch the problem.
Matching exception messages
~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can pass a ``match`` keyword parameter to the context-manager to test
that a regular expression matches on the string representation of an exception
(similar to the ``TestCase.assertRaisesRegex`` method from ``unittest``):
@@ -136,113 +115,36 @@ that a regular expression matches on the string representation of an exception
with pytest.raises(ValueError, match=r".* 123 .*"):
myfunc()
Notes:
The regexp parameter of the ``match`` method is matched with the ``re.search``
function, so in the above example ``match='123'`` would have worked as
well.
* The ``match`` parameter is matched with the :func:`re.search`
function, so in the above example ``match='123'`` would have worked as well.
* The ``match`` parameter also matches against `PEP-678 <https://peps.python.org/pep-0678/>`__ ``__notes__``.
.. _`assert-matching-exception-groups`:
Matching exception groups
~~~~~~~~~~~~~~~~~~~~~~~~~
You can also use the :func:`excinfo.group_contains() <pytest.ExceptionInfo.group_contains>`
method to test for exceptions returned as part of an :class:`ExceptionGroup`:
There's an alternate form of the :func:`pytest.raises` function where you pass
a function that will be executed with the given ``*args`` and ``**kwargs`` and
assert that the given exception is raised:
.. code-block:: python
def test_exception_in_group():
with pytest.raises(ExceptionGroup) as excinfo:
raise ExceptionGroup(
"Group message",
[
RuntimeError("Exception 123 raised"),
],
)
assert excinfo.group_contains(RuntimeError, match=r".* 123 .*")
assert not excinfo.group_contains(TypeError)
The optional ``match`` keyword parameter works the same way as for
:func:`pytest.raises`.
By default ``group_contains()`` will recursively search for a matching
exception at any level of nested ``ExceptionGroup`` instances. You can
specify a ``depth`` keyword parameter if you only want to match an
exception at a specific level; exceptions contained directly in the top
``ExceptionGroup`` would match ``depth=1``.
.. code-block:: python
def test_exception_in_group_at_given_depth():
with pytest.raises(ExceptionGroup) as excinfo:
raise ExceptionGroup(
"Group message",
[
RuntimeError(),
ExceptionGroup(
"Nested group",
[
TypeError(),
],
),
],
)
assert excinfo.group_contains(RuntimeError, depth=1)
assert excinfo.group_contains(TypeError, depth=2)
assert not excinfo.group_contains(RuntimeError, depth=2)
assert not excinfo.group_contains(TypeError, depth=1)
Alternate form (legacy)
~~~~~~~~~~~~~~~~~~~~~~~
There is an alternate form where you pass
a function that will be executed, along ``*args`` and ``**kwargs``, and :func:`pytest.raises`
will execute the function with the arguments and assert that the given exception is raised:
.. code-block:: python
def func(x):
if x <= 0:
raise ValueError("x needs to be larger than zero")
pytest.raises(ValueError, func, x=-1)
pytest.raises(ExpectedException, func, *args, **kwargs)
The reporter will provide you with helpful output in case of failures such as *no
exception* or *wrong exception*.
This form was the original :func:`pytest.raises` API, developed before the ``with`` statement was
added to the Python language. Nowadays, this form is rarely used, with the context-manager form (using ``with``)
being considered more readable.
Nonetheless, this form is fully supported and not deprecated in any way.
xfail mark and pytest.raises
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is also possible to specify a ``raises`` argument to
:ref:`pytest.mark.xfail <pytest.mark.xfail ref>`, which checks that the test is failing in a more
Note that it is also possible to specify a "raises" argument to
``pytest.mark.xfail``, which checks that the test is failing in a more
specific way than just having any exception raised:
.. code-block:: python
def f():
raise IndexError()
@pytest.mark.xfail(raises=IndexError)
def test_f():
f()
This will only "xfail" if the test fails by raising ``IndexError`` or subclasses.
* Using :ref:`pytest.mark.xfail <pytest.mark.xfail ref>` with the ``raises`` parameter is probably better for something
like documenting unfixed bugs (where the test describes what "should" happen) or bugs in dependencies.
* Using :func:`pytest.raises` is likely to be better for cases where you are
testing exceptions your own code is deliberately raising, which is the majority of cases.
Using :func:`pytest.raises` is likely to be better for cases where you are
testing exceptions your own code is deliberately raising, whereas using
``@pytest.mark.xfail`` with a check function is probably better for something
like documenting unfixed bugs (where the test describes what "should" happen)
or bugs in dependencies.
.. _`assertwarns`:
@@ -280,7 +182,7 @@ if you run this module:
$ pytest test_assert2.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -294,7 +196,6 @@ if you run this module:
set2 = set("8035")
> assert set1 == set2
E AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'}
E
E Extra items in the left set:
E '1'
E Extra items in the right set:

View File

@@ -86,7 +86,7 @@ If you then run it with ``--lf``:
$ pytest --lf
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
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
@@ -132,7 +132,7 @@ of ``FF`` and dots):
$ pytest --ff
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
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
@@ -213,12 +213,12 @@ across pytest invocations:
@pytest.fixture
def mydata(pytestconfig):
val = pytestconfig.cache.get("example/value", None)
def mydata(request):
val = request.config.cache.get("example/value", None)
if val is None:
expensive_computation()
val = 42
pytestconfig.cache.set("example/value", val)
request.config.cache.set("example/value", val)
return val
@@ -281,7 +281,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-8.x.y, pluggy-1.x.y
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 '*' ---------------------------
@@ -303,7 +303,7 @@ filtering:
$ pytest --cache-show example/*
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
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/*' -----------------------

View File

@@ -83,7 +83,7 @@ of the failing function and hide the other one:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items

View File

@@ -28,7 +28,7 @@ Running pytest now produces this output:
$ pytest test_show_warnings.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -382,6 +382,8 @@ warnings: a WarningsRecorder instance. To view the recorded warnings, you can
iterate over this instance, call ``len`` on it to get the number of recorded
warnings, or index into it to get a particular recorded warning.
.. currentmodule:: _pytest.warnings
Full API: :class:`~_pytest.recwarn.WarningsRecorder`.
.. _`warns use cases`:

View File

@@ -30,7 +30,7 @@ then you can just invoke ``pytest`` directly:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -58,7 +58,7 @@ and functions, including from test modules:
$ pytest --doctest-modules
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items

View File

@@ -4,8 +4,8 @@ How to use pytest with an existing test suite
==============================================
Pytest can be used with most existing test suites, but its
behavior differs from other test runners such as Python's
default unittest framework.
behavior differs from other test runners such as :ref:`nose <noseintegration>` or
Python's default unittest framework.
Before using this section you will want to :ref:`install pytest <getstarted>`.

View File

@@ -135,6 +135,10 @@ Warning about unraisable exceptions and unhandled thread exceptions
.. versionadded:: 6.2
.. note::
These features only work on Python>=3.8.
Unhandled exceptions are exceptions that are raised in a situation in which
they cannot propagate to a caller. The most common case is an exception raised
in a :meth:`__del__ <object.__del__>` implementation.

View File

@@ -433,7 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
@@ -494,7 +494,7 @@ Fixtures are created when first requested by a test, and are destroyed based on
* ``function``: the default scope, the fixture is destroyed at the end of the test.
* ``class``: the fixture is destroyed during teardown of the last test in the class.
* ``module``: the fixture is destroyed during teardown of the last test in the module.
* ``package``: the fixture is destroyed during teardown of the last test in the package where the fixture is defined, including sub-packages and sub-directories within it.
* ``package``: the fixture is destroyed during teardown of the last test in the package.
* ``session``: the fixture is destroyed at the end of the test session.
.. note::
@@ -771,7 +771,7 @@ For yield fixtures, the first teardown code to run is from the right-most fixtur
$ pytest -s test_finalizers.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -805,7 +805,7 @@ For finalizers, the first fixture to run is last call to `request.addfinalizer`.
$ pytest -s test_finalizers.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -1271,7 +1271,7 @@ configured in multiple ways.
Extending the previous example, we can flag the fixture to create two
``smtp_connection`` fixture instances which will cause all tests using the fixture
to run twice. The fixture function gets access to each parameter
through the special :py:class:`request <pytest.FixtureRequest>` object:
through the special :py:class:`request <FixtureRequest>` object:
.. code-block:: python
@@ -1414,28 +1414,27 @@ 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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 12 items
<Dir fixtures.rst-214>
<Module test_anothersmtp.py>
<Function test_showhelo[smtp.gmail.com]>
<Function test_showhelo[mail.python.org]>
<Module test_emaillib.py>
<Function test_email_received>
<Module test_finalizers.py>
<Function test_bar>
<Module test_ids.py>
<Function test_a[spam]>
<Function test_a[ham]>
<Function test_b[eggs]>
<Function test_b[1]>
<Module test_module.py>
<Function test_ehlo[smtp.gmail.com]>
<Function test_noop[smtp.gmail.com]>
<Function test_ehlo[mail.python.org]>
<Function test_noop[mail.python.org]>
<Module test_anothersmtp.py>
<Function test_showhelo[smtp.gmail.com]>
<Function test_showhelo[mail.python.org]>
<Module test_emaillib.py>
<Function test_email_received>
<Module test_finalizers.py>
<Function test_bar>
<Module test_ids.py>
<Function test_a[spam]>
<Function test_a[ham]>
<Function test_b[eggs]>
<Function test_b[1]>
<Module test_module.py>
<Function test_ehlo[smtp.gmail.com]>
<Function test_noop[smtp.gmail.com]>
<Function test_ehlo[mail.python.org]>
<Function test_noop[mail.python.org]>
======================= 12 tests collected in 0.12s ========================
@@ -1469,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-8.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
@@ -1519,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-8.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
@@ -1599,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-8.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
@@ -1721,7 +1720,8 @@ You can specify multiple fixtures like this:
.. code-block:: python
@pytest.mark.usefixtures("cleandir", "anotherfixture")
def test(): ...
def test():
...
and you may specify fixture usage at the test module level using :globalvar:`pytestmark`:
@@ -1749,9 +1749,11 @@ into an ini-file:
@pytest.mark.usefixtures("my_other_fixture")
@pytest.fixture
def my_fixture_that_sadly_wont_use_my_other_fixture(): ...
def my_fixture_that_sadly_wont_use_my_other_fixture():
...
This generates a deprecation warning, and will become an error in Pytest 8.
Currently this will not generate any error or warning, but this is intended
to be handled by :issue:`3664`.
.. _`override fixtures`:

View File

@@ -52,6 +52,7 @@ pytest and other test systems
existingtestsuite
unittest
nose
xunit_setup
pytest development environment

View File

@@ -172,13 +172,6 @@ the records for the ``setup`` and ``call`` stages during teardown like so:
The full API is available at :class:`pytest.LogCaptureFixture`.
.. warning::
The ``caplog`` fixture adds a handler to the root logger to capture logs. If the root logger is
modified during a test, for example with ``logging.config.dictConfig``, this handler may be
removed and cause no logs to be captured. To avoid this, ensure that any root logger configuration
only adds to the existing handlers.
.. _live_logs:
@@ -206,9 +199,8 @@ option names are:
* ``log_cli_date_format``
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 by default which
``--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.
If you'd like the file opened in append mode instead, then you can pass ``--log-file-mode=a``.
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.
@@ -224,13 +216,12 @@ All of the log file options can also be set in the configuration INI file. The
option names are:
* ``log_file``
* ``log_file_mode``
* ``log_file_level``
* ``log_file_format``
* ``log_file_date_format``
You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality
is considered **experimental**. Note that ``set_log_path()`` respects the ``log_file_mode`` option.
is considered **experimental**.
.. _log_colors:
@@ -243,7 +234,7 @@ through ``add_color_level()``. Example:
.. code-block:: python
@pytest.hookimpl(trylast=True)
@pytest.hookimpl
def pytest_configure(config):
logging_plugin = config.pluginmanager.get_plugin("logging-plugin")

82
doc/en/how-to/nose.rst Normal file
View File

@@ -0,0 +1,82 @@
.. _`noseintegration`:
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
-------------
After :ref:`installation` type:
.. code-block:: bash
python setup.py develop # make sure tests can import our package
pytest # instead of 'nosetests'
and you should be able to run your nose style tests and
make use of pytest's capabilities.
Supported nose Idioms
----------------------
* ``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
Unsupported idioms / known issues
----------------------------------
- unittest-style ``setUp, tearDown, setUpClass, tearDownClass``
are recognized only on ``unittest.TestCase`` classes but not
on plain classes. ``nose`` supports these methods also on plain
classes but pytest deliberately does not. As nose and pytest already
both support ``setup_class, teardown_class, setup_method, teardown_method``
it doesn't seem useful to duplicate the unittest-API like nose does.
If you however rather think pytest should support the unittest-spelling on
plain classes please post to :issue:`377`.
- nose imports test modules with the same import path (e.g.
``tests.test_mode``) but different file system paths
(e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``)
by extending sys.path/import semantics. pytest does not do that
but there is discussion in :issue:`268` for adding some support. Note that
`nose2 choose to avoid this sys.path/import hackery <https://nose2.readthedocs.io/en/latest/differences.html#test-discovery-and-loading>`_.
If you place a conftest.py file in the root directory of your project
(as determined by pytest) pytest will run tests "nose style" against
the code below that directory by adding it to your ``sys.path`` instead of
running against your installed code.
You may find yourself wanting to do this if you ran ``python setup.py install``
to set up your project, as opposed to ``python setup.py develop`` or any of
the package manager equivalents. Installing with develop in a
virtual environment like tox is recommended over this pattern.
- nose-style doctests are not collected and executed correctly,
also doctest fixtures don't work.
- no nose-configuration is recognized.
- ``yield``-based methods are unsupported as of pytest 4.1.0. They are
fundamentally incompatible with pytest because they don't support fixtures
properly since collection and test execution are separated.
Migrating from nose to pytest
------------------------------
`nose2pytest <https://github.com/pytest-dev/nose2pytest>`_ is a Python script
and pytest plugin to help convert Nose-based tests into pytest-based tests.
Specifically, the script transforms nose.tools.assert_* function calls into
raw assert statements, while preserving format of original arguments
as much as possible.
.. _nose: https://nose.readthedocs.io/en/latest/

View File

@@ -16,12 +16,6 @@ Examples for modifying traceback printing:
pytest -l # show local variables (shortcut)
pytest --no-showlocals # hide local variables (if addopts enables them)
pytest --capture=fd # default, capture at the file descriptor level
pytest --capture=sys # capture at the sys level
pytest --capture=no # don't capture
pytest -s # don't capture (shortcut)
pytest --capture=tee-sys # capture to logs but also output to sys level streams
pytest --tb=auto # (default) 'long' tracebacks for the first and last
# entry, but 'short' style for the other entries
pytest --tb=long # exhaustive, informative traceback formatting
@@ -42,16 +36,6 @@ option you make sure a trace is shown.
Verbosity
--------------------------------------------------
Examples for modifying printing verbosity:
.. code-block:: bash
pytest --quiet # quiet - less verbose - mode
pytest -q # quiet - less verbose - mode (shortcut)
pytest -v # increase verbosity, display individual test names
pytest -vv # more verbose, display more details from the test output
pytest -vvv # not a standard , but may be used for even more detail in certain setups
The ``-v`` flag controls the verbosity of pytest output in various aspects: test session progress, assertion
details when tests fail, fixtures details with ``--fixtures``, etc.
@@ -100,7 +84,6 @@ Executing pytest normally gives us this output (we are skipping the header to fo
fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
> assert fruits1 == fruits2
E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
E
E At index 2 diff: 'grapes' != 'orange'
E Use -v to get more diff
@@ -112,7 +95,6 @@ Executing pytest normally gives us this output (we are skipping the header to fo
number_to_text2 = {str(x * 10): x * 10 for x in range(5)}
> assert number_to_text1 == number_to_text2
E AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...}
E
E Omitting 1 identical items, use -vv to show
E Left contains 4 more items:
E {'1': 1, '2': 2, '3': 3, '4': 4}
@@ -164,15 +146,12 @@ Now we can increase pytest's verbosity:
fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
> assert fruits1 == fruits2
E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
E
E At index 2 diff: 'grapes' != 'orange'
E
E Full diff:
E [
E 'banana',
E 'apple',...
E
E ...Full output truncated (7 lines hidden), use '-vv' to show
E - ['banana', 'apple', 'orange', 'melon', 'kiwi']
E ? ^ ^^
E + ['banana', 'apple', 'grapes', 'melon', 'kiwi']
E ? ^ ^ +
test_verbosity_example.py:8: AssertionError
____________________________ test_numbers_fail _____________________________
@@ -182,15 +161,15 @@ Now we can increase pytest's verbosity:
number_to_text2 = {str(x * 10): x * 10 for x in range(5)}
> assert number_to_text1 == number_to_text2
E AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...}
E
E Omitting 1 identical items, use -vv to show
E Left contains 4 more items:
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 ...
E
E ...Full output truncated (16 lines hidden), use '-vv' to show
E Full diff:
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 ____________________________
@@ -236,20 +215,12 @@ Now if we increase verbosity even more:
fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
> assert fruits1 == fruits2
E AssertionError: assert ['banana', 'apple', 'grapes', 'melon', 'kiwi'] == ['banana', 'apple', 'orange', 'melon', 'kiwi']
E
E At index 2 diff: 'grapes' != 'orange'
E
E Full diff:
E [
E 'banana',
E 'apple',
E - 'orange',
E ? ^ ^^
E + 'grapes',
E ? ^ ^ +
E 'melon',
E 'kiwi',
E ]
E - ['banana', 'apple', 'orange', 'melon', 'kiwi']
E ? ^ ^^
E + ['banana', 'apple', 'grapes', 'melon', 'kiwi']
E ? ^ ^ +
test_verbosity_example.py:8: AssertionError
____________________________ test_numbers_fail _____________________________
@@ -259,30 +230,16 @@ Now if we increase verbosity even more:
number_to_text2 = {str(x * 10): x * 10 for x in range(5)}
> assert number_to_text1 == number_to_text2
E AssertionError: assert {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} == {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}
E
E Common items:
E {'0': 0}
E Left contains 4 more items:
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
E Full diff:
E {
E '0': 0,
E - '10': 10,
E ? - -
E + '1': 1,
E - '20': 20,
E ? - -
E + '2': 2,
E - '30': 30,
E ? - -
E + '3': 3,
E - '40': 40,
E ? - -
E + '4': 4,
E }
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 ____________________________
@@ -313,22 +270,6 @@ situations, for example you are shown even fixtures that start with ``_`` if you
Using higher verbosity levels (``-vvv``, ``-vvvv``, ...) is supported, but has no effect in pytest itself at the moment,
however some plugins might make use of higher verbosity.
.. _`pytest.fine_grained_verbosity`:
Fine-grained verbosity
~~~~~~~~~~~~~~~~~~~~~~
In addition to specifying the application wide verbosity level, it is possible to control specific aspects independently.
This is done by setting a verbosity level in the configuration file for the specific aspect of the output.
:confval:`verbosity_assertions`: Controls how verbose the assertion output should be when pytest is executed. Running
``pytest --no-header`` with a value of ``2`` would have the same output as the previous example, but each test inside
the file is shown by a single character in the output.
:confval:`verbosity_test_cases`: Controls how verbose the test execution output should be when pytest is executed.
Running ``pytest --no-header`` with a value of ``2`` would have the same output as the first verbosity example, but each
test inside the file gets its own line in the output.
.. _`pytest.detailed_failed_tests_usage`:
Producing a detailed summary report
@@ -383,7 +324,7 @@ Example:
$ pytest -ra
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 6 items
@@ -406,19 +347,10 @@ Example:
E assert 0
test_example.py:14: AssertionError
================================ XFAILURES =================================
________________________________ test_xfail ________________________________
def test_xfail():
> pytest.xfail("xfailing this test")
E _pytest.outcomes.XFailed: xfailing this test
test_example.py:26: XFailed
================================= XPASSES ==================================
========================= short test summary info ==========================
SKIPPED [1] test_example.py:22: skipping this test
XFAIL test_example.py::test_xfail - reason: xfailing this test
XPASS test_example.py::test_xpass - always xfail
XPASS test_example.py::test_xpass always xfail
ERROR test_example.py::test_error - assert 0
FAILED test_example.py::test_fail - assert 0
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
@@ -448,7 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 6 items
@@ -483,7 +415,7 @@ captured output:
$ pytest -rpP
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 6 items

View File

@@ -56,7 +56,7 @@ them in turn:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items
@@ -167,7 +167,7 @@ Let's run this:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items

View File

@@ -47,7 +47,8 @@ which may be passed an optional ``reason``:
.. code-block:: python
@pytest.mark.skip(reason="no way of currently testing this")
def test_the_unknown(): ...
def test_the_unknown():
...
Alternatively, it is also possible to skip imperatively during test execution or setup
@@ -92,7 +93,8 @@ when run on an interpreter earlier than Python3.10:
@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_function(): ...
def test_function():
...
If the condition evaluates to ``True`` during collection, the test function will be skipped,
with the specified reason appearing in the summary when using ``-rs``.
@@ -110,7 +112,8 @@ You can share ``skipif`` markers between modules. Consider this test module:
@minversion
def test_function(): ...
def test_function():
...
You can import the marker and reuse it in another test module:
@@ -121,7 +124,8 @@ You can import the marker and reuse it in another test module:
@minversion
def test_anotherfunction(): ...
def test_anotherfunction():
...
For larger test suites it's usually a good idea to have one file
where you define the markers which you then consistently apply
@@ -228,7 +232,8 @@ expect a test to fail:
.. code-block:: python
@pytest.mark.xfail
def test_function(): ...
def test_function():
...
This test will run but no traceback will be reported when it fails. Instead, terminal
reporting will list it in the "expected to fail" (``XFAIL``) or "unexpectedly
@@ -270,7 +275,8 @@ that condition as the first parameter:
.. code-block:: python
@pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library")
def test_function(): ...
def test_function():
...
Note that you have to pass a reason as well (see the parameter description at
:ref:`pytest.mark.xfail ref`).
@@ -283,7 +289,8 @@ You can specify the motive of an expected failure with the ``reason`` parameter:
.. code-block:: python
@pytest.mark.xfail(reason="known parser issue")
def test_function(): ...
def test_function():
...
``raises`` parameter
@@ -295,7 +302,8 @@ a single exception, or a tuple of exceptions, in the ``raises`` argument.
.. code-block:: python
@pytest.mark.xfail(raises=RuntimeError)
def test_function(): ...
def test_function():
...
Then the test will be reported as a regular failure if it fails with an
exception not mentioned in ``raises``.
@@ -309,7 +317,8 @@ even executed, use the ``run`` parameter as ``False``:
.. code-block:: python
@pytest.mark.xfail(run=False)
def test_function(): ...
def test_function():
...
This is specially useful for xfailing tests that are crashing the interpreter and should be
investigated later.
@@ -325,7 +334,8 @@ You can change this by setting the ``strict`` keyword-only parameter to ``True``
.. code-block:: python
@pytest.mark.xfail(strict=True)
def test_function(): ...
def test_function():
...
This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite.

View File

@@ -8,8 +8,9 @@ How to use temporary directories and files in tests
The ``tmp_path`` fixture
------------------------
You can use the ``tmp_path`` fixture which will provide a temporary directory
unique to each test function.
You can use the ``tmp_path`` fixture which will
provide a temporary directory unique to the test invocation,
created in the `base temporary directory`_.
``tmp_path`` is a :class:`pathlib.Path` object. Here is an example test usage:
@@ -35,7 +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-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -61,11 +62,6 @@ Running this would result in a passed test except for the last
FAILED test_tmp_path.py::test_create_file - assert 0
============================ 1 failed in 0.12s =============================
By default, ``pytest`` retains the temporary directory for the last 3 ``pytest``
invocations. Concurrent invocations of the same test function are supported by
configuring the base temporary directory to be unique for each concurrent
run. See `temporary directory location and retention`_ for details.
.. _`tmp_path_factory example`:
The ``tmp_path_factory`` fixture
@@ -104,7 +100,7 @@ See :ref:`tmp_path_factory API <tmp_path_factory factory api>` for details.
.. _tmpdir:
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
@@ -128,10 +124,10 @@ See :fixture:`tmpdir <tmpdir>` :fixture:`tmpdir_factory <tmpdir_factory>`
API for details.
.. _`temporary directory location and retention`:
.. _`base temporary directory`:
Temporary directory location and retention
------------------------------------------
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
@@ -156,7 +152,7 @@ You can override the default temporary directory setting like this:
for that purpose only.
When distributing tests on the local machine using ``pytest-xdist``, care is taken to
automatically configure a `basetemp` directory for the sub processes such that all temporary
data lands below a single per-test run temporary directory.
automatically configure a basetemp directory for the sub processes such that all temporary
data lands below a single per-test run basetemp directory.
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html

View File

@@ -140,7 +140,7 @@ the ``self.db`` values in the traceback:
$ pytest test_unittest_db.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items

View File

@@ -44,34 +44,23 @@ Use ``""`` instead of ``''`` in expression when running this on Windows
.. _nodeids:
**Run tests by collection arguments**
**Run tests by node ids**
Pass the module filename relative to the working directory, followed by specifiers like the class name and function name
separated by ``::`` characters, and parameters from parameterization enclosed in ``[]``.
Each collected test is assigned a unique ``nodeid`` which consist of the module filename followed
by specifiers like class names, function names and parameters from parametrization, separated by ``::`` characters.
To run a specific test within a module:
.. code-block:: bash
pytest tests/test_mod.py::test_func
pytest test_mod.py::test_func
To run all tests in a class:
Another example specifying a test method in the command line:
.. code-block:: bash
pytest tests/test_mod.py::TestClass
Specifying a specific test method:
.. code-block:: bash
pytest tests/test_mod.py::TestClass::test_method
Specifying a specific parametrization of a test:
.. code-block:: bash
pytest tests/test_mod.py::test_func[x1,y2]
pytest test_mod.py::TestClass::test_method
**Run tests by marker expressions**

View File

@@ -56,17 +56,23 @@ The remaining hook functions will not be called in this case.
.. _`hookwrapper`:
hook wrappers: executing around other hooks
hookwrapper: executing around other hooks
-------------------------------------------------
.. currentmodule:: _pytest.core
pytest plugins can implement hook wrappers which wrap the execution
of other hook implementations. A hook wrapper is a generator function
which yields exactly once. When pytest invokes hooks it first executes
hook wrappers and passes the same arguments as to the regular hooks.
At the yield point of the hook wrapper pytest will execute the next hook
implementations and return their result to the yield point, or will
propagate an exception if they raised.
implementations and return their result to the yield point in the form of
a :py:class:`Result <pluggy._Result>` instance which encapsulates a result or
exception info. The yield point itself will thus typically not raise
exceptions (unless there are bugs).
Here is an example definition of a hook wrapper:
@@ -75,35 +81,26 @@ Here is an example definition of a hook wrapper:
import pytest
@pytest.hookimpl(wrapper=True)
@pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
do_something_before_next_hook_executes()
# If the outcome is an exception, will raise the exception.
res = yield
outcome = yield
# outcome.excinfo may be None or a (cls, val, tb) tuple
new_res = post_process_result(res)
res = outcome.get_result() # will raise if outcome was exception
# Override the return value to the plugin system.
return new_res
post_process_result(res)
The hook wrapper needs to return a result for the hook, or raise an exception.
outcome.force_result(new_res) # to override the return value to the plugin system
In many cases, the wrapper only needs to perform tracing or other side effects
around the actual hook implementations, in which case it can return the result
value of the ``yield``. The simplest (though useless) hook wrapper is
``return (yield)``.
In other cases, the wrapper wants the adjust or adapt the result, in which case
it can return a new value. If the result of the underlying hook is a mutable
object, the wrapper may modify that result, but it's probably better to avoid it.
If the hook implementation failed with an exception, the wrapper can handle that
exception using a ``try-catch-finally`` around the ``yield``, by propagating it,
supressing it, or raising a different exception entirely.
Note that hook wrappers don't return results themselves, they merely
perform tracing or other side effects around the actual hook implementations.
If the result of the underlying hook is a mutable object, they may modify
that result but it's probably better to avoid it.
For more information, consult the
:ref:`pluggy documentation about hook wrappers <pluggy:hookwrappers>`.
:ref:`pluggy documentation about hookwrappers <pluggy:hookwrappers>`.
.. _plugin-hookorder:
@@ -133,14 +130,11 @@ after others, i.e. the position in the ``N``-sized list of functions:
# Plugin 3
@pytest.hookimpl(wrapper=True)
@pytest.hookimpl(hookwrapper=True)
def pytest_collection_modifyitems(items):
# will execute even before the tryfirst one above!
try:
return (yield)
finally:
# will execute after all non-wrappers executed
...
outcome = yield
# will execute after all non-hookwrappers executed
Here is the order of execution:
@@ -155,13 +149,13 @@ Here is the order of execution:
Plugin1).
4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
point. The yield receives the result from calling the non-wrappers, or raises
an exception if the non-wrappers raised.
point. The yield receives a :py:class:`Result <pluggy._Result>` instance which encapsulates
the result from calling the non-wrappers. Wrappers shall not modify the result.
It's possible to use ``tryfirst`` and ``trylast`` also on hook wrappers
in which case it will influence the ordering of hook wrappers among each other.
It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
``hookwrapper=True`` in which case it will influence the ordering of hookwrappers
among each other.
.. _`declaringhooks`:
Declaring new hooks
------------------------
@@ -171,11 +165,13 @@ Declaring new hooks
This is a quick overview on how to add new hooks and how they work in general, but a more complete
overview can be found in `the pluggy documentation <https://pluggy.readthedocs.io/en/latest/>`__.
.. currentmodule:: _pytest.hookspec
Plugins and ``conftest.py`` files may declare new hooks that can then be
implemented by other plugins in order to alter behaviour or interact with
the new plugin:
.. autofunction:: _pytest.hookspec.pytest_addhooks
.. autofunction:: pytest_addhooks
:noindex:
Hooks are usually declared as do-nothing functions that contain only

View File

@@ -46,18 +46,24 @@ Plugin discovery order at tool startup
5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable.
6. by loading all "initial ":file:`conftest.py` files:
6. by loading all :file:`conftest.py` files as inferred by the command line
invocation:
- determine the test paths: specified on the command line, otherwise in
:confval:`testpaths` if defined and running from the rootdir, otherwise the
current dir
- for each test path, load ``conftest.py`` and ``test*/conftest.py`` relative
to the directory part of the test path, if exist. Before a ``conftest.py``
file is loaded, load ``conftest.py`` files in all of its parent directories.
After a ``conftest.py`` file is loaded, recursively load all plugins specified
in its :globalvar:`pytest_plugins` variable if present.
- if no test paths are specified, use the current dir as a test path
- if exists, load ``conftest.py`` and ``test*/conftest.py`` relative
to the directory part of the first test path. After the ``conftest.py``
file is loaded, load all plugins specified in its
:globalvar:`pytest_plugins` variable if present.
Note that pytest does not find ``conftest.py`` files in deeper nested
sub directories at tool startup. It is usually a good idea to keep
your ``conftest.py`` file in the top level test or project root directory.
7. by recursively loading all plugins specified by the
:globalvar:`pytest_plugins` variable in ``conftest.py`` files.
.. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/
.. _`conftest.py plugins`:
.. _`localplugin`:
.. _`local conftest plugins`:
@@ -102,9 +108,9 @@ Here is how you might run it::
See also: :ref:`pythonpath`.
.. note::
Some hooks cannot be implemented in conftest.py files which are not
:ref:`initial <pluginorder>` due to how pytest discovers plugins during
startup. See the documentation of each hook for details.
Some hooks should be implemented only in plugins or conftest.py files situated at the
tests root directory due to how pytest discovers plugins during startup,
see the documentation of each hook for details.
Writing your own plugin
-----------------------
@@ -442,7 +448,7 @@ in our ``pytest.ini`` to tell pytest where to look for example files.
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
configfile: pytest.ini
collected 2 items

View File

@@ -20,7 +20,7 @@ The ``pytest`` framework makes it easy to write small, readable tests, and can
scale to support complex functional testing for applications and libraries.
``pytest`` requires: Python 3.8+ or PyPy3.
``pytest`` requires: Python 3.7+ or PyPy3.
**PyPI package name**: :pypi:`pytest`
@@ -45,7 +45,7 @@ To execute it:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
@@ -77,11 +77,11 @@ Features
- :ref:`Modular fixtures <fixture>` for managing small or parametrized long-lived test resources
- Can run :ref:`unittest <unittest>` (including trial) test suites out of the box
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box
- Python 3.8+ or PyPy 3
- Python 3.7+ or PyPy 3
- Rich plugin architecture, with over 1300+ :ref:`external plugins <plugin-list>` and thriving community
- Rich plugin architecture, with over 800+ :ref:`external plugins <plugin-list>` and thriving community
Documentation

View File

@@ -177,20 +177,13 @@ Files will only be matched for configuration if:
* ``tox.ini``: contains a ``[pytest]`` section.
* ``setup.cfg``: contains a ``[tool:pytest]`` section.
Finally, a ``pyproject.toml`` file will be considered the ``configfile`` if no other match was found, in this case
even if it does not contain a ``[tool.pytest.ini_options]`` table (this was added in ``8.1``).
The files are considered in the order above. Options from multiple ``configfiles`` candidates
are never merged - the first match wins.
The configuration file also determines the value of the ``rootpath``.
The :class:`Config <pytest.Config>` object (accessible via hooks or through the :fixture:`pytestconfig` fixture)
will subsequently carry these attributes:
- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist. It is used as
a reference directory for constructing test addresses ("nodeids") and can be used also by plugins for storing
per-testrun information.
- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist.
- :attr:`config.inipath <pytest.Config.inipath>`: the determined ``configfile``, may be ``None``
(it is named ``inipath`` for historical reasons).
@@ -200,7 +193,9 @@ will subsequently carry these attributes:
versions of the older ``config.rootdir`` and ``config.inifile``, which have type
``py.path.local``, and still exist for backward compatibility.
The ``rootdir`` is used as a reference directory for constructing test
addresses ("nodeids") and can be used also by plugins for storing
per-testrun information.
Example:

View File

@@ -11,6 +11,9 @@ Fixtures reference
.. seealso:: :ref:`about-fixtures`
.. seealso:: :ref:`how-to-fixtures`
.. currentmodule:: _pytest.python
.. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection
@@ -73,13 +76,15 @@ Built-in fixtures
:class:`pathlib.Path` objects.
:fixture:`tmpdir`
Provide a `py.path.local <https://py.readthedocs.io/en/latest/path.html>`_ object to a temporary
Provide a :class:`py.path.local` object to a temporary
directory which is unique to each test function;
replaced by :fixture:`tmp_path`.
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
:fixture:`tmpdir_factory`
Make session-scoped temporary directories and return
``py.path.local`` objects;
:class:`py.path.local` objects;
replaced by :fixture:`tmp_path_factory`.
@@ -93,7 +98,7 @@ Fixture availability is determined from the perspective of the test. A fixture
is only available for tests to request if they are in the scope that fixture is
defined in. If a fixture is defined inside a class, it can only be requested by
tests inside that class. But if a fixture is defined inside the global scope of
the module, then every test in that module, even if it's defined inside a class,
the module, than every test in that module, even if it's defined inside a class,
can request it.
Similarly, a test can also only be affected by an autouse fixture if that test

File diff suppressed because it is too large Load Diff

View File

@@ -164,7 +164,8 @@ Add warning filters to marked test items.
.. code-block:: python
@pytest.mark.filterwarnings("ignore:.*usage will be deprecated.*:DeprecationWarning")
def test_foo(): ...
def test_foo():
...
.. _`pytest.mark.parametrize ref`:
@@ -238,23 +239,22 @@ pytest.mark.xfail
Marks a test function as *expected to fail*.
.. py:function:: pytest.mark.xfail(condition=False, *, reason=None, raises=None, run=True, strict=xfail_strict)
.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False)
:keyword Union[bool, str] condition:
:type condition: bool or str
:param condition:
Condition for marking the test function as xfail (``True/False`` or a
:ref:`condition string <string conditions>`). If a ``bool``, you also have
:ref:`condition string <string conditions>`). If a bool, you also have
to specify ``reason`` (see :ref:`condition string <string conditions>`).
:keyword str reason:
Reason why the test function is marked as xfail.
:keyword Type[Exception] raises:
Exception class (or tuple of classes) expected to be raised by the test function; other exceptions will fail the test.
Note that subclasses of the classes passed will also result in a match (similar to how the ``except`` statement works).
Exception subclass (or tuple of subclasses) expected to be raised by the test function; other exceptions will fail the test.
:keyword bool run:
Whether the test function should actually be executed. If ``False``, the function will always xfail and will
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).
:keyword bool strict:
* If ``False`` the function will be shown in the terminal output as ``xfailed`` if it fails
* If ``False`` (the default) the function will be shown in the terminal output as ``xfailed`` if it fails
and as ``xpass`` if it passes. In both cases this will not cause the test suite to fail as a whole. This
is particularly useful to mark *flaky* tests (tests that fail at random) to be tackled later.
* If ``True``, the function will be shown in the terminal output as ``xfailed`` if it fails, but if it
@@ -262,8 +262,6 @@ Marks a test function as *expected to fail*.
that are always failing and there should be a clear indication if they unexpectedly start to pass (for example
a new release of a library fixes a known bug).
Defaults to :confval:`xfail_strict`, which is ``False`` by default.
Custom marks
~~~~~~~~~~~~
@@ -275,7 +273,8 @@ For example:
.. code-block:: python
@pytest.mark.timeout(10, "slow", method="thread")
def test_function(): ...
def test_function():
...
Will create and attach a :class:`Mark <pytest.Mark>` object to the collected
:class:`Item <pytest.Item>`, which can then be accessed by fixtures or hooks with
@@ -292,7 +291,8 @@ Example for using multiple custom markers:
@pytest.mark.timeout(10, "slow", method="thread")
@pytest.mark.slow
def test_function(): ...
def test_function():
...
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(...)``.
@@ -609,30 +609,10 @@ Hooks
**Tutorial**: :ref:`writing-plugins`
Reference to all hooks which can be implemented by :ref:`conftest.py files <localplugin>` and :ref:`plugins <plugins>`.
@pytest.hookimpl
~~~~~~~~~~~~~~~~
.. function:: pytest.hookimpl
:decorator:
pytest's decorator for marking functions as hook implementations.
See :ref:`writinghooks` and :func:`pluggy.HookimplMarker`.
@pytest.hookspec
~~~~~~~~~~~~~~~~
.. function:: pytest.hookspec
:decorator:
pytest's decorator for marking functions as hook specifications.
See :ref:`declaringhooks` and :func:`pluggy.HookspecMarker`.
.. currentmodule:: _pytest.hookspec
Reference to all hooks which can be implemented by :ref:`conftest.py files <localplugin>` and :ref:`plugins <plugins>`.
Bootstrapping hooks
~~~~~~~~~~~~~~~~~~~
@@ -640,6 +620,8 @@ Bootstrapping hooks called for plugins registered early enough (internal and set
.. 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
@@ -677,8 +659,6 @@ Collection hooks
.. autofunction:: pytest_collection
.. hook:: pytest_ignore_collect
.. autofunction:: pytest_ignore_collect
.. hook:: pytest_collect_directory
.. autofunction:: pytest_collect_directory
.. hook:: pytest_collect_file
.. autofunction:: pytest_collect_file
.. hook:: pytest_pycollect_makemodule
@@ -818,7 +798,6 @@ Node
.. autoclass:: _pytest.nodes.Node()
:members:
:show-inheritance:
Collector
~~~~~~~~~
@@ -918,18 +897,6 @@ Config
.. autoclass:: pytest.Config()
:members:
Dir
~~~
.. autoclass:: pytest.Dir()
:members:
Directory
~~~~~~~~~
.. autoclass:: pytest.Directory()
:members:
ExceptionInfo
~~~~~~~~~~~~~
@@ -947,7 +914,7 @@ ExitCode
FixtureDef
~~~~~~~~~~
.. autoclass:: pytest.FixtureDef()
.. autoclass:: _pytest.fixtures.FixtureDef()
:members:
:show-inheritance:
@@ -1155,22 +1122,19 @@ When set to ``0``, pytest will not use color.
.. envvar:: NO_COLOR
When set to a non-empty string (regardless of value), pytest will not use color in terminal output.
When set (regardless of value), pytest will not use color in terminal output.
``PY_COLORS`` takes precedence over ``NO_COLOR``, which takes precedence over ``FORCE_COLOR``.
See `no-color.org <https://no-color.org/>`__ for other libraries supporting this community standard.
.. envvar:: FORCE_COLOR
When set to a non-empty string (regardless of value), pytest will use color in terminal output.
When set (regardless of value), pytest will use color in terminal output.
``PY_COLORS`` and ``NO_COLOR`` take precedence over ``FORCE_COLOR``.
Exceptions
----------
.. autoexception:: pytest.UsageError()
:show-inheritance:
.. autoexception:: pytest.FixtureLookupError()
.. autoclass:: pytest.UsageError()
:show-inheritance:
.. _`warnings ref`:
@@ -1204,7 +1168,7 @@ Custom warnings generated in some situations such as improper usage or deprecate
.. autoclass:: pytest.PytestReturnNotNoneWarning
:show-inheritance:
.. autoclass:: pytest.PytestRemovedIn9Warning
.. autoclass:: pytest.PytestRemovedIn8Warning
:show-inheritance:
.. autoclass:: pytest.PytestUnhandledCoroutineWarning
@@ -1274,19 +1238,6 @@ passed multiple times. The expected format is ``name=value``. For example::
variables, that will be expanded. For more information about cache plugin
please refer to :ref:`cache_provider`.
.. confval:: consider_namespace_packages
Controls if pytest should attempt to identify `namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages>`__
when collecting Python modules. Default is ``False``.
Set to ``True`` if you are testing namespace packages installed into a virtual environment and it is important for
your packages to be imported using their full namespace package name.
Only `native namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages/#native-namespace-packages>`__
are supported, with no plans to support `legacy namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages/#legacy-namespace-packages>`__.
.. versionadded:: 8.1
.. confval:: console_output_style
Sets the console output style while running tests:
@@ -1686,11 +1637,11 @@ passed multiple times. The expected format is ``name=value``. For example::
Additionally, ``pytest`` will attempt to intelligently identify and ignore a
virtualenv by the presence of an activation script. Any directory deemed to
be the root of a virtual environment will not be considered during test
collection unless ``--collect-in-virtualenv`` is given. Note also that
``norecursedirs`` takes precedence over ``--collect-in-virtualenv``; e.g. if
collection unless ``collectinvirtualenv`` is given. Note also that
``norecursedirs`` takes precedence over ``collectinvirtualenv``; e.g. if
you intend to run tests in a virtualenv with a base directory that matches
``'.*'`` you *must* override ``norecursedirs`` in addition to using the
``--collect-in-virtualenv`` flag.
``collectinvirtualenv`` flag.
.. confval:: python_classes
@@ -1865,32 +1816,6 @@ passed multiple times. The expected format is ``name=value``. For example::
clean_db
.. confval:: verbosity_assertions
Set a verbosity level specifically for assertion related output, overriding the application wide level.
.. code-block:: ini
[pytest]
verbosity_assertions = 2
Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
"auto" can be used to explicitly use the global verbosity level.
.. confval:: verbosity_test_cases
Set a verbosity level specifically for test case execution related output, overriding the application wide level.
.. code-block:: ini
[pytest]
verbosity_test_cases = 2
Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
"auto" can be used to explicitly use the global verbosity level.
.. confval:: xfail_strict
If set to ``True``, tests marked with ``@pytest.mark.xfail`` that actually succeed will by default fail the
@@ -2054,7 +1979,7 @@ All the command-line flags can be obtained by running ``pytest --help``::
failure
--doctest-glob=pat Doctests file matching pattern, default: test*.txt
--doctest-ignore-import-errors
Ignore doctest collection errors
Ignore doctest ImportErrors
--doctest-continue-on-failure
For a given doctest, continue to run after the first
failure
@@ -2103,8 +2028,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
--log-cli-date-format=LOG_CLI_DATE_FORMAT
Log date format used by the logging module
--log-file=LOG_FILE Path to a file when logging will be written to
--log-file-mode={w,a}
Log file open mode
--log-file-level=LOG_FILE_LEVEL
Log file logging level
--log-file-format=LOG_FILE_FORMAT
@@ -2120,7 +2043,7 @@ All the command-line flags can be obtained by running ``pytest --help``::
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:
markers (linelist): Register new 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
@@ -2130,9 +2053,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
Each line specifies a pattern for
warnings.filterwarnings. Processed after
-W/--pythonwarnings.
consider_namespace_packages (bool):
Consider namespace packages when resolving module
names during import
usefixtures (args): List of default fixtures to be used with this
project
python_files (args): Glob-style file patterns for Python test module
@@ -2151,11 +2071,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
progress information ("progress" (percentage) |
"count" | "progress-even-when-capture-no" (forces
progress even when capture=no)
verbosity_test_cases (string):
Specify a verbosity level for test case execution,
overriding the main level. Higher levels will
provide more detailed information about each test
case executed.
xfail_strict (bool): Default for the strict parameter of xfail markers
when not given explicitly (default: False)
tmp_path_retention_count (string):
@@ -2169,10 +2084,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
enable_assertion_pass_hook (bool):
Enables the pytest_assertion_pass hook. Make sure to
delete any previously generated pyc cache files.
verbosity_assertions (string):
Specify a verbosity level for assertions, overriding
the main level. Higher levels will provide more
detailed explanation when an assertion fails.
junit_suite_name (string):
Test suite name for JUnit report
junit_logging (string):
@@ -2203,8 +2114,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
log_cli_date_format (string):
Default value for --log-cli-date-format
log_file (string): Default value for --log-file
log_file_mode (string):
Default value for --log-file-mode
log_file_level (string):
Default value for --log-file-level
log_file_format (string):

View File

@@ -1,8 +1,8 @@
pallets-sphinx-themes
pluggy>=1.2.0
pluggy>=1.0
pygments-pytest>=2.3.0
sphinx-removed-in>=0.2.0
sphinx>=7
sphinx>=5,<6
sphinxcontrib-trio
sphinxcontrib-svg2pdfconverter
# Pin packaging because it no longer handles 'latest' version, which

View File

@@ -3,7 +3,6 @@ from pathlib import Path
import requests
issues_url = "https://api.github.com/repos/pytest-dev/pytest/issues"

View File

@@ -1,178 +1,14 @@
[project]
name = "pytest"
description = "pytest: simple powerful testing with Python"
readme = "README.rst"
keywords = [
"test",
"unittest",
]
license = {text = "MIT"}
authors = [
{name = "Holger Krekel"},
{name = "Bruno Oliveira"},
{name = "Ronny Pfannschmidt"},
{name = "Floris Bruynooghe"},
{name = "Brianna Laugher"},
{name = "Florian Bruhin"},
{name = "Others (See AUTHORS)"},
]
requires-python = ">=3.8"
classifiers = [
"Development Status :: 6 - Mature",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX",
"Operating System :: Unix",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Testing",
"Topic :: Utilities",
]
dynamic = [
"version",
]
dependencies = [
'colorama; sys_platform == "win32"',
'exceptiongroup>=1.0.0rc8; python_version < "3.11"',
"iniconfig",
"packaging",
"pluggy<2.0,>=1.4",
'tomli>=1; python_version < "3.11"',
]
[project.optional-dependencies]
testing = [
"argcomplete",
"attrs>=19.2",
"hypothesis>=3.56",
"mock",
"pygments>=2.7.2",
"requests",
"setuptools",
"xmlschema",
]
[project.urls]
Changelog = "https://docs.pytest.org/en/stable/changelog.html"
Homepage = "https://docs.pytest.org/en/latest/"
Source = "https://github.com/pytest-dev/pytest"
Tracker = "https://github.com/pytest-dev/pytest/issues"
Twitter = "https://twitter.com/pytestdotorg"
[project.scripts]
"py.test" = "pytest:console_main"
pytest = "pytest:console_main"
[build-system]
build-backend = "setuptools.build_meta"
requires = [
"setuptools>=61",
"setuptools-scm[toml]>=6.2.3",
# sync with setup.py until we discard non-pep-517/518
"setuptools>=45.0",
"setuptools-scm[toml]>=6.2.3",
]
[tool.setuptools.package-data]
"_pytest" = ["py.typed"]
"pytest" = ["py.typed"]
build-backend = "setuptools.build_meta"
[tool.setuptools_scm]
write_to = "src/_pytest/_version.py"
[tool.black]
target-version = ['py38']
[tool.ruff]
src = ["src"]
line-length = 88
[tool.ruff.format]
docstring-code-format = true
[tool.ruff.lint]
select = [
"B", # bugbear
"D", # pydocstyle
"E", # pycodestyle
"F", # pyflakes
"I", # isort
"PYI", # flake8-pyi
"UP", # pyupgrade
"RUF", # ruff
"W", # pycodestyle
"PIE", # flake8-pie
"PGH004", # pygrep-hooks - Use specific rule codes when using noqa
"PLE", # pylint error
"PLW", # pylint warning
"PLR1714", # Consider merging multiple comparisons
]
ignore = [
# bugbear ignore
"B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable.
"B007", # Loop control variable `i` not used within loop body
"B009", # Do not call `getattr` with a constant attribute value
"B010", # [*] Do not call `setattr` with a constant attribute value.
"B011", # Do not `assert False` (`python -O` removes these calls)
"B028", # No explicit `stacklevel` keyword argument found
# pycodestyle ignore
# pytest can do weird low-level things, and we usually know
# what we're doing when we use type(..) is ...
"E721", # Do not compare types, use `isinstance()`
# pydocstyle ignore
"D100", # Missing docstring in public module
"D101", # Missing docstring in public class
"D102", # Missing docstring in public method
"D103", # Missing docstring in public function
"D104", # Missing docstring in public package
"D105", # Missing docstring in magic method
"D106", # Missing docstring in public nested class
"D107", # Missing docstring in `__init__`
"D209", # [*] Multi-line docstring closing quotes should be on a separate line
"D205", # 1 blank line required between summary line and description
"D400", # First line should end with a period
"D401", # First line of docstring should be in imperative mood
"D402", # First line should not be the function's signature
"D404", # First word of the docstring should not be "This"
"D415", # First line should end with a period, question mark, or exclamation point
# ruff ignore
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
# pylint ignore
"PLW0603", # Using the global statement
"PLW0120", # remove the else and dedent its contents
"PLW2901", # for loop variable overwritten by assignment target
"PLR5501", # Use `elif` instead of `else` then `if`
]
[tool.ruff.lint.pycodestyle]
# In order to be able to format for 88 char in ruff format
max-line-length = 120
[tool.ruff.lint.pydocstyle]
convention = "pep257"
[tool.ruff.lint.isort]
force-single-line = true
combine-as-imports = true
force-sort-within-sections = true
order-by-type = false
known-local-folder = ["pytest", "_pytest"]
lines-after-imports = 2
[tool.ruff.lint.per-file-ignores]
"src/_pytest/_py/**/*.py" = ["B", "PYI"]
"src/_pytest/_version.py" = ["I001"]
"testing/python/approx.py" = ["B015"]
[tool.check-wheel-contents]
# check-wheel-contents is executed by the build-and-inspect-python-package action.
# W009: Wheel contains multiple toplevel library entries
ignore = "W009"
[tool.pyproject-fmt]
indent = 4
[tool.pytest.ini_options]
minversion = "2.0"
addopts = "-rfEX -p pytester --strict-markers"
@@ -181,12 +17,7 @@ python_classes = ["Test", "Acceptance"]
python_functions = ["test"]
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
testpaths = ["testing"]
norecursedirs = [
"testing/example_scripts",
".*",
"build",
"dist",
]
norecursedirs = ["testing/example_scripts"]
xfail_strict = true
filterwarnings = [
"error",
@@ -232,6 +63,7 @@ markers = [
"uses_pexpect",
]
[tool.towncrier]
package = "pytest"
package_dir = "src"
@@ -280,16 +112,10 @@ template = "changelog/_template.rst"
name = "Trivial/Internal Changes"
showcontent = true
[tool.mypy]
mypy_path = ["src"]
check_untyped_defs = true
disallow_any_generics = true
disallow_untyped_defs = true
ignore_missing_imports = true
show_error_codes = true
strict_equality = true
warn_redundant_casts = true
warn_return_any = true
warn_unreachable = true
warn_unused_configs = true
no_implicit_reexport = true
[tool.black]
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"

1
scripts/.gitignore vendored
View File

@@ -1 +0,0 @@
latest-release-notes.md

View File

@@ -1,66 +0,0 @@
# mypy: disallow-untyped-defs
"""
Script used to generate a Markdown file containing only the changelog entries of a specific pytest release, which
is then published as a GitHub Release during deploy (see workflows/deploy.yml).
The script requires ``pandoc`` to be previously installed in the system -- we need to convert from RST (the format of
our CHANGELOG) into Markdown (which is required by GitHub Releases).
Requires Python3.6+.
"""
from pathlib import Path
import re
import sys
from typing import Sequence
import pypandoc
def extract_changelog_entries_for(version: str) -> str:
p = Path(__file__).parent.parent / "doc/en/changelog.rst"
changelog_lines = p.read_text(encoding="UTF-8").splitlines()
title_regex = re.compile(r"pytest (\d\.\d+\.\d+\w*) \(\d{4}-\d{2}-\d{2}\)")
consuming_version = False
version_lines = []
for line in changelog_lines:
m = title_regex.match(line)
if m:
# Found the version we want: start to consume lines until we find the next version title.
if m.group(1) == version:
consuming_version = True
# Found a new version title while parsing the version we want: break out.
elif consuming_version:
break
if consuming_version:
version_lines.append(line)
return "\n".join(version_lines)
def convert_rst_to_md(text: str) -> str:
result = pypandoc.convert_text(
text, "md", format="rst", extra_args=["--wrap=preserve"]
)
assert isinstance(result, str), repr(result)
return result
def main(argv: Sequence[str]) -> int:
if len(argv) != 3:
print("Usage: generate-gh-release-notes VERSION FILE")
return 2
version, filename = argv[1:3]
print(f"Generating GitHub release notes for version {version}")
rst_body = extract_changelog_entries_for(version)
md_body = convert_rst_to_md(rst_body)
Path(filename).write_text(md_body, encoding="UTF-8")
print()
print(f"Done: {filename}")
print()
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))

View File

@@ -1,4 +1,3 @@
# mypy: disallow-untyped-defs
"""
This script is part of the pytest release process which is triggered manually in the Actions
tab of the repository.
@@ -14,8 +13,8 @@ After that, it will create a release using the `release` tox environment, and pu
`pytest bot <pytestbot@gmail.com>` commit author.
"""
import argparse
from pathlib import Path
import re
from pathlib import Path
from subprocess import check_call
from subprocess import check_output
from subprocess import run
@@ -79,7 +78,7 @@ def prepare_release_pr(
)
except InvalidFeatureRelease as e:
print(f"{Fore.RED}{e}")
raise SystemExit(1) from None
raise SystemExit(1)
print(f"Version: {Fore.CYAN}{version}")

View File

@@ -0,0 +1,102 @@
"""
Script used to publish GitHub release notes extracted from CHANGELOG.rst.
This script is meant to be executed after a successful deployment in GitHub actions.
Uses the following environment variables:
* GIT_TAG: the name of the tag of the current commit.
* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions.
Create one at:
https://github.com/settings/tokens
This token should be set in a secret in the repository, which is exposed as an
environment variable in the main.yml workflow file.
The script also requires ``pandoc`` to be previously installed in the system.
Requires Python3.6+.
"""
import os
import re
import sys
from pathlib import Path
import github3
import pypandoc
def publish_github_release(slug, token, tag_name, body):
github = github3.login(token=token)
owner, repo = slug.split("/")
repo = github.repository(owner, repo)
return repo.create_release(tag_name=tag_name, body=body)
def parse_changelog(tag_name):
p = Path(__file__).parent.parent / "doc/en/changelog.rst"
changelog_lines = p.read_text(encoding="UTF-8").splitlines()
title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)")
consuming_version = False
version_lines = []
for line in changelog_lines:
m = title_regex.match(line)
if m:
# found the version we want: start to consume lines until we find the next version title
if m.group(1) == tag_name:
consuming_version = True
# found a new version title while parsing the version we want: break out
elif consuming_version:
break
if consuming_version:
version_lines.append(line)
return "\n".join(version_lines)
def convert_rst_to_md(text):
return pypandoc.convert_text(
text, "md", format="rst", extra_args=["--wrap=preserve"]
)
def main(argv):
if len(argv) > 1:
tag_name = argv[1]
else:
tag_name = os.environ.get("GITHUB_REF")
if not tag_name:
print("tag_name not given and $GITHUB_REF not set", file=sys.stderr)
return 1
if tag_name.startswith("refs/tags/"):
tag_name = tag_name[len("refs/tags/") :]
token = os.environ.get("GH_RELEASE_NOTES_TOKEN")
if not token:
print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr)
return 1
slug = os.environ.get("GITHUB_REPOSITORY")
if not slug:
print("GITHUB_REPOSITORY not set", file=sys.stderr)
return 1
rst_body = parse_changelog(tag_name)
md_body = convert_rst_to_md(rst_body)
if not publish_github_release(slug, token, tag_name, md_body):
print("Could not publish release notes:", file=sys.stderr)
print(md_body, file=sys.stderr)
return 5
print()
print(f"Release notes for {tag_name} published successfully:")
print(f"https://github.com/{slug}/releases/tag/{tag_name}")
print()
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))

View File

@@ -1,4 +1,3 @@
# mypy: disallow-untyped-defs
"""Invoke development tasks."""
import argparse
import os
@@ -11,15 +10,15 @@ from colorama import Fore
from colorama import init
def announce(version: str, template_name: str, doc_version: str) -> None:
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"], encoding="UTF-8")
stdout = check_output(["git", "describe", "--abbrev=0", "--tags"])
stdout = stdout.decode("utf-8")
last_version = stdout.strip()
stdout = check_output(
["git", "log", f"{last_version}..HEAD", "--format=%aN"], encoding="UTF-8"
)
stdout = check_output(["git", "log", f"{last_version}..HEAD", "--format=%aN"])
stdout = stdout.decode("utf-8")
contributors = {
name
@@ -62,7 +61,7 @@ def announce(version: str, template_name: str, doc_version: str) -> None:
check_call(["git", "add", str(target)])
def regen(version: str) -> None:
def regen(version):
"""Call regendoc tool to update examples and pytest output in the docs."""
print(f"{Fore.CYAN}[generate.regen] {Fore.RESET}Updating docs")
check_call(
@@ -71,7 +70,7 @@ def regen(version: str) -> None:
)
def fix_formatting() -> None:
def fix_formatting():
"""Runs pre-commit in all files to ensure they are formatted correctly"""
print(
f"{Fore.CYAN}[generate.fix linting] {Fore.RESET}Fixing formatting using pre-commit"
@@ -79,15 +78,13 @@ def fix_formatting() -> None:
call(["pre-commit", "run", "--all-files"])
def check_links() -> None:
def check_links():
"""Runs sphinx-build to check links"""
print(f"{Fore.CYAN}[generate.check_links] {Fore.RESET}Checking links")
check_call(["tox", "-e", "docs-checklinks"])
def pre_release(
version: str, template_name: str, doc_version: str, *, skip_check_links: bool
) -> None:
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, doc_version)
regen(version)
@@ -105,12 +102,12 @@ def pre_release(
print("Please push your branch and open a PR.")
def changelog(version: str, write_out: bool = False) -> None:
def changelog(version, write_out=False):
addopts = [] if write_out else ["--draft"]
check_call(["towncrier", "--yes", "--version", version, *addopts])
check_call(["towncrier", "--yes", "--version", version] + addopts)
def main() -> None:
def main():
init(autoreset=True)
parser = argparse.ArgumentParser()
parser.add_argument("version", help="Release version")

View File

@@ -1,12 +1,11 @@
# mypy: disallow-untyped-defs
from subprocess import call
import sys
from subprocess import call
def main() -> int:
def main():
"""
Platform-agnostic wrapper script for towncrier.
Fixes the issue (#7251) where Windows users are unable to natively run tox -e docs to build pytest docs.
Platform agnostic wrapper script for towncrier.
Fixes the issue (#7251) where windows users are unable to natively run tox -e docs to build pytest docs.
"""
with open(
"doc/en/_changelog_towncrier_draft.rst", "w", encoding="utf-8"

View File

@@ -1,35 +1,23 @@
# mypy: disallow-untyped-defs
import datetime
import pathlib
import re
from textwrap import dedent
from textwrap import indent
from typing import Any
from typing import Iterable
from typing import Iterator
from typing import TypedDict
import packaging.version
import platformdirs
from requests_cache import CachedResponse
from requests_cache import CachedSession
from requests_cache import OriginalResponse
from requests_cache import SQLiteCache
import requests
import tabulate
from tqdm import tqdm
import wcwidth
from tqdm import tqdm
FILE_HEAD = r"""
.. Note this file is autogenerated by scripts/update-plugin-list.py - usually weekly via github action
.. _plugin-list:
Pytest Plugin List
==================
Below is an automated compilation of ``pytest``` plugins available on `PyPI <https://pypi.org>`_.
It includes PyPI projects whose names begin with ``pytest-`` or ``pytest_`` and a handful of manually selected projects.
It includes PyPI projects whose names begin with "pytest-" and a handful of manually selected projects.
Packages classified as inactive are excluded.
For detailed insights into how this list is generated,
@@ -61,9 +49,6 @@ DEVELOPMENT_STATUS_CLASSIFIERS = (
)
ADDITIONAL_PROJECTS = { # set of additional projects to consider as plugins
"logassert",
"logot",
"nuts",
"flask_fixture",
}
@@ -80,59 +65,19 @@ def escape_rst(text: str) -> str:
return text
def project_response_with_refresh(
session: CachedSession, name: str, last_serial: int
) -> OriginalResponse | CachedResponse:
"""Get a http cached pypi project
def iter_plugins():
regex = r">([\d\w-]*)</a>"
response = requests.get("https://pypi.org/simple")
force refresh in case of last serial mismatch
"""
response = session.get(f"https://pypi.org/pypi/{name}/json")
if int(response.headers.get("X-PyPI-Last-Serial", -1)) != last_serial:
response = session.get(f"https://pypi.org/pypi/{name}/json", refresh=True)
return response
match_names = (match.groups()[0] for match in re.finditer(regex, response.text))
plugin_names = [
name
for name in match_names
if name.startswith("pytest-") or name in ADDITIONAL_PROJECTS
]
def get_session() -> CachedSession:
"""Configures the requests-cache session"""
cache_path = platformdirs.user_cache_path("pytest-plugin-list")
cache_path.mkdir(exist_ok=True, parents=True)
cache_file = cache_path.joinpath("http_cache.sqlite3")
return CachedSession(backend=SQLiteCache(cache_file))
def pytest_plugin_projects_from_pypi(session: CachedSession) -> dict[str, int]:
response = session.get(
"https://pypi.org/simple",
headers={"Accept": "application/vnd.pypi.simple.v1+json"},
refresh=True,
)
return {
name: p["_last-serial"]
for p in response.json()["projects"]
if (
(name := p["name"]).startswith(("pytest-", "pytest_"))
or name in ADDITIONAL_PROJECTS
)
}
class PluginInfo(TypedDict):
"""Relevant information about a plugin to generate the summary."""
name: str
summary: str
last_release: str
status: str
requires: str
def iter_plugins() -> Iterator[PluginInfo]:
session = get_session()
name_2_serial = pytest_plugin_projects_from_pypi(session)
for name, last_serial in tqdm(name_2_serial.items(), smoothing=0):
response = project_response_with_refresh(session, name, last_serial)
for name in tqdm(plugin_names, smoothing=0):
response = requests.get(f"https://pypi.org/pypi/{name}/json")
if response.status_code == 404:
# Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple
# but return 404 on the JSON API. Skip.
@@ -154,7 +99,7 @@ def iter_plugins() -> Iterator[PluginInfo]:
requires = requirement
break
def version_sort_key(version_string: str) -> Any:
def version_sort_key(version_string):
"""
Return the sort key for the given version string
returned by the API.
@@ -180,19 +125,20 @@ def iter_plugins() -> Iterator[PluginInfo]:
yield {
"name": name,
"summary": summary.strip(),
"last_release": last_release,
"last release": last_release,
"status": status,
"requires": requires,
}
def plugin_definitions(plugins: Iterable[PluginInfo]) -> Iterator[str]:
def plugin_definitions(plugins):
"""Return RST for the plugin list that fits better on a vertical page."""
for plugin in plugins:
yield dedent(
f"""
{plugin['name']}
*last release*: {plugin["last_release"]},
*last release*: {plugin["last release"]},
*status*: {plugin["status"]},
*requires*: {plugin["requires"]}
@@ -201,8 +147,8 @@ def plugin_definitions(plugins: Iterable[PluginInfo]) -> Iterator[str]:
)
def main() -> None:
plugins = [*iter_plugins()]
def main():
plugins = list(iter_plugins())
reference_dir = pathlib.Path("doc", "en", "reference")
@@ -212,7 +158,7 @@ def main() -> None:
f.write(f"This list contains {len(plugins)} plugins.\n\n")
f.write(".. only:: not latex\n\n")
_ = wcwidth # reference library that must exist for tabulate to work
wcwidth # reference library that must exist for tabulate to work
plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst")
f.write(indent(plugin_table, " "))
f.write("\n\n")

107
setup.cfg Normal file
View File

@@ -0,0 +1,107 @@
[metadata]
name = pytest
description = pytest: simple powerful testing with Python
long_description = file: README.rst
long_description_content_type = text/x-rst
url = https://docs.pytest.org/en/latest/
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
license = MIT
license_files = LICENSE
platforms = unix, linux, osx, cygwin, win32
classifiers =
Development Status :: 6 - Mature
Intended Audience :: Developers
License :: OSI Approved :: MIT License
Operating System :: MacOS :: MacOS X
Operating System :: Microsoft :: Windows
Operating System :: POSIX
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
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
Programming Language :: Python :: 3.12
Topic :: Software Development :: Libraries
Topic :: Software Development :: Testing
Topic :: Utilities
keywords = test, unittest
project_urls =
Changelog=https://docs.pytest.org/en/stable/changelog.html
Twitter=https://twitter.com/pytestdotorg
Source=https://github.com/pytest-dev/pytest
Tracker=https://github.com/pytest-dev/pytest/issues
[options]
packages =
_pytest
_pytest._code
_pytest._io
_pytest._py
_pytest.assertion
_pytest.config
_pytest.mark
pytest
py_modules = py
install_requires =
iniconfig
packaging
pluggy>=0.12,<2.0
colorama;sys_platform=="win32"
exceptiongroup>=1.0.0rc8;python_version<"3.11"
importlib-metadata>=0.12;python_version<"3.8"
tomli>=1.0.0;python_version<"3.11"
python_requires = >=3.7
package_dir =
=src
setup_requires =
setuptools
setuptools-scm>=6.0
zip_safe = no
[options.entry_points]
console_scripts =
pytest=pytest:console_main
py.test=pytest:console_main
[options.extras_require]
testing =
argcomplete
attrs>=19.2.0
hypothesis>=3.56
mock
nose
pygments>=2.7.2
requests
setuptools
xmlschema
[options.package_data]
_pytest = py.typed
pytest = py.typed
[build_sphinx]
source_dir = doc/en/
build_dir = doc/build
all_files = 1
[check-manifest]
ignore =
src/_pytest/_version.py
[devpi:upload]
formats = sdist.tgz,bdist_wheel
[mypy]
mypy_path = src
check_untyped_defs = True
disallow_any_generics = True
ignore_missing_imports = True
show_error_codes = True
strict_equality = True
warn_redundant_casts = True
warn_return_any = True
warn_unreachable = True
warn_unused_configs = True
no_implicit_reexport = True

4
setup.py Normal file
View File

@@ -0,0 +1,4 @@
from setuptools import setup
if __name__ == "__main__":
setup()

View File

@@ -1,8 +1,7 @@
__all__ = ["__version__", "version_tuple"]
try:
from ._version import version as __version__
from ._version import version_tuple
from ._version import version as __version__, version_tuple
except ImportError: # pragma: no cover
# broken installation, we don't even try
# unknown only works because we do poor mans version compare

View File

@@ -61,11 +61,10 @@ If things do not work right away:
which should throw a KeyError: 'COMPLINE' (which is properly set by the
global argcomplete script).
"""
import argparse
from glob import glob
import os
import sys
from glob import glob
from typing import Any
from typing import List
from typing import Optional

View File

@@ -1,5 +1,4 @@
"""Python inspection/code generation API."""
from .code import Code
from .code import ExceptionInfo
from .code import filter_traceback
@@ -10,7 +9,6 @@ from .code import TracebackEntry
from .source import getrawcode
from .source import Source
__all__ = [
"Code",
"ExceptionInfo",

View File

@@ -1,15 +1,14 @@
# mypy: allow-untyped-defs
import ast
import dataclasses
import inspect
from inspect import CO_VARARGS
from inspect import CO_VARKEYWORDS
from io import StringIO
import os
from pathlib import Path
import re
import sys
import traceback
from inspect import CO_VARARGS
from inspect import CO_VARKEYWORDS
from io import StringIO
from pathlib import Path
from traceback import format_exception_only
from types import CodeType
from types import FrameType
@@ -18,21 +17,18 @@ from typing import Any
from typing import Callable
from typing import ClassVar
from typing import Dict
from typing import Final
from typing import final
from typing import Generic
from typing import Iterable
from typing import List
from typing import Literal
from typing import Mapping
from typing import Optional
from typing import overload
from typing import Pattern
from typing import Sequence
from typing import Set
from typing import SupportsIndex
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
@@ -46,17 +42,22 @@ from _pytest._code.source import Source
from _pytest._io import TerminalWriter
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import final
from _pytest.compat import get_real_func
from _pytest.deprecated import check_ispytest
from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath
if TYPE_CHECKING:
from typing_extensions import Final
from typing_extensions import Literal
from typing_extensions import SupportsIndex
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
if sys.version_info[:2] < (3, 11):
from exceptiongroup import BaseExceptionGroup
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
class Code:
"""Wrapper around Python code objects."""
@@ -395,11 +396,11 @@ class Traceback(List[TracebackEntry]):
def filter(
self,
excinfo_or_fn: Union[
# TODO(py38): change to positional only.
_excinfo_or_fn: Union[
"ExceptionInfo[BaseException]",
Callable[[TracebackEntry], bool],
],
/,
) -> "Traceback":
"""Return a Traceback instance with certain items removed.
@@ -410,10 +411,10 @@ class Traceback(List[TracebackEntry]):
``TracebackEntry`` instance, and should return True when the item should
be added to the ``Traceback``, False when not.
"""
if isinstance(excinfo_or_fn, ExceptionInfo):
fn = lambda x: not x.ishidden(excinfo_or_fn) # noqa: E731
if isinstance(_excinfo_or_fn, ExceptionInfo):
fn = lambda x: not x.ishidden(_excinfo_or_fn) # noqa: E731
else:
fn = excinfo_or_fn
fn = _excinfo_or_fn
return Traceback(filter(fn, self))
def recursionindex(self) -> Optional[int]:
@@ -488,10 +489,9 @@ class ExceptionInfo(Generic[E]):
.. versionadded:: 7.4
"""
assert exception.__traceback__, (
"Exceptions passed to ExcInfo.from_exception(...)"
" must have a non-None __traceback__."
)
assert (
exception.__traceback__
), "Exceptions passed to ExcInfo.from_exception(...) must have a non-None __traceback__."
exc_info = (type(exception), exception, exception.__traceback__)
return cls.from_exc_info(exc_info, exprinfo)
@@ -590,7 +590,9 @@ class ExceptionInfo(Generic[E]):
def __repr__(self) -> str:
if self._excinfo is None:
return "<ExceptionInfo for raises contextmanager>"
return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>"
return "<{} {} tblen={}>".format(
self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback)
)
def exconly(self, tryshort: bool = False) -> str:
"""Return the exception as a string.
@@ -631,7 +633,7 @@ class ExceptionInfo(Generic[E]):
def getrepr(
self,
showlocals: bool = False,
style: _TracebackStyle = "long",
style: "_TracebackStyle" = "long",
abspath: bool = False,
tbfilter: Union[
bool, Callable[["ExceptionInfo[BaseException]"], Traceback]
@@ -698,25 +700,6 @@ class ExceptionInfo(Generic[E]):
)
return fmt.repr_excinfo(self)
def _stringify_exception(self, exc: BaseException) -> str:
try:
notes = getattr(exc, "__notes__", [])
except KeyError:
# Workaround for https://github.com/python/cpython/issues/98778 on
# Python <= 3.9, and some 3.10 and 3.11 patch versions.
HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ())
if sys.version_info[:2] <= (3, 11) and isinstance(exc, HTTPError):
notes = []
else:
raise
return "\n".join(
[
str(exc),
*notes,
]
)
def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]":
"""Check whether the regular expression `regexp` matches the string
representation of the exception using :func:`python:re.search`.
@@ -724,7 +707,7 @@ class ExceptionInfo(Generic[E]):
If it matches `True` is returned, otherwise an `AssertionError` is raised.
"""
__tracebackhide__ = True
value = self._stringify_exception(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?"
@@ -732,69 +715,6 @@ class ExceptionInfo(Generic[E]):
# Return True to allow for "assert excinfo.match()".
return True
def _group_contains(
self,
exc_group: BaseExceptionGroup[BaseException],
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
match: Union[str, Pattern[str], None],
target_depth: Optional[int] = None,
current_depth: int = 1,
) -> bool:
"""Return `True` if a `BaseExceptionGroup` contains a matching exception."""
if (target_depth is not None) and (current_depth > target_depth):
# already descended past the target depth
return False
for exc in exc_group.exceptions:
if isinstance(exc, BaseExceptionGroup):
if self._group_contains(
exc, expected_exception, match, target_depth, current_depth + 1
):
return True
if (target_depth is not None) and (current_depth != target_depth):
# not at the target depth, no match
continue
if not isinstance(exc, expected_exception):
continue
if match is not None:
value = self._stringify_exception(exc)
if not re.search(match, value):
continue
return True
return False
def group_contains(
self,
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
*,
match: Union[str, Pattern[str], None] = None,
depth: Optional[int] = None,
) -> bool:
"""Check whether a captured exception group contains a matching exception.
:param Type[BaseException] | Tuple[Type[BaseException]] expected_exception:
The expected exception type, or a tuple if one of multiple possible
exception types are expected.
:param str | 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 and its `PEP-678 <https://peps.python.org/pep-0678/>` `__notes__`
using :func:`re.search`.
To match a literal string that may contain :ref:`special characters
<re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
:param Optional[int] depth:
If `None`, will search for a matching exception at any nesting depth.
If >= 1, will only match an exception if it's at the specified depth (depth = 1 being
the exceptions contained within the topmost exception group).
"""
msg = "Captured exception is not an instance of `BaseExceptionGroup`"
assert isinstance(self.value, BaseExceptionGroup), msg
msg = "`depth` must be >= 1 if specified"
assert (depth is None) or (depth >= 1), msg
return self._group_contains(self.value, expected_exception, match, depth)
@dataclasses.dataclass
class FormattedExcinfo:
@@ -805,7 +725,7 @@ class FormattedExcinfo:
fail_marker: ClassVar = "E"
showlocals: bool = False
style: _TracebackStyle = "long"
style: "_TracebackStyle" = "long"
abspath: bool = True
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
funcargs: bool = False
@@ -1018,8 +938,13 @@ class FormattedExcinfo:
extraline: Optional[str] = (
"!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
" The following exception happened when comparing locals in the stack frame:\n"
f" {type(e).__name__}: {e!s}\n"
f" Displaying first and last {max_frames} stack frames out of {len(traceback)}."
" {exc_type}: {exc_msg}\n"
" Displaying first and last {max_frames} stack frames out of {total}."
).format(
exc_type=type(e).__name__,
exc_msg=str(e),
max_frames=max_frames,
total=len(traceback),
)
# Type ignored because adding two instances of a List subtype
# currently incorrectly has type List instead of the subtype.
@@ -1165,7 +1090,7 @@ class ReprExceptionInfo(ExceptionRepr):
class ReprTraceback(TerminalRepr):
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
extraline: Optional[str]
style: _TracebackStyle
style: "_TracebackStyle"
entrysep: ClassVar = "_ "
@@ -1199,7 +1124,7 @@ class ReprTracebackNative(ReprTraceback):
class ReprEntryNative(TerminalRepr):
lines: Sequence[str]
style: ClassVar[_TracebackStyle] = "native"
style: ClassVar["_TracebackStyle"] = "native"
def toterminal(self, tw: TerminalWriter) -> None:
tw.write("".join(self.lines))
@@ -1211,7 +1136,7 @@ class ReprEntry(TerminalRepr):
reprfuncargs: Optional["ReprFuncArgs"]
reprlocals: Optional["ReprLocals"]
reprfileloc: Optional["ReprFileLocation"]
style: _TracebackStyle
style: "_TracebackStyle"
def _write_entry_lines(self, tw: TerminalWriter) -> None:
"""Write the source code portions of a list of traceback entries with syntax highlighting.
@@ -1226,6 +1151,7 @@ class ReprEntry(TerminalRepr):
the "E" prefix) using syntax highlighting, taking care to not highlighting the ">"
character, as doing so might break line continuations.
"""
if not self.lines:
return

View File

@@ -1,10 +1,10 @@
# mypy: allow-untyped-defs
import ast
from bisect import bisect_right
import inspect
import textwrap
import tokenize
import types
import warnings
from bisect import bisect_right
from typing import Iterable
from typing import Iterator
from typing import List
@@ -12,7 +12,6 @@ from typing import Optional
from typing import overload
from typing import Tuple
from typing import Union
import warnings
class Source:
@@ -150,7 +149,8 @@ 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)):
# The lineno points to the class/def, so need to include the decorators.
# 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)
@@ -197,9 +197,7 @@ def getstatementrange_ast(
# by using the BlockFinder helper used which inspect.getsource() uses itself.
block_finder = inspect.BlockFinder()
# If we start with an indented line, put blockfinder to "started" mode.
block_finder.started = (
bool(source.lines[start]) and source.lines[start][0].isspace()
)
block_finder.started = source.lines[start][0].isspace()
it = ((x + "\n") for x in source.lines[start:end])
try:
for tok in tokenize.generate_tokens(lambda: next(it)):

Some files were not shown because too many files have changed in this diff Show More