Compare commits
38 Commits
main
...
backport-1
Author | SHA1 | Date |
---|---|---|
|
9205c946ff | |
|
53d2d4717d | |
|
56c0b1db3a | |
|
eb8721d387 | |
|
36f0ea10b4 | |
|
f3a494cca3 | |
|
329d371214 | |
|
214d098fcc | |
|
153a436bc4 | |
|
b41d5a52bb | |
|
9bb73d734f | |
|
4569a01e3d | |
|
1d103e5cdc | |
|
240a252d34 | |
|
a5ee3c4126 | |
|
f7358aec28 | |
|
558e4fa71a | |
|
f0e7a07667 | |
|
22e885f109 | |
|
bddbeba74b | |
|
66ff8dffdf | |
|
3ffcfd122c | |
|
0b28313b46 | |
|
f3dd93ad8d | |
|
bb5a1257b0 | |
|
f179bf252f | |
|
2b671b5f92 | |
|
65ab7cb96c | |
|
4d5fb7d71c | |
|
cbe5996cc6 | |
|
c9e9315725 | |
|
328001eab1 | |
|
8fdb72947e | |
|
5c1c73b961 | |
|
a152c2cee4 | |
|
69c3bcea36 | |
|
6bd3f31344 | |
|
9b6219b5e8 |
|
@ -29,5 +29,3 @@ exclude_lines =
|
||||||
|
|
||||||
^\s*if TYPE_CHECKING:
|
^\s*if TYPE_CHECKING:
|
||||||
^\s*@overload( |$)
|
^\s*@overload( |$)
|
||||||
|
|
||||||
^\s*@pytest\.mark\.xfail
|
|
||||||
|
|
|
@ -31,5 +31,3 @@ c9df77cbd6a365dcb73c39618e4842711817e871
|
||||||
4546d5445aaefe6a03957db028c263521dfb5c4b
|
4546d5445aaefe6a03957db028c263521dfb5c4b
|
||||||
# Migration to ruff / ruff format
|
# Migration to ruff / ruff format
|
||||||
4588653b2497ed25976b7aaff225b889fb476756
|
4588653b2497ed25976b7aaff225b889fb476756
|
||||||
# Use format specifiers instead of percent format
|
|
||||||
4788165e69d08e10fc6b9c0124083fb358e2e9b0
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
backport_branch_prefix: patchback/backports/
|
|
||||||
backport_label_prefix: 'backport ' # IMPORTANT: the labels are space-delimited
|
|
||||||
# target_branch_prefix: '' # The project's backport branches are non-prefixed
|
|
||||||
|
|
||||||
...
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: backport
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Note that `pull_request_target` has security implications:
|
||||||
|
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
|
||||||
|
# In particular:
|
||||||
|
# - Only allow triggers that can be used only be trusted users
|
||||||
|
# - Don't execute any code from the target branch
|
||||||
|
# - Don't use cache
|
||||||
|
pull_request_target:
|
||||||
|
types: [labeled]
|
||||||
|
|
||||||
|
# Set permissions at the job level.
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
backport:
|
||||||
|
if: startsWith(github.event.label.name, 'backport ') && github.event.pull_request.merged
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: true
|
||||||
|
|
||||||
|
- name: Create backport PR
|
||||||
|
run: |
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
git config --global user.name "pytest bot"
|
||||||
|
git config --global user.email "pytestbot@gmail.com"
|
||||||
|
|
||||||
|
label='${{ github.event.label.name }}'
|
||||||
|
target_branch="${label#backport }"
|
||||||
|
backport_branch=backport-${{ github.event.number }}-to-"${target_branch}"
|
||||||
|
subject="[$target_branch] $(gh pr view --json title -q .title ${{ github.event.number }})"
|
||||||
|
|
||||||
|
git checkout origin/"${target_branch}" -b "${backport_branch}"
|
||||||
|
git cherry-pick -x --mainline 1 ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
|
git commit --amend --message "$subject"
|
||||||
|
git push --set-upstream origin --force-with-lease "${backport_branch}"
|
||||||
|
gh pr create \
|
||||||
|
--base "${target_branch}" \
|
||||||
|
--title "${subject}" \
|
||||||
|
--body "Backport of PR #${{ github.event.number }} to $target_branch branch. PR created by backport workflow."
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -31,7 +31,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Build and Check Package
|
- name: Build and Check Package
|
||||||
uses: hynek/build-and-inspect-python-package@v2.6.0
|
uses: hynek/build-and-inspect-python-package@v2.5.0
|
||||||
with:
|
with:
|
||||||
attest-build-provenance-github: 'true'
|
attest-build-provenance-github: 'true'
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ jobs:
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
- name: Publish package to PyPI
|
- name: Publish package to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@v1.9.0
|
uses: pypa/gh-action-pypi-publish@v1.8.14
|
||||||
|
|
||||||
- name: Push tag
|
- name: Push tag
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -14,11 +14,6 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- "[0-9]+.[0-9]+.x"
|
- "[0-9]+.[0-9]+.x"
|
||||||
types:
|
|
||||||
- opened # default
|
|
||||||
- synchronize # default
|
|
||||||
- reopened # default
|
|
||||||
- ready_for_review # used in PRs created from the release workflow
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTEST_ADDOPTS: "--color=yes"
|
PYTEST_ADDOPTS: "--color=yes"
|
||||||
|
@ -40,7 +35,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Build and Check Package
|
- name: Build and Check Package
|
||||||
uses: hynek/build-and-inspect-python-package@v2.6.0
|
uses: hynek/build-and-inspect-python-package@v2.5.0
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: [package]
|
needs: [package]
|
||||||
|
@ -187,26 +182,6 @@ jobs:
|
||||||
tox_env: "doctesting"
|
tox_env: "doctesting"
|
||||||
use_coverage: true
|
use_coverage: true
|
||||||
|
|
||||||
continue-on-error: >-
|
|
||||||
${{
|
|
||||||
contains(
|
|
||||||
fromJSON(
|
|
||||||
'[
|
|
||||||
"windows-py38-pluggy",
|
|
||||||
"windows-py313",
|
|
||||||
"ubuntu-py38-pluggy",
|
|
||||||
"ubuntu-py38-freeze",
|
|
||||||
"ubuntu-py313",
|
|
||||||
"macos-py38",
|
|
||||||
"macos-py313"
|
|
||||||
]'
|
|
||||||
),
|
|
||||||
matrix.name
|
|
||||||
)
|
|
||||||
&& true
|
|
||||||
|| false
|
|
||||||
}}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
@ -247,21 +222,8 @@ jobs:
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
if: "matrix.use_coverage"
|
if: "matrix.use_coverage"
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v4
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: false
|
fail_ci_if_error: true
|
||||||
files: ./coverage.xml
|
files: ./coverage.xml
|
||||||
verbose: true
|
verbose: true
|
||||||
|
|
||||||
check: # This job does nothing and is only used for the branch protection
|
|
||||||
if: always()
|
|
||||||
|
|
||||||
needs:
|
|
||||||
- build
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Decide whether the needed jobs succeeded or failed
|
|
||||||
uses: re-actors/alls-green@223e4bb7a751b91f43eda76992bcfbf23b8b0302
|
|
||||||
with:
|
|
||||||
jobs: ${{ toJSON(needs) }}
|
|
||||||
|
|
|
@ -46,8 +46,7 @@ jobs:
|
||||||
run: python scripts/update-plugin-list.py
|
run: python scripts/update-plugin-list.py
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: pr
|
uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83
|
||||||
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c
|
|
||||||
with:
|
with:
|
||||||
commit-message: '[automated] Update plugin list'
|
commit-message: '[automated] Update plugin list'
|
||||||
author: 'pytest bot <pytestbot@users.noreply.github.com>'
|
author: 'pytest bot <pytestbot@users.noreply.github.com>'
|
||||||
|
@ -56,13 +55,3 @@ jobs:
|
||||||
branch-suffix: short-commit-hash
|
branch-suffix: short-commit-hash
|
||||||
title: '[automated] Update plugin list'
|
title: '[automated] Update plugin list'
|
||||||
body: '[automated] Update plugin list'
|
body: '[automated] Update plugin list'
|
||||||
draft: true
|
|
||||||
|
|
||||||
- name: Instruct the maintainers to trigger CI by undrafting the PR
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: >-
|
|
||||||
gh pr comment
|
|
||||||
--body 'Please mark the PR as ready for review to trigger PR checks.'
|
|
||||||
--repo '${{ github.repository }}'
|
|
||||||
'${{ steps.pr.outputs.pull-request-number }}'
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ src/_pytest/_version.py
|
||||||
|
|
||||||
doc/*/_build
|
doc/*/_build
|
||||||
doc/*/.doctrees
|
doc/*/.doctrees
|
||||||
|
doc/*/_changelog_towncrier_draft.rst
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: "v0.4.10"
|
rev: "v0.4.1"
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: ["--fix"]
|
args: ["--fix"]
|
||||||
|
@ -10,7 +10,12 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
- id: fix-encoding-pragma
|
||||||
|
args: [--remove]
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
|
- id: debug-statements
|
||||||
|
exclude: _pytest/(debugging|hookspec).py
|
||||||
|
language_version: python3
|
||||||
- repo: https://github.com/adamchainz/blacken-docs
|
- repo: https://github.com/adamchainz/blacken-docs
|
||||||
rev: 1.16.0
|
rev: 1.16.0
|
||||||
hooks:
|
hooks:
|
||||||
|
@ -21,7 +26,7 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: python-use-type-annotations
|
- id: python-use-type-annotations
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v1.10.0
|
rev: v1.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
files: ^(src/|testing/|scripts/)
|
files: ^(src/|testing/|scripts/)
|
||||||
|
@ -38,25 +43,20 @@ repos:
|
||||||
# on <3.11
|
# on <3.11
|
||||||
- exceptiongroup>=1.0.0rc8
|
- exceptiongroup>=1.0.0rc8
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: "2.1.3"
|
rev: "1.8.0"
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
|
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
|
||||||
additional_dependencies: ["tox>=4.9"]
|
additional_dependencies: ["tox>=4.9"]
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
|
||||||
rev: v3.16.0
|
|
||||||
hooks:
|
|
||||||
- id: pyupgrade
|
|
||||||
stages: [manual]
|
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: pylint
|
- id: pylint
|
||||||
name: pylint
|
name: pylint
|
||||||
entry: pylint
|
entry: pylint
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
args: ["-rn", "-sn", "--fail-on=I"]
|
args: ["-rn", "-sn", "--fail-on=I"]
|
||||||
stages: [manual]
|
stages: [manual]
|
||||||
- id: rst
|
- id: rst
|
||||||
name: rst
|
name: rst
|
||||||
entry: rst-lint --encoding utf-8
|
entry: rst-lint --encoding utf-8
|
||||||
|
@ -66,50 +66,9 @@ repos:
|
||||||
- id: changelogs-rst
|
- id: changelogs-rst
|
||||||
name: changelog filenames
|
name: changelog filenames
|
||||||
language: fail
|
language: fail
|
||||||
entry: >-
|
entry: 'changelog files must be named ####.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst'
|
||||||
changelog files must be named
|
exclude: changelog/(\d+\.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst|README.rst|_template.rst)
|
||||||
####.(
|
|
||||||
breaking
|
|
||||||
| deprecation
|
|
||||||
| feature
|
|
||||||
| improvement
|
|
||||||
| bugfix
|
|
||||||
| vendor
|
|
||||||
| doc
|
|
||||||
| packaging
|
|
||||||
| contrib
|
|
||||||
| misc
|
|
||||||
)(.#)?(.rst)?
|
|
||||||
exclude: >-
|
|
||||||
(?x)
|
|
||||||
^
|
|
||||||
changelog/(
|
|
||||||
\.gitignore
|
|
||||||
|\d+\.(
|
|
||||||
breaking
|
|
||||||
|deprecation
|
|
||||||
|feature
|
|
||||||
|improvement
|
|
||||||
|bugfix
|
|
||||||
|vendor
|
|
||||||
|doc
|
|
||||||
|packaging
|
|
||||||
|contrib
|
|
||||||
|misc
|
|
||||||
)(\.\d+)?(\.rst)?
|
|
||||||
|README\.rst
|
|
||||||
|_template\.rst
|
|
||||||
)
|
|
||||||
$
|
|
||||||
files: ^changelog/
|
files: ^changelog/
|
||||||
- id: changelogs-user-role
|
|
||||||
name: Changelog files should use a non-broken :user:`name` role
|
|
||||||
language: pygrep
|
|
||||||
entry: :user:([^`]+`?|`[^`]+[\s,])
|
|
||||||
pass_filenames: true
|
|
||||||
types:
|
|
||||||
- file
|
|
||||||
- rst
|
|
||||||
- id: py-deprecated
|
- id: py-deprecated
|
||||||
name: py library is deprecated
|
name: py library is deprecated
|
||||||
language: pygrep
|
language: pygrep
|
||||||
|
|
|
@ -14,16 +14,11 @@ sphinx:
|
||||||
fail_on_warning: true
|
fail_on_warning: true
|
||||||
|
|
||||||
build:
|
build:
|
||||||
os: ubuntu-24.04
|
os: ubuntu-20.04
|
||||||
tools:
|
tools:
|
||||||
python: >-
|
python: "3.9"
|
||||||
3.12
|
|
||||||
apt_packages:
|
apt_packages:
|
||||||
- inkscape
|
- inkscape
|
||||||
jobs:
|
|
||||||
post_checkout:
|
|
||||||
- git fetch --unshallow || true
|
|
||||||
- git fetch --tags || true
|
|
||||||
|
|
||||||
formats:
|
formats:
|
||||||
- epub
|
- epub
|
7
AUTHORS
7
AUTHORS
|
@ -149,7 +149,6 @@ Evgeny Seliverstov
|
||||||
Fabian Sturm
|
Fabian Sturm
|
||||||
Fabien Zarifian
|
Fabien Zarifian
|
||||||
Fabio Zadrozny
|
Fabio Zadrozny
|
||||||
Farbod Ahmadian
|
|
||||||
faph
|
faph
|
||||||
Felix Hofstätter
|
Felix Hofstätter
|
||||||
Felix Nieuwenhuizen
|
Felix Nieuwenhuizen
|
||||||
|
@ -213,7 +212,6 @@ Jordan Guymon
|
||||||
Jordan Moldow
|
Jordan Moldow
|
||||||
Jordan Speicher
|
Jordan Speicher
|
||||||
Joseph Hunkeler
|
Joseph Hunkeler
|
||||||
Joseph Sawaya
|
|
||||||
Josh Karpel
|
Josh Karpel
|
||||||
Joshua Bronson
|
Joshua Bronson
|
||||||
Jurko Gospodnetić
|
Jurko Gospodnetić
|
||||||
|
@ -246,7 +244,6 @@ Levon Saldamli
|
||||||
Lewis Cowles
|
Lewis Cowles
|
||||||
Llandy Riveron Del Risco
|
Llandy Riveron Del Risco
|
||||||
Loic Esteve
|
Loic Esteve
|
||||||
lovetheguitar
|
|
||||||
Lukas Bednar
|
Lukas Bednar
|
||||||
Luke Murphy
|
Luke Murphy
|
||||||
Maciek Fijalkowski
|
Maciek Fijalkowski
|
||||||
|
@ -283,7 +280,6 @@ Michael Droettboom
|
||||||
Michael Goerz
|
Michael Goerz
|
||||||
Michael Krebs
|
Michael Krebs
|
||||||
Michael Seifert
|
Michael Seifert
|
||||||
Michael Vogt
|
|
||||||
Michal Wajszczuk
|
Michal Wajszczuk
|
||||||
Michał Górny
|
Michał Górny
|
||||||
Michał Zięba
|
Michał Zięba
|
||||||
|
@ -395,7 +391,6 @@ Stefano Taschini
|
||||||
Steffen Allner
|
Steffen Allner
|
||||||
Stephan Obermann
|
Stephan Obermann
|
||||||
Sven-Hendrik Haase
|
Sven-Hendrik Haase
|
||||||
Sviatoslav Sydorenko
|
|
||||||
Sylvain Marié
|
Sylvain Marié
|
||||||
Tadek Teleżyński
|
Tadek Teleżyński
|
||||||
Takafumi Arakaki
|
Takafumi Arakaki
|
||||||
|
@ -430,7 +425,6 @@ Victor Rodriguez
|
||||||
Victor Uriarte
|
Victor Uriarte
|
||||||
Vidar T. Fauske
|
Vidar T. Fauske
|
||||||
Vijay Arora
|
Vijay Arora
|
||||||
Virendra Patil
|
|
||||||
Virgil Dupras
|
Virgil Dupras
|
||||||
Vitaly Lashmanov
|
Vitaly Lashmanov
|
||||||
Vivaan Verma
|
Vivaan Verma
|
||||||
|
@ -455,7 +449,6 @@ Yusuke Kadowaki
|
||||||
Yutian Li
|
Yutian Li
|
||||||
Yuval Shimon
|
Yuval Shimon
|
||||||
Zac Hatfield-Dodds
|
Zac Hatfield-Dodds
|
||||||
Zach Snicker
|
|
||||||
Zachary Kneupper
|
Zachary Kneupper
|
||||||
Zachary OBrien
|
Zachary OBrien
|
||||||
Zhouxin Qiu
|
Zhouxin Qiu
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +8,7 @@ if __name__ == "__main__":
|
||||||
import pytest # noqa: F401
|
import pytest # noqa: F401
|
||||||
|
|
||||||
script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
|
script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
|
||||||
cProfile.run(f"pytest.cmdline.main({script!r})", "prof")
|
cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
|
||||||
p = pstats.Stats("prof")
|
p = pstats.Stats("prof")
|
||||||
p.strip_dirs()
|
p.strip_dirs()
|
||||||
p.sort_stats("cumulative")
|
p.sort_stats("cumulative")
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
# 2.7.5 3.3.2
|
# 2.7.5 3.3.2
|
||||||
# FilesCompleter 75.1109 69.2116
|
# FilesCompleter 75.1109 69.2116
|
||||||
# FastFilesCompleter 0.7383 1.0760
|
# FastFilesCompleter 0.7383 1.0760
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import timeit
|
import timeit
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
for i in range(1000):
|
for i in range(1000):
|
||||||
exec("def test_func_%d(): pass" % i)
|
exec("def test_func_%d(): pass" % i)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from unittest import TestCase # noqa: F401
|
from unittest import TestCase # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
for i in range(5000):
|
for i in range(5000):
|
||||||
exec(
|
exec(
|
||||||
f"""
|
f"""
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
*
|
|
||||||
!.gitignore
|
|
||||||
!_template.rst
|
|
||||||
!README.rst
|
|
||||||
!*.bugfix
|
|
||||||
!*.bugfix.rst
|
|
||||||
!*.bugfix.*.rst
|
|
||||||
!*.breaking
|
|
||||||
!*.breaking.rst
|
|
||||||
!*.breaking.*.rst
|
|
||||||
!*.contrib
|
|
||||||
!*.contrib.rst
|
|
||||||
!*.contrib.*.rst
|
|
||||||
!*.deprecation
|
|
||||||
!*.deprecation.rst
|
|
||||||
!*.deprecation.*.rst
|
|
||||||
!*.doc
|
|
||||||
!*.doc.rst
|
|
||||||
!*.doc.*.rst
|
|
||||||
!*.feature
|
|
||||||
!*.feature.rst
|
|
||||||
!*.feature.*.rst
|
|
||||||
!*.improvement
|
|
||||||
!*.improvement.rst
|
|
||||||
!*.improvement.*.rst
|
|
||||||
!*.misc
|
|
||||||
!*.misc.rst
|
|
||||||
!*.misc.*.rst
|
|
||||||
!*.packaging
|
|
||||||
!*.packaging.rst
|
|
||||||
!*.packaging.*.rst
|
|
||||||
!*.vendor
|
|
||||||
!*.vendor.rst
|
|
||||||
!*.vendor.*.rst
|
|
|
@ -1,4 +0,0 @@
|
||||||
Fix reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`.
|
|
||||||
|
|
||||||
Originally added in pytest 8.0.0, but reverted in 8.0.2 due to a regression in pytest-xdist.
|
|
||||||
This regression was fixed in pytest-xdist 3.6.1.
|
|
|
@ -1 +0,0 @@
|
||||||
:func:`pytest.approx` now correctly handles :class:`Sequence <collections.abc.Sequence>`-like objects.
|
|
|
@ -1 +0,0 @@
|
||||||
Documented using :envvar:`PYTEST_VERSION` to detect if code is running from within a pytest run.
|
|
|
@ -1,11 +0,0 @@
|
||||||
Fixed a regression in pytest 8.0 where tracebacks get longer and longer when multiple
|
|
||||||
tests fail due to a shared higher-scope fixture which raised -- by :user:`bluetech`.
|
|
||||||
|
|
||||||
Also fixed a similar regression in pytest 5.4 for collectors which raise during setup.
|
|
||||||
|
|
||||||
The fix necessitated internal changes which may affect some plugins:
|
|
||||||
|
|
||||||
* ``FixtureDef.cached_result[2]`` is now a tuple ``(exc, tb)``
|
|
||||||
instead of ``exc``.
|
|
||||||
* ``SetupState.stack`` failures are now a tuple ``(exc, tb)``
|
|
||||||
instead of ``exc``.
|
|
|
@ -1,11 +0,0 @@
|
||||||
Added `--xfail-tb` flag, which turns on traceback output for XFAIL results.
|
|
||||||
|
|
||||||
* If the `--xfail-tb` flag is not sent, tracebacks for XFAIL results are NOT shown.
|
|
||||||
* The style of traceback for XFAIL is set with `--tb`, and can be `auto|long|short|line|native|no`.
|
|
||||||
* Note: Even if you have `--xfail-tb` set, you won't see them if `--tb=no`.
|
|
||||||
|
|
||||||
Some history:
|
|
||||||
|
|
||||||
With pytest 8.0, `-rx` or `-ra` would not only turn on summary reports for xfail, but also report the tracebacks for xfail results. This caused issues with some projects that utilize xfail, but don't want to see all of the xfail tracebacks.
|
|
||||||
|
|
||||||
This change detaches xfail tracebacks from `-rx`, and now we turn on xfail tracebacks with `--xfail-tb`. With this, the default `-rx`/ `-ra` behavior is identical to pre-8.0 with respect to xfail tracebacks. While this is a behavior change, it brings default behavior back to pre-8.0.0 behavior, which ultimately was considered the better course of action.
|
|
|
@ -1 +0,0 @@
|
||||||
12204.bugfix.rst
|
|
|
@ -1 +0,0 @@
|
||||||
Fix collection error upon encountering an :mod:`abstract <abc>` class, including abstract `unittest.TestCase` subclasses.
|
|
|
@ -1,8 +0,0 @@
|
||||||
Added support for keyword matching in marker expressions.
|
|
||||||
|
|
||||||
Now tests can be selected by marker keyword arguments.
|
|
||||||
Supported values are :class:`int`, (unescaped) :class:`str`, :class:`bool` & :data:`None`.
|
|
||||||
|
|
||||||
See :ref:`marker examples <marker_keyword_expression_example>` for more information.
|
|
||||||
|
|
||||||
-- by :user:`lovetheguitar`
|
|
|
@ -1 +0,0 @@
|
||||||
Fix a regression in pytest 8.0.0 where package-scoped parameterized items were not correctly reordered to minimize setups/teardowns in some cases.
|
|
|
@ -1,3 +0,0 @@
|
||||||
Migrated all internal type-annotations to the python3.10+ style by using the `annotations` future import.
|
|
||||||
|
|
||||||
-- by :user:`RonnyPfannschmidt`
|
|
|
@ -1,6 +0,0 @@
|
||||||
The external plugin mentions in the documentation now avoid mentioning
|
|
||||||
:std:doc:`setuptools entry-points <setuptools:index>` as the concept is
|
|
||||||
much more generic nowadays. Instead, the terminology of "external",
|
|
||||||
"installed", or "third-party" plugins (or packages) replaces that.
|
|
||||||
|
|
||||||
-- by :user:`webknjaz`
|
|
|
@ -1,4 +0,0 @@
|
||||||
The console output now uses the "third-party plugins" terminology,
|
|
||||||
replacing the previously established but confusing and outdated
|
|
||||||
reference to :std:doc:`setuptools <setuptools:index>`
|
|
||||||
-- by :user:`webknjaz`.
|
|
|
@ -1 +0,0 @@
|
||||||
Fixed a crash when returning category ``"error"`` or ``"failed"`` with a custom test status from :hook:`pytest_report_teststatus` hook -- :user:`pbrezina`.
|
|
|
@ -1,13 +0,0 @@
|
||||||
The change log draft preview integration has been refactored to use a
|
|
||||||
third party extension ``sphinxcontib-towncrier``. The previous in-repo
|
|
||||||
script was putting the change log preview file at
|
|
||||||
:file:`doc/en/_changelog_towncrier_draft.rst`. Said file is no longer
|
|
||||||
ignored in Git and might show up among untracked files in the
|
|
||||||
development environments of the contributors. To address that, the
|
|
||||||
contributors can run the following command that will clean it up:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ git clean -x -i -- doc/en/_changelog_towncrier_draft.rst
|
|
||||||
|
|
||||||
-- by :user:`webknjaz`
|
|
|
@ -1,5 +0,0 @@
|
||||||
All the undocumented ``tox`` environments now have descriptions.
|
|
||||||
They can be listed in one's development environment by invoking
|
|
||||||
``tox -av`` in a terminal.
|
|
||||||
|
|
||||||
-- by :user:`webknjaz`
|
|
|
@ -1,11 +0,0 @@
|
||||||
The changelog configuration has been updated to introduce more accurate
|
|
||||||
audience-tailored categories. Previously, there was a ``trivial``
|
|
||||||
change log fragment type with an unclear and broad meaning. It was
|
|
||||||
removed and we now have ``contrib``, ``misc`` and ``packaging`` in
|
|
||||||
place of it.
|
|
||||||
|
|
||||||
The new change note types target the readers who are downstream
|
|
||||||
packagers and project contributors. Additionally, the miscellaneous
|
|
||||||
section is kept for unspecified updates that do not fit anywhere else.
|
|
||||||
|
|
||||||
-- by :user:`webknjaz`
|
|
|
@ -1,7 +0,0 @@
|
||||||
The UX of the GitHub automation making pull requests to update the
|
|
||||||
plugin list has been updated. Previously, the maintainers had to close
|
|
||||||
the automatically created pull requests and re-open them to trigger the
|
|
||||||
CI runs. From now on, they only need to click the `Ready for review`
|
|
||||||
button instead.
|
|
||||||
|
|
||||||
-- by :user:`webknjaz`
|
|
|
@ -1 +0,0 @@
|
||||||
Improve handling of invalid regex patterns in :func:`pytest.raises(match=r'...') <pytest.raises>` by providing a clear error message.
|
|
|
@ -1,4 +0,0 @@
|
||||||
The ``:pull:`` RST role has been replaced with a shorter
|
|
||||||
``:pr:`` due to starting to use the implementation from
|
|
||||||
the third-party :pypi:`sphinx-issues` Sphinx extension
|
|
||||||
-- by :user:`webknjaz`.
|
|
|
@ -1,6 +0,0 @@
|
||||||
The coverage reporting configuration has been updated to exclude
|
|
||||||
pytest's own tests marked as expected to fail from the coverage
|
|
||||||
report. This has an effect of reducing the influence of flaky
|
|
||||||
tests on the resulting number.
|
|
||||||
|
|
||||||
-- by :user:`webknjaz`
|
|
|
@ -1,7 +0,0 @@
|
||||||
The ``extlinks`` Sphinx extension is no longer enabled. The ``:bpo:``
|
|
||||||
role it used to declare has been removed with that. BPO itself has
|
|
||||||
migrated to GitHub some years ago and it is possible to link the
|
|
||||||
respective issues by using their GitHub issue numbers and the
|
|
||||||
``:issue:`` role that the ``sphinx-issues`` extension implements.
|
|
||||||
|
|
||||||
-- by :user:`webknjaz`
|
|
|
@ -1,3 +0,0 @@
|
||||||
The ``_in_venv()`` function now detects Python virtual environments by
|
|
||||||
checking for a :file:`pyvenv.cfg` file, ensuring reliable detection on
|
|
||||||
various platforms -- by :user:`zachsnickers`.
|
|
|
@ -1 +0,0 @@
|
||||||
12544.improvement.rst
|
|
|
@ -1,2 +0,0 @@
|
||||||
Possible typos in using the ``:user:`` RST role is now being linted
|
|
||||||
through the pre-commit tool integration -- by :user:`webknjaz`.
|
|
|
@ -1 +0,0 @@
|
||||||
Do not truncate arguments to functions in output when running with `-vvv`.
|
|
|
@ -1,38 +0,0 @@
|
||||||
The readability of assertion introspection of bound methods has been enhanced
|
|
||||||
-- by :user:`farbodahm`, :user:`webknjaz`, :user:`obestwalter`, :user:`flub`
|
|
||||||
and :user:`glyphack`.
|
|
||||||
|
|
||||||
Earlier, it was like:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
=================================== FAILURES ===================================
|
|
||||||
_____________________________________ test _____________________________________
|
|
||||||
|
|
||||||
def test():
|
|
||||||
> assert Help().fun() == 2
|
|
||||||
E assert 1 == 2
|
|
||||||
E + where 1 = <bound method Help.fun of <example.Help instance at 0x256a830>>()
|
|
||||||
E + where <bound method Help.fun of <example.Help instance at 0x256a830>> = <example.Help instance at 0x256a830>.fun
|
|
||||||
E + where <example.Help instance at 0x256a830> = Help()
|
|
||||||
|
|
||||||
example.py:7: AssertionError
|
|
||||||
=========================== 1 failed in 0.03 seconds ===========================
|
|
||||||
|
|
||||||
|
|
||||||
And now it's like:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
=================================== FAILURES ===================================
|
|
||||||
_____________________________________ test _____________________________________
|
|
||||||
|
|
||||||
def test():
|
|
||||||
> assert Help().fun() == 2
|
|
||||||
E assert 1 == 2
|
|
||||||
E + where 1 = fun()
|
|
||||||
E + where fun = <test_local.Help object at 0x1074be230>.fun
|
|
||||||
E + where <test_local.Help object at 0x1074be230> = Help()
|
|
||||||
|
|
||||||
test_local.py:13: AssertionError
|
|
||||||
=========================== 1 failed in 0.03 seconds ===========================
|
|
|
@ -1 +0,0 @@
|
||||||
Fixed progress percentages (the ``[ 87%]`` at the edge of the screen) sometimes not aligning correctly when running with pytest-xdist ``-n``.
|
|
|
@ -1 +0,0 @@
|
||||||
Added timezone information to the testsuite timestamp in the JUnit XML report.
|
|
|
@ -20,22 +20,10 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
||||||
* ``deprecation``: feature deprecation.
|
* ``deprecation``: feature deprecation.
|
||||||
* ``breaking``: a change which may break existing suites, such as feature removal or behavior change.
|
* ``breaking``: a change which may break existing suites, such as feature removal or behavior change.
|
||||||
* ``vendor``: changes in packages vendored in pytest.
|
* ``vendor``: changes in packages vendored in pytest.
|
||||||
* ``packaging``: notes for downstreams about unobvious side effects
|
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
|
||||||
and tooling. changes in the test invocation considerations and
|
|
||||||
runtime assumptions.
|
|
||||||
* ``contrib``: stuff that affects the contributor experience. e.g.
|
|
||||||
Running tests, building the docs, setting up the development
|
|
||||||
environment.
|
|
||||||
* ``misc``: changes that are hard to assign to any of the above
|
|
||||||
categories.
|
|
||||||
|
|
||||||
So for example: ``123.feature.rst``, ``456.bugfix.rst``.
|
So for example: ``123.feature.rst``, ``456.bugfix.rst``.
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
See :file:`pyproject.toml` for all available categories
|
|
||||||
(``tool.towncrier.type``).
|
|
||||||
|
|
||||||
If your PR fixes an issue, use that number here. If there is no issue,
|
If your PR fixes an issue, use that number here. If there is no issue,
|
||||||
then after you submit the PR and get the PR number you can add a
|
then after you submit the PR and get the PR number you can add a
|
||||||
changelog using that instead.
|
changelog using that instead.
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
# reference: https://docs.codecov.io/docs/codecovyml-reference
|
# reference: https://docs.codecov.io/docs/codecovyml-reference
|
||||||
---
|
|
||||||
|
|
||||||
codecov:
|
|
||||||
token: 1eca3b1f-31a2-4fb8-a8c3-138b441b50a7 #repo token
|
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
status:
|
status:
|
||||||
patch: true
|
patch: true
|
||||||
|
|
|
@ -45,7 +45,7 @@ The py.test Development Team
|
||||||
**New Features**
|
**New Features**
|
||||||
|
|
||||||
* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
|
* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
|
||||||
Thanks :user:`MichaelAquilina` for the complete PR (:pr:`1040`).
|
Thanks :user:`MichaelAquilina` for the complete PR (:pull:`1040`).
|
||||||
|
|
||||||
* ``--doctest-glob`` may now be passed multiple times in the command-line.
|
* ``--doctest-glob`` may now be passed multiple times in the command-line.
|
||||||
Thanks :user:`jab` and :user:`nicoddemus` for the PR.
|
Thanks :user:`jab` and :user:`nicoddemus` for the PR.
|
||||||
|
|
|
@ -44,7 +44,7 @@ The py.test Development Team
|
||||||
Thanks :user:`nicoddemus` for the PR.
|
Thanks :user:`nicoddemus` for the PR.
|
||||||
|
|
||||||
* Fix (:issue:`469`): junit parses report.nodeid incorrectly, when params IDs
|
* Fix (:issue:`469`): junit parses report.nodeid incorrectly, when params IDs
|
||||||
contain ``::``. Thanks :user:`tomviner` for the PR (:pr:`1431`).
|
contain ``::``. Thanks :user:`tomviner` for the PR (:pull:`1431`).
|
||||||
|
|
||||||
* Fix (:issue:`578`): SyntaxErrors
|
* Fix (:issue:`578`): SyntaxErrors
|
||||||
containing non-ascii lines at the point of failure generated an internal
|
containing non-ascii lines at the point of failure generated an internal
|
||||||
|
|
|
@ -44,14 +44,14 @@ The py.test Development Team
|
||||||
|
|
||||||
* Fix Xfail does not work with condition keyword argument.
|
* Fix Xfail does not work with condition keyword argument.
|
||||||
Thanks :user:`astraw38` for reporting the issue (:issue:`1496`) and :user:`tomviner`
|
Thanks :user:`astraw38` for reporting the issue (:issue:`1496`) and :user:`tomviner`
|
||||||
for PR the (:pr:`1524`).
|
for PR the (:pull:`1524`).
|
||||||
|
|
||||||
* Fix win32 path issue when putting custom config file with absolute path
|
* Fix win32 path issue when putting custom config file with absolute path
|
||||||
in ``pytest.main("-c your_absolute_path")``.
|
in ``pytest.main("-c your_absolute_path")``.
|
||||||
|
|
||||||
* Fix maximum recursion depth detection when raised error class is not aware
|
* Fix maximum recursion depth detection when raised error class is not aware
|
||||||
of unicode/encoded bytes.
|
of unicode/encoded bytes.
|
||||||
Thanks :user:`prusse-martin` for the PR (:pr:`1506`).
|
Thanks :user:`prusse-martin` for the PR (:pull:`1506`).
|
||||||
|
|
||||||
* Fix ``pytest.mark.skip`` mark when used in strict mode.
|
* Fix ``pytest.mark.skip`` mark when used in strict mode.
|
||||||
Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for
|
Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for
|
||||||
|
|
|
@ -19,15 +19,12 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
we named the news folder changelog
|
we named the news folder changelog
|
||||||
|
|
||||||
|
|
||||||
.. only:: not is_release
|
.. only:: changelog_towncrier_draft
|
||||||
|
|
||||||
To be included in v\ |release| (if present)
|
.. The 'changelog_towncrier_draft' tag is included by our 'tox -e docs',
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
but not on readthedocs.
|
||||||
|
|
||||||
.. towncrier-draft-entries:: |release| [UNRELEASED DRAFT]
|
.. include:: _changelog_towncrier_draft.rst
|
||||||
|
|
||||||
Released versions
|
|
||||||
^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
@ -268,7 +265,7 @@ 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``.
|
- `#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 :pr:`12043` for a comparison with pytest 8.0 and <8.
|
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.
|
- `#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.
|
||||||
|
@ -1422,7 +1419,7 @@ Bug Fixes
|
||||||
tests/link -> tests/real
|
tests/link -> tests/real
|
||||||
|
|
||||||
running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``.
|
running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``.
|
||||||
This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pr:`6523` for details).
|
This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details).
|
||||||
|
|
||||||
|
|
||||||
- `#9626 <https://github.com/pytest-dev/pytest/issues/9626>`_: Fixed count of selected tests on terminal collection summary when there were errors or skipped modules.
|
- `#9626 <https://github.com/pytest-dev/pytest/issues/9626>`_: Fixed count of selected tests on terminal collection summary when there were errors or skipped modules.
|
||||||
|
@ -2591,7 +2588,7 @@ Breaking Changes
|
||||||
Resolving symlinks for the current directory and during collection was introduced as a bugfix in 3.9.0, but it actually is a new feature which had unfortunate consequences in Windows and surprising results in other platforms.
|
Resolving symlinks for the current directory and during collection was introduced as a bugfix in 3.9.0, but it actually is a new feature which had unfortunate consequences in Windows and surprising results in other platforms.
|
||||||
|
|
||||||
The team decided to step back on resolving symlinks at all, planning to review this in the future with a more solid solution (see discussion in
|
The team decided to step back on resolving symlinks at all, planning to review this in the future with a more solid solution (see discussion in
|
||||||
:pr:`6523` for details).
|
:pull:`6523` for details).
|
||||||
|
|
||||||
This might break test suites which made use of this feature; the fix is to create a symlink
|
This might break test suites which made use of this feature; the fix is to create a symlink
|
||||||
for the entire test tree, and not only to partial files/tress as it was possible previously.
|
for the entire test tree, and not only to partial files/tress as it was possible previously.
|
||||||
|
@ -2874,7 +2871,7 @@ Bug Fixes
|
||||||
- :issue:`6871`: Fix crash with captured output when using :fixture:`capsysbinary`.
|
- :issue:`6871`: Fix crash with captured output when using :fixture:`capsysbinary`.
|
||||||
|
|
||||||
|
|
||||||
- :issue:`6909`: Revert the change introduced by :pr:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
|
- :issue:`6909`: Revert the change introduced by :pull:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
|
||||||
|
|
||||||
The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted.
|
The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted.
|
||||||
|
|
||||||
|
@ -3044,7 +3041,7 @@ pytest 5.4.1 (2020-03-13)
|
||||||
Bug Fixes
|
Bug Fixes
|
||||||
---------
|
---------
|
||||||
|
|
||||||
- :issue:`6909`: Revert the change introduced by :pr:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
|
- :issue:`6909`: Revert the change introduced by :pull:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
|
||||||
|
|
||||||
The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted.
|
The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted.
|
||||||
|
|
||||||
|
@ -3360,9 +3357,7 @@ 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.LineMatcher.no_fnmatch_line` when used after positive matching.
|
||||||
|
|
||||||
|
|
||||||
- :issue:`6082`: Fix line detection for doctest samples inside
|
- :issue:`6082`: Fix line detection for doctest samples inside :py:class:`python:property` docstrings, as a workaround to :bpo:`17446`.
|
||||||
:py:class:`python:property` docstrings, as a workaround to
|
|
||||||
:issue:`python/cpython#61648`.
|
|
||||||
|
|
||||||
|
|
||||||
- :issue:`6254`: Fix compatibility with pytest-parallel (regression in pytest 5.3.0).
|
- :issue:`6254`: Fix compatibility with pytest-parallel (regression in pytest 5.3.0).
|
||||||
|
@ -4069,7 +4064,7 @@ Bug Fixes
|
||||||
(``--collect-only``) when ``--log-cli-level`` is used.
|
(``--collect-only``) when ``--log-cli-level`` is used.
|
||||||
|
|
||||||
|
|
||||||
- :issue:`5389`: Fix regressions of :pr:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
|
- :issue:`5389`: Fix regressions of :pull:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
|
||||||
|
|
||||||
|
|
||||||
- :issue:`5390`: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods.
|
- :issue:`5390`: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods.
|
||||||
|
@ -4270,7 +4265,7 @@ Bug Fixes
|
||||||
(``--collect-only``) when ``--log-cli-level`` is used.
|
(``--collect-only``) when ``--log-cli-level`` is used.
|
||||||
|
|
||||||
|
|
||||||
- :issue:`5389`: Fix regressions of :pr:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
|
- :issue:`5389`: Fix regressions of :pull:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
|
||||||
|
|
||||||
|
|
||||||
- :issue:`5390`: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods.
|
- :issue:`5390`: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods.
|
||||||
|
@ -7231,10 +7226,10 @@ New Features
|
||||||
* Added ``junit_suite_name`` ini option to specify root ``<testsuite>`` name for JUnit XML reports (:issue:`533`).
|
* Added ``junit_suite_name`` ini option to specify root ``<testsuite>`` name for JUnit XML reports (:issue:`533`).
|
||||||
|
|
||||||
* Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files.
|
* Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files.
|
||||||
Thanks :user:`wheerd` for the PR (:pr:`2101`).
|
Thanks :user:`wheerd` for the PR (:pull:`2101`).
|
||||||
|
|
||||||
* ``pytest.warns`` now checks for subclass relationship rather than
|
* ``pytest.warns`` now checks for subclass relationship rather than
|
||||||
class equality. Thanks :user:`lesteve` for the PR (:pr:`2166`)
|
class equality. Thanks :user:`lesteve` for the PR (:pull:`2166`)
|
||||||
|
|
||||||
* ``pytest.raises`` now asserts that the error message matches a text or regex
|
* ``pytest.raises`` now asserts that the error message matches a text or regex
|
||||||
with the ``match`` keyword argument. Thanks :user:`Kriechi` for the PR.
|
with the ``match`` keyword argument. Thanks :user:`Kriechi` for the PR.
|
||||||
|
@ -7262,7 +7257,7 @@ Changes
|
||||||
the failure. (:issue:`2228`) Thanks to :user:`kkoukiou` for the PR.
|
the failure. (:issue:`2228`) Thanks to :user:`kkoukiou` for the PR.
|
||||||
|
|
||||||
* Testcase reports with a ``url`` attribute will now properly write this to junitxml.
|
* Testcase reports with a ``url`` attribute will now properly write this to junitxml.
|
||||||
Thanks :user:`fushi` for the PR (:pr:`1874`).
|
Thanks :user:`fushi` for the PR (:pull:`1874`).
|
||||||
|
|
||||||
* Remove common items from dict comparison output when verbosity=1. Also update
|
* Remove common items from dict comparison output when verbosity=1. Also update
|
||||||
the truncation message to make it clearer that pytest truncates all
|
the truncation message to make it clearer that pytest truncates all
|
||||||
|
@ -7271,7 +7266,7 @@ Changes
|
||||||
|
|
||||||
* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use
|
* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use
|
||||||
``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks :user:`davidszotten` for
|
``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks :user:`davidszotten` for
|
||||||
the PR (:pr:`1952`).
|
the PR (:pull:`1952`).
|
||||||
|
|
||||||
* fix :issue:`2013`: turn RecordedWarning into ``namedtuple``,
|
* fix :issue:`2013`: turn RecordedWarning into ``namedtuple``,
|
||||||
to give it a comprehensible repr while preventing unwarranted modification.
|
to give it a comprehensible repr while preventing unwarranted modification.
|
||||||
|
@ -7525,7 +7520,7 @@ Bug Fixes
|
||||||
a sequence of strings) when modules are considered for assertion rewriting.
|
a sequence of strings) when modules are considered for assertion rewriting.
|
||||||
Due to this bug, much more modules were being rewritten than necessary
|
Due to this bug, much more modules were being rewritten than necessary
|
||||||
if a test suite uses ``pytest_plugins`` to load internal plugins (:issue:`1888`).
|
if a test suite uses ``pytest_plugins`` to load internal plugins (:issue:`1888`).
|
||||||
Thanks :user:`jaraco` for the report and :user:`nicoddemus` for the PR (:pr:`1891`).
|
Thanks :user:`jaraco` for the report and :user:`nicoddemus` for the PR (:pull:`1891`).
|
||||||
|
|
||||||
* Do not call tearDown and cleanups when running tests from
|
* Do not call tearDown and cleanups when running tests from
|
||||||
``unittest.TestCase`` subclasses with ``--pdb``
|
``unittest.TestCase`` subclasses with ``--pdb``
|
||||||
|
@ -7580,12 +7575,12 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
* ``--nomagic``: use ``--assert=plain`` instead;
|
* ``--nomagic``: use ``--assert=plain`` instead;
|
||||||
* ``--report``: use ``-r`` instead;
|
* ``--report``: use ``-r`` instead;
|
||||||
|
|
||||||
Thanks to :user:`RedBeardCode` for the PR (:pr:`1664`).
|
Thanks to :user:`RedBeardCode` for the PR (:pull:`1664`).
|
||||||
|
|
||||||
* ImportErrors in plugins now are a fatal error instead of issuing a
|
* ImportErrors in plugins now are a fatal error instead of issuing a
|
||||||
pytest warning (:issue:`1479`). Thanks to :user:`The-Compiler` for the PR.
|
pytest warning (:issue:`1479`). Thanks to :user:`The-Compiler` for the PR.
|
||||||
|
|
||||||
* Removed support code for Python 3 versions < 3.3 (:pr:`1627`).
|
* Removed support code for Python 3 versions < 3.3 (:pull:`1627`).
|
||||||
|
|
||||||
* Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points
|
* Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points
|
||||||
were never documented and a leftover from a pre-virtualenv era. These entry
|
were never documented and a leftover from a pre-virtualenv era. These entry
|
||||||
|
@ -7596,19 +7591,19 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
* ``pytest.skip()`` now raises an error when used to decorate a test function,
|
* ``pytest.skip()`` now raises an error when used to decorate a test function,
|
||||||
as opposed to its original intent (to imperatively skip a test inside a test function). Previously
|
as opposed to its original intent (to imperatively skip a test inside a test function). Previously
|
||||||
this usage would cause the entire module to be skipped (:issue:`607`).
|
this usage would cause the entire module to be skipped (:issue:`607`).
|
||||||
Thanks :user:`omarkohl` for the complete PR (:pr:`1519`).
|
Thanks :user:`omarkohl` for the complete PR (:pull:`1519`).
|
||||||
|
|
||||||
* Exit tests if a collection error occurs. A poll indicated most users will hit CTRL-C
|
* Exit tests if a collection error occurs. A poll indicated most users will hit CTRL-C
|
||||||
anyway as soon as they see collection errors, so pytest might as well make that the default behavior (:issue:`1421`).
|
anyway as soon as they see collection errors, so pytest might as well make that the default behavior (:issue:`1421`).
|
||||||
A ``--continue-on-collection-errors`` option has been added to restore the previous behaviour.
|
A ``--continue-on-collection-errors`` option has been added to restore the previous behaviour.
|
||||||
Thanks :user:`olegpidsadnyi` and :user:`omarkohl` for the complete PR (:pr:`1628`).
|
Thanks :user:`olegpidsadnyi` and :user:`omarkohl` for the complete PR (:pull:`1628`).
|
||||||
|
|
||||||
* Renamed the pytest ``pdb`` module (plugin) into ``debugging`` to avoid clashes with the builtin ``pdb`` module.
|
* Renamed the pytest ``pdb`` module (plugin) into ``debugging`` to avoid clashes with the builtin ``pdb`` module.
|
||||||
|
|
||||||
* Raise a helpful failure message when requesting a parametrized fixture at runtime,
|
* Raise a helpful failure message when requesting a parametrized fixture at runtime,
|
||||||
e.g. with ``request.getfixturevalue``. Previously these parameters were simply
|
e.g. with ``request.getfixturevalue``. Previously these parameters were simply
|
||||||
never defined, so a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])``
|
never defined, so a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])``
|
||||||
only ran once (:pr:`460`).
|
only ran once (:pull:`460`).
|
||||||
Thanks to :user:`nikratio` for the bug report, :user:`RedBeardCode` and :user:`tomviner` for the PR.
|
Thanks to :user:`nikratio` for the bug report, :user:`RedBeardCode` and :user:`tomviner` for the PR.
|
||||||
|
|
||||||
* ``_pytest.monkeypatch.monkeypatch`` class has been renamed to ``_pytest.monkeypatch.MonkeyPatch``
|
* ``_pytest.monkeypatch.monkeypatch`` class has been renamed to ``_pytest.monkeypatch.MonkeyPatch``
|
||||||
|
@ -7626,7 +7621,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
|
|
||||||
* New ``doctest_namespace`` fixture for injecting names into the
|
* New ``doctest_namespace`` fixture for injecting names into the
|
||||||
namespace in which doctests run.
|
namespace in which doctests run.
|
||||||
Thanks :user:`milliams` for the complete PR (:pr:`1428`).
|
Thanks :user:`milliams` for the complete PR (:pull:`1428`).
|
||||||
|
|
||||||
* New ``--doctest-report`` option available to change the output format of diffs
|
* New ``--doctest-report`` option available to change the output format of diffs
|
||||||
when running (failing) doctests (implements :issue:`1749`).
|
when running (failing) doctests (implements :issue:`1749`).
|
||||||
|
@ -7634,23 +7629,23 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
|
|
||||||
* New ``name`` argument to ``pytest.fixture`` decorator which allows a custom name
|
* New ``name`` argument to ``pytest.fixture`` decorator which allows a custom name
|
||||||
for a fixture (to solve the funcarg-shadowing-fixture problem).
|
for a fixture (to solve the funcarg-shadowing-fixture problem).
|
||||||
Thanks :user:`novas0x2a` for the complete PR (:pr:`1444`).
|
Thanks :user:`novas0x2a` for the complete PR (:pull:`1444`).
|
||||||
|
|
||||||
* New ``approx()`` function for easily comparing floating-point numbers in
|
* New ``approx()`` function for easily comparing floating-point numbers in
|
||||||
tests.
|
tests.
|
||||||
Thanks :user:`kalekundert` for the complete PR (:pr:`1441`).
|
Thanks :user:`kalekundert` for the complete PR (:pull:`1441`).
|
||||||
|
|
||||||
* Ability to add global properties in the final xunit output file by accessing
|
* Ability to add global properties in the final xunit output file by accessing
|
||||||
the internal ``junitxml`` plugin (experimental).
|
the internal ``junitxml`` plugin (experimental).
|
||||||
Thanks :user:`tareqalayan` for the complete PR :pr:`1454`).
|
Thanks :user:`tareqalayan` for the complete PR :pull:`1454`).
|
||||||
|
|
||||||
* New ``ExceptionInfo.match()`` method to match a regular expression on the
|
* New ``ExceptionInfo.match()`` method to match a regular expression on the
|
||||||
string representation of an exception (:issue:`372`).
|
string representation of an exception (:issue:`372`).
|
||||||
Thanks :user:`omarkohl` for the complete PR (:pr:`1502`).
|
Thanks :user:`omarkohl` for the complete PR (:pull:`1502`).
|
||||||
|
|
||||||
* ``__tracebackhide__`` can now also be set to a callable which then can decide
|
* ``__tracebackhide__`` can now also be set to a callable which then can decide
|
||||||
whether to filter the traceback based on the ``ExceptionInfo`` object passed
|
whether to filter the traceback based on the ``ExceptionInfo`` object passed
|
||||||
to it. Thanks :user:`The-Compiler` for the complete PR (:pr:`1526`).
|
to it. Thanks :user:`The-Compiler` for the complete PR (:pull:`1526`).
|
||||||
|
|
||||||
* New ``pytest_make_parametrize_id(config, val)`` hook which can be used by plugins to provide
|
* New ``pytest_make_parametrize_id(config, val)`` hook which can be used by plugins to provide
|
||||||
friendly strings for custom types.
|
friendly strings for custom types.
|
||||||
|
@ -7668,7 +7663,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
* Introduce ``pytest`` command as recommended entry point. Note that ``py.test``
|
* Introduce ``pytest`` command as recommended entry point. Note that ``py.test``
|
||||||
still works and is not scheduled for removal. Closes proposal
|
still works and is not scheduled for removal. Closes proposal
|
||||||
:issue:`1629`. Thanks :user:`obestwalter` and :user:`davehunt` for the complete PR
|
:issue:`1629`. Thanks :user:`obestwalter` and :user:`davehunt` for the complete PR
|
||||||
(:pr:`1633`).
|
(:pull:`1633`).
|
||||||
|
|
||||||
* New cli flags:
|
* New cli flags:
|
||||||
|
|
||||||
|
@ -7712,19 +7707,19 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
|
|
||||||
* Change ``report.outcome`` for ``xpassed`` tests to ``"passed"`` in non-strict
|
* Change ``report.outcome`` for ``xpassed`` tests to ``"passed"`` in non-strict
|
||||||
mode and ``"failed"`` in strict mode. Thanks to :user:`hackebrot` for the PR
|
mode and ``"failed"`` in strict mode. Thanks to :user:`hackebrot` for the PR
|
||||||
(:pr:`1795`) and :user:`gprasad84` for report (:issue:`1546`).
|
(:pull:`1795`) and :user:`gprasad84` for report (:issue:`1546`).
|
||||||
|
|
||||||
* Tests marked with ``xfail(strict=False)`` (the default) now appear in
|
* Tests marked with ``xfail(strict=False)`` (the default) now appear in
|
||||||
JUnitXML reports as passing tests instead of skipped.
|
JUnitXML reports as passing tests instead of skipped.
|
||||||
Thanks to :user:`hackebrot` for the PR (:pr:`1795`).
|
Thanks to :user:`hackebrot` for the PR (:pull:`1795`).
|
||||||
|
|
||||||
* Highlight path of the file location in the error report to make it easier to copy/paste.
|
* Highlight path of the file location in the error report to make it easier to copy/paste.
|
||||||
Thanks :user:`suzaku` for the PR (:pr:`1778`).
|
Thanks :user:`suzaku` for the PR (:pull:`1778`).
|
||||||
|
|
||||||
* Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like
|
* Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like
|
||||||
those marked with the ``@pytest.yield_fixture`` decorator. This change renders
|
those marked with the ``@pytest.yield_fixture`` decorator. This change renders
|
||||||
``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements
|
``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements
|
||||||
the preferred way to write teardown code (:pr:`1461`).
|
the preferred way to write teardown code (:pull:`1461`).
|
||||||
Thanks :user:`csaftoiu` for bringing this to attention and :user:`nicoddemus` for the PR.
|
Thanks :user:`csaftoiu` for bringing this to attention and :user:`nicoddemus` for the PR.
|
||||||
|
|
||||||
* Explicitly passed parametrize ids do not get escaped to ascii (:issue:`1351`).
|
* Explicitly passed parametrize ids do not get escaped to ascii (:issue:`1351`).
|
||||||
|
@ -7735,11 +7730,11 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
Thanks :user:`nicoddemus` for the PR.
|
Thanks :user:`nicoddemus` for the PR.
|
||||||
|
|
||||||
* ``pytest_terminal_summary`` hook now receives the ``exitstatus``
|
* ``pytest_terminal_summary`` hook now receives the ``exitstatus``
|
||||||
of the test session as argument. Thanks :user:`blueyed` for the PR (:pr:`1809`).
|
of the test session as argument. Thanks :user:`blueyed` for the PR (:pull:`1809`).
|
||||||
|
|
||||||
* Parametrize ids can accept ``None`` as specific test id, in which case the
|
* Parametrize ids can accept ``None`` as specific test id, in which case the
|
||||||
automatically generated id for that argument will be used.
|
automatically generated id for that argument will be used.
|
||||||
Thanks :user:`palaviv` for the complete PR (:pr:`1468`).
|
Thanks :user:`palaviv` for the complete PR (:pull:`1468`).
|
||||||
|
|
||||||
* The parameter to xunit-style setup/teardown methods (``setup_method``,
|
* The parameter to xunit-style setup/teardown methods (``setup_method``,
|
||||||
``setup_module``, etc.) is now optional and may be omitted.
|
``setup_module``, etc.) is now optional and may be omitted.
|
||||||
|
@ -7747,32 +7742,32 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
|
|
||||||
* Improved automatic id generation selection in case of duplicate ids in
|
* Improved automatic id generation selection in case of duplicate ids in
|
||||||
parametrize.
|
parametrize.
|
||||||
Thanks :user:`palaviv` for the complete PR (:pr:`1474`).
|
Thanks :user:`palaviv` for the complete PR (:pull:`1474`).
|
||||||
|
|
||||||
* Now pytest warnings summary is shown up by default. Added a new flag
|
* Now pytest warnings summary is shown up by default. Added a new flag
|
||||||
``--disable-pytest-warnings`` to explicitly disable the warnings summary (:issue:`1668`).
|
``--disable-pytest-warnings`` to explicitly disable the warnings summary (:issue:`1668`).
|
||||||
|
|
||||||
* Make ImportError during collection more explicit by reminding
|
* Make ImportError during collection more explicit by reminding
|
||||||
the user to check the name of the test module/package(s) (:issue:`1426`).
|
the user to check the name of the test module/package(s) (:issue:`1426`).
|
||||||
Thanks :user:`omarkohl` for the complete PR (:pr:`1520`).
|
Thanks :user:`omarkohl` for the complete PR (:pull:`1520`).
|
||||||
|
|
||||||
* Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks
|
* Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks
|
||||||
:user:`mikofski` for the report and :user:`tomviner` for the PR (:issue:`1544`).
|
:user:`mikofski` for the report and :user:`tomviner` for the PR (:issue:`1544`).
|
||||||
|
|
||||||
* ``pytest.raises`` in the context manager form accepts a custom
|
* ``pytest.raises`` in the context manager form accepts a custom
|
||||||
``message`` to raise when no exception occurred.
|
``message`` to raise when no exception occurred.
|
||||||
Thanks :user:`palaviv` for the complete PR (:pr:`1616`).
|
Thanks :user:`palaviv` for the complete PR (:pull:`1616`).
|
||||||
|
|
||||||
* ``conftest.py`` files now benefit from assertion rewriting; previously it
|
* ``conftest.py`` files now benefit from assertion rewriting; previously it
|
||||||
was only available for test modules. Thanks :user:`flub`, :user:`sober7` and
|
was only available for test modules. Thanks :user:`flub`, :user:`sober7` and
|
||||||
:user:`nicoddemus` for the PR (:issue:`1619`).
|
:user:`nicoddemus` for the PR (:issue:`1619`).
|
||||||
|
|
||||||
* Text documents without any doctests no longer appear as "skipped".
|
* Text documents without any doctests no longer appear as "skipped".
|
||||||
Thanks :user:`graingert` for reporting and providing a full PR (:pr:`1580`).
|
Thanks :user:`graingert` for reporting and providing a full PR (:pull:`1580`).
|
||||||
|
|
||||||
* Ensure that a module within a namespace package can be found when it
|
* Ensure that a module within a namespace package can be found when it
|
||||||
is specified on the command line together with the ``--pyargs``
|
is specified on the command line together with the ``--pyargs``
|
||||||
option. Thanks to :user:`taschini` for the PR (:pr:`1597`).
|
option. Thanks to :user:`taschini` for the PR (:pull:`1597`).
|
||||||
|
|
||||||
* Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding
|
* Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding
|
||||||
sub-expressions that happened to be ``False``, assuming this was redundant information.
|
sub-expressions that happened to be ``False``, assuming this was redundant information.
|
||||||
|
@ -7788,20 +7783,20 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
Thanks :user:`nicoddemus` for the PR.
|
Thanks :user:`nicoddemus` for the PR.
|
||||||
|
|
||||||
* ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
|
* ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
|
||||||
to avoid conflicts with other distutils commands (see :pr:`567`). ``[pytest]`` sections in
|
to avoid conflicts with other distutils commands (see :pull:`567`). ``[pytest]`` sections in
|
||||||
``pytest.ini`` or ``tox.ini`` files are supported and unchanged.
|
``pytest.ini`` or ``tox.ini`` files are supported and unchanged.
|
||||||
Thanks :user:`nicoddemus` for the PR.
|
Thanks :user:`nicoddemus` for the PR.
|
||||||
|
|
||||||
* Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be
|
* Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be
|
||||||
removed in pytest-4.0 (:pr:`1684`).
|
removed in pytest-4.0 (:pull:`1684`).
|
||||||
Thanks :user:`nicoddemus` for the PR.
|
Thanks :user:`nicoddemus` for the PR.
|
||||||
|
|
||||||
* Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled
|
* Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled
|
||||||
for removal in pytest-4.0. It is recommended to pass a list of arguments instead (:pr:`1723`).
|
for removal in pytest-4.0. It is recommended to pass a list of arguments instead (:pull:`1723`).
|
||||||
|
|
||||||
* Rename ``getfuncargvalue`` to ``getfixturevalue``. ``getfuncargvalue`` is
|
* Rename ``getfuncargvalue`` to ``getfixturevalue``. ``getfuncargvalue`` is
|
||||||
still present but is now considered deprecated. Thanks to :user:`RedBeardCode` and :user:`tomviner`
|
still present but is now considered deprecated. Thanks to :user:`RedBeardCode` and :user:`tomviner`
|
||||||
for the PR (:pr:`1626`).
|
for the PR (:pull:`1626`).
|
||||||
|
|
||||||
* ``optparse`` type usage now triggers DeprecationWarnings (:issue:`1740`).
|
* ``optparse`` type usage now triggers DeprecationWarnings (:issue:`1740`).
|
||||||
|
|
||||||
|
@ -7859,11 +7854,11 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
:user:`tomviner` for the PR.
|
:user:`tomviner` for the PR.
|
||||||
|
|
||||||
* ``ConftestImportFailure`` now shows the traceback making it easier to
|
* ``ConftestImportFailure`` now shows the traceback making it easier to
|
||||||
identify bugs in ``conftest.py`` files (:pr:`1516`). Thanks :user:`txomon` for
|
identify bugs in ``conftest.py`` files (:pull:`1516`). Thanks :user:`txomon` for
|
||||||
the PR.
|
the PR.
|
||||||
|
|
||||||
* Text documents without any doctests no longer appear as "skipped".
|
* Text documents without any doctests no longer appear as "skipped".
|
||||||
Thanks :user:`graingert` for reporting and providing a full PR (:pr:`1580`).
|
Thanks :user:`graingert` for reporting and providing a full PR (:pull:`1580`).
|
||||||
|
|
||||||
* Fixed collection of classes with custom ``__new__`` method.
|
* Fixed collection of classes with custom ``__new__`` method.
|
||||||
Fixes :issue:`1579`. Thanks to :user:`Stranger6667` for the PR.
|
Fixes :issue:`1579`. Thanks to :user:`Stranger6667` for the PR.
|
||||||
|
@ -7871,7 +7866,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
* Fixed scope overriding inside metafunc.parametrize (:issue:`634`).
|
* Fixed scope overriding inside metafunc.parametrize (:issue:`634`).
|
||||||
Thanks to :user:`Stranger6667` for the PR.
|
Thanks to :user:`Stranger6667` for the PR.
|
||||||
|
|
||||||
* Fixed the total tests tally in junit xml output (:pr:`1798`).
|
* Fixed the total tests tally in junit xml output (:pull:`1798`).
|
||||||
Thanks to :user:`cboelsen` for the PR.
|
Thanks to :user:`cboelsen` for the PR.
|
||||||
|
|
||||||
* Fixed off-by-one error with lines from ``request.node.warn``.
|
* Fixed off-by-one error with lines from ``request.node.warn``.
|
||||||
|
@ -7888,14 +7883,14 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
|
|
||||||
* Fix Xfail does not work with condition keyword argument.
|
* Fix Xfail does not work with condition keyword argument.
|
||||||
Thanks :user:`astraw38` for reporting the issue (:issue:`1496`) and :user:`tomviner`
|
Thanks :user:`astraw38` for reporting the issue (:issue:`1496`) and :user:`tomviner`
|
||||||
for PR the (:pr:`1524`).
|
for PR the (:pull:`1524`).
|
||||||
|
|
||||||
* Fix win32 path issue when putting custom config file with absolute path
|
* Fix win32 path issue when putting custom config file with absolute path
|
||||||
in ``pytest.main("-c your_absolute_path")``.
|
in ``pytest.main("-c your_absolute_path")``.
|
||||||
|
|
||||||
* Fix maximum recursion depth detection when raised error class is not aware
|
* Fix maximum recursion depth detection when raised error class is not aware
|
||||||
of unicode/encoded bytes.
|
of unicode/encoded bytes.
|
||||||
Thanks :user:`prusse-martin` for the PR (:pr:`1506`).
|
Thanks :user:`prusse-martin` for the PR (:pull:`1506`).
|
||||||
|
|
||||||
* Fix ``pytest.mark.skip`` mark when used in strict mode.
|
* Fix ``pytest.mark.skip`` mark when used in strict mode.
|
||||||
Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for
|
Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for
|
||||||
|
@ -7922,7 +7917,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
Thanks :user:`nicoddemus` for the PR.
|
Thanks :user:`nicoddemus` for the PR.
|
||||||
|
|
||||||
* Fix (:issue:`469`): junit parses report.nodeid incorrectly, when params IDs
|
* Fix (:issue:`469`): junit parses report.nodeid incorrectly, when params IDs
|
||||||
contain ``::``. Thanks :user:`tomviner` for the PR (:pr:`1431`).
|
contain ``::``. Thanks :user:`tomviner` for the PR (:pull:`1431`).
|
||||||
|
|
||||||
* Fix (:issue:`578`): SyntaxErrors
|
* Fix (:issue:`578`): SyntaxErrors
|
||||||
containing non-ascii lines at the point of failure generated an internal
|
containing non-ascii lines at the point of failure generated an internal
|
||||||
|
@ -7943,7 +7938,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
**New Features**
|
**New Features**
|
||||||
|
|
||||||
* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
|
* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
|
||||||
Thanks :user:`MichaelAquilina` for the complete PR (:pr:`1040`).
|
Thanks :user:`MichaelAquilina` for the complete PR (:pull:`1040`).
|
||||||
|
|
||||||
* ``--doctest-glob`` may now be passed multiple times in the command-line.
|
* ``--doctest-glob`` may now be passed multiple times in the command-line.
|
||||||
Thanks :user:`jab` and :user:`nicoddemus` for the PR.
|
Thanks :user:`jab` and :user:`nicoddemus` for the PR.
|
||||||
|
@ -7954,14 +7949,14 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
* ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS``
|
* ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS``
|
||||||
tests to fail the test suite (defaulting to ``False``). There's also a
|
tests to fail the test suite (defaulting to ``False``). There's also a
|
||||||
``xfail_strict`` ini option that can be used to configure it project-wise.
|
``xfail_strict`` ini option that can be used to configure it project-wise.
|
||||||
Thanks :user:`rabbbit` for the request and :user:`nicoddemus` for the PR (:pr:`1355`).
|
Thanks :user:`rabbbit` for the request and :user:`nicoddemus` for the PR (:pull:`1355`).
|
||||||
|
|
||||||
* ``Parser.addini`` now supports options of type ``bool``.
|
* ``Parser.addini`` now supports options of type ``bool``.
|
||||||
Thanks :user:`nicoddemus` for the PR.
|
Thanks :user:`nicoddemus` for the PR.
|
||||||
|
|
||||||
* New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings
|
* New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings
|
||||||
in doctest output (similar to ``ALLOW_UNICODE``).
|
in doctest output (similar to ``ALLOW_UNICODE``).
|
||||||
Thanks :user:`jaraco` for the request and :user:`nicoddemus` for the PR (:pr:`1287`).
|
Thanks :user:`jaraco` for the request and :user:`nicoddemus` for the PR (:pull:`1287`).
|
||||||
|
|
||||||
* Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors.
|
* Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors.
|
||||||
Fixes :issue:`1366`.
|
Fixes :issue:`1366`.
|
||||||
|
@ -7993,7 +7988,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
|
|
||||||
* Removed code and documentation for Python 2.5 or lower versions,
|
* Removed code and documentation for Python 2.5 or lower versions,
|
||||||
including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
|
including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
|
||||||
Thanks :user:`nicoddemus` for the PR (:pr:`1226`).
|
Thanks :user:`nicoddemus` for the PR (:pull:`1226`).
|
||||||
|
|
||||||
* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
|
* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
|
||||||
found in the environment, even when ``-vv`` isn't used.
|
found in the environment, even when ``-vv`` isn't used.
|
||||||
|
|
|
@ -15,33 +15,17 @@
|
||||||
#
|
#
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
import shutil
|
import shutil
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from _pytest import __version__ as full_version
|
from _pytest import __version__ as version
|
||||||
|
|
||||||
|
|
||||||
version = full_version.split("+")[0]
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import sphinx.application
|
import sphinx.application
|
||||||
|
|
||||||
|
|
||||||
PROJECT_ROOT_DIR = Path(__file__).parents[2].resolve()
|
|
||||||
IS_RELEASE_ON_RTD = (
|
|
||||||
os.getenv("READTHEDOCS", "False") == "True"
|
|
||||||
and os.environ["READTHEDOCS_VERSION_TYPE"] == "tag"
|
|
||||||
)
|
|
||||||
if IS_RELEASE_ON_RTD:
|
|
||||||
tags: set[str]
|
|
||||||
# pylint: disable-next=used-before-assignment
|
|
||||||
tags.add("is_release") # noqa: F821
|
|
||||||
|
|
||||||
release = ".".join(version.split(".")[:2])
|
release = ".".join(version.split(".")[:2])
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
@ -82,13 +66,12 @@ extensions = [
|
||||||
"pygments_pytest",
|
"pygments_pytest",
|
||||||
"sphinx.ext.autodoc",
|
"sphinx.ext.autodoc",
|
||||||
"sphinx.ext.autosummary",
|
"sphinx.ext.autosummary",
|
||||||
|
"sphinx.ext.extlinks",
|
||||||
"sphinx.ext.intersphinx",
|
"sphinx.ext.intersphinx",
|
||||||
"sphinx.ext.todo",
|
"sphinx.ext.todo",
|
||||||
"sphinx.ext.viewcode",
|
"sphinx.ext.viewcode",
|
||||||
"sphinx_removed_in",
|
"sphinx_removed_in",
|
||||||
"sphinxcontrib_trio",
|
"sphinxcontrib_trio",
|
||||||
"sphinxcontrib.towncrier.ext", # provides `towncrier-draft-entries` directive
|
|
||||||
"sphinx_issues", # implements `:issue:`, `:pr:` and other GH-related roles
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Building PDF docs on readthedocs requires inkscape for svg to pdf
|
# Building PDF docs on readthedocs requires inkscape for svg to pdf
|
||||||
|
@ -170,6 +153,16 @@ linkcheck_ignore = [
|
||||||
linkcheck_workers = 5
|
linkcheck_workers = 5
|
||||||
|
|
||||||
|
|
||||||
|
_repo = "https://github.com/pytest-dev/pytest"
|
||||||
|
extlinks = {
|
||||||
|
"bpo": ("https://bugs.python.org/issue%s", "bpo-%s"),
|
||||||
|
"pypi": ("https://pypi.org/project/%s/", "%s"),
|
||||||
|
"issue": (f"{_repo}/issues/%s", "issue #%s"),
|
||||||
|
"pull": (f"{_repo}/pull/%s", "pull request #%s"),
|
||||||
|
"user": ("https://github.com/%s", "@%s"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
nitpicky = True
|
nitpicky = True
|
||||||
nitpick_ignore = [
|
nitpick_ignore = [
|
||||||
# TODO (fix in pluggy?)
|
# TODO (fix in pluggy?)
|
||||||
|
@ -183,7 +176,6 @@ nitpick_ignore = [
|
||||||
("py:class", "SubRequest"),
|
("py:class", "SubRequest"),
|
||||||
("py:class", "TerminalReporter"),
|
("py:class", "TerminalReporter"),
|
||||||
("py:class", "_pytest._code.code.TerminalRepr"),
|
("py:class", "_pytest._code.code.TerminalRepr"),
|
||||||
("py:class", "TerminalRepr"),
|
|
||||||
("py:class", "_pytest.fixtures.FixtureFunctionMarker"),
|
("py:class", "_pytest.fixtures.FixtureFunctionMarker"),
|
||||||
("py:class", "_pytest.logging.LogCaptureHandler"),
|
("py:class", "_pytest.logging.LogCaptureHandler"),
|
||||||
("py:class", "_pytest.mark.structures.ParameterSet"),
|
("py:class", "_pytest.mark.structures.ParameterSet"),
|
||||||
|
@ -205,16 +197,13 @@ nitpick_ignore = [
|
||||||
("py:class", "_PluggyPlugin"),
|
("py:class", "_PluggyPlugin"),
|
||||||
# TypeVars
|
# TypeVars
|
||||||
("py:class", "_pytest._code.code.E"),
|
("py:class", "_pytest._code.code.E"),
|
||||||
("py:class", "E"), # due to delayed annotation
|
|
||||||
("py:class", "_pytest.fixtures.FixtureFunction"),
|
("py:class", "_pytest.fixtures.FixtureFunction"),
|
||||||
("py:class", "_pytest.nodes._NodeType"),
|
("py:class", "_pytest.nodes._NodeType"),
|
||||||
("py:class", "_NodeType"), # due to delayed annotation
|
|
||||||
("py:class", "_pytest.python_api.E"),
|
("py:class", "_pytest.python_api.E"),
|
||||||
("py:class", "_pytest.recwarn.T"),
|
("py:class", "_pytest.recwarn.T"),
|
||||||
("py:class", "_pytest.runner.TResult"),
|
("py:class", "_pytest.runner.TResult"),
|
||||||
("py:obj", "_pytest.fixtures.FixtureValue"),
|
("py:obj", "_pytest.fixtures.FixtureValue"),
|
||||||
("py:obj", "_pytest.stash.T"),
|
("py:obj", "_pytest.stash.T"),
|
||||||
("py:class", "_ScopeName"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -237,7 +226,7 @@ html_theme = "furo"
|
||||||
html_title = "pytest documentation"
|
html_title = "pytest documentation"
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
html_short_title = f"pytest-{release}"
|
html_short_title = "pytest-%s" % release
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# of the sidebar.
|
||||||
|
@ -433,18 +422,6 @@ texinfo_documents = [
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# -- Options for towncrier_draft extension -----------------------------------
|
|
||||||
|
|
||||||
towncrier_draft_autoversion_mode = "draft" # or: 'sphinx-version', 'sphinx-release'
|
|
||||||
towncrier_draft_include_empty = True
|
|
||||||
towncrier_draft_working_directory = PROJECT_ROOT_DIR
|
|
||||||
towncrier_draft_config_path = "pyproject.toml" # relative to cwd
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for sphinx_issues extension -----------------------------------
|
|
||||||
|
|
||||||
issues_github_path = "pytest-dev/pytest"
|
|
||||||
|
|
||||||
|
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
"pluggy": ("https://pluggy.readthedocs.io/en/stable", None),
|
"pluggy": ("https://pluggy.readthedocs.io/en/stable", None),
|
||||||
|
@ -458,7 +435,31 @@ intersphinx_mapping = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def setup(app: sphinx.application.Sphinx) -> None:
|
def configure_logging(app: "sphinx.application.Sphinx") -> None:
|
||||||
|
"""Configure Sphinx's WarningHandler to handle (expected) missing include."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import sphinx.util.logging
|
||||||
|
|
||||||
|
class WarnLogFilter(logging.Filter):
|
||||||
|
def filter(self, record: logging.LogRecord) -> bool:
|
||||||
|
"""Ignore warnings about missing include with "only" directive.
|
||||||
|
|
||||||
|
Ref: https://github.com/sphinx-doc/sphinx/issues/2150."""
|
||||||
|
if (
|
||||||
|
record.msg.startswith('Problems with "include" directive path:')
|
||||||
|
and "_changelog_towncrier_draft.rst" in record.msg
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
logger = logging.getLogger(sphinx.util.logging.NAMESPACE)
|
||||||
|
warn_handler = [x for x in logger.handlers if x.level == logging.WARNING]
|
||||||
|
assert len(warn_handler) == 1, warn_handler
|
||||||
|
warn_handler[0].filters.insert(0, WarnLogFilter())
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app: "sphinx.application.Sphinx") -> None:
|
||||||
app.add_crossref_type(
|
app.add_crossref_type(
|
||||||
"fixture",
|
"fixture",
|
||||||
"fixture",
|
"fixture",
|
||||||
|
@ -487,6 +488,8 @@ def setup(app: sphinx.application.Sphinx) -> None:
|
||||||
indextemplate="pair: %s; hook",
|
indextemplate="pair: %s; hook",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
configure_logging(app)
|
||||||
|
|
||||||
# legacypath.py monkey-patches pytest.Testdir in. Import the file so
|
# legacypath.py monkey-patches pytest.Testdir in. Import the file so
|
||||||
# that autodoc can discover references to it.
|
# that autodoc can discover references to it.
|
||||||
import _pytest.legacypath # noqa: F401
|
import _pytest.legacypath # noqa: F401
|
||||||
|
|
|
@ -1,4 +1 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
collect_ignore = ["conf.py"]
|
collect_ignore = ["conf.py"]
|
||||||
|
|
|
@ -22,9 +22,9 @@ Contact channels
|
||||||
requests to GitHub.
|
requests to GitHub.
|
||||||
|
|
||||||
- ``#pytest`` `on irc.libera.chat <ircs://irc.libera.chat:6697/#pytest>`_ IRC
|
- ``#pytest`` `on irc.libera.chat <ircs://irc.libera.chat:6697/#pytest>`_ IRC
|
||||||
channel for random questions (using an IRC client, or `via webchat
|
channel for random questions (using an IRC client, `via webchat
|
||||||
<https://web.libera.chat/#pytest>`)
|
<https://web.libera.chat/#pytest>`_, or `via Matrix
|
||||||
- ``#pytest`` `on Matrix https://matrix.to/#/#pytest:matrix.org>`.
|
<https://matrix.to/#/%23pytest:libera.chat>`_).
|
||||||
|
|
||||||
|
|
||||||
.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
|
.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
hello = "world"
|
hello = "world"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def setup_module(module):
|
def setup_module(module):
|
||||||
module.TestStateFullThing.classcount = 0
|
module.TestStateFullThing.classcount = 0
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
collect_ignore = ["nonpython", "customdirectory"]
|
collect_ignore = ["nonpython", "customdirectory"]
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# content of conftest.py
|
# content of conftest.py
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
# content of test_first.py
|
# content of test_first.py
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def test_1():
|
def test_1():
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
# content of test_second.py
|
# content of test_second.py
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def test_2():
|
def test_2():
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
# content of test_third.py
|
# content of test_third.py
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def test_3():
|
def test_3():
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,10 @@ You can "mark" a test function with custom metadata like this:
|
||||||
pass # perform some webtest test for your app
|
pass # perform some webtest test for your app
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.device(serial="123")
|
|
||||||
def test_something_quick():
|
def test_something_quick():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.device(serial="abc")
|
|
||||||
def test_another():
|
def test_another():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -73,28 +71,6 @@ Or the inverse, running all tests except the webtest ones:
|
||||||
|
|
||||||
===================== 3 passed, 1 deselected in 0.12s ======================
|
===================== 3 passed, 1 deselected in 0.12s ======================
|
||||||
|
|
||||||
.. _`marker_keyword_expression_example`:
|
|
||||||
|
|
||||||
Additionally, you can restrict a test run to only run tests matching one or multiple marker
|
|
||||||
keyword arguments, e.g. to run only tests marked with ``device`` and the specific ``serial="123"``:
|
|
||||||
|
|
||||||
.. code-block:: pytest
|
|
||||||
|
|
||||||
$ pytest -v -m "device(serial='123')"
|
|
||||||
=========================== test session starts ============================
|
|
||||||
platform linux -- Python 3.x.y, pytest-8.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
|
|
||||||
|
|
||||||
test_server.py::test_something_quick PASSED [100%]
|
|
||||||
|
|
||||||
===================== 1 passed, 3 deselected in 0.12s ======================
|
|
||||||
|
|
||||||
.. note:: Only keyword argument matching is supported in marker expressions.
|
|
||||||
|
|
||||||
.. note:: Only :class:`int`, (unescaped) :class:`str`, :class:`bool` & :data:`None` values are supported in marker expressions.
|
|
||||||
|
|
||||||
Selecting tests based on their node ID
|
Selecting tests based on their node ID
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
"""Module containing a parametrized tests testing cross-python serialization
|
"""Module containing a parametrized tests testing cross-python serialization
|
||||||
via the pickle module."""
|
via the pickle module."""
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# content of conftest.py
|
# content of conftest.py
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# run this with $ pytest --collect-only test_collectonly.py
|
# run this with $ pytest --collect-only test_collectonly.py
|
||||||
#
|
#
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def test_function():
|
def test_function():
|
||||||
|
|
|
@ -212,7 +212,7 @@ the command line arguments before they get processed:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# installable external plugin
|
# setuptools plugin
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
@ -405,20 +405,35 @@ Detect if running from within a pytest run
|
||||||
Usually it is a bad idea to make application code
|
Usually it is a bad idea to make application code
|
||||||
behave differently if called from a test. But if you
|
behave differently if called from a test. But if you
|
||||||
absolutely must find out if your application code is
|
absolutely must find out if your application code is
|
||||||
running from a test you can do this:
|
running from a test you can do something like this:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import os
|
# content of your_module.py
|
||||||
|
|
||||||
|
|
||||||
if os.environ.get("PYTEST_VERSION") is not None:
|
_called_from_test = False
|
||||||
# Things you want to to do if your code is called by pytest.
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
your_module._called_from_test = True
|
||||||
|
|
||||||
|
and then check for the ``your_module._called_from_test`` flag:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
if your_module._called_from_test:
|
||||||
|
# called from within a test run
|
||||||
...
|
...
|
||||||
else:
|
else:
|
||||||
# Things you want to to do if your code is not called by pytest.
|
# called "normally"
|
||||||
...
|
...
|
||||||
|
|
||||||
|
accordingly in your application.
|
||||||
|
|
||||||
Adding info to test report header
|
Adding info to test report header
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
|
@ -1073,8 +1088,8 @@ Instead of freezing the pytest runner as a separate executable, you can make
|
||||||
your frozen program work as the pytest runner by some clever
|
your frozen program work as the pytest runner by some clever
|
||||||
argument handling during program startup. This allows you to
|
argument handling during program startup. This allows you to
|
||||||
have a single executable, which is usually more convenient.
|
have a single executable, which is usually more convenient.
|
||||||
Please note that the mechanism for plugin discovery used by pytest (:ref:`entry
|
Please note that the mechanism for plugin discovery used by pytest
|
||||||
points <pip-installable plugins>`) doesn't work with frozen executables so pytest
|
(setuptools entry points) doesn't work with frozen executables so pytest
|
||||||
can't find any third party plugins automatically. To include third party plugins
|
can't find any third party plugins automatically. To include third party plugins
|
||||||
like ``pytest-timeout`` they must be imported explicitly and passed on to pytest.main.
|
like ``pytest-timeout`` they must be imported explicitly and passed on to pytest.main.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ Here is a non-exhaustive list of issues fixed by the new implementation:
|
||||||
|
|
||||||
* Marker transfer incompatible with inheritance (:issue:`535`).
|
* Marker transfer incompatible with inheritance (:issue:`535`).
|
||||||
|
|
||||||
More details can be found in the :pr:`original PR <3317>`.
|
More details can be found in the :pull:`original PR <3317>`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
|
|
@ -1931,7 +1931,7 @@ The same applies for the test folder level obviously.
|
||||||
Using fixtures from other projects
|
Using fixtures from other projects
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
Usually projects that provide pytest support will use :ref:`entry points <pip-installable plugins>`,
|
Usually projects that provide pytest support will use :ref:`entry points <setuptools entry points>`,
|
||||||
so just installing those projects into an environment will make those fixtures available for use.
|
so just installing those projects into an environment will make those fixtures available for use.
|
||||||
|
|
||||||
In case you want to use fixtures from a project that does not use entry points, you can
|
In case you want to use fixtures from a project that does not use entry points, you can
|
||||||
|
|
|
@ -76,19 +76,11 @@ Specifying a specific parametrization of a test:
|
||||||
|
|
||||||
**Run tests by marker expressions**
|
**Run tests by marker expressions**
|
||||||
|
|
||||||
To run all tests which are decorated with the ``@pytest.mark.slow`` decorator:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
pytest -m slow
|
pytest -m slow
|
||||||
|
|
||||||
|
Will run all tests which are decorated with the ``@pytest.mark.slow`` decorator.
|
||||||
To run all tests which are decorated with the annotated ``@pytest.mark.slow(phase=1)`` decorator,
|
|
||||||
with the ``phase`` keyword argument set to ``1``:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
pytest -m "slow(phase=1)"
|
|
||||||
|
|
||||||
For more information see :ref:`marks <mark>`.
|
For more information see :ref:`marks <mark>`.
|
||||||
|
|
||||||
|
@ -162,7 +154,7 @@ You can early-load plugins (internal and external) explicitly in the command-lin
|
||||||
The option receives a ``name`` parameter, which can be:
|
The option receives a ``name`` parameter, which can be:
|
||||||
|
|
||||||
* A full module dotted name, for example ``myproject.plugins``. This dotted name must be importable.
|
* A full module dotted name, for example ``myproject.plugins``. This dotted name must be importable.
|
||||||
* The entry-point name of a plugin. This is the name passed to ``importlib`` when the plugin is
|
* The entry-point name of a plugin. This is the name passed to ``setuptools`` when the plugin is
|
||||||
registered. For example to early-load the :pypi:`pytest-cov` plugin you can use::
|
registered. For example to early-load the :pypi:`pytest-cov` plugin you can use::
|
||||||
|
|
||||||
pytest -p pytest_cov
|
pytest -p pytest_cov
|
||||||
|
|
|
@ -16,8 +16,8 @@ reporting by calling :ref:`well specified hooks <hook-reference>` of the followi
|
||||||
|
|
||||||
* builtin plugins: loaded from pytest's internal ``_pytest`` directory.
|
* builtin plugins: loaded from pytest's internal ``_pytest`` directory.
|
||||||
|
|
||||||
* :ref:`external plugins <extplugins>`: installed third-party modules discovered
|
* :ref:`external plugins <extplugins>`: modules discovered through
|
||||||
through :ref:`entry points <pip-installable plugins>` in their packaging metadata
|
`setuptools entry points`_
|
||||||
|
|
||||||
* `conftest.py plugins`_: modules auto-discovered in test directories
|
* `conftest.py plugins`_: modules auto-discovered in test directories
|
||||||
|
|
||||||
|
@ -42,8 +42,7 @@ Plugin discovery order at tool startup
|
||||||
3. by scanning the command line for the ``-p name`` option
|
3. by scanning the command line for the ``-p name`` option
|
||||||
and loading the specified plugin. This happens before normal command-line parsing.
|
and loading the specified plugin. This happens before normal command-line parsing.
|
||||||
|
|
||||||
4. by loading all plugins registered through installed third-party package
|
4. by loading all plugins registered through `setuptools entry points`_.
|
||||||
:ref:`entry points <pip-installable plugins>`.
|
|
||||||
|
|
||||||
5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable.
|
5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable.
|
||||||
|
|
||||||
|
@ -143,8 +142,7 @@ Making your plugin installable by others
|
||||||
If you want to make your plugin externally available, you
|
If you want to make your plugin externally available, you
|
||||||
may define a so-called entry point for your distribution so
|
may define a so-called entry point for your distribution so
|
||||||
that ``pytest`` finds your plugin module. Entry points are
|
that ``pytest`` finds your plugin module. Entry points are
|
||||||
a feature that is provided by :std:doc:`packaging tools
|
a feature that is provided by :std:doc:`setuptools <setuptools:index>`.
|
||||||
<packaging:specifications/entry-points>`.
|
|
||||||
|
|
||||||
pytest looks up the ``pytest11`` entrypoint to discover its
|
pytest looks up the ``pytest11`` entrypoint to discover its
|
||||||
plugins, thus you can make your plugin available by defining
|
plugins, thus you can make your plugin available by defining
|
||||||
|
@ -267,9 +265,8 @@ of the variable will also be loaded as plugins, and so on.
|
||||||
tests root directory is deprecated, and will raise a warning.
|
tests root directory is deprecated, and will raise a warning.
|
||||||
|
|
||||||
This mechanism makes it easy to share fixtures within applications or even
|
This mechanism makes it easy to share fixtures within applications or even
|
||||||
external applications without the need to create external plugins using the
|
external applications without the need to create external plugins using
|
||||||
:std:doc:`entry point packaging metadata
|
the ``setuptools``'s entry point technique.
|
||||||
<packaging:guides/creating-and-discovering-plugins>` technique.
|
|
||||||
|
|
||||||
Plugins imported by :globalvar:`pytest_plugins` will also automatically be marked
|
Plugins imported by :globalvar:`pytest_plugins` will also automatically be marked
|
||||||
for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
|
for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
.. sidebar:: **Next Open Trainings and Events**
|
.. sidebar:: **Next Open Trainings and Events**
|
||||||
|
|
||||||
- `pytest tips and tricks for a better testsuite <https://ep2024.europython.eu/session/pytest-tips-and-tricks-for-a-better-testsuite>`_, at `Europython 2024 <https://ep2024.europython.eu/>`_, **July 9th 2024** (3h), Prague (CZ)
|
- `pytest development sprint <https://github.com/pytest-dev/sprint>`_, **June 17th -- 22nd 2024**, Klaus (AT) / Remote
|
||||||
|
- `pytest tips and tricks for a better testsuite <https://ep2024.europython.eu/session/pytest-tips-and-tricks-for-a-better-testsuite>`_, at `Europython 2024 <https://ep2024.europython.eu/>`_, **July 8th -- 14th 2024** (3h), Prague (CZ)
|
||||||
- `pytest: Professionelles Testen (nicht nur) für Python <https://pretalx.com/workshoptage-2024/talk/9VUHYB/>`_, at `CH Open Workshoptage <https://workshoptage.ch/>`_, **September 2nd 2024**, HSLU Rotkreuz (CH)
|
- `pytest: Professionelles Testen (nicht nur) für Python <https://pretalx.com/workshoptage-2024/talk/9VUHYB/>`_, at `CH Open Workshoptage <https://workshoptage.ch/>`_, **September 2nd 2024**, HSLU Rotkreuz (CH)
|
||||||
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_ (3 day in-depth training), **March 4th -- 6th 2025**, Leipzig (DE) / Remote
|
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_ (3 day in-depth training), **March 4th -- 6th 2025**, Leipzig (DE) / Remote
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ and can also be used to hold pytest configuration if they have a ``[pytest]`` se
|
||||||
setup.cfg
|
setup.cfg
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
``setup.cfg`` files are general purpose configuration files, used originally by ``distutils`` (now deprecated) and :std:doc:`setuptools <setuptools:userguide/declarative_config>`, and can also be used to hold pytest configuration
|
``setup.cfg`` files are general purpose configuration files, used originally by ``distutils`` (now deprecated) and `setuptools <https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`__, and can also be used to hold pytest configuration
|
||||||
if they have a ``[tool:pytest]`` section.
|
if they have a ``[tool:pytest]`` section.
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -650,7 +650,7 @@ Reference to all hooks which can be implemented by :ref:`conftest.py files <loca
|
||||||
Bootstrapping hooks
|
Bootstrapping hooks
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Bootstrapping hooks called for plugins registered early enough (internal and third-party plugins).
|
Bootstrapping hooks called for plugins registered early enough (internal and setuptools plugins).
|
||||||
|
|
||||||
.. hook:: pytest_load_initial_conftests
|
.. hook:: pytest_load_initial_conftests
|
||||||
.. autofunction:: pytest_load_initial_conftests
|
.. autofunction:: pytest_load_initial_conftests
|
||||||
|
@ -1147,9 +1147,8 @@ When set, pytest will print tracing and debug information.
|
||||||
|
|
||||||
.. envvar:: PYTEST_DISABLE_PLUGIN_AUTOLOAD
|
.. envvar:: PYTEST_DISABLE_PLUGIN_AUTOLOAD
|
||||||
|
|
||||||
When set, disables plugin auto-loading through :std:doc:`entry point packaging
|
When set, disables plugin auto-loading through setuptools entrypoints. Only explicitly specified plugins will be
|
||||||
metadata <packaging:guides/creating-and-discovering-plugins>`. Only explicitly
|
loaded.
|
||||||
specified plugins will be loaded.
|
|
||||||
|
|
||||||
.. envvar:: PYTEST_PLUGINS
|
.. envvar:: PYTEST_PLUGINS
|
||||||
|
|
||||||
|
@ -1702,13 +1701,13 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||||
This would tell ``pytest`` to not look into typical subversion or
|
This would tell ``pytest`` to not look into typical subversion or
|
||||||
sphinx-build directories or into any ``tmp`` prefixed directory.
|
sphinx-build directories or into any ``tmp`` prefixed directory.
|
||||||
|
|
||||||
Additionally, ``pytest`` will attempt to intelligently identify and ignore
|
Additionally, ``pytest`` will attempt to intelligently identify and ignore a
|
||||||
a virtualenv. Any directory deemed to be the root of a virtual environment
|
virtualenv by the presence of an activation script. Any directory deemed to
|
||||||
will not be considered during test collection unless
|
be the root of a virtual environment will not be considered during test
|
||||||
``--collect-in-virtualenv`` is given. Note also that ``norecursedirs``
|
collection unless ``--collect-in-virtualenv`` is given. Note also that
|
||||||
takes precedence over ``--collect-in-virtualenv``; e.g. if you intend to
|
``norecursedirs`` takes precedence over ``--collect-in-virtualenv``; e.g. if
|
||||||
run tests in a virtualenv with a base directory that matches ``'.*'`` you
|
you intend to run tests in a virtualenv with a base directory that matches
|
||||||
*must* override ``norecursedirs`` in addition to using the
|
``'.*'`` you *must* override ``norecursedirs`` in addition to using the
|
||||||
``--collect-in-virtualenv`` flag.
|
``--collect-in-virtualenv`` flag.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,3 @@ sphinxcontrib-svg2pdfconverter
|
||||||
# See https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045.
|
# See https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045.
|
||||||
packaging
|
packaging
|
||||||
furo
|
furo
|
||||||
sphinxcontrib-towncrier
|
|
||||||
sphinx-issues
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -20,7 +17,7 @@ def get_issues():
|
||||||
if r.status_code == 403:
|
if r.status_code == 403:
|
||||||
# API request limit exceeded
|
# API request limit exceeded
|
||||||
print(data["message"])
|
print(data["message"])
|
||||||
sys.exit(1)
|
exit(1)
|
||||||
issues.extend(data)
|
issues.extend(data)
|
||||||
|
|
||||||
# Look for next page
|
# Look for next page
|
||||||
|
@ -63,7 +60,7 @@ def report(issues):
|
||||||
kind = _get_kind(issue)
|
kind = _get_kind(issue)
|
||||||
status = issue["state"]
|
status = issue["state"]
|
||||||
number = issue["number"]
|
number = issue["number"]
|
||||||
link = f"https://github.com/pytest-dev/pytest/issues/{number}/"
|
link = "https://github.com/pytest-dev/pytest/issues/%s/" % number
|
||||||
print("----")
|
print("----")
|
||||||
print(status, kind, link)
|
print(status, kind, link)
|
||||||
print(title)
|
print(title)
|
||||||
|
@ -72,7 +69,7 @@ def report(issues):
|
||||||
# print("\n".join(lines[:3]))
|
# print("\n".join(lines[:3]))
|
||||||
# if len(lines) > 3 or len(body) > 240:
|
# if len(lines) > 3 or len(body) > 240:
|
||||||
# print("...")
|
# print("...")
|
||||||
print(f"\n\nFound {len(issues)} open issues")
|
print("\n\nFound %s open issues" % len(issues))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
327
pyproject.toml
327
pyproject.toml
|
@ -1,10 +1,3 @@
|
||||||
[build-system]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
||||||
requires = [
|
|
||||||
"setuptools>=61",
|
|
||||||
"setuptools-scm[toml]>=6.2.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
|
@ -13,15 +6,15 @@ keywords = [
|
||||||
"test",
|
"test",
|
||||||
"unittest",
|
"unittest",
|
||||||
]
|
]
|
||||||
license = { text = "MIT" }
|
license = {text = "MIT"}
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Holger Krekel" },
|
{name = "Holger Krekel"},
|
||||||
{ name = "Bruno Oliveira" },
|
{name = "Bruno Oliveira"},
|
||||||
{ name = "Ronny Pfannschmidt" },
|
{name = "Ronny Pfannschmidt"},
|
||||||
{ name = "Floris Bruynooghe" },
|
{name = "Floris Bruynooghe"},
|
||||||
{ name = "Brianna Laugher" },
|
{name = "Brianna Laugher"},
|
||||||
{ name = "Florian Bruhin" },
|
{name = "Florian Bruhin"},
|
||||||
{ name = "Others (See AUTHORS)" },
|
{name = "Others (See AUTHORS)"},
|
||||||
]
|
]
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
|
@ -38,6 +31,7 @@ classifiers = [
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
"Topic :: Software Development :: Libraries",
|
"Topic :: Software Development :: Libraries",
|
||||||
"Topic :: Software Development :: Testing",
|
"Topic :: Software Development :: Testing",
|
||||||
"Topic :: Utilities",
|
"Topic :: Utilities",
|
||||||
|
@ -46,14 +40,15 @@ dynamic = [
|
||||||
"version",
|
"version",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"colorama; sys_platform=='win32'",
|
'colorama; sys_platform == "win32"',
|
||||||
"exceptiongroup>=1.0.0rc8; python_version<'3.11'",
|
'exceptiongroup>=1.0.0rc8; python_version < "3.11"',
|
||||||
"iniconfig",
|
"iniconfig",
|
||||||
"packaging",
|
"packaging",
|
||||||
"pluggy<2,>=1.5",
|
"pluggy<2.0,>=1.5",
|
||||||
"tomli>=1; python_version<'3.11'",
|
'tomli>=1; python_version < "3.11"',
|
||||||
]
|
]
|
||||||
optional-dependencies.dev = [
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
"argcomplete",
|
"argcomplete",
|
||||||
"attrs>=19.2",
|
"attrs>=19.2",
|
||||||
"hypothesis>=3.56",
|
"hypothesis>=3.56",
|
||||||
|
@ -63,55 +58,58 @@ optional-dependencies.dev = [
|
||||||
"setuptools",
|
"setuptools",
|
||||||
"xmlschema",
|
"xmlschema",
|
||||||
]
|
]
|
||||||
urls.Changelog = "https://docs.pytest.org/en/stable/changelog.html"
|
[project.urls]
|
||||||
urls.Homepage = "https://docs.pytest.org/en/latest/"
|
Changelog = "https://docs.pytest.org/en/stable/changelog.html"
|
||||||
urls.Source = "https://github.com/pytest-dev/pytest"
|
Homepage = "https://docs.pytest.org/en/latest/"
|
||||||
urls.Tracker = "https://github.com/pytest-dev/pytest/issues"
|
Source = "https://github.com/pytest-dev/pytest"
|
||||||
urls.Twitter = "https://twitter.com/pytestdotorg"
|
Tracker = "https://github.com/pytest-dev/pytest/issues"
|
||||||
scripts."py.test" = "pytest:console_main"
|
Twitter = "https://twitter.com/pytestdotorg"
|
||||||
scripts.pytest = "pytest:console_main"
|
[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",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
"_pytest" = [
|
"_pytest" = ["py.typed"]
|
||||||
"py.typed",
|
"pytest" = ["py.typed"]
|
||||||
]
|
|
||||||
"pytest" = [
|
|
||||||
"py.typed",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.setuptools_scm]
|
[tool.setuptools_scm]
|
||||||
write_to = "src/_pytest/_version.py"
|
write_to = "src/_pytest/_version.py"
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
target-version = [
|
target-version = ['py38']
|
||||||
'py38',
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
src = ["src"]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
src = [
|
|
||||||
"src",
|
[tool.ruff.format]
|
||||||
]
|
docstring-code-format = true
|
||||||
format.docstring-code-format = true
|
|
||||||
lint.select = [
|
[tool.ruff.lint]
|
||||||
"B", # bugbear
|
select = [
|
||||||
"D", # pydocstyle
|
"B", # bugbear
|
||||||
"E", # pycodestyle
|
"D", # pydocstyle
|
||||||
"F", # pyflakes
|
"E", # pycodestyle
|
||||||
"FA100", # add future annotations
|
"F", # pyflakes
|
||||||
"I", # isort
|
"I", # isort
|
||||||
"PGH004", # pygrep-hooks - Use specific rule codes when using noqa
|
"PYI", # flake8-pyi
|
||||||
"PIE", # flake8-pie
|
"UP", # pyupgrade
|
||||||
"PLE", # pylint error
|
"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
|
"PLR1714", # Consider merging multiple comparisons
|
||||||
"PLW", # pylint warning
|
|
||||||
"PYI", # flake8-pyi
|
|
||||||
"RUF", # ruff
|
|
||||||
"T100", # flake8-debugger
|
|
||||||
"UP", # pyupgrade
|
|
||||||
"W", # pycodestyle
|
|
||||||
]
|
]
|
||||||
lint.ignore = [
|
ignore = [
|
||||||
# bugbear ignore
|
# bugbear ignore
|
||||||
"B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable.
|
"B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable.
|
||||||
"B007", # Loop control variable `i` not used within loop body
|
"B007", # Loop control variable `i` not used within loop body
|
||||||
|
@ -119,6 +117,10 @@ lint.ignore = [
|
||||||
"B010", # [*] Do not call `setattr` 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)
|
"B011", # Do not `assert False` (`python -O` removes these calls)
|
||||||
"B028", # No explicit `stacklevel` keyword argument found
|
"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
|
# pydocstyle ignore
|
||||||
"D100", # Missing docstring in public module
|
"D100", # Missing docstring in public module
|
||||||
"D101", # Missing docstring in public class
|
"D101", # Missing docstring in public class
|
||||||
|
@ -128,59 +130,46 @@ lint.ignore = [
|
||||||
"D105", # Missing docstring in magic method
|
"D105", # Missing docstring in magic method
|
||||||
"D106", # Missing docstring in public nested class
|
"D106", # Missing docstring in public nested class
|
||||||
"D107", # Missing docstring in `__init__`
|
"D107", # Missing docstring in `__init__`
|
||||||
"D205", # 1 blank line required between summary line and description
|
|
||||||
"D209", # [*] Multi-line docstring closing quotes should be on a separate line
|
"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
|
"D400", # First line should end with a period
|
||||||
"D401", # First line of docstring should be in imperative mood
|
"D401", # First line of docstring should be in imperative mood
|
||||||
"D402", # First line should not be the function's signature
|
"D402", # First line should not be the function's signature
|
||||||
"D404", # First word of the docstring should not be "This"
|
"D404", # First word of the docstring should not be "This"
|
||||||
"D415", # First line should end with a period, question mark, or exclamation point
|
"D415", # First line should end with a period, question mark, or exclamation point
|
||||||
# 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()`
|
|
||||||
# pylint ignore
|
|
||||||
"PLR5501", # Use `elif` instead of `else` then `if`
|
|
||||||
"PLW0120", # remove the else and dedent its contents
|
|
||||||
"PLW0603", # Using the global statement
|
|
||||||
"PLW2901", # for loop variable overwritten by assignment target
|
|
||||||
# ruff ignore
|
# ruff ignore
|
||||||
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
|
"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`
|
||||||
]
|
]
|
||||||
lint.per-file-ignores."src/_pytest/_py/**/*.py" = [
|
|
||||||
"B",
|
[tool.ruff.lint.pycodestyle]
|
||||||
"PYI",
|
|
||||||
]
|
|
||||||
lint.per-file-ignores."src/_pytest/_version.py" = [
|
|
||||||
"I001",
|
|
||||||
]
|
|
||||||
lint.per-file-ignores."testing/python/approx.py" = [
|
|
||||||
"B015",
|
|
||||||
]
|
|
||||||
lint.extend-safe-fixes = [
|
|
||||||
"UP006",
|
|
||||||
"UP007",
|
|
||||||
]
|
|
||||||
lint.isort.combine-as-imports = true
|
|
||||||
lint.isort.force-single-line = true
|
|
||||||
lint.isort.force-sort-within-sections = true
|
|
||||||
lint.isort.known-local-folder = [
|
|
||||||
"pytest",
|
|
||||||
"_pytest",
|
|
||||||
]
|
|
||||||
lint.isort.lines-after-imports = 2
|
|
||||||
lint.isort.order-by-type = false
|
|
||||||
lint.isort.required-imports = [
|
|
||||||
"from __future__ import annotations",
|
|
||||||
]
|
|
||||||
# In order to be able to format for 88 char in ruff format
|
# In order to be able to format for 88 char in ruff format
|
||||||
lint.pycodestyle.max-line-length = 120
|
max-line-length = 120
|
||||||
lint.pydocstyle.convention = "pep257"
|
|
||||||
lint.pyupgrade.keep-runtime-typing = false
|
[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.pylint.main]
|
[tool.pylint.main]
|
||||||
# Maximum number of characters on a single line.
|
# Maximum number of characters on a single line.
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
disable = [
|
disable= [
|
||||||
"abstract-method",
|
"abstract-method",
|
||||||
"arguments-differ",
|
"arguments-differ",
|
||||||
"arguments-renamed",
|
"arguments-renamed",
|
||||||
|
@ -190,25 +179,28 @@ disable = [
|
||||||
"bad-mcs-method-argument",
|
"bad-mcs-method-argument",
|
||||||
"broad-exception-caught",
|
"broad-exception-caught",
|
||||||
"broad-exception-raised",
|
"broad-exception-raised",
|
||||||
"cell-var-from-loop", # B023 from ruff / flake8-bugbear
|
"cell-var-from-loop",
|
||||||
"comparison-of-constants",
|
"comparison-of-constants",
|
||||||
"comparison-with-callable",
|
"comparison-with-callable",
|
||||||
"comparison-with-itself",
|
"comparison-with-itself",
|
||||||
"condition-evals-to-constant",
|
"condition-evals-to-constant",
|
||||||
"consider-using-dict-items",
|
"consider-using-dict-items",
|
||||||
|
"consider-using-enumerate",
|
||||||
"consider-using-from-import",
|
"consider-using-from-import",
|
||||||
"consider-using-f-string",
|
"consider-using-f-string",
|
||||||
"consider-using-in",
|
"consider-using-in",
|
||||||
|
"consider-using-sys-exit",
|
||||||
"consider-using-ternary",
|
"consider-using-ternary",
|
||||||
"consider-using-with",
|
"consider-using-with",
|
||||||
"cyclic-import",
|
"cyclic-import",
|
||||||
"disallowed-name", # foo / bar are used often in tests
|
"disallowed-name",
|
||||||
"duplicate-code",
|
"duplicate-code",
|
||||||
"eval-used",
|
"eval-used",
|
||||||
"exec-used",
|
"exec-used",
|
||||||
"expression-not-assigned",
|
"expression-not-assigned",
|
||||||
"fixme",
|
"fixme",
|
||||||
"global-statement",
|
"global-statement",
|
||||||
|
"implicit-str-concat",
|
||||||
"import-error",
|
"import-error",
|
||||||
"import-outside-toplevel",
|
"import-outside-toplevel",
|
||||||
"inconsistent-return-statements",
|
"inconsistent-return-statements",
|
||||||
|
@ -219,9 +211,10 @@ disable = [
|
||||||
"keyword-arg-before-vararg",
|
"keyword-arg-before-vararg",
|
||||||
"line-too-long",
|
"line-too-long",
|
||||||
"method-hidden",
|
"method-hidden",
|
||||||
|
"misplaced-bare-raise",
|
||||||
"missing-docstring",
|
"missing-docstring",
|
||||||
"missing-timeout",
|
"missing-timeout",
|
||||||
"multiple-statements", # multiple-statements-on-one-line-colon (E701) from ruff
|
"multiple-statements",
|
||||||
"no-else-break",
|
"no-else-break",
|
||||||
"no-else-continue",
|
"no-else-continue",
|
||||||
"no-else-raise",
|
"no-else-raise",
|
||||||
|
@ -234,7 +227,6 @@ disable = [
|
||||||
"pointless-exception-statement",
|
"pointless-exception-statement",
|
||||||
"pointless-statement",
|
"pointless-statement",
|
||||||
"pointless-string-statement",
|
"pointless-string-statement",
|
||||||
"possibly-used-before-assignment",
|
|
||||||
"protected-access",
|
"protected-access",
|
||||||
"raise-missing-from",
|
"raise-missing-from",
|
||||||
"redefined-argument-from-local",
|
"redefined-argument-from-local",
|
||||||
|
@ -282,6 +274,7 @@ disable = [
|
||||||
"useless-else-on-loop",
|
"useless-else-on-loop",
|
||||||
"useless-import-alias",
|
"useless-import-alias",
|
||||||
"useless-return",
|
"useless-return",
|
||||||
|
"use-maxsplit-arg",
|
||||||
"using-constant-test",
|
"using-constant-test",
|
||||||
"wrong-import-order",
|
"wrong-import-order",
|
||||||
]
|
]
|
||||||
|
@ -297,27 +290,16 @@ indent = 4
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
minversion = "2.0"
|
minversion = "2.0"
|
||||||
addopts = "-rfEX -p pytester --strict-markers"
|
addopts = "-rfEX -p pytester --strict-markers"
|
||||||
python_files = [
|
python_files = ["test_*.py", "*_test.py", "testing/python/*.py"]
|
||||||
"test_*.py",
|
python_classes = ["Test", "Acceptance"]
|
||||||
"*_test.py",
|
python_functions = ["test"]
|
||||||
"testing/python/*.py",
|
|
||||||
]
|
|
||||||
python_classes = [
|
|
||||||
"Test",
|
|
||||||
"Acceptance",
|
|
||||||
]
|
|
||||||
python_functions = [
|
|
||||||
"test",
|
|
||||||
]
|
|
||||||
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
|
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
|
||||||
testpaths = [
|
testpaths = ["testing"]
|
||||||
"testing",
|
|
||||||
]
|
|
||||||
norecursedirs = [
|
norecursedirs = [
|
||||||
"testing/example_scripts",
|
"testing/example_scripts",
|
||||||
".*",
|
".*",
|
||||||
"build",
|
"build",
|
||||||
"dist",
|
"dist",
|
||||||
]
|
]
|
||||||
xfail_strict = true
|
xfail_strict = true
|
||||||
filterwarnings = [
|
filterwarnings = [
|
||||||
|
@ -358,9 +340,6 @@ markers = [
|
||||||
"foo",
|
"foo",
|
||||||
"bar",
|
"bar",
|
||||||
"baz",
|
"baz",
|
||||||
"number_mark",
|
|
||||||
"builtin_matchers_mark",
|
|
||||||
"str_mark",
|
|
||||||
# conftest.py reorders tests moving slow ones to the end of the list
|
# conftest.py reorders tests moving slow ones to the end of the list
|
||||||
"slow",
|
"slow",
|
||||||
# experimental mark for all tests using pexpect
|
# experimental mark for all tests using pexpect
|
||||||
|
@ -375,85 +354,49 @@ directory = "changelog/"
|
||||||
title_format = "pytest {version} ({project_date})"
|
title_format = "pytest {version} ({project_date})"
|
||||||
template = "changelog/_template.rst"
|
template = "changelog/_template.rst"
|
||||||
|
|
||||||
# NOTE: The types are declared because:
|
[[tool.towncrier.type]]
|
||||||
# NOTE: - there is no mechanism to override just the value of
|
directory = "breaking"
|
||||||
# NOTE: `tool.towncrier.type.misc.showcontent`;
|
name = "Breaking Changes"
|
||||||
# NOTE: - and, we want to declare extra non-default types for
|
showcontent = true
|
||||||
# NOTE: clarity and flexibility.
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
# When something public gets removed in a breaking way. Could be
|
directory = "deprecation"
|
||||||
# deprecated in an earlier release.
|
name = "Deprecations"
|
||||||
directory = "breaking"
|
showcontent = true
|
||||||
name = "Removals and backward incompatible breaking changes"
|
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
# Declarations of future API removals and breaking changes in behavior.
|
directory = "feature"
|
||||||
directory = "deprecation"
|
name = "Features"
|
||||||
name = "Deprecations (removal in next major release)"
|
showcontent = true
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
# New behaviors, public APIs. That sort of stuff.
|
directory = "improvement"
|
||||||
directory = "feature"
|
name = "Improvements"
|
||||||
name = "New features"
|
showcontent = true
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
# New behaviors in existing features.
|
directory = "bugfix"
|
||||||
directory = "improvement"
|
name = "Bug Fixes"
|
||||||
name = "Improvements in existing functionality"
|
showcontent = true
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
# Something we deemed an improper undesired behavior that got corrected
|
directory = "vendor"
|
||||||
# in the release to match pre-agreed expectations.
|
name = "Vendored Libraries"
|
||||||
directory = "bugfix"
|
showcontent = true
|
||||||
name = "Bug fixes"
|
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
# Updates regarding bundling dependencies.
|
directory = "doc"
|
||||||
directory = "vendor"
|
name = "Improved Documentation"
|
||||||
name = "Vendored libraries"
|
showcontent = true
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
# Notable updates to the documentation structure or build process.
|
directory = "trivial"
|
||||||
directory = "doc"
|
name = "Trivial/Internal Changes"
|
||||||
name = "Improved documentation"
|
showcontent = true
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
|
||||||
# Notes for downstreams about unobvious side effects and tooling. Changes
|
|
||||||
# in the test invocation considerations and runtime assumptions.
|
|
||||||
directory = "packaging"
|
|
||||||
name = "Packaging updates and notes for downstreams"
|
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
|
||||||
# Stuff that affects the contributor experience. e.g. Running tests,
|
|
||||||
# building the docs, setting up the development environment.
|
|
||||||
directory = "contrib"
|
|
||||||
name = "Contributor-facing changes"
|
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
|
||||||
# Changes that are hard to assign to any of the above categories.
|
|
||||||
directory = "misc"
|
|
||||||
name = "Miscellaneous internal changes"
|
|
||||||
showcontent = true
|
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
files = [
|
files = ["src", "testing", "scripts"]
|
||||||
"src",
|
mypy_path = ["src"]
|
||||||
"testing",
|
|
||||||
"scripts",
|
|
||||||
]
|
|
||||||
mypy_path = [
|
|
||||||
"src",
|
|
||||||
]
|
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_any_generics = true
|
disallow_any_generics = true
|
||||||
disallow_untyped_defs = true
|
disallow_untyped_defs = true
|
||||||
|
|
|
@ -9,8 +9,6 @@ our CHANGELOG) into Markdown (which is required by GitHub Releases).
|
||||||
Requires Python3.6+.
|
Requires Python3.6+.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
|
@ -14,8 +14,6 @@ After that, it will create a release using the `release` tox environment, and pu
|
||||||
`pytest bot <pytestbot@gmail.com>` commit author.
|
`pytest bot <pytestbot@gmail.com>` commit author.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# mypy: disallow-untyped-defs
|
# mypy: disallow-untyped-defs
|
||||||
"""Invoke development tasks."""
|
"""Invoke development tasks."""
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# mypy: disallow-untyped-defs
|
||||||
|
from subprocess import call
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
"""
|
||||||
|
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"
|
||||||
|
) as draft_file:
|
||||||
|
return call(("towncrier", "--draft"), stdout=draft_file)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
|
@ -1,6 +1,4 @@
|
||||||
# mypy: disallow-untyped-defs
|
# mypy: disallow-untyped-defs
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["__version__", "version_tuple"]
|
__all__ = ["__version__", "version_tuple"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -62,13 +62,13 @@ If things do not work right away:
|
||||||
global argcomplete script).
|
global argcomplete script).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from glob import glob
|
from glob import glob
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class FastFilesCompleter:
|
class FastFilesCompleter:
|
||||||
|
@ -77,7 +77,7 @@ class FastFilesCompleter:
|
||||||
def __init__(self, directories: bool = True) -> None:
|
def __init__(self, directories: bool = True) -> None:
|
||||||
self.directories = directories
|
self.directories = directories
|
||||||
|
|
||||||
def __call__(self, prefix: str, **kwargs: Any) -> list[str]:
|
def __call__(self, prefix: str, **kwargs: Any) -> List[str]:
|
||||||
# Only called on non option completions.
|
# Only called on non option completions.
|
||||||
if os.sep in prefix[1:]:
|
if os.sep in prefix[1:]:
|
||||||
prefix_dir = len(os.path.dirname(prefix) + os.sep)
|
prefix_dir = len(os.path.dirname(prefix) + os.sep)
|
||||||
|
@ -104,7 +104,7 @@ if os.environ.get("_ARGCOMPLETE"):
|
||||||
import argcomplete.completers
|
import argcomplete.completers
|
||||||
except ImportError:
|
except ImportError:
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
filescompleter: FastFilesCompleter | None = FastFilesCompleter()
|
filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter()
|
||||||
|
|
||||||
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
|
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
|
||||||
argcomplete.autocomplete(parser, always_complete_options=False)
|
argcomplete.autocomplete(parser, always_complete_options=False)
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
"""Python inspection/code generation API."""
|
"""Python inspection/code generation API."""
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from .code import Code
|
from .code import Code
|
||||||
from .code import ExceptionInfo
|
from .code import ExceptionInfo
|
||||||
from .code import filter_traceback
|
from .code import filter_traceback
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -19,6 +17,7 @@ from types import TracebackType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
from typing import Dict
|
||||||
from typing import Final
|
from typing import Final
|
||||||
from typing import final
|
from typing import final
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
@ -26,9 +25,11 @@ from typing import Iterable
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
from typing import Mapping
|
from typing import Mapping
|
||||||
|
from typing import Optional
|
||||||
from typing import overload
|
from typing import overload
|
||||||
from typing import Pattern
|
from typing import Pattern
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
from typing import Set
|
||||||
from typing import SupportsIndex
|
from typing import SupportsIndex
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
@ -54,9 +55,7 @@ from _pytest.pathlib import bestrelpath
|
||||||
if sys.version_info < (3, 11):
|
if sys.version_info < (3, 11):
|
||||||
from exceptiongroup import BaseExceptionGroup
|
from exceptiongroup import BaseExceptionGroup
|
||||||
|
|
||||||
TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
||||||
|
|
||||||
EXCEPTION_OR_MORE = Union[Type[Exception], Tuple[Type[Exception], ...]]
|
|
||||||
|
|
||||||
|
|
||||||
class Code:
|
class Code:
|
||||||
|
@ -68,7 +67,7 @@ class Code:
|
||||||
self.raw = obj
|
self.raw = obj
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_function(cls, obj: object) -> Code:
|
def from_function(cls, obj: object) -> "Code":
|
||||||
return cls(getrawcode(obj))
|
return cls(getrawcode(obj))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
@ -86,7 +85,7 @@ class Code:
|
||||||
return self.raw.co_name
|
return self.raw.co_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path | str:
|
def path(self) -> Union[Path, str]:
|
||||||
"""Return a path object pointing to source code, or an ``str`` in
|
"""Return a path object pointing to source code, or an ``str`` in
|
||||||
case of ``OSError`` / non-existing file."""
|
case of ``OSError`` / non-existing file."""
|
||||||
if not self.raw.co_filename:
|
if not self.raw.co_filename:
|
||||||
|
@ -103,17 +102,17 @@ class Code:
|
||||||
return self.raw.co_filename
|
return self.raw.co_filename
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fullsource(self) -> Source | None:
|
def fullsource(self) -> Optional["Source"]:
|
||||||
"""Return a _pytest._code.Source object for the full source file of the code."""
|
"""Return a _pytest._code.Source object for the full source file of the code."""
|
||||||
full, _ = findsource(self.raw)
|
full, _ = findsource(self.raw)
|
||||||
return full
|
return full
|
||||||
|
|
||||||
def source(self) -> Source:
|
def source(self) -> "Source":
|
||||||
"""Return a _pytest._code.Source object for the code object's source only."""
|
"""Return a _pytest._code.Source object for the code object's source only."""
|
||||||
# return source only for that part of code
|
# return source only for that part of code
|
||||||
return Source(self.raw)
|
return Source(self.raw)
|
||||||
|
|
||||||
def getargs(self, var: bool = False) -> tuple[str, ...]:
|
def getargs(self, var: bool = False) -> Tuple[str, ...]:
|
||||||
"""Return a tuple with the argument names for the code object.
|
"""Return a tuple with the argument names for the code object.
|
||||||
|
|
||||||
If 'var' is set True also return the names of the variable and
|
If 'var' is set True also return the names of the variable and
|
||||||
|
@ -142,11 +141,11 @@ class Frame:
|
||||||
return self.raw.f_lineno - 1
|
return self.raw.f_lineno - 1
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def f_globals(self) -> dict[str, Any]:
|
def f_globals(self) -> Dict[str, Any]:
|
||||||
return self.raw.f_globals
|
return self.raw.f_globals
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def f_locals(self) -> dict[str, Any]:
|
def f_locals(self) -> Dict[str, Any]:
|
||||||
return self.raw.f_locals
|
return self.raw.f_locals
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -154,7 +153,7 @@ class Frame:
|
||||||
return Code(self.raw.f_code)
|
return Code(self.raw.f_code)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def statement(self) -> Source:
|
def statement(self) -> "Source":
|
||||||
"""Statement this frame is at."""
|
"""Statement this frame is at."""
|
||||||
if self.code.fullsource is None:
|
if self.code.fullsource is None:
|
||||||
return Source("")
|
return Source("")
|
||||||
|
@ -198,14 +197,14 @@ class TracebackEntry:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
rawentry: TracebackType,
|
rawentry: TracebackType,
|
||||||
repr_style: Literal["short", "long"] | None = None,
|
repr_style: Optional['Literal["short", "long"]'] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._rawentry: Final = rawentry
|
self._rawentry: "Final" = rawentry
|
||||||
self._repr_style: Final = repr_style
|
self._repr_style: "Final" = repr_style
|
||||||
|
|
||||||
def with_repr_style(
|
def with_repr_style(
|
||||||
self, repr_style: Literal["short", "long"] | None
|
self, repr_style: Optional['Literal["short", "long"]']
|
||||||
) -> TracebackEntry:
|
) -> "TracebackEntry":
|
||||||
return TracebackEntry(self._rawentry, repr_style)
|
return TracebackEntry(self._rawentry, repr_style)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -224,19 +223,19 @@ class TracebackEntry:
|
||||||
return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
|
return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def statement(self) -> Source:
|
def statement(self) -> "Source":
|
||||||
"""_pytest._code.Source object for the current statement."""
|
"""_pytest._code.Source object for the current statement."""
|
||||||
source = self.frame.code.fullsource
|
source = self.frame.code.fullsource
|
||||||
assert source is not None
|
assert source is not None
|
||||||
return source.getstatement(self.lineno)
|
return source.getstatement(self.lineno)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path | str:
|
def path(self) -> Union[Path, str]:
|
||||||
"""Path to the source code."""
|
"""Path to the source code."""
|
||||||
return self.frame.code.path
|
return self.frame.code.path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def locals(self) -> dict[str, Any]:
|
def locals(self) -> Dict[str, Any]:
|
||||||
"""Locals of underlying frame."""
|
"""Locals of underlying frame."""
|
||||||
return self.frame.f_locals
|
return self.frame.f_locals
|
||||||
|
|
||||||
|
@ -244,8 +243,8 @@ class TracebackEntry:
|
||||||
return self.frame.code.firstlineno
|
return self.frame.code.firstlineno
|
||||||
|
|
||||||
def getsource(
|
def getsource(
|
||||||
self, astcache: dict[str | Path, ast.AST] | None = None
|
self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None
|
||||||
) -> Source | None:
|
) -> Optional["Source"]:
|
||||||
"""Return failing source code."""
|
"""Return failing source code."""
|
||||||
# we use the passed in astcache to not reparse asttrees
|
# we use the passed in astcache to not reparse asttrees
|
||||||
# within exception info printing
|
# within exception info printing
|
||||||
|
@ -271,7 +270,7 @@ class TracebackEntry:
|
||||||
|
|
||||||
source = property(getsource)
|
source = property(getsource)
|
||||||
|
|
||||||
def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool:
|
def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool:
|
||||||
"""Return True if the current frame has a var __tracebackhide__
|
"""Return True if the current frame has a var __tracebackhide__
|
||||||
resolving to True.
|
resolving to True.
|
||||||
|
|
||||||
|
@ -280,7 +279,9 @@ class TracebackEntry:
|
||||||
|
|
||||||
Mostly for internal use.
|
Mostly for internal use.
|
||||||
"""
|
"""
|
||||||
tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False
|
tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = (
|
||||||
|
False
|
||||||
|
)
|
||||||
for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals):
|
for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals):
|
||||||
# in normal cases, f_locals and f_globals are dictionaries
|
# in normal cases, f_locals and f_globals are dictionaries
|
||||||
# however via `exec(...)` / `eval(...)` they can be other types
|
# however via `exec(...)` / `eval(...)` they can be other types
|
||||||
|
@ -325,13 +326,13 @@ class Traceback(List[TracebackEntry]):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tb: TracebackType | Iterable[TracebackEntry],
|
tb: Union[TracebackType, Iterable[TracebackEntry]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize from given python traceback object and ExceptionInfo."""
|
"""Initialize from given python traceback object and ExceptionInfo."""
|
||||||
if isinstance(tb, TracebackType):
|
if isinstance(tb, TracebackType):
|
||||||
|
|
||||||
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
|
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
|
||||||
cur_: TracebackType | None = cur
|
cur_: Optional[TracebackType] = cur
|
||||||
while cur_ is not None:
|
while cur_ is not None:
|
||||||
yield TracebackEntry(cur_)
|
yield TracebackEntry(cur_)
|
||||||
cur_ = cur_.tb_next
|
cur_ = cur_.tb_next
|
||||||
|
@ -342,11 +343,11 @@ class Traceback(List[TracebackEntry]):
|
||||||
|
|
||||||
def cut(
|
def cut(
|
||||||
self,
|
self,
|
||||||
path: os.PathLike[str] | str | None = None,
|
path: Optional[Union["os.PathLike[str]", str]] = None,
|
||||||
lineno: int | None = None,
|
lineno: Optional[int] = None,
|
||||||
firstlineno: int | None = None,
|
firstlineno: Optional[int] = None,
|
||||||
excludepath: os.PathLike[str] | None = None,
|
excludepath: Optional["os.PathLike[str]"] = None,
|
||||||
) -> Traceback:
|
) -> "Traceback":
|
||||||
"""Return a Traceback instance wrapping part of this Traceback.
|
"""Return a Traceback instance wrapping part of this Traceback.
|
||||||
|
|
||||||
By providing any combination of path, lineno and firstlineno, the
|
By providing any combination of path, lineno and firstlineno, the
|
||||||
|
@ -377,12 +378,14 @@ class Traceback(List[TracebackEntry]):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ...
|
def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self, key: slice) -> Traceback: ...
|
def __getitem__(self, key: slice) -> "Traceback": ...
|
||||||
|
|
||||||
def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback:
|
def __getitem__(
|
||||||
|
self, key: Union["SupportsIndex", slice]
|
||||||
|
) -> Union[TracebackEntry, "Traceback"]:
|
||||||
if isinstance(key, slice):
|
if isinstance(key, slice):
|
||||||
return self.__class__(super().__getitem__(key))
|
return self.__class__(super().__getitem__(key))
|
||||||
else:
|
else:
|
||||||
|
@ -390,9 +393,12 @@ class Traceback(List[TracebackEntry]):
|
||||||
|
|
||||||
def filter(
|
def filter(
|
||||||
self,
|
self,
|
||||||
excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool],
|
excinfo_or_fn: Union[
|
||||||
|
"ExceptionInfo[BaseException]",
|
||||||
|
Callable[[TracebackEntry], bool],
|
||||||
|
],
|
||||||
/,
|
/,
|
||||||
) -> Traceback:
|
) -> "Traceback":
|
||||||
"""Return a Traceback instance with certain items removed.
|
"""Return a Traceback instance with certain items removed.
|
||||||
|
|
||||||
If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s
|
If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s
|
||||||
|
@ -408,10 +414,10 @@ class Traceback(List[TracebackEntry]):
|
||||||
fn = excinfo_or_fn
|
fn = excinfo_or_fn
|
||||||
return Traceback(filter(fn, self))
|
return Traceback(filter(fn, self))
|
||||||
|
|
||||||
def recursionindex(self) -> int | None:
|
def recursionindex(self) -> Optional[int]:
|
||||||
"""Return the index of the frame/TracebackEntry where recursion originates if
|
"""Return the index of the frame/TracebackEntry where recursion originates if
|
||||||
appropriate, None if no recursion occurred."""
|
appropriate, None if no recursion occurred."""
|
||||||
cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {}
|
cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {}
|
||||||
for i, entry in enumerate(self):
|
for i, entry in enumerate(self):
|
||||||
# id for the code.raw is needed to work around
|
# id for the code.raw is needed to work around
|
||||||
# the strange metaprogramming in the decorator lib from pypi
|
# the strange metaprogramming in the decorator lib from pypi
|
||||||
|
@ -439,15 +445,15 @@ class ExceptionInfo(Generic[E]):
|
||||||
|
|
||||||
_assert_start_repr: ClassVar = "AssertionError('assert "
|
_assert_start_repr: ClassVar = "AssertionError('assert "
|
||||||
|
|
||||||
_excinfo: tuple[type[E], E, TracebackType] | None
|
_excinfo: Optional[Tuple[Type["E"], "E", TracebackType]]
|
||||||
_striptext: str
|
_striptext: str
|
||||||
_traceback: Traceback | None
|
_traceback: Optional[Traceback]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
excinfo: tuple[type[E], E, TracebackType] | None,
|
excinfo: Optional[Tuple[Type["E"], "E", TracebackType]],
|
||||||
striptext: str = "",
|
striptext: str = "",
|
||||||
traceback: Traceback | None = None,
|
traceback: Optional[Traceback] = None,
|
||||||
*,
|
*,
|
||||||
_ispytest: bool = False,
|
_ispytest: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -463,8 +469,8 @@ class ExceptionInfo(Generic[E]):
|
||||||
# This is OK to ignore because this class is (conceptually) readonly.
|
# This is OK to ignore because this class is (conceptually) readonly.
|
||||||
# See https://github.com/python/mypy/issues/7049.
|
# See https://github.com/python/mypy/issues/7049.
|
||||||
exception: E, # type: ignore[misc]
|
exception: E, # type: ignore[misc]
|
||||||
exprinfo: str | None = None,
|
exprinfo: Optional[str] = None,
|
||||||
) -> ExceptionInfo[E]:
|
) -> "ExceptionInfo[E]":
|
||||||
"""Return an ExceptionInfo for an existing exception.
|
"""Return an ExceptionInfo for an existing exception.
|
||||||
|
|
||||||
The exception must have a non-``None`` ``__traceback__`` attribute,
|
The exception must have a non-``None`` ``__traceback__`` attribute,
|
||||||
|
@ -489,9 +495,9 @@ class ExceptionInfo(Generic[E]):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_exc_info(
|
def from_exc_info(
|
||||||
cls,
|
cls,
|
||||||
exc_info: tuple[type[E], E, TracebackType],
|
exc_info: Tuple[Type[E], E, TracebackType],
|
||||||
exprinfo: str | None = None,
|
exprinfo: Optional[str] = None,
|
||||||
) -> ExceptionInfo[E]:
|
) -> "ExceptionInfo[E]":
|
||||||
"""Like :func:`from_exception`, but using old-style exc_info tuple."""
|
"""Like :func:`from_exception`, but using old-style exc_info tuple."""
|
||||||
_striptext = ""
|
_striptext = ""
|
||||||
if exprinfo is None and isinstance(exc_info[1], AssertionError):
|
if exprinfo is None and isinstance(exc_info[1], AssertionError):
|
||||||
|
@ -504,7 +510,9 @@ class ExceptionInfo(Generic[E]):
|
||||||
return cls(exc_info, _striptext, _ispytest=True)
|
return cls(exc_info, _striptext, _ispytest=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]:
|
def from_current(
|
||||||
|
cls, exprinfo: Optional[str] = None
|
||||||
|
) -> "ExceptionInfo[BaseException]":
|
||||||
"""Return an ExceptionInfo matching the current traceback.
|
"""Return an ExceptionInfo matching the current traceback.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
@ -524,17 +532,17 @@ class ExceptionInfo(Generic[E]):
|
||||||
return ExceptionInfo.from_exc_info(exc_info, exprinfo)
|
return ExceptionInfo.from_exc_info(exc_info, exprinfo)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def for_later(cls) -> ExceptionInfo[E]:
|
def for_later(cls) -> "ExceptionInfo[E]":
|
||||||
"""Return an unfilled ExceptionInfo."""
|
"""Return an unfilled ExceptionInfo."""
|
||||||
return cls(None, _ispytest=True)
|
return cls(None, _ispytest=True)
|
||||||
|
|
||||||
def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None:
|
def fill_unfilled(self, exc_info: Tuple[Type[E], E, TracebackType]) -> None:
|
||||||
"""Fill an unfilled ExceptionInfo created with ``for_later()``."""
|
"""Fill an unfilled ExceptionInfo created with ``for_later()``."""
|
||||||
assert self._excinfo is None, "ExceptionInfo was already filled"
|
assert self._excinfo is None, "ExceptionInfo was already filled"
|
||||||
self._excinfo = exc_info
|
self._excinfo = exc_info
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self) -> type[E]:
|
def type(self) -> Type[E]:
|
||||||
"""The exception class."""
|
"""The exception class."""
|
||||||
assert (
|
assert (
|
||||||
self._excinfo is not None
|
self._excinfo is not None
|
||||||
|
@ -597,14 +605,16 @@ class ExceptionInfo(Generic[E]):
|
||||||
text = text[len(self._striptext) :]
|
text = text[len(self._striptext) :]
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool:
|
def errisinstance(
|
||||||
|
self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]]
|
||||||
|
) -> bool:
|
||||||
"""Return True if the exception is an instance of exc.
|
"""Return True if the exception is an instance of exc.
|
||||||
|
|
||||||
Consider using ``isinstance(excinfo.value, exc)`` instead.
|
Consider using ``isinstance(excinfo.value, exc)`` instead.
|
||||||
"""
|
"""
|
||||||
return isinstance(self.value, exc)
|
return isinstance(self.value, exc)
|
||||||
|
|
||||||
def _getreprcrash(self) -> ReprFileLocation | None:
|
def _getreprcrash(self) -> Optional["ReprFileLocation"]:
|
||||||
# Find last non-hidden traceback entry that led to the exception of the
|
# Find last non-hidden traceback entry that led to the exception of the
|
||||||
# traceback, or None if all hidden.
|
# traceback, or None if all hidden.
|
||||||
for i in range(-1, -len(self.traceback) - 1, -1):
|
for i in range(-1, -len(self.traceback) - 1, -1):
|
||||||
|
@ -618,15 +628,15 @@ class ExceptionInfo(Generic[E]):
|
||||||
def getrepr(
|
def getrepr(
|
||||||
self,
|
self,
|
||||||
showlocals: bool = False,
|
showlocals: bool = False,
|
||||||
style: TracebackStyle = "long",
|
style: _TracebackStyle = "long",
|
||||||
abspath: bool = False,
|
abspath: bool = False,
|
||||||
tbfilter: bool
|
tbfilter: Union[
|
||||||
| Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True,
|
bool, Callable[["ExceptionInfo[BaseException]"], Traceback]
|
||||||
|
] = True,
|
||||||
funcargs: bool = False,
|
funcargs: bool = False,
|
||||||
truncate_locals: bool = True,
|
truncate_locals: bool = True,
|
||||||
truncate_args: bool = True,
|
|
||||||
chain: bool = True,
|
chain: bool = True,
|
||||||
) -> ReprExceptionInfo | ExceptionChainRepr:
|
) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
|
||||||
"""Return str()able representation of this exception info.
|
"""Return str()able representation of this exception info.
|
||||||
|
|
||||||
:param bool showlocals:
|
:param bool showlocals:
|
||||||
|
@ -655,9 +665,6 @@ class ExceptionInfo(Generic[E]):
|
||||||
:param bool truncate_locals:
|
:param bool truncate_locals:
|
||||||
With ``showlocals==True``, make sure locals can be safely represented as strings.
|
With ``showlocals==True``, make sure locals can be safely represented as strings.
|
||||||
|
|
||||||
:param bool truncate_args:
|
|
||||||
With ``showargs==True``, make sure args can be safely represented as strings.
|
|
||||||
|
|
||||||
:param bool chain:
|
:param bool chain:
|
||||||
If chained exceptions in Python 3 should be shown.
|
If chained exceptions in Python 3 should be shown.
|
||||||
|
|
||||||
|
@ -684,7 +691,6 @@ class ExceptionInfo(Generic[E]):
|
||||||
tbfilter=tbfilter,
|
tbfilter=tbfilter,
|
||||||
funcargs=funcargs,
|
funcargs=funcargs,
|
||||||
truncate_locals=truncate_locals,
|
truncate_locals=truncate_locals,
|
||||||
truncate_args=truncate_args,
|
|
||||||
chain=chain,
|
chain=chain,
|
||||||
)
|
)
|
||||||
return fmt.repr_excinfo(self)
|
return fmt.repr_excinfo(self)
|
||||||
|
@ -708,7 +714,7 @@ class ExceptionInfo(Generic[E]):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def match(self, regexp: str | Pattern[str]) -> Literal[True]:
|
def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]":
|
||||||
"""Check whether the regular expression `regexp` matches the string
|
"""Check whether the regular expression `regexp` matches the string
|
||||||
representation of the exception using :func:`python:re.search`.
|
representation of the exception using :func:`python:re.search`.
|
||||||
|
|
||||||
|
@ -726,9 +732,9 @@ class ExceptionInfo(Generic[E]):
|
||||||
def _group_contains(
|
def _group_contains(
|
||||||
self,
|
self,
|
||||||
exc_group: BaseExceptionGroup[BaseException],
|
exc_group: BaseExceptionGroup[BaseException],
|
||||||
expected_exception: EXCEPTION_OR_MORE,
|
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
||||||
match: str | Pattern[str] | None,
|
match: Union[str, Pattern[str], None],
|
||||||
target_depth: int | None = None,
|
target_depth: Optional[int] = None,
|
||||||
current_depth: int = 1,
|
current_depth: int = 1,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Return `True` if a `BaseExceptionGroup` contains a matching exception."""
|
"""Return `True` if a `BaseExceptionGroup` contains a matching exception."""
|
||||||
|
@ -755,10 +761,10 @@ class ExceptionInfo(Generic[E]):
|
||||||
|
|
||||||
def group_contains(
|
def group_contains(
|
||||||
self,
|
self,
|
||||||
expected_exception: EXCEPTION_OR_MORE,
|
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
||||||
*,
|
*,
|
||||||
match: str | Pattern[str] | None = None,
|
match: Union[str, Pattern[str], None] = None,
|
||||||
depth: int | None = None,
|
depth: Optional[int] = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Check whether a captured exception group contains a matching exception.
|
"""Check whether a captured exception group contains a matching exception.
|
||||||
|
|
||||||
|
@ -798,18 +804,17 @@ class FormattedExcinfo:
|
||||||
fail_marker: ClassVar = "E"
|
fail_marker: ClassVar = "E"
|
||||||
|
|
||||||
showlocals: bool = False
|
showlocals: bool = False
|
||||||
style: TracebackStyle = "long"
|
style: _TracebackStyle = "long"
|
||||||
abspath: bool = True
|
abspath: bool = True
|
||||||
tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True
|
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
|
||||||
funcargs: bool = False
|
funcargs: bool = False
|
||||||
truncate_locals: bool = True
|
truncate_locals: bool = True
|
||||||
truncate_args: bool = True
|
|
||||||
chain: bool = True
|
chain: bool = True
|
||||||
astcache: dict[str | Path, ast.AST] = dataclasses.field(
|
astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field(
|
||||||
default_factory=dict, init=False, repr=False
|
default_factory=dict, init=False, repr=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def _getindent(self, source: Source) -> int:
|
def _getindent(self, source: "Source") -> int:
|
||||||
# Figure out indent for the given source.
|
# Figure out indent for the given source.
|
||||||
try:
|
try:
|
||||||
s = str(source.getstatement(len(source) - 1))
|
s = str(source.getstatement(len(source) - 1))
|
||||||
|
@ -824,31 +829,27 @@ class FormattedExcinfo:
|
||||||
return 0
|
return 0
|
||||||
return 4 + (len(s) - len(s.lstrip()))
|
return 4 + (len(s) - len(s.lstrip()))
|
||||||
|
|
||||||
def _getentrysource(self, entry: TracebackEntry) -> Source | None:
|
def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]:
|
||||||
source = entry.getsource(self.astcache)
|
source = entry.getsource(self.astcache)
|
||||||
if source is not None:
|
if source is not None:
|
||||||
source = source.deindent()
|
source = source.deindent()
|
||||||
return source
|
return source
|
||||||
|
|
||||||
def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None:
|
def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]:
|
||||||
if self.funcargs:
|
if self.funcargs:
|
||||||
args = []
|
args = []
|
||||||
for argname, argvalue in entry.frame.getargs(var=True):
|
for argname, argvalue in entry.frame.getargs(var=True):
|
||||||
if self.truncate_args:
|
args.append((argname, saferepr(argvalue)))
|
||||||
str_repr = saferepr(argvalue)
|
|
||||||
else:
|
|
||||||
str_repr = saferepr(argvalue, maxsize=None)
|
|
||||||
args.append((argname, str_repr))
|
|
||||||
return ReprFuncArgs(args)
|
return ReprFuncArgs(args)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_source(
|
def get_source(
|
||||||
self,
|
self,
|
||||||
source: Source | None,
|
source: Optional["Source"],
|
||||||
line_index: int = -1,
|
line_index: int = -1,
|
||||||
excinfo: ExceptionInfo[BaseException] | None = None,
|
excinfo: Optional[ExceptionInfo[BaseException]] = None,
|
||||||
short: bool = False,
|
short: bool = False,
|
||||||
) -> list[str]:
|
) -> List[str]:
|
||||||
"""Return formatted and marked up source lines."""
|
"""Return formatted and marked up source lines."""
|
||||||
lines = []
|
lines = []
|
||||||
if source is not None and line_index < 0:
|
if source is not None and line_index < 0:
|
||||||
|
@ -877,7 +878,7 @@ class FormattedExcinfo:
|
||||||
excinfo: ExceptionInfo[BaseException],
|
excinfo: ExceptionInfo[BaseException],
|
||||||
indent: int = 4,
|
indent: int = 4,
|
||||||
markall: bool = False,
|
markall: bool = False,
|
||||||
) -> list[str]:
|
) -> List[str]:
|
||||||
lines = []
|
lines = []
|
||||||
indentstr = " " * indent
|
indentstr = " " * indent
|
||||||
# Get the real exception information out.
|
# Get the real exception information out.
|
||||||
|
@ -889,7 +890,7 @@ class FormattedExcinfo:
|
||||||
failindent = indentstr
|
failindent = indentstr
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None:
|
def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]:
|
||||||
if self.showlocals:
|
if self.showlocals:
|
||||||
lines = []
|
lines = []
|
||||||
keys = [loc for loc in locals if loc[0] != "@"]
|
keys = [loc for loc in locals if loc[0] != "@"]
|
||||||
|
@ -917,10 +918,10 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
def repr_traceback_entry(
|
def repr_traceback_entry(
|
||||||
self,
|
self,
|
||||||
entry: TracebackEntry | None,
|
entry: Optional[TracebackEntry],
|
||||||
excinfo: ExceptionInfo[BaseException] | None = None,
|
excinfo: Optional[ExceptionInfo[BaseException]] = None,
|
||||||
) -> ReprEntry:
|
) -> "ReprEntry":
|
||||||
lines: list[str] = []
|
lines: List[str] = []
|
||||||
style = (
|
style = (
|
||||||
entry._repr_style
|
entry._repr_style
|
||||||
if entry is not None and entry._repr_style is not None
|
if entry is not None and entry._repr_style is not None
|
||||||
|
@ -938,7 +939,7 @@ class FormattedExcinfo:
|
||||||
s = self.get_source(source, line_index, excinfo, short=short)
|
s = self.get_source(source, line_index, excinfo, short=short)
|
||||||
lines.extend(s)
|
lines.extend(s)
|
||||||
if short:
|
if short:
|
||||||
message = f"in {entry.name}"
|
message = "in %s" % (entry.name)
|
||||||
else:
|
else:
|
||||||
message = excinfo and excinfo.typename or ""
|
message = excinfo and excinfo.typename or ""
|
||||||
entry_path = entry.path
|
entry_path = entry.path
|
||||||
|
@ -955,7 +956,7 @@ class FormattedExcinfo:
|
||||||
lines.extend(self.get_exconly(excinfo, indent=4))
|
lines.extend(self.get_exconly(excinfo, indent=4))
|
||||||
return ReprEntry(lines, None, None, None, style)
|
return ReprEntry(lines, None, None, None, style)
|
||||||
|
|
||||||
def _makepath(self, path: Path | str) -> str:
|
def _makepath(self, path: Union[Path, str]) -> str:
|
||||||
if not self.abspath and isinstance(path, Path):
|
if not self.abspath and isinstance(path, Path):
|
||||||
try:
|
try:
|
||||||
np = bestrelpath(Path.cwd(), path)
|
np = bestrelpath(Path.cwd(), path)
|
||||||
|
@ -965,7 +966,7 @@ class FormattedExcinfo:
|
||||||
return np
|
return np
|
||||||
return str(path)
|
return str(path)
|
||||||
|
|
||||||
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback:
|
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback":
|
||||||
traceback = excinfo.traceback
|
traceback = excinfo.traceback
|
||||||
if callable(self.tbfilter):
|
if callable(self.tbfilter):
|
||||||
traceback = self.tbfilter(excinfo)
|
traceback = self.tbfilter(excinfo)
|
||||||
|
@ -996,7 +997,7 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
def _truncate_recursive_traceback(
|
def _truncate_recursive_traceback(
|
||||||
self, traceback: Traceback
|
self, traceback: Traceback
|
||||||
) -> tuple[Traceback, str | None]:
|
) -> Tuple[Traceback, Optional[str]]:
|
||||||
"""Truncate the given recursive traceback trying to find the starting
|
"""Truncate the given recursive traceback trying to find the starting
|
||||||
point of the recursion.
|
point of the recursion.
|
||||||
|
|
||||||
|
@ -1013,7 +1014,7 @@ class FormattedExcinfo:
|
||||||
recursionindex = traceback.recursionindex()
|
recursionindex = traceback.recursionindex()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
max_frames = 10
|
max_frames = 10
|
||||||
extraline: str | None = (
|
extraline: Optional[str] = (
|
||||||
"!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
|
"!!! 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"
|
" The following exception happened when comparing locals in the stack frame:\n"
|
||||||
f" {type(e).__name__}: {e!s}\n"
|
f" {type(e).__name__}: {e!s}\n"
|
||||||
|
@ -1031,12 +1032,16 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
return traceback, extraline
|
return traceback, extraline
|
||||||
|
|
||||||
def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr:
|
def repr_excinfo(
|
||||||
repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = []
|
self, excinfo: ExceptionInfo[BaseException]
|
||||||
e: BaseException | None = excinfo.value
|
) -> "ExceptionChainRepr":
|
||||||
excinfo_: ExceptionInfo[BaseException] | None = excinfo
|
repr_chain: List[
|
||||||
|
Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]
|
||||||
|
] = []
|
||||||
|
e: Optional[BaseException] = excinfo.value
|
||||||
|
excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo
|
||||||
descr = None
|
descr = None
|
||||||
seen: set[int] = set()
|
seen: Set[int] = set()
|
||||||
while e is not None and id(e) not in seen:
|
while e is not None and id(e) not in seen:
|
||||||
seen.add(id(e))
|
seen.add(id(e))
|
||||||
|
|
||||||
|
@ -1045,7 +1050,7 @@ class FormattedExcinfo:
|
||||||
# full support for exception groups added to ExceptionInfo.
|
# full support for exception groups added to ExceptionInfo.
|
||||||
# See https://github.com/pytest-dev/pytest/issues/9159
|
# See https://github.com/pytest-dev/pytest/issues/9159
|
||||||
if isinstance(e, BaseExceptionGroup):
|
if isinstance(e, BaseExceptionGroup):
|
||||||
reprtraceback: ReprTracebackNative | ReprTraceback = (
|
reprtraceback: Union[ReprTracebackNative, ReprTraceback] = (
|
||||||
ReprTracebackNative(
|
ReprTracebackNative(
|
||||||
traceback.format_exception(
|
traceback.format_exception(
|
||||||
type(excinfo_.value),
|
type(excinfo_.value),
|
||||||
|
@ -1103,9 +1108,9 @@ class TerminalRepr:
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ExceptionRepr(TerminalRepr):
|
class ExceptionRepr(TerminalRepr):
|
||||||
# Provided by subclasses.
|
# Provided by subclasses.
|
||||||
reprtraceback: ReprTraceback
|
reprtraceback: "ReprTraceback"
|
||||||
reprcrash: ReprFileLocation | None
|
reprcrash: Optional["ReprFileLocation"]
|
||||||
sections: list[tuple[str, str, str]] = dataclasses.field(
|
sections: List[Tuple[str, str, str]] = dataclasses.field(
|
||||||
init=False, default_factory=list
|
init=False, default_factory=list
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1120,11 +1125,13 @@ class ExceptionRepr(TerminalRepr):
|
||||||
|
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ExceptionChainRepr(ExceptionRepr):
|
class ExceptionChainRepr(ExceptionRepr):
|
||||||
chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]]
|
chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]],
|
chain: Sequence[
|
||||||
|
Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
|
||||||
|
],
|
||||||
) -> None:
|
) -> None:
|
||||||
# reprcrash and reprtraceback of the outermost (the newest) exception
|
# reprcrash and reprtraceback of the outermost (the newest) exception
|
||||||
# in the chain.
|
# in the chain.
|
||||||
|
@ -1145,8 +1152,8 @@ class ExceptionChainRepr(ExceptionRepr):
|
||||||
|
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ReprExceptionInfo(ExceptionRepr):
|
class ReprExceptionInfo(ExceptionRepr):
|
||||||
reprtraceback: ReprTraceback
|
reprtraceback: "ReprTraceback"
|
||||||
reprcrash: ReprFileLocation | None
|
reprcrash: Optional["ReprFileLocation"]
|
||||||
|
|
||||||
def toterminal(self, tw: TerminalWriter) -> None:
|
def toterminal(self, tw: TerminalWriter) -> None:
|
||||||
self.reprtraceback.toterminal(tw)
|
self.reprtraceback.toterminal(tw)
|
||||||
|
@ -1155,9 +1162,9 @@ class ReprExceptionInfo(ExceptionRepr):
|
||||||
|
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ReprTraceback(TerminalRepr):
|
class ReprTraceback(TerminalRepr):
|
||||||
reprentries: Sequence[ReprEntry | ReprEntryNative]
|
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
|
||||||
extraline: str | None
|
extraline: Optional[str]
|
||||||
style: TracebackStyle
|
style: _TracebackStyle
|
||||||
|
|
||||||
entrysep: ClassVar = "_ "
|
entrysep: ClassVar = "_ "
|
||||||
|
|
||||||
|
@ -1191,7 +1198,7 @@ class ReprTracebackNative(ReprTraceback):
|
||||||
class ReprEntryNative(TerminalRepr):
|
class ReprEntryNative(TerminalRepr):
|
||||||
lines: Sequence[str]
|
lines: Sequence[str]
|
||||||
|
|
||||||
style: ClassVar[TracebackStyle] = "native"
|
style: ClassVar[_TracebackStyle] = "native"
|
||||||
|
|
||||||
def toterminal(self, tw: TerminalWriter) -> None:
|
def toterminal(self, tw: TerminalWriter) -> None:
|
||||||
tw.write("".join(self.lines))
|
tw.write("".join(self.lines))
|
||||||
|
@ -1200,10 +1207,10 @@ class ReprEntryNative(TerminalRepr):
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ReprEntry(TerminalRepr):
|
class ReprEntry(TerminalRepr):
|
||||||
lines: Sequence[str]
|
lines: Sequence[str]
|
||||||
reprfuncargs: ReprFuncArgs | None
|
reprfuncargs: Optional["ReprFuncArgs"]
|
||||||
reprlocals: ReprLocals | None
|
reprlocals: Optional["ReprLocals"]
|
||||||
reprfileloc: ReprFileLocation | None
|
reprfileloc: Optional["ReprFileLocation"]
|
||||||
style: TracebackStyle
|
style: _TracebackStyle
|
||||||
|
|
||||||
def _write_entry_lines(self, tw: TerminalWriter) -> None:
|
def _write_entry_lines(self, tw: TerminalWriter) -> None:
|
||||||
"""Write the source code portions of a list of traceback entries with syntax highlighting.
|
"""Write the source code portions of a list of traceback entries with syntax highlighting.
|
||||||
|
@ -1226,9 +1233,9 @@ class ReprEntry(TerminalRepr):
|
||||||
# such as "> assert 0"
|
# such as "> assert 0"
|
||||||
fail_marker = f"{FormattedExcinfo.fail_marker} "
|
fail_marker = f"{FormattedExcinfo.fail_marker} "
|
||||||
indent_size = len(fail_marker)
|
indent_size = len(fail_marker)
|
||||||
indents: list[str] = []
|
indents: List[str] = []
|
||||||
source_lines: list[str] = []
|
source_lines: List[str] = []
|
||||||
failure_lines: list[str] = []
|
failure_lines: List[str] = []
|
||||||
for index, line in enumerate(self.lines):
|
for index, line in enumerate(self.lines):
|
||||||
is_failure_line = line.startswith(fail_marker)
|
is_failure_line = line.startswith(fail_marker)
|
||||||
if is_failure_line:
|
if is_failure_line:
|
||||||
|
@ -1307,7 +1314,7 @@ class ReprLocals(TerminalRepr):
|
||||||
|
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ReprFuncArgs(TerminalRepr):
|
class ReprFuncArgs(TerminalRepr):
|
||||||
args: Sequence[tuple[str, object]]
|
args: Sequence[Tuple[str, object]]
|
||||||
|
|
||||||
def toterminal(self, tw: TerminalWriter) -> None:
|
def toterminal(self, tw: TerminalWriter) -> None:
|
||||||
if self.args:
|
if self.args:
|
||||||
|
@ -1328,7 +1335,7 @@ class ReprFuncArgs(TerminalRepr):
|
||||||
tw.line("")
|
tw.line("")
|
||||||
|
|
||||||
|
|
||||||
def getfslineno(obj: object) -> tuple[str | Path, int]:
|
def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
|
||||||
"""Return source location (path, lineno) for the given object.
|
"""Return source location (path, lineno) for the given object.
|
||||||
|
|
||||||
If the source cannot be determined return ("", -1).
|
If the source cannot be determined return ("", -1).
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
from bisect import bisect_right
|
from bisect import bisect_right
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -9,7 +7,11 @@ import tokenize
|
||||||
import types
|
import types
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
from typing import overload
|
from typing import overload
|
||||||
|
from typing import Tuple
|
||||||
|
from typing import Union
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ class Source:
|
||||||
|
|
||||||
def __init__(self, obj: object = None) -> None:
|
def __init__(self, obj: object = None) -> None:
|
||||||
if not obj:
|
if not obj:
|
||||||
self.lines: list[str] = []
|
self.lines: List[str] = []
|
||||||
elif isinstance(obj, Source):
|
elif isinstance(obj, Source):
|
||||||
self.lines = obj.lines
|
self.lines = obj.lines
|
||||||
elif isinstance(obj, (tuple, list)):
|
elif isinstance(obj, (tuple, list)):
|
||||||
|
@ -48,9 +50,9 @@ class Source:
|
||||||
def __getitem__(self, key: int) -> str: ...
|
def __getitem__(self, key: int) -> str: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(self, key: slice) -> Source: ...
|
def __getitem__(self, key: slice) -> "Source": ...
|
||||||
|
|
||||||
def __getitem__(self, key: int | slice) -> str | Source:
|
def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]:
|
||||||
if isinstance(key, int):
|
if isinstance(key, int):
|
||||||
return self.lines[key]
|
return self.lines[key]
|
||||||
else:
|
else:
|
||||||
|
@ -66,7 +68,7 @@ class Source:
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self.lines)
|
return len(self.lines)
|
||||||
|
|
||||||
def strip(self) -> Source:
|
def strip(self) -> "Source":
|
||||||
"""Return new Source object with trailing and leading blank lines removed."""
|
"""Return new Source object with trailing and leading blank lines removed."""
|
||||||
start, end = 0, len(self)
|
start, end = 0, len(self)
|
||||||
while start < end and not self.lines[start].strip():
|
while start < end and not self.lines[start].strip():
|
||||||
|
@ -77,20 +79,20 @@ class Source:
|
||||||
source.lines[:] = self.lines[start:end]
|
source.lines[:] = self.lines[start:end]
|
||||||
return source
|
return source
|
||||||
|
|
||||||
def indent(self, indent: str = " " * 4) -> Source:
|
def indent(self, indent: str = " " * 4) -> "Source":
|
||||||
"""Return a copy of the source object with all lines indented by the
|
"""Return a copy of the source object with all lines indented by the
|
||||||
given indent-string."""
|
given indent-string."""
|
||||||
newsource = Source()
|
newsource = Source()
|
||||||
newsource.lines = [(indent + line) for line in self.lines]
|
newsource.lines = [(indent + line) for line in self.lines]
|
||||||
return newsource
|
return newsource
|
||||||
|
|
||||||
def getstatement(self, lineno: int) -> Source:
|
def getstatement(self, lineno: int) -> "Source":
|
||||||
"""Return Source statement which contains the given linenumber
|
"""Return Source statement which contains the given linenumber
|
||||||
(counted from 0)."""
|
(counted from 0)."""
|
||||||
start, end = self.getstatementrange(lineno)
|
start, end = self.getstatementrange(lineno)
|
||||||
return self[start:end]
|
return self[start:end]
|
||||||
|
|
||||||
def getstatementrange(self, lineno: int) -> tuple[int, int]:
|
def getstatementrange(self, lineno: int) -> Tuple[int, int]:
|
||||||
"""Return (start, end) tuple which spans the minimal statement region
|
"""Return (start, end) tuple which spans the minimal statement region
|
||||||
which containing the given lineno."""
|
which containing the given lineno."""
|
||||||
if not (0 <= lineno < len(self)):
|
if not (0 <= lineno < len(self)):
|
||||||
|
@ -98,7 +100,7 @@ class Source:
|
||||||
ast, start, end = getstatementrange_ast(lineno, self)
|
ast, start, end = getstatementrange_ast(lineno, self)
|
||||||
return start, end
|
return start, end
|
||||||
|
|
||||||
def deindent(self) -> Source:
|
def deindent(self) -> "Source":
|
||||||
"""Return a new Source object deindented."""
|
"""Return a new Source object deindented."""
|
||||||
newsource = Source()
|
newsource = Source()
|
||||||
newsource.lines[:] = deindent(self.lines)
|
newsource.lines[:] = deindent(self.lines)
|
||||||
|
@ -113,7 +115,7 @@ class Source:
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
def findsource(obj) -> tuple[Source | None, int]:
|
def findsource(obj) -> Tuple[Optional[Source], int]:
|
||||||
try:
|
try:
|
||||||
sourcelines, lineno = inspect.findsource(obj)
|
sourcelines, lineno = inspect.findsource(obj)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -136,14 +138,14 @@ def getrawcode(obj: object, trycall: bool = True) -> types.CodeType:
|
||||||
raise TypeError(f"could not get code object for {obj!r}")
|
raise TypeError(f"could not get code object for {obj!r}")
|
||||||
|
|
||||||
|
|
||||||
def deindent(lines: Iterable[str]) -> list[str]:
|
def deindent(lines: Iterable[str]) -> List[str]:
|
||||||
return textwrap.dedent("\n".join(lines)).splitlines()
|
return textwrap.dedent("\n".join(lines)).splitlines()
|
||||||
|
|
||||||
|
|
||||||
def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]:
|
def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]:
|
||||||
# Flatten all statements and except handlers into one lineno-list.
|
# Flatten all statements and except handlers into one lineno-list.
|
||||||
# AST's line numbers start indexing at 1.
|
# AST's line numbers start indexing at 1.
|
||||||
values: list[int] = []
|
values: List[int] = []
|
||||||
for x in ast.walk(node):
|
for x in ast.walk(node):
|
||||||
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
|
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
|
||||||
# The lineno points to the class/def, so need to include the decorators.
|
# The lineno points to the class/def, so need to include the decorators.
|
||||||
|
@ -152,7 +154,7 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None
|
||||||
values.append(d.lineno - 1)
|
values.append(d.lineno - 1)
|
||||||
values.append(x.lineno - 1)
|
values.append(x.lineno - 1)
|
||||||
for name in ("finalbody", "orelse"):
|
for name in ("finalbody", "orelse"):
|
||||||
val: list[ast.stmt] | None = getattr(x, name, None)
|
val: Optional[List[ast.stmt]] = getattr(x, name, None)
|
||||||
if val:
|
if val:
|
||||||
# Treat the finally/orelse part as its own statement.
|
# Treat the finally/orelse part as its own statement.
|
||||||
values.append(val[0].lineno - 1 - 1)
|
values.append(val[0].lineno - 1 - 1)
|
||||||
|
@ -170,8 +172,8 @@ def getstatementrange_ast(
|
||||||
lineno: int,
|
lineno: int,
|
||||||
source: Source,
|
source: Source,
|
||||||
assertion: bool = False,
|
assertion: bool = False,
|
||||||
astnode: ast.AST | None = None,
|
astnode: Optional[ast.AST] = None,
|
||||||
) -> tuple[ast.AST, int, int]:
|
) -> Tuple[ast.AST, int, int]:
|
||||||
if astnode is None:
|
if astnode is None:
|
||||||
content = str(source)
|
content = str(source)
|
||||||
# See #4260:
|
# See #4260:
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from .terminalwriter import get_terminal_width
|
from .terminalwriter import get_terminal_width
|
||||||
from .terminalwriter import TerminalWriter
|
from .terminalwriter import TerminalWriter
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue