Merge branch 'main' into main
This commit is contained in:
commit
293ece6932
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
branch-protection-check-name: Changelog entry
|
||||||
|
action-hints:
|
||||||
|
check-title-prefix: "Chronographer: "
|
||||||
|
external-docs-url: >-
|
||||||
|
https://docs.pytest.org/en/latest/contributing.html#preparing-pull-requests
|
||||||
|
inline-markdown: >-
|
||||||
|
See
|
||||||
|
https://docs.pytest.org/en/latest/contributing.html#preparing-pull-requests
|
||||||
|
for details.
|
||||||
|
enforce-name:
|
||||||
|
suffix: .rst
|
||||||
|
exclude:
|
||||||
|
humans:
|
||||||
|
- pyup-bot
|
||||||
|
labels:
|
||||||
|
skip-changelog: skip news
|
||||||
|
|
||||||
|
...
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
...
|
|
@ -1,51 +0,0 @@
|
||||||
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 }}
|
|
|
@ -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.8.14
|
uses: pypa/gh-action-pypi-publish@v1.9.0
|
||||||
|
|
||||||
- name: Push tag
|
- name: Push tag
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -14,6 +14,11 @@ 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"
|
||||||
|
@ -182,6 +187,26 @@ 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:
|
||||||
|
@ -222,8 +247,22 @@ 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: true
|
fail_ci_if_error: false
|
||||||
files: ./coverage.xml
|
files: ./coverage.xml
|
||||||
|
token: 1eca3b1f-31a2-4fb8-a8c3-138b441b50a7 #repo token; cfg read fails
|
||||||
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,7 +46,8 @@ jobs:
|
||||||
run: python scripts/update-plugin-list.py
|
run: python scripts/update-plugin-list.py
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e
|
id: pr
|
||||||
|
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>'
|
||||||
|
@ -55,3 +56,13 @@ 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,7 +25,6 @@ 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.8"
|
rev: "v0.4.10"
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: ["--fix"]
|
args: ["--fix"]
|
||||||
|
@ -66,8 +66,41 @@ repos:
|
||||||
- id: changelogs-rst
|
- id: changelogs-rst
|
||||||
name: changelog filenames
|
name: changelog filenames
|
||||||
language: fail
|
language: fail
|
||||||
entry: 'changelog files must be named ####.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst'
|
entry: >-
|
||||||
exclude: changelog/(\d+\.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst|README.rst|_template.rst)
|
changelog files must be named
|
||||||
|
####.(
|
||||||
|
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: py-deprecated
|
- id: py-deprecated
|
||||||
name: py library is deprecated
|
name: py library is deprecated
|
||||||
|
|
|
@ -14,11 +14,16 @@ sphinx:
|
||||||
fail_on_warning: true
|
fail_on_warning: true
|
||||||
|
|
||||||
build:
|
build:
|
||||||
os: ubuntu-20.04
|
os: ubuntu-24.04
|
||||||
tools:
|
tools:
|
||||||
python: "3.9"
|
python: >-
|
||||||
|
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,6 +149,7 @@ 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
|
||||||
|
@ -212,6 +213,7 @@ 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ć
|
||||||
|
@ -244,6 +246,7 @@ 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
|
||||||
|
@ -259,6 +262,7 @@ Marc Bresson
|
||||||
Marco Gorelli
|
Marco Gorelli
|
||||||
Mark Abramowitz
|
Mark Abramowitz
|
||||||
Mark Dickinson
|
Mark Dickinson
|
||||||
|
Mark Vong
|
||||||
Marko Pacak
|
Marko Pacak
|
||||||
Markus Unterwaditzer
|
Markus Unterwaditzer
|
||||||
Martijn Faassen
|
Martijn Faassen
|
||||||
|
@ -302,6 +306,7 @@ Nicholas Devenish
|
||||||
Nicholas Murphy
|
Nicholas Murphy
|
||||||
Niclas Olofsson
|
Niclas Olofsson
|
||||||
Nicolas Delaby
|
Nicolas Delaby
|
||||||
|
Nico Vidal
|
||||||
Nikolay Kondratyev
|
Nikolay Kondratyev
|
||||||
Nipunn Koorapati
|
Nipunn Koorapati
|
||||||
Oleg Pidsadnyi
|
Oleg Pidsadnyi
|
||||||
|
@ -391,6 +396,7 @@ 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
|
||||||
|
@ -450,6 +456,7 @@ 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,5 @@
|
||||||
============================
|
============================
|
||||||
Contribution getting started
|
Contributing
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Contributions are highly welcomed and appreciated. Every little bit of help counts,
|
Contributions are highly welcomed and appreciated. Every little bit of help counts,
|
||||||
|
@ -124,7 +124,7 @@ For example:
|
||||||
Submitting Plugins to pytest-dev
|
Submitting Plugins to pytest-dev
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
Pytest development of the core, some plugins and support code happens
|
Development of the pytest core, support code, and some plugins happens
|
||||||
in repositories living under the ``pytest-dev`` organisations:
|
in repositories living under the ``pytest-dev`` organisations:
|
||||||
|
|
||||||
- `pytest-dev on GitHub <https://github.com/pytest-dev>`_
|
- `pytest-dev on GitHub <https://github.com/pytest-dev>`_
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
# 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,2 +1,5 @@
|
||||||
|
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,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from unittest import TestCase # noqa: F401
|
from unittest import TestCase # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
for i in range(5000):
|
for i in range(5000):
|
||||||
exec(
|
exec(
|
||||||
f"""
|
f"""
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
*
|
||||||
|
!.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
|
|
@ -0,0 +1 @@
|
||||||
|
:func:`pytest.approx` now correctly handles :class:`Sequence <collections.abc.Sequence>`-like objects.
|
|
@ -0,0 +1,8 @@
|
||||||
|
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`
|
|
@ -0,0 +1,3 @@
|
||||||
|
Migrated all internal type-annotations to the python3.10+ style by using the `annotations` future import.
|
||||||
|
|
||||||
|
-- by :user:`RonnyPfannschmidt`
|
|
@ -0,0 +1,6 @@
|
||||||
|
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`
|
|
@ -0,0 +1,4 @@
|
||||||
|
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`.
|
|
@ -0,0 +1,13 @@
|
||||||
|
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`
|
|
@ -0,0 +1,5 @@
|
||||||
|
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`
|
|
@ -0,0 +1,11 @@
|
||||||
|
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`
|
|
@ -0,0 +1,7 @@
|
||||||
|
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`.
|
|
@ -0,0 +1,4 @@
|
||||||
|
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`.
|
|
@ -0,0 +1,7 @@
|
||||||
|
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`
|
|
@ -0,0 +1,4 @@
|
||||||
|
The _in_venv function now detects Python virtual environments by checking
|
||||||
|
for a pyvenv.cfg file, ensuring reliable detection on various platforms.
|
||||||
|
|
||||||
|
-- by :user:`zachsnickers`.
|
|
@ -0,0 +1,38 @@
|
||||||
|
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 ===========================
|
|
@ -0,0 +1 @@
|
||||||
|
Added timezone information to the testsuite timestamp in the JUnit XML report.
|
|
@ -20,10 +20,22 @@ 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.
|
||||||
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
|
* ``packaging``: notes for downstreams about unobvious side effects
|
||||||
|
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,4 +1,9 @@
|
||||||
# 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 (:pull:`1040`).
|
Thanks :user:`MichaelAquilina` for the complete PR (:pr:`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 (:pull:`1431`).
|
contain ``::``. Thanks :user:`tomviner` for the PR (:pr:`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 (:pull:`1524`).
|
for PR the (:pr:`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 (:pull:`1506`).
|
Thanks :user:`prusse-martin` for the PR (:pr:`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
|
||||||
|
|
|
@ -5,30 +5,26 @@ Backwards Compatibility Policy
|
||||||
|
|
||||||
.. versionadded: 6.0
|
.. versionadded: 6.0
|
||||||
|
|
||||||
pytest is actively evolving and is a project that has been decades in the making,
|
Pytest is an actively evolving project that has been decades in the making.
|
||||||
we keep learning about new and better structures to express different details about testing.
|
We keep learning about new and better structures to express different details about testing.
|
||||||
|
|
||||||
While we implement those modifications we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors.
|
While we implement those modifications, we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors.
|
||||||
|
|
||||||
As of now, pytest considers multiple types of backward compatibility transitions:
|
As of now, pytest considers multiple types of backward compatibility transitions:
|
||||||
|
|
||||||
a) trivial: APIs which trivially translate to the new mechanism,
|
a) trivial: APIs that trivially translate to the new mechanism and do not cause problematic changes.
|
||||||
and do not cause problematic changes.
|
|
||||||
|
|
||||||
We try to support those indefinitely while encouraging users to switch to newer/better mechanisms through documentation.
|
We try to support those indefinitely while encouraging users to switch to newer or better mechanisms through documentation.
|
||||||
|
|
||||||
b) transitional: the old and new API don't conflict
|
b) transitional: the old and new APIs don't conflict, and we can help users transition by using warnings while supporting both for a prolonged period of time.
|
||||||
and we can help users transition by using warnings, while supporting both for a prolonged time.
|
|
||||||
|
|
||||||
We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
|
We will only start the removal of deprecated functionality in major releases (e.g., if we deprecate something in 3.0, we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g., if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
|
||||||
|
|
||||||
A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationWarning`).
|
A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationWarning`).
|
||||||
|
|
||||||
When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g. `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
|
When the deprecation expires (e.g., 4.0 is released), we won't remove the deprecated functionality immediately but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g., `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g., 4.1), the feature will be effectively removed.
|
||||||
|
|
||||||
|
c) True breakage should only be considered when a normal transition is unreasonably unsustainable and would offset important developments or features by years. In addition, they should be limited to APIs where the number of actual users is very small (for example, only impacting some plugins) and can be coordinated with the community in advance.
|
||||||
c) true breakage: should only be considered when normal transition is unreasonably unsustainable and would offset important development/features by years.
|
|
||||||
In addition, they should be limited to APIs where the number of actual users is very small (for example only impacting some plugins), and can be coordinated with the community in advance.
|
|
||||||
|
|
||||||
Examples for such upcoming changes:
|
Examples for such upcoming changes:
|
||||||
|
|
||||||
|
@ -62,11 +58,11 @@ Focus primary on smooth transition - stance (pre 6.0)
|
||||||
|
|
||||||
Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary.
|
Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary.
|
||||||
|
|
||||||
With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around.
|
With the pytest 3.0 release, we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around.
|
||||||
|
|
||||||
To communicate changes we issue deprecation warnings using a custom warning hierarchy (see :ref:`internal-warnings`). These warnings may be suppressed using the standard means: ``-W`` command-line flag or ``filterwarnings`` ini options (see :ref:`warnings`), but we suggest to use these sparingly and temporarily, and heed the warnings when possible.
|
To communicate changes, we issue deprecation warnings using a custom warning hierarchy (see :ref:`internal-warnings`). These warnings may be suppressed using the standard means: ``-W`` command-line flag or ``filterwarnings`` ini options (see :ref:`warnings`), but we suggest to use these sparingly and temporarily, and heed the warnings when possible.
|
||||||
|
|
||||||
We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
|
We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0, we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
|
||||||
|
|
||||||
When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
|
When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,15 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
we named the news folder changelog
|
we named the news folder changelog
|
||||||
|
|
||||||
|
|
||||||
.. only:: changelog_towncrier_draft
|
.. only:: not is_release
|
||||||
|
|
||||||
.. The 'changelog_towncrier_draft' tag is included by our 'tox -e docs',
|
To be included in v\ |release| (if present)
|
||||||
but not on readthedocs.
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.. include:: _changelog_towncrier_draft.rst
|
.. towncrier-draft-entries:: |release| [UNRELEASED DRAFT]
|
||||||
|
|
||||||
|
Released versions
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
@ -265,7 +268,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 :pull:`12043` for a comparison with pytest 8.0 and <8.
|
This change improves the collection tree for tests specified using ``--pyargs``, see :pr:`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.
|
||||||
|
@ -1419,7 +1422,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 :pull:`6523` for details).
|
This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pr:`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.
|
||||||
|
@ -2588,7 +2591,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
|
||||||
:pull:`6523` for details).
|
:pr:`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.
|
||||||
|
@ -2871,7 +2874,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 :pull:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
|
- :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.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
@ -3041,7 +3044,7 @@ pytest 5.4.1 (2020-03-13)
|
||||||
Bug Fixes
|
Bug Fixes
|
||||||
---------
|
---------
|
||||||
|
|
||||||
- :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.
|
- :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.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
@ -3357,7 +3360,9 @@ 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 :py:class:`python:property` docstrings, as a workaround to :bpo:`17446`.
|
- :issue:`6082`: Fix line detection for doctest samples inside
|
||||||
|
: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).
|
||||||
|
@ -4064,7 +4069,7 @@ Bug Fixes
|
||||||
(``--collect-only``) when ``--log-cli-level`` is used.
|
(``--collect-only``) when ``--log-cli-level`` is used.
|
||||||
|
|
||||||
|
|
||||||
- :issue:`5389`: Fix regressions of :pull:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
|
- :issue:`5389`: Fix regressions of :pr:`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.
|
||||||
|
@ -4265,7 +4270,7 @@ Bug Fixes
|
||||||
(``--collect-only``) when ``--log-cli-level`` is used.
|
(``--collect-only``) when ``--log-cli-level`` is used.
|
||||||
|
|
||||||
|
|
||||||
- :issue:`5389`: Fix regressions of :pull:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
|
- :issue:`5389`: Fix regressions of :pr:`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.
|
||||||
|
@ -7226,10 +7231,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 (:pull:`2101`).
|
Thanks :user:`wheerd` for the PR (:pr:`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 (:pull:`2166`)
|
class equality. Thanks :user:`lesteve` for the PR (:pr:`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.
|
||||||
|
@ -7257,7 +7262,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 (:pull:`1874`).
|
Thanks :user:`fushi` for the PR (:pr:`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
|
||||||
|
@ -7266,7 +7271,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 (:pull:`1952`).
|
the PR (:pr:`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.
|
||||||
|
@ -7520,7 +7525,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 (:pull:`1891`).
|
Thanks :user:`jaraco` for the report and :user:`nicoddemus` for the PR (:pr:`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``
|
||||||
|
@ -7575,12 +7580,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 (:pull:`1664`).
|
Thanks to :user:`RedBeardCode` for the PR (:pr:`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 (:pull:`1627`).
|
* Removed support code for Python 3 versions < 3.3 (:pr:`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
|
||||||
|
@ -7591,19 +7596,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 (:pull:`1519`).
|
Thanks :user:`omarkohl` for the complete PR (:pr:`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 (:pull:`1628`).
|
Thanks :user:`olegpidsadnyi` and :user:`omarkohl` for the complete PR (:pr:`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 (:pull:`460`).
|
only ran once (:pr:`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``
|
||||||
|
@ -7621,7 +7626,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 (:pull:`1428`).
|
Thanks :user:`milliams` for the complete PR (:pr:`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`).
|
||||||
|
@ -7629,23 +7634,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 (:pull:`1444`).
|
Thanks :user:`novas0x2a` for the complete PR (:pr:`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 (:pull:`1441`).
|
Thanks :user:`kalekundert` for the complete PR (:pr:`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 :pull:`1454`).
|
Thanks :user:`tareqalayan` for the complete PR :pr:`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 (:pull:`1502`).
|
Thanks :user:`omarkohl` for the complete PR (:pr:`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 (:pull:`1526`).
|
to it. Thanks :user:`The-Compiler` for the complete PR (:pr:`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.
|
||||||
|
@ -7663,7 +7668,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
|
||||||
(:pull:`1633`).
|
(:pr:`1633`).
|
||||||
|
|
||||||
* New cli flags:
|
* New cli flags:
|
||||||
|
|
||||||
|
@ -7707,19 +7712,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
|
||||||
(:pull:`1795`) and :user:`gprasad84` for report (:issue:`1546`).
|
(:pr:`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 (:pull:`1795`).
|
Thanks to :user:`hackebrot` for the PR (:pr:`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 (:pull:`1778`).
|
Thanks :user:`suzaku` for the PR (:pr:`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 (:pull:`1461`).
|
the preferred way to write teardown code (:pr:`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`).
|
||||||
|
@ -7730,11 +7735,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 (:pull:`1809`).
|
of the test session as argument. Thanks :user:`blueyed` for the PR (:pr:`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 (:pull:`1468`).
|
Thanks :user:`palaviv` for the complete PR (:pr:`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.
|
||||||
|
@ -7742,32 +7747,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 (:pull:`1474`).
|
Thanks :user:`palaviv` for the complete PR (:pr:`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 (:pull:`1520`).
|
Thanks :user:`omarkohl` for the complete PR (:pr:`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 (:pull:`1616`).
|
Thanks :user:`palaviv` for the complete PR (:pr:`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 (:pull:`1580`).
|
Thanks :user:`graingert` for reporting and providing a full PR (:pr:`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 (:pull:`1597`).
|
option. Thanks to :user:`taschini` for the PR (:pr:`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.
|
||||||
|
@ -7783,20 +7788,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 :pull:`567`). ``[pytest]`` sections in
|
to avoid conflicts with other distutils commands (see :pr:`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 (:pull:`1684`).
|
removed in pytest-4.0 (:pr:`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 (:pull:`1723`).
|
for removal in pytest-4.0. It is recommended to pass a list of arguments instead (:pr:`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 (:pull:`1626`).
|
for the PR (:pr:`1626`).
|
||||||
|
|
||||||
* ``optparse`` type usage now triggers DeprecationWarnings (:issue:`1740`).
|
* ``optparse`` type usage now triggers DeprecationWarnings (:issue:`1740`).
|
||||||
|
|
||||||
|
@ -7854,11 +7859,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 (:pull:`1516`). Thanks :user:`txomon` for
|
identify bugs in ``conftest.py`` files (:pr:`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 (:pull:`1580`).
|
Thanks :user:`graingert` for reporting and providing a full PR (:pr:`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.
|
||||||
|
@ -7866,7 +7871,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 (:pull:`1798`).
|
* Fixed the total tests tally in junit xml output (:pr:`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``.
|
||||||
|
@ -7883,14 +7888,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 (:pull:`1524`).
|
for PR the (:pr:`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 (:pull:`1506`).
|
Thanks :user:`prusse-martin` for the PR (:pr:`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
|
||||||
|
@ -7917,7 +7922,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 (:pull:`1431`).
|
contain ``::``. Thanks :user:`tomviner` for the PR (:pr:`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
|
||||||
|
@ -7938,7 +7943,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 (:pull:`1040`).
|
Thanks :user:`MichaelAquilina` for the complete PR (:pr:`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.
|
||||||
|
@ -7949,14 +7954,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 (:pull:`1355`).
|
Thanks :user:`rabbbit` for the request and :user:`nicoddemus` for the PR (:pr:`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 (:pull:`1287`).
|
Thanks :user:`jaraco` for the request and :user:`nicoddemus` for the PR (:pr:`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`.
|
||||||
|
@ -7988,7 +7993,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 (:pull:`1226`).
|
Thanks :user:`nicoddemus` for the PR (:pr:`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,17 +15,33 @@
|
||||||
#
|
#
|
||||||
# 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 version
|
from _pytest import __version__ as full_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,
|
||||||
|
@ -66,12 +82,13 @@ 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
|
||||||
|
@ -153,16 +170,6 @@ 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?)
|
||||||
|
@ -176,6 +183,7 @@ 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"),
|
||||||
|
@ -197,13 +205,16 @@ 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"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -422,6 +433,18 @@ 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),
|
||||||
|
@ -435,31 +458,7 @@ intersphinx_mapping = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def configure_logging(app: "sphinx.application.Sphinx") -> None:
|
def setup(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",
|
||||||
|
@ -488,8 +487,6 @@ 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 +1,4 @@
|
||||||
|
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, `via webchat
|
channel for random questions (using an IRC client, or `via webchat
|
||||||
<https://web.libera.chat/#pytest>`_, or `via Matrix
|
<https://web.libera.chat/#pytest>`)
|
||||||
<https://matrix.to/#/%23pytest:libera.chat>`_).
|
- ``#pytest`` `on Matrix https://matrix.to/#/#pytest:matrix.org>`.
|
||||||
|
|
||||||
|
|
||||||
.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
|
.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
hello = "world"
|
hello = "world"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
def setup_module(module):
|
def setup_module(module):
|
||||||
module.TestStateFullThing.classcount = 0
|
module.TestStateFullThing.classcount = 0
|
||||||
|
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
collect_ignore = ["nonpython", "customdirectory"]
|
collect_ignore = ["nonpython", "customdirectory"]
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# content of conftest.py
|
# content of conftest.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
# content of test_first.py
|
# content of test_first.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
def test_1():
|
def test_1():
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
# content of test_second.py
|
# content of test_second.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
def test_2():
|
def test_2():
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
# content of test_third.py
|
# content of test_third.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
def test_3():
|
def test_3():
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,12 @@ 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
|
||||||
|
|
||||||
|
@ -71,6 +73,28 @@ 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,6 +1,8 @@
|
||||||
"""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,4 +1,6 @@
|
||||||
# content of conftest.py
|
# content of conftest.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# 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
|
||||||
|
|
||||||
# setuptools plugin
|
# installable external plugin
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
@ -1073,8 +1073,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
|
Please note that the mechanism for plugin discovery used by pytest (:ref:`entry
|
||||||
(setuptools entry points) doesn't work with frozen executables so pytest
|
points <pip-installable plugins>`) 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,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,15 +8,15 @@ pytest import mechanisms and ``sys.path``/``PYTHONPATH``
|
||||||
Import modes
|
Import modes
|
||||||
------------
|
------------
|
||||||
|
|
||||||
pytest as a testing framework needs to import test modules and ``conftest.py`` files for execution.
|
pytest as a testing framework that needs to import test modules and ``conftest.py`` files for execution.
|
||||||
|
|
||||||
Importing files in Python is a non-trivial processes, so aspects of the
|
Importing files in Python is a non-trivial process, so aspects of the
|
||||||
import process can be controlled through the ``--import-mode`` command-line flag, which can assume
|
import process can be controlled through the ``--import-mode`` command-line flag, which can assume
|
||||||
these values:
|
these values:
|
||||||
|
|
||||||
.. _`import-mode-prepend`:
|
.. _`import-mode-prepend`:
|
||||||
|
|
||||||
* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning*
|
* ``prepend`` (default): The directory path containing each module will be inserted into the *beginning*
|
||||||
of :py:data:`sys.path` if not already there, and then imported with
|
of :py:data:`sys.path` if not already there, and then imported with
|
||||||
the :func:`importlib.import_module <importlib.import_module>` function.
|
the :func:`importlib.import_module <importlib.import_module>` function.
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ these values:
|
||||||
* ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already
|
* ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already
|
||||||
there, and imported with :func:`importlib.import_module <importlib.import_module>`.
|
there, and imported with :func:`importlib.import_module <importlib.import_module>`.
|
||||||
|
|
||||||
This better allows to run test modules against installed versions of a package even if the
|
This better allows users to run test modules against installed versions of a package even if the
|
||||||
package under test has the same import root. For example:
|
package under test has the same import root. For example:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
@ -45,7 +45,7 @@ these values:
|
||||||
|
|
||||||
the tests will run against the installed version
|
the tests will run against the installed version
|
||||||
of ``pkg_under_test`` when ``--import-mode=append`` is used whereas
|
of ``pkg_under_test`` when ``--import-mode=append`` is used whereas
|
||||||
with ``prepend`` they would pick up the local version. This kind of confusion is why
|
with ``prepend``, they would pick up the local version. This kind of confusion is why
|
||||||
we advocate for using :ref:`src-layouts <src-layout>`.
|
we advocate for using :ref:`src-layouts <src-layout>`.
|
||||||
|
|
||||||
Same as ``prepend``, requires test module names to be unique when the test directory tree is
|
Same as ``prepend``, requires test module names to be unique when the test directory tree is
|
||||||
|
@ -67,7 +67,7 @@ these values:
|
||||||
are not importable. The recommendation in this case it to place testing utility modules together with the application/library
|
are not importable. The recommendation in this case it to place testing utility modules together with the application/library
|
||||||
code, for example ``app.testing.helpers``.
|
code, for example ``app.testing.helpers``.
|
||||||
|
|
||||||
Important: by "test utility modules" we mean functions/classes which are imported by
|
Important: by "test utility modules", we mean functions/classes which are imported by
|
||||||
other tests directly; this does not include fixtures, which should be placed in ``conftest.py`` files, along
|
other tests directly; this does not include fixtures, which should be placed in ``conftest.py`` files, along
|
||||||
with the test modules, and are discovered automatically by pytest.
|
with the test modules, and are discovered automatically by pytest.
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ these values:
|
||||||
1. Given a certain module path, for example ``tests/core/test_models.py``, derives a canonical name
|
1. Given a certain module path, for example ``tests/core/test_models.py``, derives a canonical name
|
||||||
like ``tests.core.test_models`` and tries to import it.
|
like ``tests.core.test_models`` and tries to import it.
|
||||||
|
|
||||||
For non-test modules this will work if they are accessible via :py:data:`sys.path`, so
|
For non-test modules, this will work if they are accessible via :py:data:`sys.path`. So
|
||||||
for example ``.env/lib/site-packages/app/core.py`` will be importable as ``app.core``.
|
for example, ``.env/lib/site-packages/app/core.py`` will be importable as ``app.core``.
|
||||||
This is happens when plugins import non-test modules (for example doctesting).
|
This is happens when plugins import non-test modules (for example doctesting).
|
||||||
|
|
||||||
If this step succeeds, the module is returned.
|
If this step succeeds, the module is returned.
|
||||||
|
|
|
@ -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 :pull:`original PR <3317>`.
|
More details can be found in the :pr:`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 <setuptools entry points>`,
|
Usually projects that provide pytest support will use :ref:`entry points <pip-installable plugins>`,
|
||||||
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,11 +76,19 @@ 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>`.
|
||||||
|
|
||||||
|
@ -154,7 +162,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 ``setuptools`` when the plugin is
|
* The entry-point name of a plugin. This is the name passed to ``importlib`` 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>`: modules discovered through
|
* :ref:`external plugins <extplugins>`: installed third-party modules discovered
|
||||||
`setuptools entry points`_
|
through :ref:`entry points <pip-installable plugins>` in their packaging metadata
|
||||||
|
|
||||||
* `conftest.py plugins`_: modules auto-discovered in test directories
|
* `conftest.py plugins`_: modules auto-discovered in test directories
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ 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 `setuptools entry points`_.
|
4. by loading all plugins registered through installed third-party package
|
||||||
|
: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.
|
||||||
|
|
||||||
|
@ -142,7 +143,8 @@ 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:`setuptools <setuptools:index>`.
|
a feature that is provided by :std:doc:`packaging tools
|
||||||
|
<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
|
||||||
|
@ -265,8 +267,9 @@ 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
|
external applications without the need to create external plugins using the
|
||||||
the ``setuptools``'s entry point technique.
|
:std:doc:`entry point packaging metadata
|
||||||
|
<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,8 +4,7 @@
|
||||||
|
|
||||||
.. sidebar:: **Next Open Trainings and Events**
|
.. sidebar:: **Next Open Trainings and Events**
|
||||||
|
|
||||||
- `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 9th 2024** (3h), Prague (CZ)
|
||||||
- `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 `setuptools <https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`__, and can also be used to hold pytest configuration
|
``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
|
||||||
if they have a ``[tool:pytest]`` section.
|
if they have a ``[tool:pytest]`` section.
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
|
@ -27,7 +27,7 @@ please refer to `the update script <https://github.com/pytest-dev/pytest/blob/ma
|
||||||
creating a PDF, because otherwise the table gets far too wide for the
|
creating a PDF, because otherwise the table gets far too wide for the
|
||||||
page.
|
page.
|
||||||
|
|
||||||
This list contains 1476 plugins.
|
This list contains 1479 plugins.
|
||||||
|
|
||||||
.. only:: not latex
|
.. only:: not latex
|
||||||
|
|
||||||
|
@ -86,6 +86,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-api` An ASGI middleware to populate OpenAPI Specification examples from pytest functions May 12, 2022 N/A pytest (>=7.1.1,<8.0.0)
|
:pypi:`pytest-api` An ASGI middleware to populate OpenAPI Specification examples from pytest functions May 12, 2022 N/A pytest (>=7.1.1,<8.0.0)
|
||||||
:pypi:`pytest-api-soup` Validate multiple endpoints with unit testing using a single source of truth. Aug 27, 2022 N/A N/A
|
:pypi:`pytest-api-soup` Validate multiple endpoints with unit testing using a single source of truth. Aug 27, 2022 N/A N/A
|
||||||
:pypi:`pytest-apistellar` apistellar plugin for pytest. Jun 18, 2019 N/A N/A
|
:pypi:`pytest-apistellar` apistellar plugin for pytest. Jun 18, 2019 N/A N/A
|
||||||
|
:pypi:`pytest-apiver` Jun 21, 2024 N/A pytest
|
||||||
:pypi:`pytest-appengine` AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A
|
:pypi:`pytest-appengine` AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A
|
||||||
:pypi:`pytest-appium` Pytest plugin for appium Dec 05, 2019 N/A N/A
|
:pypi:`pytest-appium` Pytest plugin for appium Dec 05, 2019 N/A N/A
|
||||||
:pypi:`pytest-approvaltests` A plugin to use approvaltests with pytest May 08, 2022 4 - Beta pytest (>=7.0.1)
|
:pypi:`pytest-approvaltests` A plugin to use approvaltests with pytest May 08, 2022 4 - Beta pytest (>=7.0.1)
|
||||||
|
@ -100,6 +101,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-assertions` Pytest Assertions Apr 27, 2022 N/A N/A
|
:pypi:`pytest-assertions` Pytest Assertions Apr 27, 2022 N/A N/A
|
||||||
:pypi:`pytest-assertutil` pytest-assertutil May 10, 2019 N/A N/A
|
:pypi:`pytest-assertutil` pytest-assertutil May 10, 2019 N/A N/A
|
||||||
:pypi:`pytest-assert-utils` Useful assertion utilities for use with pytest Apr 14, 2022 3 - Alpha N/A
|
:pypi:`pytest-assert-utils` Useful assertion utilities for use with pytest Apr 14, 2022 3 - Alpha N/A
|
||||||
|
:pypi:`pytest-assist` load testing library Jun 22, 2024 N/A pytest
|
||||||
:pypi:`pytest-assume` A pytest plugin that allows multiple failures per test Jun 24, 2021 N/A pytest (>=2.7)
|
:pypi:`pytest-assume` A pytest plugin that allows multiple failures per test Jun 24, 2021 N/A pytest (>=2.7)
|
||||||
:pypi:`pytest-assurka` A pytest plugin for Assurka Studio Aug 04, 2022 N/A N/A
|
:pypi:`pytest-assurka` A pytest plugin for Assurka Studio Aug 04, 2022 N/A N/A
|
||||||
:pypi:`pytest-ast-back-to-python` A plugin for pytest devs to view how assertion rewriting recodes the AST Sep 29, 2019 4 - Beta N/A
|
:pypi:`pytest-ast-back-to-python` A plugin for pytest devs to view how assertion rewriting recodes the AST Sep 29, 2019 4 - Beta N/A
|
||||||
|
@ -125,7 +127,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-automock` Pytest plugin for automatical mocks creation May 16, 2023 N/A pytest ; extra == 'dev'
|
:pypi:`pytest-automock` Pytest plugin for automatical mocks creation May 16, 2023 N/A pytest ; extra == 'dev'
|
||||||
:pypi:`pytest-auto-parametrize` pytest plugin: avoid repeating arguments in parametrize Oct 02, 2016 3 - Alpha N/A
|
:pypi:`pytest-auto-parametrize` pytest plugin: avoid repeating arguments in parametrize Oct 02, 2016 3 - Alpha N/A
|
||||||
:pypi:`pytest-autotest` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Aug 25, 2021 N/A pytest
|
:pypi:`pytest-autotest` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Aug 25, 2021 N/A pytest
|
||||||
:pypi:`pytest-aux` templates/examples and aux for pytest May 31, 2024 N/A N/A
|
:pypi:`pytest-aux` templates/examples and aux for pytest Jun 18, 2024 N/A N/A
|
||||||
:pypi:`pytest-aviator` Aviator's Flakybot pytest plugin that automatically reruns flaky tests. Nov 04, 2022 4 - Beta pytest
|
:pypi:`pytest-aviator` Aviator's Flakybot pytest plugin that automatically reruns flaky tests. Nov 04, 2022 4 - Beta pytest
|
||||||
:pypi:`pytest-avoidance` Makes pytest skip tests that don not need rerunning May 23, 2019 4 - Beta pytest (>=3.5.0)
|
:pypi:`pytest-avoidance` Makes pytest skip tests that don not need rerunning May 23, 2019 4 - Beta pytest (>=3.5.0)
|
||||||
:pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A
|
:pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A
|
||||||
|
@ -150,7 +152,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-bdd-wrappers` Feb 11, 2020 2 - Pre-Alpha N/A
|
:pypi:`pytest-bdd-wrappers` Feb 11, 2020 2 - Pre-Alpha N/A
|
||||||
:pypi:`pytest-beakerlib` A pytest plugin that reports test results to the BeakerLib framework Mar 17, 2017 5 - Production/Stable pytest
|
:pypi:`pytest-beakerlib` A pytest plugin that reports test results to the BeakerLib framework Mar 17, 2017 5 - Production/Stable pytest
|
||||||
:pypi:`pytest-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest
|
:pypi:`pytest-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest
|
||||||
:pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 07, 2024 3 - Alpha pytest
|
:pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 21, 2024 3 - Alpha pytest
|
||||||
:pypi:`pytest-beds` Fixtures for testing Google Appengine (GAE) apps Jun 07, 2016 4 - Beta N/A
|
:pypi:`pytest-beds` Fixtures for testing Google Appengine (GAE) apps Jun 07, 2016 4 - Beta N/A
|
||||||
:pypi:`pytest-beeprint` use icdiff for better error messages in pytest assertions Jul 04, 2023 4 - Beta N/A
|
:pypi:`pytest-beeprint` use icdiff for better error messages in pytest assertions Jul 04, 2023 4 - Beta N/A
|
||||||
:pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A
|
:pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A
|
||||||
|
@ -160,7 +162,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-bg-process` Pytest plugin to initialize background process Jan 24, 2022 4 - Beta pytest (>=3.5.0)
|
:pypi:`pytest-bg-process` Pytest plugin to initialize background process Jan 24, 2022 4 - Beta pytest (>=3.5.0)
|
||||||
:pypi:`pytest-bigchaindb` A BigchainDB plugin for pytest. Jan 24, 2022 4 - Beta N/A
|
:pypi:`pytest-bigchaindb` A BigchainDB plugin for pytest. Jan 24, 2022 4 - Beta N/A
|
||||||
:pypi:`pytest-bigquery-mock` Provides a mock fixture for python bigquery client Dec 28, 2022 N/A pytest (>=5.0)
|
:pypi:`pytest-bigquery-mock` Provides a mock fixture for python bigquery client Dec 28, 2022 N/A pytest (>=5.0)
|
||||||
:pypi:`pytest-bisect-tests` Find tests leaking state and affecting other Mar 25, 2024 N/A N/A
|
:pypi:`pytest-bisect-tests` Find tests leaking state and affecting other Jun 09, 2024 N/A N/A
|
||||||
:pypi:`pytest-black` A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A
|
:pypi:`pytest-black` A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A
|
||||||
:pypi:`pytest-black-multipy` Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing'
|
:pypi:`pytest-black-multipy` Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing'
|
||||||
:pypi:`pytest-black-ng` A pytest plugin to enable format checking with black Oct 20, 2022 4 - Beta pytest (>=7.0.0)
|
:pypi:`pytest-black-ng` A pytest plugin to enable format checking with black Oct 20, 2022 4 - Beta pytest (>=7.0.0)
|
||||||
|
@ -222,7 +224,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-check-library` check your missing library Jul 17, 2022 N/A N/A
|
:pypi:`pytest-check-library` check your missing library Jul 17, 2022 N/A N/A
|
||||||
:pypi:`pytest-check-libs` check your missing library Jul 17, 2022 N/A N/A
|
:pypi:`pytest-check-libs` check your missing library Jul 17, 2022 N/A N/A
|
||||||
:pypi:`pytest-check-links` Check links in files Jul 29, 2020 N/A pytest<9,>=7.0
|
:pypi:`pytest-check-links` Check links in files Jul 29, 2020 N/A pytest<9,>=7.0
|
||||||
:pypi:`pytest-checklist` Pytest plugin to track and report unit/function coverage. Mar 12, 2024 N/A N/A
|
:pypi:`pytest-checklist` Pytest plugin to track and report unit/function coverage. Jun 10, 2024 N/A N/A
|
||||||
:pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest
|
:pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest
|
||||||
:pypi:`pytest-check-requirements` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A
|
:pypi:`pytest-check-requirements` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A
|
||||||
:pypi:`pytest-ch-framework` My pytest framework Apr 17, 2024 N/A pytest==8.0.1
|
:pypi:`pytest-ch-framework` My pytest framework Apr 17, 2024 N/A pytest==8.0.1
|
||||||
|
@ -236,7 +238,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-ckan` Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8 Apr 28, 2020 4 - Beta pytest
|
:pypi:`pytest-ckan` Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8 Apr 28, 2020 4 - Beta pytest
|
||||||
:pypi:`pytest-clarity` A plugin providing an alternative, colourful diff output for failing assertions. Jun 11, 2021 N/A N/A
|
:pypi:`pytest-clarity` A plugin providing an alternative, colourful diff output for failing assertions. Jun 11, 2021 N/A N/A
|
||||||
:pypi:`pytest-cldf` Easy quality control for CLDF datasets using pytest Nov 07, 2022 N/A pytest (>=3.6)
|
:pypi:`pytest-cldf` Easy quality control for CLDF datasets using pytest Nov 07, 2022 N/A pytest (>=3.6)
|
||||||
:pypi:`pytest-cleanslate` Collects and executes pytest tests separately May 30, 2024 N/A pytest
|
:pypi:`pytest-cleanslate` Collects and executes pytest tests separately Jun 17, 2024 N/A pytest
|
||||||
:pypi:`pytest_cleanup` Automated, comprehensive and well-organised pytest test cases. Jan 28, 2020 N/A N/A
|
:pypi:`pytest_cleanup` Automated, comprehensive and well-organised pytest test cases. Jan 28, 2020 N/A N/A
|
||||||
:pypi:`pytest-cleanuptotal` A cleanup plugin for pytest Mar 19, 2024 5 - Production/Stable N/A
|
:pypi:`pytest-cleanuptotal` A cleanup plugin for pytest Mar 19, 2024 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-clerk` A set of pytest fixtures to help with integration testing with Clerk. Apr 19, 2024 N/A pytest<9.0.0,>=8.0.0
|
:pypi:`pytest-clerk` A set of pytest fixtures to help with integration testing with Clerk. Apr 19, 2024 N/A pytest<9.0.0,>=8.0.0
|
||||||
|
@ -261,12 +263,11 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-collect-formatter` Formatter for pytest collect output Mar 29, 2021 5 - Production/Stable N/A
|
:pypi:`pytest-collect-formatter` Formatter for pytest collect output Mar 29, 2021 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-collect-formatter2` Formatter for pytest collect output May 31, 2021 5 - Production/Stable N/A
|
:pypi:`pytest-collect-formatter2` Formatter for pytest collect output May 31, 2021 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-collect-interface-info-plugin` Get executed interface information in pytest interface automation framework Sep 25, 2023 4 - Beta N/A
|
:pypi:`pytest-collect-interface-info-plugin` Get executed interface information in pytest interface automation framework Sep 25, 2023 4 - Beta N/A
|
||||||
:pypi:`pytest-collect-jmeter-report-tests` A simple plugin to use with pytest May 20, 2024 4 - Beta pytest>=7.2.1
|
|
||||||
:pypi:`pytest-collector` Python package for collecting pytest. Aug 02, 2022 N/A pytest (>=7.0,<8.0)
|
:pypi:`pytest-collector` Python package for collecting pytest. Aug 02, 2022 N/A pytest (>=7.0,<8.0)
|
||||||
:pypi:`pytest-collect-pytest-interinfo` A simple plugin to use with pytest Sep 26, 2023 4 - Beta N/A
|
:pypi:`pytest-collect-pytest-interinfo` A simple plugin to use with pytest Sep 26, 2023 4 - Beta N/A
|
||||||
:pypi:`pytest-colordots` Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A
|
:pypi:`pytest-colordots` Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-commander` An interactive GUI test runner for PyTest Aug 17, 2021 N/A pytest (<7.0.0,>=6.2.4)
|
:pypi:`pytest-commander` An interactive GUI test runner for PyTest Aug 17, 2021 N/A pytest (<7.0.0,>=6.2.4)
|
||||||
:pypi:`pytest-common-subject` pytest framework for testing different aspects of a common method Jun 03, 2024 N/A pytest<9,>=3.6
|
:pypi:`pytest-common-subject` pytest framework for testing different aspects of a common method Jun 12, 2024 N/A pytest<9,>=3.6
|
||||||
:pypi:`pytest-compare` pytest plugin for comparing call arguments. Jun 22, 2023 5 - Production/Stable N/A
|
:pypi:`pytest-compare` pytest plugin for comparing call arguments. Jun 22, 2023 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-concurrent` Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1)
|
:pypi:`pytest-concurrent` Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1)
|
||||||
:pypi:`pytest-config` Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A
|
:pypi:`pytest-config` Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A
|
||||||
|
@ -305,7 +306,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-custom-concurrency` Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A
|
:pypi:`pytest-custom-concurrency` Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A
|
||||||
:pypi:`pytest-custom-exit-code` Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2)
|
:pypi:`pytest-custom-exit-code` Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2)
|
||||||
:pypi:`pytest-custom-nodeid` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 07, 2021 N/A N/A
|
:pypi:`pytest-custom-nodeid` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 07, 2021 N/A N/A
|
||||||
:pypi:`pytest-custom-outputs` A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail May 30, 2024 4 - Beta pytest>=6.2.0
|
:pypi:`pytest-custom-outputs` A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures. Jun 17, 2024 4 - Beta pytest>=6.2.0
|
||||||
:pypi:`pytest-custom-report` Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest
|
:pypi:`pytest-custom-report` Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest
|
||||||
:pypi:`pytest-custom-scheduling` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 01, 2021 N/A N/A
|
:pypi:`pytest-custom-scheduling` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 01, 2021 N/A N/A
|
||||||
:pypi:`pytest-cython` A plugin for testing Cython extension modules Apr 05, 2024 5 - Production/Stable pytest>=8
|
:pypi:`pytest-cython` A plugin for testing Cython extension modules Apr 05, 2024 5 - Production/Stable pytest>=8
|
||||||
|
@ -314,7 +315,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A
|
:pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A
|
||||||
:pypi:`pytest-dashboard` May 30, 2024 N/A pytest<8.0.0,>=7.4.3
|
:pypi:`pytest-dashboard` May 30, 2024 N/A pytest<8.0.0,>=7.4.3
|
||||||
:pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A
|
:pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-databases` Reusable database fixtures for any and all databases. May 25, 2024 4 - Beta pytest
|
:pypi:`pytest-databases` Reusable database fixtures for any and all databases. Jun 11, 2024 4 - Beta pytest
|
||||||
:pypi:`pytest-databricks` Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest
|
:pypi:`pytest-databricks` Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest
|
||||||
:pypi:`pytest-datadir` pytest plugin for test data directories and files Oct 03, 2023 5 - Production/Stable pytest >=5.0
|
:pypi:`pytest-datadir` pytest plugin for test data directories and files Oct 03, 2023 5 - Production/Stable pytest >=5.0
|
||||||
:pypi:`pytest-datadir-mgr` Manager for test data: downloads, artifact caching, and a tmpdir context. Apr 06, 2023 5 - Production/Stable pytest (>=7.1)
|
:pypi:`pytest-datadir-mgr` Manager for test data: downloads, artifact caching, and a tmpdir context. Apr 06, 2023 5 - Production/Stable pytest (>=7.1)
|
||||||
|
@ -366,15 +367,16 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0)
|
:pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0)
|
||||||
:pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. May 11, 2024 4 - Beta pytest!=6.0.0,<9,>=3.3.2
|
:pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. May 11, 2024 4 - Beta pytest!=6.0.0,<9,>=3.3.2
|
||||||
:pypi:`pytest-discover` Pytest plugin to record discovered tests in a file Mar 26, 2024 N/A pytest
|
:pypi:`pytest-discover` Pytest plugin to record discovered tests in a file Mar 26, 2024 N/A pytest
|
||||||
:pypi:`pytest-ditto` Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. May 29, 2024 4 - Beta pytest>=3.5.0
|
:pypi:`pytest-ditto` Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. Jun 09, 2024 4 - Beta pytest>=3.5.0
|
||||||
:pypi:`pytest-ditto-pandas` pytest-ditto plugin for pandas snapshots. May 29, 2024 4 - Beta pytest>=3.5.0
|
:pypi:`pytest-ditto-pandas` pytest-ditto plugin for pandas snapshots. May 29, 2024 4 - Beta pytest>=3.5.0
|
||||||
|
:pypi:`pytest-ditto-pyarrow` pytest-ditto plugin for pyarrow tables. Jun 09, 2024 4 - Beta pytest>=3.5.0
|
||||||
:pypi:`pytest-django` A Django plugin for pytest. Jan 30, 2024 5 - Production/Stable pytest >=7.0.0
|
:pypi:`pytest-django` A Django plugin for pytest. Jan 30, 2024 5 - Production/Stable pytest >=7.0.0
|
||||||
:pypi:`pytest-django-ahead` A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9)
|
:pypi:`pytest-django-ahead` A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9)
|
||||||
:pypi:`pytest-djangoapp` Nice pytest plugin to help you with Django pluggable application testing. May 19, 2023 4 - Beta pytest
|
:pypi:`pytest-djangoapp` Nice pytest plugin to help you with Django pluggable application testing. May 19, 2023 4 - Beta pytest
|
||||||
:pypi:`pytest-django-cache-xdist` A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A
|
:pypi:`pytest-django-cache-xdist` A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A
|
||||||
:pypi:`pytest-django-casperjs` Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A
|
:pypi:`pytest-django-casperjs` Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A
|
||||||
:pypi:`pytest-django-class` A pytest plugin for running django in class-scoped fixtures Aug 08, 2023 4 - Beta N/A
|
:pypi:`pytest-django-class` A pytest plugin for running django in class-scoped fixtures Aug 08, 2023 4 - Beta N/A
|
||||||
:pypi:`pytest-django-docker-pg` May 21, 2024 5 - Production/Stable pytest<9.0.0,>=7.0.0
|
:pypi:`pytest-django-docker-pg` Jun 13, 2024 5 - Production/Stable pytest<9.0.0,>=7.0.0
|
||||||
:pypi:`pytest-django-dotenv` Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0)
|
:pypi:`pytest-django-dotenv` Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0)
|
||||||
:pypi:`pytest-django-factories` Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A
|
:pypi:`pytest-django-factories` Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A
|
||||||
:pypi:`pytest-django-filefield` Replaces FileField.storage with something you can patch globally. May 09, 2022 5 - Production/Stable pytest >= 5.2
|
:pypi:`pytest-django-filefield` Replaces FileField.storage with something you can patch globally. May 09, 2022 5 - Production/Stable pytest >= 5.2
|
||||||
|
@ -443,7 +445,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-ebics-sandbox` A pytest plugin for testing against an EBICS sandbox server. Requires docker. Aug 15, 2022 N/A N/A
|
:pypi:`pytest-ebics-sandbox` A pytest plugin for testing against an EBICS sandbox server. Requires docker. Aug 15, 2022 N/A N/A
|
||||||
:pypi:`pytest-ec2` Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A
|
:pypi:`pytest-ec2` Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A
|
||||||
:pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Dec 05, 2023 5 - Production/Stable pytest >=2.2
|
:pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Dec 05, 2023 5 - Production/Stable pytest >=2.2
|
||||||
:pypi:`pytest-edit` Edit the source code of a failed test with \`pytest --edit\`. Jun 07, 2024 N/A pytest
|
:pypi:`pytest-edit` Edit the source code of a failed test with \`pytest --edit\`. Jun 09, 2024 N/A pytest
|
||||||
:pypi:`pytest-ekstazi` Pytest plugin to select test using Ekstazi algorithm Sep 10, 2022 N/A pytest
|
:pypi:`pytest-ekstazi` Pytest plugin to select test using Ekstazi algorithm Sep 10, 2022 N/A pytest
|
||||||
:pypi:`pytest-elasticsearch` Elasticsearch fixtures and fixture factories for Pytest. Mar 15, 2024 5 - Production/Stable pytest >=7.0
|
:pypi:`pytest-elasticsearch` Elasticsearch fixtures and fixture factories for Pytest. Mar 15, 2024 5 - Production/Stable pytest >=7.0
|
||||||
:pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0)
|
:pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0)
|
||||||
|
@ -487,7 +489,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. Jul 11, 2023 4 - Beta pytest>=7
|
:pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. Jul 11, 2023 4 - Beta pytest>=7
|
||||||
:pypi:`pytest-exasol-itde` Feb 15, 2024 N/A pytest (>=7,<9)
|
:pypi:`pytest-exasol-itde` Feb 15, 2024 N/A pytest (>=7,<9)
|
||||||
:pypi:`pytest-exasol-saas` Jun 07, 2024 N/A pytest<9,>=7
|
:pypi:`pytest-exasol-saas` Jun 07, 2024 N/A pytest<9,>=7
|
||||||
:pypi:`pytest-excel` pytest plugin for generating excel reports Sep 14, 2023 5 - Production/Stable N/A
|
:pypi:`pytest-excel` pytest plugin for generating excel reports Jun 18, 2024 5 - Production/Stable pytest>3.6
|
||||||
:pypi:`pytest-exceptional` Better exceptions Mar 16, 2017 4 - Beta N/A
|
:pypi:`pytest-exceptional` Better exceptions Mar 16, 2017 4 - Beta N/A
|
||||||
:pypi:`pytest-exception-script` Walk your code through exception script to check it's resiliency to failures. Aug 04, 2020 3 - Alpha pytest
|
:pypi:`pytest-exception-script` Walk your code through exception script to check it's resiliency to failures. Aug 04, 2020 3 - Alpha pytest
|
||||||
:pypi:`pytest-executable` pytest plugin for testing executables Oct 07, 2023 N/A pytest <8,>=5
|
:pypi:`pytest-executable` pytest plugin for testing executables Oct 07, 2023 N/A pytest <8,>=5
|
||||||
|
@ -636,7 +638,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0)
|
:pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0)
|
||||||
:pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable pytest
|
:pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable pytest
|
||||||
:pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A
|
:pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A
|
||||||
:pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jun 08, 2024 3 - Alpha pytest==8.2.0
|
:pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jun 22, 2024 3 - Alpha pytest==8.2.0
|
||||||
:pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0)
|
:pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0)
|
||||||
:pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A
|
:pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A
|
||||||
:pypi:`pytest-hot-reloading` Apr 18, 2024 N/A N/A
|
:pypi:`pytest-hot-reloading` Apr 18, 2024 N/A N/A
|
||||||
|
@ -678,6 +680,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-image-diff` Mar 09, 2023 3 - Alpha pytest
|
:pypi:`pytest-image-diff` Mar 09, 2023 3 - Alpha pytest
|
||||||
:pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Dec 01, 2023 4 - Beta pytest >=3.5.0
|
:pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Dec 01, 2023 4 - Beta pytest >=3.5.0
|
||||||
:pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A
|
:pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A
|
||||||
|
:pypi:`pytest-infinity` Jun 09, 2024 N/A pytest<9.0.0,>=8.0.0
|
||||||
:pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A
|
:pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A
|
||||||
:pypi:`pytest-info-collector` pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A
|
:pypi:`pytest-info-collector` pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A
|
||||||
:pypi:`pytest-info-plugin` Get executed interface information in pytest interface automation framework Sep 14, 2023 N/A N/A
|
:pypi:`pytest-info-plugin` Get executed interface information in pytest interface automation framework Sep 14, 2023 N/A N/A
|
||||||
|
@ -723,13 +726,12 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-jobserver` Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest
|
:pypi:`pytest-jobserver` Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest
|
||||||
:pypi:`pytest-joke` Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1)
|
:pypi:`pytest-joke` Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1)
|
||||||
:pypi:`pytest-json` Generate JSON test reports Jan 18, 2016 4 - Beta N/A
|
:pypi:`pytest-json` Generate JSON test reports Jan 18, 2016 4 - Beta N/A
|
||||||
:pypi:`pytest-json-ctrf` Pytest plugin to generate json report in CTRF (Common Test Report Format) May 21, 2024 N/A pytest>6.0.0
|
:pypi:`pytest-json-ctrf` Pytest plugin to generate json report in CTRF (Common Test Report Format) Jun 15, 2024 N/A pytest>6.0.0
|
||||||
:pypi:`pytest-json-fixtures` JSON output for the --fixtures flag Mar 14, 2023 4 - Beta N/A
|
:pypi:`pytest-json-fixtures` JSON output for the --fixtures flag Mar 14, 2023 4 - Beta N/A
|
||||||
:pypi:`pytest-jsonlint` UNKNOWN Aug 04, 2016 N/A N/A
|
:pypi:`pytest-jsonlint` UNKNOWN Aug 04, 2016 N/A N/A
|
||||||
:pypi:`pytest-json-report` A pytest plugin to report test results as JSON files Mar 15, 2022 4 - Beta pytest (>=3.8.0)
|
:pypi:`pytest-json-report` A pytest plugin to report test results as JSON files Mar 15, 2022 4 - Beta pytest (>=3.8.0)
|
||||||
:pypi:`pytest-json-report-wip` A pytest plugin to report test results as JSON files Oct 28, 2023 4 - Beta pytest >=3.8.0
|
:pypi:`pytest-json-report-wip` A pytest plugin to report test results as JSON files Oct 28, 2023 4 - Beta pytest >=3.8.0
|
||||||
:pypi:`pytest-jsonschema` A pytest plugin to perform JSONSchema validations Mar 27, 2024 4 - Beta pytest>=6.2.0
|
:pypi:`pytest-jsonschema` A pytest plugin to perform JSONSchema validations Mar 27, 2024 4 - Beta pytest>=6.2.0
|
||||||
:pypi:`pytest-jtl-collector` A simple plugin to use with pytest May 20, 2024 4 - Beta pytest>=7.2.1
|
|
||||||
:pypi:`pytest-jtr` pytest plugin supporting json test report output Jun 04, 2024 N/A pytest<8.0.0,>=7.1.2
|
:pypi:`pytest-jtr` pytest plugin supporting json test report output Jun 04, 2024 N/A pytest<8.0.0,>=7.1.2
|
||||||
:pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Apr 04, 2024 4 - Beta pytest>=7.0
|
:pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Apr 04, 2024 4 - Beta pytest>=7.0
|
||||||
:pypi:`pytest-jupyterhub` A reusable JupyterHub pytest plugin Apr 25, 2023 5 - Production/Stable pytest
|
:pypi:`pytest-jupyterhub` A reusable JupyterHub pytest plugin Apr 25, 2023 5 - Production/Stable pytest
|
||||||
|
@ -824,7 +826,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-messenger` Pytest to Slack reporting plugin Nov 24, 2022 5 - Production/Stable N/A
|
:pypi:`pytest-messenger` Pytest to Slack reporting plugin Nov 24, 2022 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-metadata` pytest plugin for test session metadata Feb 12, 2024 5 - Production/Stable pytest>=7.0.0
|
:pypi:`pytest-metadata` pytest plugin for test session metadata Feb 12, 2024 5 - Production/Stable pytest>=7.0.0
|
||||||
:pypi:`pytest-metrics` Custom metrics report for pytest Apr 04, 2020 N/A pytest
|
:pypi:`pytest-metrics` Custom metrics report for pytest Apr 04, 2020 N/A pytest
|
||||||
:pypi:`pytest-mh` Pytest multihost plugin May 28, 2024 N/A pytest
|
:pypi:`pytest-mh` Pytest multihost plugin Jun 13, 2024 N/A pytest
|
||||||
:pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2)
|
:pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2)
|
||||||
:pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1)
|
:pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1)
|
||||||
:pypi:`pytest-mini` A plugin to test mp Feb 06, 2023 N/A pytest (>=7.2.0,<8.0.0)
|
:pypi:`pytest-mini` A plugin to test mp Feb 06, 2023 N/A pytest (>=7.2.0,<8.0.0)
|
||||||
|
@ -839,7 +841,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-mock-helper` Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest
|
:pypi:`pytest-mock-helper` Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest
|
||||||
:pypi:`pytest-mockito` Base fixtures for mockito Jul 11, 2018 4 - Beta N/A
|
:pypi:`pytest-mockito` Base fixtures for mockito Jul 11, 2018 4 - Beta N/A
|
||||||
:pypi:`pytest-mockredis` An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A
|
:pypi:`pytest-mockredis` An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A
|
||||||
:pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. Apr 11, 2024 N/A pytest>=1.0
|
:pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. Jun 20, 2024 N/A pytest>=1.0
|
||||||
:pypi:`pytest-mock-server` Mock server plugin for pytest Jan 09, 2022 4 - Beta pytest (>=3.5.0)
|
:pypi:`pytest-mock-server` Mock server plugin for pytest Jan 09, 2022 4 - Beta pytest (>=3.5.0)
|
||||||
:pypi:`pytest-mockservers` A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0)
|
:pypi:`pytest-mockservers` A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0)
|
||||||
:pypi:`pytest-mocktcp` A pytest plugin for testing TCP clients Oct 11, 2022 N/A pytest
|
:pypi:`pytest-mocktcp` A pytest plugin for testing TCP clients Oct 11, 2022 N/A pytest
|
||||||
|
@ -877,7 +879,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-ndb` pytest notebook debugger Apr 28, 2024 N/A pytest
|
:pypi:`pytest-ndb` pytest notebook debugger Apr 28, 2024 N/A pytest
|
||||||
:pypi:`pytest-needle` pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0)
|
:pypi:`pytest-needle` pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0)
|
||||||
:pypi:`pytest-neo` pytest-neo is a plugin for pytest that shows tests like screen of Matrix. Jan 08, 2022 3 - Alpha pytest (>=6.2.0)
|
:pypi:`pytest-neo` pytest-neo is a plugin for pytest that shows tests like screen of Matrix. Jan 08, 2022 3 - Alpha pytest (>=6.2.0)
|
||||||
:pypi:`pytest-neos` Pytest plugin for neos May 01, 2024 1 - Planning N/A
|
:pypi:`pytest-neos` Pytest plugin for neos Jun 11, 2024 1 - Planning N/A
|
||||||
:pypi:`pytest-netdut` "Automated software testing for switches using pytest" Mar 07, 2024 N/A pytest <7.3,>=3.5.0
|
:pypi:`pytest-netdut` "Automated software testing for switches using pytest" Mar 07, 2024 N/A pytest <7.3,>=3.5.0
|
||||||
:pypi:`pytest-network` A simple plugin to disable network on socket level. May 07, 2020 N/A N/A
|
:pypi:`pytest-network` A simple plugin to disable network on socket level. May 07, 2020 N/A N/A
|
||||||
:pypi:`pytest-network-endpoints` Network endpoints plugin for pytest Mar 06, 2022 N/A pytest
|
:pypi:`pytest-network-endpoints` Network endpoints plugin for pytest Mar 06, 2022 N/A pytest
|
||||||
|
@ -886,7 +888,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-nginx-iplweb` nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A
|
:pypi:`pytest-nginx-iplweb` nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-ngrok` Jan 20, 2022 3 - Alpha pytest
|
:pypi:`pytest-ngrok` Jan 20, 2022 3 - Alpha pytest
|
||||||
:pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0)
|
:pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0)
|
||||||
:pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies May 20, 2024 N/A pytest<9.0.0,>=8.2.0
|
:pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Jun 18, 2024 N/A pytest<9.0.0,>=8.2.0
|
||||||
:pypi:`pytest-nice` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest
|
:pypi:`pytest-nice` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest
|
||||||
:pypi:`pytest-nice-parametrize` A small snippet for nicer PyTest's Parametrize Apr 17, 2021 5 - Production/Stable N/A
|
:pypi:`pytest-nice-parametrize` A small snippet for nicer PyTest's Parametrize Apr 17, 2021 5 - Production/Stable N/A
|
||||||
:pypi:`pytest_nlcov` Pytest plugin to get the coverage of the new lines (based on git diff) only Apr 11, 2024 N/A N/A
|
:pypi:`pytest_nlcov` Pytest plugin to get the coverage of the new lines (based on git diff) only Apr 11, 2024 N/A N/A
|
||||||
|
@ -950,7 +952,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-paste-config` Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A
|
:pypi:`pytest-paste-config` Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A
|
||||||
:pypi:`pytest-patch` An automagic \`patch\` fixture that can patch objects directly or by name. Apr 29, 2023 3 - Alpha pytest (>=7.0.0)
|
:pypi:`pytest-patch` An automagic \`patch\` fixture that can patch objects directly or by name. Apr 29, 2023 3 - Alpha pytest (>=7.0.0)
|
||||||
:pypi:`pytest-patches` A contextmanager pytest fixture for handling multiple mock patches Aug 30, 2021 4 - Beta pytest (>=3.5.0)
|
:pypi:`pytest-patches` A contextmanager pytest fixture for handling multiple mock patches Aug 30, 2021 4 - Beta pytest (>=3.5.0)
|
||||||
:pypi:`pytest-patterns` pytest plugin to make testing complicated long string output easy to write and easy to debug Nov 17, 2023 4 - Beta N/A
|
:pypi:`pytest-patterns` pytest plugin to make testing complicated long string output easy to write and easy to debug Jun 14, 2024 4 - Beta N/A
|
||||||
:pypi:`pytest-pdb` pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A
|
:pypi:`pytest-pdb` pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A
|
||||||
:pypi:`pytest-peach` pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7)
|
:pypi:`pytest-peach` pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7)
|
||||||
:pypi:`pytest-pep257` py.test plugin for pep257 Jul 09, 2016 N/A N/A
|
:pypi:`pytest-pep257` py.test plugin for pep257 Jul 09, 2016 N/A N/A
|
||||||
|
@ -1016,7 +1018,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-proceed` Apr 10, 2024 N/A pytest
|
:pypi:`pytest-proceed` Apr 10, 2024 N/A pytest
|
||||||
:pypi:`pytest-profiles` pytest plugin for configuration profiles Dec 09, 2021 4 - Beta pytest (>=3.7.0)
|
:pypi:`pytest-profiles` pytest plugin for configuration profiles Dec 09, 2021 4 - Beta pytest (>=3.7.0)
|
||||||
:pypi:`pytest-profiling` Profiling plugin for py.test May 28, 2019 5 - Production/Stable pytest
|
:pypi:`pytest-profiling` Profiling plugin for py.test May 28, 2019 5 - Production/Stable pytest
|
||||||
:pypi:`pytest-progress` pytest plugin for instant test progress status Jan 31, 2022 5 - Production/Stable N/A
|
:pypi:`pytest-progress` pytest plugin for instant test progress status Jun 18, 2024 5 - Production/Stable pytest>=2.7
|
||||||
:pypi:`pytest-prometheus` Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A
|
:pypi:`pytest-prometheus` Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A
|
||||||
:pypi:`pytest-prometheus-pushgateway` Pytest report plugin for Zulip Sep 27, 2022 5 - Production/Stable pytest
|
:pypi:`pytest-prometheus-pushgateway` Pytest report plugin for Zulip Sep 27, 2022 5 - Production/Stable pytest
|
||||||
:pypi:`pytest-prosper` Test helpers for Prosper projects Sep 24, 2018 N/A N/A
|
:pypi:`pytest-prosper` Test helpers for Prosper projects Sep 24, 2018 N/A N/A
|
||||||
|
@ -1037,7 +1039,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-pydocstyle` pytest plugin to run pydocstyle Jan 05, 2023 3 - Alpha N/A
|
:pypi:`pytest-pydocstyle` pytest plugin to run pydocstyle Jan 05, 2023 3 - Alpha N/A
|
||||||
:pypi:`pytest-pylint` pytest plugin to check source code with pylint Oct 06, 2023 5 - Production/Stable pytest >=7.0
|
:pypi:`pytest-pylint` pytest plugin to check source code with pylint Oct 06, 2023 5 - Production/Stable pytest >=7.0
|
||||||
:pypi:`pytest-pymysql-autorecord` Record PyMySQL queries and mock with the stored data. Sep 02, 2022 N/A N/A
|
:pypi:`pytest-pymysql-autorecord` Record PyMySQL queries and mock with the stored data. Sep 02, 2022 N/A N/A
|
||||||
:pypi:`pytest-pyodide` Pytest plugin for testing applications that use Pyodide Apr 30, 2024 N/A pytest
|
:pypi:`pytest-pyodide` Pytest plugin for testing applications that use Pyodide Jun 12, 2024 N/A pytest
|
||||||
:pypi:`pytest-pypi` Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A
|
:pypi:`pytest-pypi` Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A
|
||||||
:pypi:`pytest-pypom-navigation` Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7)
|
:pypi:`pytest-pypom-navigation` Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7)
|
||||||
:pypi:`pytest-pyppeteer` A plugin to run pyppeteer in pytest Apr 28, 2022 N/A pytest (>=6.2.5,<7.0.0)
|
:pypi:`pytest-pyppeteer` A plugin to run pyppeteer in pytest Apr 28, 2022 N/A pytest (>=6.2.5,<7.0.0)
|
||||||
|
@ -1059,7 +1061,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-qaseio` Pytest plugin for Qase.io integration May 30, 2024 4 - Beta pytest<9.0.0,>=7.2.2
|
:pypi:`pytest-qaseio` Pytest plugin for Qase.io integration May 30, 2024 4 - Beta pytest<9.0.0,>=7.2.2
|
||||||
:pypi:`pytest-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0)
|
:pypi:`pytest-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0)
|
||||||
:pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Feb 14, 2023 4 - Beta pytest (>=6.2.0)
|
:pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Feb 14, 2023 4 - Beta pytest (>=6.2.0)
|
||||||
:pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Nov 29, 2023 5 - Production/Stable pytest >=6.0
|
:pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Jun 14, 2024 5 - Production/Stable pytest>=6.0
|
||||||
:pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0)
|
:pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0)
|
||||||
:pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A
|
:pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A
|
||||||
:pypi:`pytest-qt` pytest support for PyQt and PySide applications Feb 07, 2024 5 - Production/Stable pytest
|
:pypi:`pytest-qt` pytest support for PyQt and PySide applications Feb 07, 2024 5 - Production/Stable pytest
|
||||||
|
@ -1086,7 +1088,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A
|
:pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A
|
||||||
:pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Dec 06, 2023 4 - Beta pytest>=3.5.0
|
:pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Dec 06, 2023 4 - Beta pytest>=3.5.0
|
||||||
:pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A
|
:pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A
|
||||||
:pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Jun 05, 2024 5 - Production/Stable pytest>=6.2
|
:pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Jun 19, 2024 5 - Production/Stable pytest>=6.2
|
||||||
:pypi:`pytest-redislite` Pytest plugin for testing code using Redis Apr 05, 2022 4 - Beta pytest
|
:pypi:`pytest-redislite` Pytest plugin for testing code using Redis Apr 05, 2022 4 - Beta pytest
|
||||||
:pypi:`pytest-redmine` Pytest plugin for redmine Mar 19, 2018 1 - Planning N/A
|
:pypi:`pytest-redmine` Pytest plugin for redmine Mar 19, 2018 1 - Planning N/A
|
||||||
:pypi:`pytest-ref` A plugin to store reference files to ease regression testing Nov 23, 2019 4 - Beta pytest (>=3.5.0)
|
:pypi:`pytest-ref` A plugin to store reference files to ease regression testing Nov 23, 2019 4 - Beta pytest (>=3.5.0)
|
||||||
|
@ -1198,7 +1200,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-sequence-markers` Pytest plugin for sequencing markers for execution of tests May 23, 2023 5 - Production/Stable N/A
|
:pypi:`pytest-sequence-markers` Pytest plugin for sequencing markers for execution of tests May 23, 2023 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-server-fixtures` Extensible server fixures for py.test Dec 19, 2023 5 - Production/Stable pytest
|
:pypi:`pytest-server-fixtures` Extensible server fixures for py.test Dec 19, 2023 5 - Production/Stable pytest
|
||||||
:pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. May 09, 2022 4 - Beta N/A
|
:pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. May 09, 2022 4 - Beta N/A
|
||||||
:pypi:`pytest-servers` pytest servers May 09, 2024 3 - Alpha pytest>=6.2
|
:pypi:`pytest-servers` pytest servers Jun 17, 2024 3 - Alpha pytest>=6.2
|
||||||
:pypi:`pytest-service` May 11, 2024 5 - Production/Stable pytest>=6.0.0
|
:pypi:`pytest-service` May 11, 2024 5 - Production/Stable pytest>=6.0.0
|
||||||
:pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A
|
:pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A
|
||||||
:pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest
|
:pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest
|
||||||
|
@ -1261,13 +1263,13 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-spiratest` Exports unit tests as test runs in Spira (SpiraTest/Team/Plan) Jan 01, 2024 N/A N/A
|
:pypi:`pytest-spiratest` Exports unit tests as test runs in Spira (SpiraTest/Team/Plan) Jan 01, 2024 N/A N/A
|
||||||
:pypi:`pytest-splinter` Splinter plugin for pytest testing framework Sep 09, 2022 6 - Mature pytest (>=3.0.0)
|
:pypi:`pytest-splinter` Splinter plugin for pytest testing framework Sep 09, 2022 6 - Mature pytest (>=3.0.0)
|
||||||
:pypi:`pytest-splinter4` Pytest plugin for the splinter automation library Feb 01, 2024 6 - Mature pytest >=8.0.0
|
:pypi:`pytest-splinter4` Pytest plugin for the splinter automation library Feb 01, 2024 6 - Mature pytest >=8.0.0
|
||||||
:pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Jan 29, 2024 4 - Beta pytest (>=5,<9)
|
:pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Jun 19, 2024 4 - Beta pytest<9,>=5
|
||||||
:pypi:`pytest-split-ext` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Sep 23, 2023 4 - Beta pytest (>=5,<8)
|
:pypi:`pytest-split-ext` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Sep 23, 2023 4 - Beta pytest (>=5,<8)
|
||||||
:pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0)
|
:pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0)
|
||||||
:pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5)
|
:pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5)
|
||||||
:pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A
|
:pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A
|
||||||
:pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Jun 03, 2024 N/A pytest<8,>5.4.0
|
:pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Jun 03, 2024 N/A pytest<8,>5.4.0
|
||||||
:pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Jun 05, 2024 N/A N/A
|
:pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Jun 14, 2024 N/A N/A
|
||||||
:pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0)
|
:pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0)
|
||||||
:pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A
|
:pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A
|
||||||
:pypi:`pytest-sqlalchemy` pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A
|
:pypi:`pytest-sqlalchemy` pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A
|
||||||
|
@ -1280,7 +1282,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-ssh` pytest plugin for ssh command run May 27, 2019 N/A pytest
|
:pypi:`pytest-ssh` pytest plugin for ssh command run May 27, 2019 N/A pytest
|
||||||
:pypi:`pytest-start-from` Start pytest run from a given point Apr 11, 2016 N/A N/A
|
:pypi:`pytest-start-from` Start pytest run from a given point Apr 11, 2016 N/A N/A
|
||||||
:pypi:`pytest-star-track-issue` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A
|
:pypi:`pytest-star-track-issue` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A
|
||||||
:pypi:`pytest-static` pytest-static Jan 15, 2024 1 - Planning pytest (>=7.4.3,<8.0.0)
|
:pypi:`pytest-static` pytest-static Jun 20, 2024 1 - Planning pytest<8.0.0,>=7.4.3
|
||||||
:pypi:`pytest-statsd` pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0)
|
:pypi:`pytest-statsd` pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0)
|
||||||
:pypi:`pytest-stepfunctions` A small description May 08, 2021 4 - Beta pytest
|
:pypi:`pytest-stepfunctions` A small description May 08, 2021 4 - Beta pytest
|
||||||
:pypi:`pytest-steps` Create step-wise / incremental tests in pytest. Sep 23, 2021 5 - Production/Stable N/A
|
:pypi:`pytest-steps` Create step-wise / incremental tests in pytest. Sep 23, 2021 5 - Production/Stable N/A
|
||||||
|
@ -1289,7 +1291,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A
|
:pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A
|
||||||
:pypi:`pytest-store` Pytest plugin to store values from test runs Nov 16, 2023 3 - Alpha pytest (>=7.0.0)
|
:pypi:`pytest-store` Pytest plugin to store values from test runs Nov 16, 2023 3 - Alpha pytest (>=7.0.0)
|
||||||
:pypi:`pytest-stress` A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0)
|
:pypi:`pytest-stress` A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0)
|
||||||
:pypi:`pytest-structlog` Structured logging assertions Jun 08, 2024 N/A pytest
|
:pypi:`pytest-structlog` Structured logging assertions Jun 09, 2024 N/A pytest
|
||||||
:pypi:`pytest-structmpd` provide structured temporary directory Oct 17, 2018 N/A N/A
|
:pypi:`pytest-structmpd` provide structured temporary directory Oct 17, 2018 N/A N/A
|
||||||
:pypi:`pytest-stub` Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A
|
:pypi:`pytest-stub` Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-stubprocess` Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0)
|
:pypi:`pytest-stubprocess` Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0)
|
||||||
|
@ -1461,7 +1463,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A
|
:pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A
|
||||||
:pypi:`pytest-web3-data` A pytest plugin to fetch test data from IPFS HTTP gateways during pytest execution. Oct 04, 2023 4 - Beta pytest
|
:pypi:`pytest-web3-data` A pytest plugin to fetch test data from IPFS HTTP gateways during pytest execution. Oct 04, 2023 4 - Beta pytest
|
||||||
:pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest
|
:pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest
|
||||||
:pypi:`pytest-webtest-extras` Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources. Nov 13, 2023 N/A pytest >= 7.0.0
|
:pypi:`pytest-webtest-extras` Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources. Jun 08, 2024 N/A pytest>=7.0.0
|
||||||
:pypi:`pytest-wetest` Welian API Automation test framework pytest plugin Nov 10, 2018 4 - Beta N/A
|
:pypi:`pytest-wetest` Welian API Automation test framework pytest plugin Nov 10, 2018 4 - Beta N/A
|
||||||
:pypi:`pytest-when` Utility which makes mocking more readable and controllable May 28, 2024 N/A pytest>=7.3.1
|
:pypi:`pytest-when` Utility which makes mocking more readable and controllable May 28, 2024 N/A pytest>=7.3.1
|
||||||
:pypi:`pytest-whirlwind` Testing Tornado. Jun 12, 2020 N/A N/A
|
:pypi:`pytest-whirlwind` Testing Tornado. Jun 12, 2020 N/A N/A
|
||||||
|
@ -1499,6 +1501,7 @@ This list contains 1476 plugins.
|
||||||
:pypi:`pytest-yapf3` Validate your Python file format with yapf Mar 29, 2023 5 - Production/Stable pytest (>=7)
|
:pypi:`pytest-yapf3` Validate your Python file format with yapf Mar 29, 2023 5 - Production/Stable pytest (>=7)
|
||||||
:pypi:`pytest-yield` PyTest plugin to run tests concurrently, each \`yield\` switch context to other one Jan 23, 2019 N/A N/A
|
:pypi:`pytest-yield` PyTest plugin to run tests concurrently, each \`yield\` switch context to other one Jan 23, 2019 N/A N/A
|
||||||
:pypi:`pytest-yls` Pytest plugin to test the YLS as a whole. Mar 30, 2024 N/A pytest<8.0.0,>=7.2.2
|
:pypi:`pytest-yls` Pytest plugin to test the YLS as a whole. Mar 30, 2024 N/A pytest<8.0.0,>=7.2.2
|
||||||
|
:pypi:`pytest-youqu-playwright` pytest-youqu-playwright Jun 12, 2024 N/A pytest
|
||||||
:pypi:`pytest-yuk` Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. Mar 26, 2021 N/A pytest>=5.0.0
|
:pypi:`pytest-yuk` Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. Mar 26, 2021 N/A pytest>=5.0.0
|
||||||
:pypi:`pytest-zafira` A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1)
|
:pypi:`pytest-zafira` A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1)
|
||||||
:pypi:`pytest-zap` OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A
|
:pypi:`pytest-zap` OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A
|
||||||
|
@ -1879,6 +1882,13 @@ This list contains 1476 plugins.
|
||||||
|
|
||||||
apistellar plugin for pytest.
|
apistellar plugin for pytest.
|
||||||
|
|
||||||
|
:pypi:`pytest-apiver`
|
||||||
|
*last release*: Jun 21, 2024,
|
||||||
|
*status*: N/A,
|
||||||
|
*requires*: pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
:pypi:`pytest-appengine`
|
:pypi:`pytest-appengine`
|
||||||
*last release*: Feb 27, 2017,
|
*last release*: Feb 27, 2017,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
|
@ -1977,6 +1987,13 @@ This list contains 1476 plugins.
|
||||||
|
|
||||||
Useful assertion utilities for use with pytest
|
Useful assertion utilities for use with pytest
|
||||||
|
|
||||||
|
:pypi:`pytest-assist`
|
||||||
|
*last release*: Jun 22, 2024,
|
||||||
|
*status*: N/A,
|
||||||
|
*requires*: pytest
|
||||||
|
|
||||||
|
load testing library
|
||||||
|
|
||||||
:pypi:`pytest-assume`
|
:pypi:`pytest-assume`
|
||||||
*last release*: Jun 24, 2021,
|
*last release*: Jun 24, 2021,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
|
@ -2153,7 +2170,7 @@ This list contains 1476 plugins.
|
||||||
This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2.
|
This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2.
|
||||||
|
|
||||||
:pypi:`pytest-aux`
|
:pypi:`pytest-aux`
|
||||||
*last release*: May 31, 2024,
|
*last release*: Jun 18, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: N/A
|
*requires*: N/A
|
||||||
|
|
||||||
|
@ -2328,7 +2345,7 @@ This list contains 1476 plugins.
|
||||||
Pytest plugin to run your tests with beartype checking enabled.
|
Pytest plugin to run your tests with beartype checking enabled.
|
||||||
|
|
||||||
:pypi:`pytest-bec-e2e`
|
:pypi:`pytest-bec-e2e`
|
||||||
*last release*: Jun 07, 2024,
|
*last release*: Jun 21, 2024,
|
||||||
*status*: 3 - Alpha,
|
*status*: 3 - Alpha,
|
||||||
*requires*: pytest
|
*requires*: pytest
|
||||||
|
|
||||||
|
@ -2398,7 +2415,7 @@ This list contains 1476 plugins.
|
||||||
Provides a mock fixture for python bigquery client
|
Provides a mock fixture for python bigquery client
|
||||||
|
|
||||||
:pypi:`pytest-bisect-tests`
|
:pypi:`pytest-bisect-tests`
|
||||||
*last release*: Mar 25, 2024,
|
*last release*: Jun 09, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: N/A
|
*requires*: N/A
|
||||||
|
|
||||||
|
@ -2832,7 +2849,7 @@ This list contains 1476 plugins.
|
||||||
Check links in files
|
Check links in files
|
||||||
|
|
||||||
:pypi:`pytest-checklist`
|
:pypi:`pytest-checklist`
|
||||||
*last release*: Mar 12, 2024,
|
*last release*: Jun 10, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: N/A
|
*requires*: N/A
|
||||||
|
|
||||||
|
@ -2930,7 +2947,7 @@ This list contains 1476 plugins.
|
||||||
Easy quality control for CLDF datasets using pytest
|
Easy quality control for CLDF datasets using pytest
|
||||||
|
|
||||||
:pypi:`pytest-cleanslate`
|
:pypi:`pytest-cleanslate`
|
||||||
*last release*: May 30, 2024,
|
*last release*: Jun 17, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest
|
*requires*: pytest
|
||||||
|
|
||||||
|
@ -3104,13 +3121,6 @@ This list contains 1476 plugins.
|
||||||
|
|
||||||
Get executed interface information in pytest interface automation framework
|
Get executed interface information in pytest interface automation framework
|
||||||
|
|
||||||
:pypi:`pytest-collect-jmeter-report-tests`
|
|
||||||
*last release*: May 20, 2024,
|
|
||||||
*status*: 4 - Beta,
|
|
||||||
*requires*: pytest>=7.2.1
|
|
||||||
|
|
||||||
A simple plugin to use with pytest
|
|
||||||
|
|
||||||
:pypi:`pytest-collector`
|
:pypi:`pytest-collector`
|
||||||
*last release*: Aug 02, 2022,
|
*last release*: Aug 02, 2022,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
|
@ -3140,7 +3150,7 @@ This list contains 1476 plugins.
|
||||||
An interactive GUI test runner for PyTest
|
An interactive GUI test runner for PyTest
|
||||||
|
|
||||||
:pypi:`pytest-common-subject`
|
:pypi:`pytest-common-subject`
|
||||||
*last release*: Jun 03, 2024,
|
*last release*: Jun 12, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest<9,>=3.6
|
*requires*: pytest<9,>=3.6
|
||||||
|
|
||||||
|
@ -3413,11 +3423,11 @@ This list contains 1476 plugins.
|
||||||
Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report
|
Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report
|
||||||
|
|
||||||
:pypi:`pytest-custom-outputs`
|
:pypi:`pytest-custom-outputs`
|
||||||
*last release*: May 30, 2024,
|
*last release*: Jun 17, 2024,
|
||||||
*status*: 4 - Beta,
|
*status*: 4 - Beta,
|
||||||
*requires*: pytest>=6.2.0
|
*requires*: pytest>=6.2.0
|
||||||
|
|
||||||
A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail
|
A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures.
|
||||||
|
|
||||||
:pypi:`pytest-custom-report`
|
:pypi:`pytest-custom-report`
|
||||||
*last release*: Jan 30, 2019,
|
*last release*: Jan 30, 2019,
|
||||||
|
@ -3476,7 +3486,7 @@ This list contains 1476 plugins.
|
||||||
Useful functions for managing data for pytest fixtures
|
Useful functions for managing data for pytest fixtures
|
||||||
|
|
||||||
:pypi:`pytest-databases`
|
:pypi:`pytest-databases`
|
||||||
*last release*: May 25, 2024,
|
*last release*: Jun 11, 2024,
|
||||||
*status*: 4 - Beta,
|
*status*: 4 - Beta,
|
||||||
*requires*: pytest
|
*requires*: pytest
|
||||||
|
|
||||||
|
@ -3840,7 +3850,7 @@ This list contains 1476 plugins.
|
||||||
Pytest plugin to record discovered tests in a file
|
Pytest plugin to record discovered tests in a file
|
||||||
|
|
||||||
:pypi:`pytest-ditto`
|
:pypi:`pytest-ditto`
|
||||||
*last release*: May 29, 2024,
|
*last release*: Jun 09, 2024,
|
||||||
*status*: 4 - Beta,
|
*status*: 4 - Beta,
|
||||||
*requires*: pytest>=3.5.0
|
*requires*: pytest>=3.5.0
|
||||||
|
|
||||||
|
@ -3853,6 +3863,13 @@ This list contains 1476 plugins.
|
||||||
|
|
||||||
pytest-ditto plugin for pandas snapshots.
|
pytest-ditto plugin for pandas snapshots.
|
||||||
|
|
||||||
|
:pypi:`pytest-ditto-pyarrow`
|
||||||
|
*last release*: Jun 09, 2024,
|
||||||
|
*status*: 4 - Beta,
|
||||||
|
*requires*: pytest>=3.5.0
|
||||||
|
|
||||||
|
pytest-ditto plugin for pyarrow tables.
|
||||||
|
|
||||||
:pypi:`pytest-django`
|
:pypi:`pytest-django`
|
||||||
*last release*: Jan 30, 2024,
|
*last release*: Jan 30, 2024,
|
||||||
*status*: 5 - Production/Stable,
|
*status*: 5 - Production/Stable,
|
||||||
|
@ -3896,7 +3913,7 @@ This list contains 1476 plugins.
|
||||||
A pytest plugin for running django in class-scoped fixtures
|
A pytest plugin for running django in class-scoped fixtures
|
||||||
|
|
||||||
:pypi:`pytest-django-docker-pg`
|
:pypi:`pytest-django-docker-pg`
|
||||||
*last release*: May 21, 2024,
|
*last release*: Jun 13, 2024,
|
||||||
*status*: 5 - Production/Stable,
|
*status*: 5 - Production/Stable,
|
||||||
*requires*: pytest<9.0.0,>=7.0.0
|
*requires*: pytest<9.0.0,>=7.0.0
|
||||||
|
|
||||||
|
@ -4379,7 +4396,7 @@ This list contains 1476 plugins.
|
||||||
pytest plugin with mechanisms for echoing environment variables, package version and generic attributes
|
pytest plugin with mechanisms for echoing environment variables, package version and generic attributes
|
||||||
|
|
||||||
:pypi:`pytest-edit`
|
:pypi:`pytest-edit`
|
||||||
*last release*: Jun 07, 2024,
|
*last release*: Jun 09, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest
|
*requires*: pytest
|
||||||
|
|
||||||
|
@ -4687,9 +4704,9 @@ This list contains 1476 plugins.
|
||||||
|
|
||||||
|
|
||||||
:pypi:`pytest-excel`
|
:pypi:`pytest-excel`
|
||||||
*last release*: Sep 14, 2023,
|
*last release*: Jun 18, 2024,
|
||||||
*status*: 5 - Production/Stable,
|
*status*: 5 - Production/Stable,
|
||||||
*requires*: N/A
|
*requires*: pytest>3.6
|
||||||
|
|
||||||
pytest plugin for generating excel reports
|
pytest plugin for generating excel reports
|
||||||
|
|
||||||
|
@ -5730,7 +5747,7 @@ This list contains 1476 plugins.
|
||||||
A pytest plugin for use with homeassistant custom components.
|
A pytest plugin for use with homeassistant custom components.
|
||||||
|
|
||||||
:pypi:`pytest-homeassistant-custom-component`
|
:pypi:`pytest-homeassistant-custom-component`
|
||||||
*last release*: Jun 08, 2024,
|
*last release*: Jun 22, 2024,
|
||||||
*status*: 3 - Alpha,
|
*status*: 3 - Alpha,
|
||||||
*requires*: pytest==8.2.0
|
*requires*: pytest==8.2.0
|
||||||
|
|
||||||
|
@ -6023,6 +6040,13 @@ This list contains 1476 plugins.
|
||||||
|
|
||||||
an incremental test runner (pytest plugin)
|
an incremental test runner (pytest plugin)
|
||||||
|
|
||||||
|
:pypi:`pytest-infinity`
|
||||||
|
*last release*: Jun 09, 2024,
|
||||||
|
*status*: N/A,
|
||||||
|
*requires*: pytest<9.0.0,>=8.0.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
:pypi:`pytest-influxdb`
|
:pypi:`pytest-influxdb`
|
||||||
*last release*: Apr 20, 2021,
|
*last release*: Apr 20, 2021,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
|
@ -6339,7 +6363,7 @@ This list contains 1476 plugins.
|
||||||
Generate JSON test reports
|
Generate JSON test reports
|
||||||
|
|
||||||
:pypi:`pytest-json-ctrf`
|
:pypi:`pytest-json-ctrf`
|
||||||
*last release*: May 21, 2024,
|
*last release*: Jun 15, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest>6.0.0
|
*requires*: pytest>6.0.0
|
||||||
|
|
||||||
|
@ -6380,13 +6404,6 @@ This list contains 1476 plugins.
|
||||||
|
|
||||||
A pytest plugin to perform JSONSchema validations
|
A pytest plugin to perform JSONSchema validations
|
||||||
|
|
||||||
:pypi:`pytest-jtl-collector`
|
|
||||||
*last release*: May 20, 2024,
|
|
||||||
*status*: 4 - Beta,
|
|
||||||
*requires*: pytest>=7.2.1
|
|
||||||
|
|
||||||
A simple plugin to use with pytest
|
|
||||||
|
|
||||||
:pypi:`pytest-jtr`
|
:pypi:`pytest-jtr`
|
||||||
*last release*: Jun 04, 2024,
|
*last release*: Jun 04, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
|
@ -7046,7 +7063,7 @@ This list contains 1476 plugins.
|
||||||
Custom metrics report for pytest
|
Custom metrics report for pytest
|
||||||
|
|
||||||
:pypi:`pytest-mh`
|
:pypi:`pytest-mh`
|
||||||
*last release*: May 28, 2024,
|
*last release*: Jun 13, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest
|
*requires*: pytest
|
||||||
|
|
||||||
|
@ -7151,7 +7168,7 @@ This list contains 1476 plugins.
|
||||||
An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database.
|
An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database.
|
||||||
|
|
||||||
:pypi:`pytest-mock-resources`
|
:pypi:`pytest-mock-resources`
|
||||||
*last release*: Apr 11, 2024,
|
*last release*: Jun 20, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest>=1.0
|
*requires*: pytest>=1.0
|
||||||
|
|
||||||
|
@ -7417,7 +7434,7 @@ This list contains 1476 plugins.
|
||||||
pytest-neo is a plugin for pytest that shows tests like screen of Matrix.
|
pytest-neo is a plugin for pytest that shows tests like screen of Matrix.
|
||||||
|
|
||||||
:pypi:`pytest-neos`
|
:pypi:`pytest-neos`
|
||||||
*last release*: May 01, 2024,
|
*last release*: Jun 11, 2024,
|
||||||
*status*: 1 - Planning,
|
*status*: 1 - Planning,
|
||||||
*requires*: N/A
|
*requires*: N/A
|
||||||
|
|
||||||
|
@ -7480,7 +7497,7 @@ This list contains 1476 plugins.
|
||||||
pytest ngs fixtures
|
pytest ngs fixtures
|
||||||
|
|
||||||
:pypi:`pytest-nhsd-apim`
|
:pypi:`pytest-nhsd-apim`
|
||||||
*last release*: May 20, 2024,
|
*last release*: Jun 18, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest<9.0.0,>=8.2.0
|
*requires*: pytest<9.0.0,>=8.2.0
|
||||||
|
|
||||||
|
@ -7928,7 +7945,7 @@ This list contains 1476 plugins.
|
||||||
A contextmanager pytest fixture for handling multiple mock patches
|
A contextmanager pytest fixture for handling multiple mock patches
|
||||||
|
|
||||||
:pypi:`pytest-patterns`
|
:pypi:`pytest-patterns`
|
||||||
*last release*: Nov 17, 2023,
|
*last release*: Jun 14, 2024,
|
||||||
*status*: 4 - Beta,
|
*status*: 4 - Beta,
|
||||||
*requires*: N/A
|
*requires*: N/A
|
||||||
|
|
||||||
|
@ -8390,9 +8407,9 @@ This list contains 1476 plugins.
|
||||||
Profiling plugin for py.test
|
Profiling plugin for py.test
|
||||||
|
|
||||||
:pypi:`pytest-progress`
|
:pypi:`pytest-progress`
|
||||||
*last release*: Jan 31, 2022,
|
*last release*: Jun 18, 2024,
|
||||||
*status*: 5 - Production/Stable,
|
*status*: 5 - Production/Stable,
|
||||||
*requires*: N/A
|
*requires*: pytest>=2.7
|
||||||
|
|
||||||
pytest plugin for instant test progress status
|
pytest plugin for instant test progress status
|
||||||
|
|
||||||
|
@ -8537,7 +8554,7 @@ This list contains 1476 plugins.
|
||||||
Record PyMySQL queries and mock with the stored data.
|
Record PyMySQL queries and mock with the stored data.
|
||||||
|
|
||||||
:pypi:`pytest-pyodide`
|
:pypi:`pytest-pyodide`
|
||||||
*last release*: Apr 30, 2024,
|
*last release*: Jun 12, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest
|
*requires*: pytest
|
||||||
|
|
||||||
|
@ -8691,7 +8708,7 @@ This list contains 1476 plugins.
|
||||||
Pytest plugin for uploading test results to your QA Touch Testrun.
|
Pytest plugin for uploading test results to your QA Touch Testrun.
|
||||||
|
|
||||||
:pypi:`pytest-qgis`
|
:pypi:`pytest-qgis`
|
||||||
*last release*: Nov 29, 2023,
|
*last release*: Jun 14, 2024,
|
||||||
*status*: 5 - Production/Stable,
|
*status*: 5 - Production/Stable,
|
||||||
*requires*: pytest>=6.0
|
*requires*: pytest>=6.0
|
||||||
|
|
||||||
|
@ -8880,7 +8897,7 @@ This list contains 1476 plugins.
|
||||||
Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal
|
Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal
|
||||||
|
|
||||||
:pypi:`pytest-redis`
|
:pypi:`pytest-redis`
|
||||||
*last release*: Jun 05, 2024,
|
*last release*: Jun 19, 2024,
|
||||||
*status*: 5 - Production/Stable,
|
*status*: 5 - Production/Stable,
|
||||||
*requires*: pytest>=6.2
|
*requires*: pytest>=6.2
|
||||||
|
|
||||||
|
@ -9664,7 +9681,7 @@ This list contains 1476 plugins.
|
||||||
Automatically mocks resources from serverless.yml in pytest using moto.
|
Automatically mocks resources from serverless.yml in pytest using moto.
|
||||||
|
|
||||||
:pypi:`pytest-servers`
|
:pypi:`pytest-servers`
|
||||||
*last release*: May 09, 2024,
|
*last release*: Jun 17, 2024,
|
||||||
*status*: 3 - Alpha,
|
*status*: 3 - Alpha,
|
||||||
*requires*: pytest>=6.2
|
*requires*: pytest>=6.2
|
||||||
|
|
||||||
|
@ -10105,9 +10122,9 @@ This list contains 1476 plugins.
|
||||||
Pytest plugin for the splinter automation library
|
Pytest plugin for the splinter automation library
|
||||||
|
|
||||||
:pypi:`pytest-split`
|
:pypi:`pytest-split`
|
||||||
*last release*: Jan 29, 2024,
|
*last release*: Jun 19, 2024,
|
||||||
*status*: 4 - Beta,
|
*status*: 4 - Beta,
|
||||||
*requires*: pytest (>=5,<9)
|
*requires*: pytest<9,>=5
|
||||||
|
|
||||||
Pytest plugin which splits the test suite to equally sized sub suites based on test execution time.
|
Pytest plugin which splits the test suite to equally sized sub suites based on test execution time.
|
||||||
|
|
||||||
|
@ -10147,7 +10164,7 @@ This list contains 1476 plugins.
|
||||||
A Dynamic test tool for Splunk Apps and Add-ons
|
A Dynamic test tool for Splunk Apps and Add-ons
|
||||||
|
|
||||||
:pypi:`pytest-splunk-addon-ui-smartx`
|
:pypi:`pytest-splunk-addon-ui-smartx`
|
||||||
*last release*: Jun 05, 2024,
|
*last release*: Jun 14, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: N/A
|
*requires*: N/A
|
||||||
|
|
||||||
|
@ -10238,9 +10255,9 @@ This list contains 1476 plugins.
|
||||||
A package to prevent Dependency Confusion attacks against Yandex.
|
A package to prevent Dependency Confusion attacks against Yandex.
|
||||||
|
|
||||||
:pypi:`pytest-static`
|
:pypi:`pytest-static`
|
||||||
*last release*: Jan 15, 2024,
|
*last release*: Jun 20, 2024,
|
||||||
*status*: 1 - Planning,
|
*status*: 1 - Planning,
|
||||||
*requires*: pytest (>=7.4.3,<8.0.0)
|
*requires*: pytest<8.0.0,>=7.4.3
|
||||||
|
|
||||||
pytest-static
|
pytest-static
|
||||||
|
|
||||||
|
@ -10301,7 +10318,7 @@ This list contains 1476 plugins.
|
||||||
A Pytest plugin that allows you to loop tests for a user defined amount of time.
|
A Pytest plugin that allows you to loop tests for a user defined amount of time.
|
||||||
|
|
||||||
:pypi:`pytest-structlog`
|
:pypi:`pytest-structlog`
|
||||||
*last release*: Jun 08, 2024,
|
*last release*: Jun 09, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest
|
*requires*: pytest
|
||||||
|
|
||||||
|
@ -11505,7 +11522,7 @@ This list contains 1476 plugins.
|
||||||
Selenium webdriver fixture for py.test
|
Selenium webdriver fixture for py.test
|
||||||
|
|
||||||
:pypi:`pytest-webtest-extras`
|
:pypi:`pytest-webtest-extras`
|
||||||
*last release*: Nov 13, 2023,
|
*last release*: Jun 08, 2024,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
*requires*: pytest>=7.0.0
|
*requires*: pytest>=7.0.0
|
||||||
|
|
||||||
|
@ -11770,6 +11787,13 @@ This list contains 1476 plugins.
|
||||||
|
|
||||||
Pytest plugin to test the YLS as a whole.
|
Pytest plugin to test the YLS as a whole.
|
||||||
|
|
||||||
|
:pypi:`pytest-youqu-playwright`
|
||||||
|
*last release*: Jun 12, 2024,
|
||||||
|
*status*: N/A,
|
||||||
|
*requires*: pytest
|
||||||
|
|
||||||
|
pytest-youqu-playwright
|
||||||
|
|
||||||
:pypi:`pytest-yuk`
|
:pypi:`pytest-yuk`
|
||||||
*last release*: Mar 26, 2021,
|
*last release*: Mar 26, 2021,
|
||||||
*status*: N/A,
|
*status*: N/A,
|
||||||
|
|
|
@ -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 setuptools plugins).
|
Bootstrapping hooks called for plugins registered early enough (internal and third-party plugins).
|
||||||
|
|
||||||
.. hook:: pytest_load_initial_conftests
|
.. hook:: pytest_load_initial_conftests
|
||||||
.. autofunction:: pytest_load_initial_conftests
|
.. autofunction:: pytest_load_initial_conftests
|
||||||
|
@ -1147,8 +1147,9 @@ 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 setuptools entrypoints. Only explicitly specified plugins will be
|
When set, disables plugin auto-loading through :std:doc:`entry point packaging
|
||||||
loaded.
|
metadata <packaging:guides/creating-and-discovering-plugins>`. Only explicitly
|
||||||
|
specified plugins will be loaded.
|
||||||
|
|
||||||
.. envvar:: PYTEST_PLUGINS
|
.. envvar:: PYTEST_PLUGINS
|
||||||
|
|
||||||
|
@ -1701,13 +1702,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 a
|
Additionally, ``pytest`` will attempt to intelligently identify and ignore
|
||||||
virtualenv by the presence of an activation script. Any directory deemed to
|
a virtualenv. Any directory deemed to be the root of a virtual environment
|
||||||
be the root of a virtual environment will not be considered during test
|
will not be considered during test collection unless
|
||||||
collection unless ``--collect-in-virtualenv`` is given. Note also that
|
``--collect-in-virtualenv`` is given. Note also that ``norecursedirs``
|
||||||
``norecursedirs`` takes precedence over ``--collect-in-virtualenv``; e.g. if
|
takes precedence over ``--collect-in-virtualenv``; e.g. if you intend to
|
||||||
you intend to run tests in a virtualenv with a base directory that matches
|
run tests in a virtualenv with a base directory that matches ``'.*'`` you
|
||||||
``'.*'`` you *must* override ``norecursedirs`` in addition to using the
|
*must* override ``norecursedirs`` in addition to using the
|
||||||
``--collect-in-virtualenv`` flag.
|
``--collect-in-virtualenv`` flag.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,3 +9,5 @@ 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,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
|
|
@ -98,6 +98,7 @@ lint.select = [
|
||||||
"D", # pydocstyle
|
"D", # pydocstyle
|
||||||
"E", # pycodestyle
|
"E", # pycodestyle
|
||||||
"F", # pyflakes
|
"F", # pyflakes
|
||||||
|
"FA100", # add future annotations
|
||||||
"I", # isort
|
"I", # isort
|
||||||
"PGH004", # pygrep-hooks - Use specific rule codes when using noqa
|
"PGH004", # pygrep-hooks - Use specific rule codes when using noqa
|
||||||
"PIE", # flake8-pie
|
"PIE", # flake8-pie
|
||||||
|
@ -155,6 +156,10 @@ lint.per-file-ignores."src/_pytest/_version.py" = [
|
||||||
lint.per-file-ignores."testing/python/approx.py" = [
|
lint.per-file-ignores."testing/python/approx.py" = [
|
||||||
"B015",
|
"B015",
|
||||||
]
|
]
|
||||||
|
lint.extend-safe-fixes = [
|
||||||
|
"UP006",
|
||||||
|
"UP007",
|
||||||
|
]
|
||||||
lint.isort.combine-as-imports = true
|
lint.isort.combine-as-imports = true
|
||||||
lint.isort.force-single-line = true
|
lint.isort.force-single-line = true
|
||||||
lint.isort.force-sort-within-sections = true
|
lint.isort.force-sort-within-sections = true
|
||||||
|
@ -164,9 +169,13 @@ lint.isort.known-local-folder = [
|
||||||
]
|
]
|
||||||
lint.isort.lines-after-imports = 2
|
lint.isort.lines-after-imports = 2
|
||||||
lint.isort.order-by-type = false
|
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
|
lint.pycodestyle.max-line-length = 120
|
||||||
lint.pydocstyle.convention = "pep257"
|
lint.pydocstyle.convention = "pep257"
|
||||||
|
lint.pyupgrade.keep-runtime-typing = false
|
||||||
|
|
||||||
[tool.pylint.main]
|
[tool.pylint.main]
|
||||||
# Maximum number of characters on a single line.
|
# Maximum number of characters on a single line.
|
||||||
|
@ -349,6 +358,9 @@ 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
|
||||||
|
@ -363,44 +375,74 @@ 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:
|
||||||
|
# NOTE: - there is no mechanism to override just the value of
|
||||||
|
# NOTE: `tool.towncrier.type.misc.showcontent`;
|
||||||
|
# NOTE: - and, we want to declare extra non-default types for
|
||||||
|
# NOTE: clarity and flexibility.
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
|
# When something public gets removed in a breaking way. Could be
|
||||||
|
# deprecated in an earlier release.
|
||||||
directory = "breaking"
|
directory = "breaking"
|
||||||
name = "Breaking Changes"
|
name = "Removals and backward incompatible breaking changes"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
|
# Declarations of future API removals and breaking changes in behavior.
|
||||||
directory = "deprecation"
|
directory = "deprecation"
|
||||||
name = "Deprecations"
|
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 = "feature"
|
directory = "feature"
|
||||||
name = "Features"
|
name = "New features"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
|
# New behaviors in existing features.
|
||||||
directory = "improvement"
|
directory = "improvement"
|
||||||
name = "Improvements"
|
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
|
||||||
|
# in the release to match pre-agreed expectations.
|
||||||
directory = "bugfix"
|
directory = "bugfix"
|
||||||
name = "Bug Fixes"
|
name = "Bug fixes"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
|
# Updates regarding bundling dependencies.
|
||||||
directory = "vendor"
|
directory = "vendor"
|
||||||
name = "Vendored Libraries"
|
name = "Vendored libraries"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
|
# Notable updates to the documentation structure or build process.
|
||||||
directory = "doc"
|
directory = "doc"
|
||||||
name = "Improved Documentation"
|
name = "Improved documentation"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
directory = "trivial"
|
# Notes for downstreams about unobvious side effects and tooling. Changes
|
||||||
name = "Trivial/Internal 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
|
showcontent = true
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
|
|
|
@ -9,6 +9,8 @@ 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,6 +14,8 @@ 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,6 +1,8 @@
|
||||||
# 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
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# 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,4 +1,6 @@
|
||||||
# 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,3 +1,6 @@
|
||||||
|
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: Optional[FastFilesCompleter] = FastFilesCompleter()
|
filescompleter: FastFilesCompleter | None = 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,5 +1,7 @@
|
||||||
"""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,4 +1,6 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -17,7 +19,6 @@ 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
|
||||||
|
@ -25,11 +26,9 @@ 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
|
||||||
|
@ -57,6 +56,8 @@ if sys.version_info < (3, 11):
|
||||||
|
|
||||||
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:
|
||||||
"""Wrapper around Python code objects."""
|
"""Wrapper around Python code objects."""
|
||||||
|
@ -67,7 +68,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):
|
||||||
|
@ -85,7 +86,7 @@ class Code:
|
||||||
return self.raw.co_name
|
return self.raw.co_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Union[Path, str]:
|
def path(self) -> 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:
|
||||||
|
@ -102,17 +103,17 @@ class Code:
|
||||||
return self.raw.co_filename
|
return self.raw.co_filename
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fullsource(self) -> Optional["Source"]:
|
def fullsource(self) -> Source | None:
|
||||||
"""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
|
||||||
|
@ -141,11 +142,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
|
||||||
|
@ -153,7 +154,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("")
|
||||||
|
@ -197,14 +198,14 @@ class TracebackEntry:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
rawentry: TracebackType,
|
rawentry: TracebackType,
|
||||||
repr_style: Optional['Literal["short", "long"]'] = None,
|
repr_style: Literal["short", "long"] | None = 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: Optional['Literal["short", "long"]']
|
self, repr_style: Literal["short", "long"] | None
|
||||||
) -> "TracebackEntry":
|
) -> TracebackEntry:
|
||||||
return TracebackEntry(self._rawentry, repr_style)
|
return TracebackEntry(self._rawentry, repr_style)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -223,19 +224,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) -> Union[Path, str]:
|
def path(self) -> 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
|
||||||
|
|
||||||
|
@ -243,8 +244,8 @@ class TracebackEntry:
|
||||||
return self.frame.code.firstlineno
|
return self.frame.code.firstlineno
|
||||||
|
|
||||||
def getsource(
|
def getsource(
|
||||||
self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None
|
self, astcache: dict[str | Path, ast.AST] | None = None
|
||||||
) -> Optional["Source"]:
|
) -> Source | None:
|
||||||
"""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
|
||||||
|
@ -270,7 +271,7 @@ class TracebackEntry:
|
||||||
|
|
||||||
source = property(getsource)
|
source = property(getsource)
|
||||||
|
|
||||||
def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool:
|
def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> 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.
|
||||||
|
|
||||||
|
@ -279,9 +280,7 @@ class TracebackEntry:
|
||||||
|
|
||||||
Mostly for internal use.
|
Mostly for internal use.
|
||||||
"""
|
"""
|
||||||
tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = (
|
tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False
|
||||||
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
|
||||||
|
@ -326,13 +325,13 @@ class Traceback(List[TracebackEntry]):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tb: Union[TracebackType, Iterable[TracebackEntry]],
|
tb: 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_: Optional[TracebackType] = cur
|
cur_: TracebackType | None = 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
|
||||||
|
@ -343,11 +342,11 @@ class Traceback(List[TracebackEntry]):
|
||||||
|
|
||||||
def cut(
|
def cut(
|
||||||
self,
|
self,
|
||||||
path: Optional[Union["os.PathLike[str]", str]] = None,
|
path: os.PathLike[str] | str | None = None,
|
||||||
lineno: Optional[int] = None,
|
lineno: int | None = None,
|
||||||
firstlineno: Optional[int] = None,
|
firstlineno: int | None = None,
|
||||||
excludepath: Optional["os.PathLike[str]"] = None,
|
excludepath: os.PathLike[str] | None = 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
|
||||||
|
@ -378,14 +377,12 @@ 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__(
|
def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback:
|
||||||
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:
|
||||||
|
@ -393,12 +390,9 @@ class Traceback(List[TracebackEntry]):
|
||||||
|
|
||||||
def filter(
|
def filter(
|
||||||
self,
|
self,
|
||||||
excinfo_or_fn: Union[
|
excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool],
|
||||||
"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
|
||||||
|
@ -414,10 +408,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) -> Optional[int]:
|
def recursionindex(self) -> int | None:
|
||||||
"""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
|
||||||
|
@ -445,15 +439,15 @@ class ExceptionInfo(Generic[E]):
|
||||||
|
|
||||||
_assert_start_repr: ClassVar = "AssertionError('assert "
|
_assert_start_repr: ClassVar = "AssertionError('assert "
|
||||||
|
|
||||||
_excinfo: Optional[Tuple[Type["E"], "E", TracebackType]]
|
_excinfo: tuple[type[E], E, TracebackType] | None
|
||||||
_striptext: str
|
_striptext: str
|
||||||
_traceback: Optional[Traceback]
|
_traceback: Traceback | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
excinfo: Optional[Tuple[Type["E"], "E", TracebackType]],
|
excinfo: tuple[type[E], E, TracebackType] | None,
|
||||||
striptext: str = "",
|
striptext: str = "",
|
||||||
traceback: Optional[Traceback] = None,
|
traceback: Traceback | None = None,
|
||||||
*,
|
*,
|
||||||
_ispytest: bool = False,
|
_ispytest: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -469,8 +463,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: Optional[str] = None,
|
exprinfo: str | None = 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,
|
||||||
|
@ -495,9 +489,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: Optional[str] = None,
|
exprinfo: str | None = 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):
|
||||||
|
@ -510,9 +504,7 @@ class ExceptionInfo(Generic[E]):
|
||||||
return cls(exc_info, _striptext, _ispytest=True)
|
return cls(exc_info, _striptext, _ispytest=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_current(
|
def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]:
|
||||||
cls, exprinfo: Optional[str] = None
|
|
||||||
) -> "ExceptionInfo[BaseException]":
|
|
||||||
"""Return an ExceptionInfo matching the current traceback.
|
"""Return an ExceptionInfo matching the current traceback.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
@ -532,17 +524,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
|
||||||
|
@ -605,16 +597,14 @@ class ExceptionInfo(Generic[E]):
|
||||||
text = text[len(self._striptext) :]
|
text = text[len(self._striptext) :]
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def errisinstance(
|
def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool:
|
||||||
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) -> Optional["ReprFileLocation"]:
|
def _getreprcrash(self) -> ReprFileLocation | None:
|
||||||
# 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):
|
||||||
|
@ -630,14 +620,13 @@ class ExceptionInfo(Generic[E]):
|
||||||
showlocals: bool = False,
|
showlocals: bool = False,
|
||||||
style: TracebackStyle = "long",
|
style: TracebackStyle = "long",
|
||||||
abspath: bool = False,
|
abspath: bool = False,
|
||||||
tbfilter: Union[
|
tbfilter: bool
|
||||||
bool, Callable[["ExceptionInfo[BaseException]"], Traceback]
|
| Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True,
|
||||||
] = True,
|
|
||||||
funcargs: bool = False,
|
funcargs: bool = False,
|
||||||
truncate_locals: bool = True,
|
truncate_locals: bool = True,
|
||||||
truncate_args: bool = True,
|
truncate_args: bool = True,
|
||||||
chain: bool = True,
|
chain: bool = True,
|
||||||
) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
|
) -> ReprExceptionInfo | ExceptionChainRepr:
|
||||||
"""Return str()able representation of this exception info.
|
"""Return str()able representation of this exception info.
|
||||||
|
|
||||||
:param bool showlocals:
|
:param bool showlocals:
|
||||||
|
@ -719,7 +708,7 @@ class ExceptionInfo(Generic[E]):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]":
|
def match(self, regexp: 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`.
|
||||||
|
|
||||||
|
@ -737,9 +726,9 @@ class ExceptionInfo(Generic[E]):
|
||||||
def _group_contains(
|
def _group_contains(
|
||||||
self,
|
self,
|
||||||
exc_group: BaseExceptionGroup[BaseException],
|
exc_group: BaseExceptionGroup[BaseException],
|
||||||
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
expected_exception: EXCEPTION_OR_MORE,
|
||||||
match: Union[str, Pattern[str], None],
|
match: str | Pattern[str] | None,
|
||||||
target_depth: Optional[int] = None,
|
target_depth: int | None = 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."""
|
||||||
|
@ -766,10 +755,10 @@ class ExceptionInfo(Generic[E]):
|
||||||
|
|
||||||
def group_contains(
|
def group_contains(
|
||||||
self,
|
self,
|
||||||
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
expected_exception: EXCEPTION_OR_MORE,
|
||||||
*,
|
*,
|
||||||
match: Union[str, Pattern[str], None] = None,
|
match: str | Pattern[str] | None = None,
|
||||||
depth: Optional[int] = None,
|
depth: int | None = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Check whether a captured exception group contains a matching exception.
|
"""Check whether a captured exception group contains a matching exception.
|
||||||
|
|
||||||
|
@ -811,16 +800,16 @@ class FormattedExcinfo:
|
||||||
showlocals: bool = False
|
showlocals: bool = False
|
||||||
style: TracebackStyle = "long"
|
style: TracebackStyle = "long"
|
||||||
abspath: bool = True
|
abspath: bool = True
|
||||||
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
|
tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True
|
||||||
funcargs: bool = False
|
funcargs: bool = False
|
||||||
truncate_locals: bool = True
|
truncate_locals: bool = True
|
||||||
truncate_args: bool = True
|
truncate_args: bool = True
|
||||||
chain: bool = True
|
chain: bool = True
|
||||||
astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field(
|
astcache: dict[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))
|
||||||
|
@ -835,13 +824,13 @@ 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) -> Optional["Source"]:
|
def _getentrysource(self, entry: TracebackEntry) -> Source | None:
|
||||||
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) -> Optional["ReprFuncArgs"]:
|
def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None:
|
||||||
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):
|
||||||
|
@ -855,11 +844,11 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
def get_source(
|
def get_source(
|
||||||
self,
|
self,
|
||||||
source: Optional["Source"],
|
source: Source | None,
|
||||||
line_index: int = -1,
|
line_index: int = -1,
|
||||||
excinfo: Optional[ExceptionInfo[BaseException]] = None,
|
excinfo: ExceptionInfo[BaseException] | None = 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:
|
||||||
|
@ -888,7 +877,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.
|
||||||
|
@ -900,7 +889,7 @@ class FormattedExcinfo:
|
||||||
failindent = indentstr
|
failindent = indentstr
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]:
|
def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None:
|
||||||
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] != "@"]
|
||||||
|
@ -928,10 +917,10 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
def repr_traceback_entry(
|
def repr_traceback_entry(
|
||||||
self,
|
self,
|
||||||
entry: Optional[TracebackEntry],
|
entry: TracebackEntry | None,
|
||||||
excinfo: Optional[ExceptionInfo[BaseException]] = None,
|
excinfo: ExceptionInfo[BaseException] | None = 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
|
||||||
|
@ -966,7 +955,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: Union[Path, str]) -> str:
|
def _makepath(self, path: 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)
|
||||||
|
@ -976,7 +965,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)
|
||||||
|
@ -1007,7 +996,7 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
def _truncate_recursive_traceback(
|
def _truncate_recursive_traceback(
|
||||||
self, traceback: Traceback
|
self, traceback: Traceback
|
||||||
) -> Tuple[Traceback, Optional[str]]:
|
) -> tuple[Traceback, str | None]:
|
||||||
"""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.
|
||||||
|
|
||||||
|
@ -1024,7 +1013,7 @@ class FormattedExcinfo:
|
||||||
recursionindex = traceback.recursionindex()
|
recursionindex = traceback.recursionindex()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
max_frames = 10
|
max_frames = 10
|
||||||
extraline: Optional[str] = (
|
extraline: str | None = (
|
||||||
"!!! 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"
|
||||||
|
@ -1042,16 +1031,12 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
return traceback, extraline
|
return traceback, extraline
|
||||||
|
|
||||||
def repr_excinfo(
|
def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr:
|
||||||
self, excinfo: ExceptionInfo[BaseException]
|
repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = []
|
||||||
) -> "ExceptionChainRepr":
|
e: BaseException | None = excinfo.value
|
||||||
repr_chain: List[
|
excinfo_: ExceptionInfo[BaseException] | None = excinfo
|
||||||
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))
|
||||||
|
|
||||||
|
@ -1060,7 +1045,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: Union[ReprTracebackNative, ReprTraceback] = (
|
reprtraceback: ReprTracebackNative | ReprTraceback = (
|
||||||
ReprTracebackNative(
|
ReprTracebackNative(
|
||||||
traceback.format_exception(
|
traceback.format_exception(
|
||||||
type(excinfo_.value),
|
type(excinfo_.value),
|
||||||
|
@ -1118,9 +1103,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: Optional["ReprFileLocation"]
|
reprcrash: ReprFileLocation | None
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1135,13 +1120,11 @@ class ExceptionRepr(TerminalRepr):
|
||||||
|
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ExceptionChainRepr(ExceptionRepr):
|
class ExceptionChainRepr(ExceptionRepr):
|
||||||
chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]]
|
chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
chain: Sequence[
|
chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]],
|
||||||
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.
|
||||||
|
@ -1162,8 +1145,8 @@ class ExceptionChainRepr(ExceptionRepr):
|
||||||
|
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ReprExceptionInfo(ExceptionRepr):
|
class ReprExceptionInfo(ExceptionRepr):
|
||||||
reprtraceback: "ReprTraceback"
|
reprtraceback: ReprTraceback
|
||||||
reprcrash: Optional["ReprFileLocation"]
|
reprcrash: ReprFileLocation | None
|
||||||
|
|
||||||
def toterminal(self, tw: TerminalWriter) -> None:
|
def toterminal(self, tw: TerminalWriter) -> None:
|
||||||
self.reprtraceback.toterminal(tw)
|
self.reprtraceback.toterminal(tw)
|
||||||
|
@ -1172,8 +1155,8 @@ class ReprExceptionInfo(ExceptionRepr):
|
||||||
|
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ReprTraceback(TerminalRepr):
|
class ReprTraceback(TerminalRepr):
|
||||||
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
|
reprentries: Sequence[ReprEntry | ReprEntryNative]
|
||||||
extraline: Optional[str]
|
extraline: str | None
|
||||||
style: TracebackStyle
|
style: TracebackStyle
|
||||||
|
|
||||||
entrysep: ClassVar = "_ "
|
entrysep: ClassVar = "_ "
|
||||||
|
@ -1217,9 +1200,9 @@ class ReprEntryNative(TerminalRepr):
|
||||||
@dataclasses.dataclass(eq=False)
|
@dataclasses.dataclass(eq=False)
|
||||||
class ReprEntry(TerminalRepr):
|
class ReprEntry(TerminalRepr):
|
||||||
lines: Sequence[str]
|
lines: Sequence[str]
|
||||||
reprfuncargs: Optional["ReprFuncArgs"]
|
reprfuncargs: ReprFuncArgs | None
|
||||||
reprlocals: Optional["ReprLocals"]
|
reprlocals: ReprLocals | None
|
||||||
reprfileloc: Optional["ReprFileLocation"]
|
reprfileloc: ReprFileLocation | None
|
||||||
style: TracebackStyle
|
style: TracebackStyle
|
||||||
|
|
||||||
def _write_entry_lines(self, tw: TerminalWriter) -> None:
|
def _write_entry_lines(self, tw: TerminalWriter) -> None:
|
||||||
|
@ -1243,9 +1226,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:
|
||||||
|
@ -1324,7 +1307,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:
|
||||||
|
@ -1345,7 +1328,7 @@ class ReprFuncArgs(TerminalRepr):
|
||||||
tw.line("")
|
tw.line("")
|
||||||
|
|
||||||
|
|
||||||
def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
|
def getfslineno(obj: object) -> tuple[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,4 +1,6 @@
|
||||||
# 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
|
||||||
|
@ -7,11 +9,7 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +21,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)):
|
||||||
|
@ -50,9 +48,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: Union[int, slice]) -> Union[str, "Source"]:
|
def __getitem__(self, key: int | slice) -> str | Source:
|
||||||
if isinstance(key, int):
|
if isinstance(key, int):
|
||||||
return self.lines[key]
|
return self.lines[key]
|
||||||
else:
|
else:
|
||||||
|
@ -68,7 +66,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():
|
||||||
|
@ -79,20 +77,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)):
|
||||||
|
@ -100,7 +98,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)
|
||||||
|
@ -115,7 +113,7 @@ class Source:
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
def findsource(obj) -> Tuple[Optional[Source], int]:
|
def findsource(obj) -> tuple[Source | None, int]:
|
||||||
try:
|
try:
|
||||||
sourcelines, lineno = inspect.findsource(obj)
|
sourcelines, lineno = inspect.findsource(obj)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -138,14 +136,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, Optional[int]]:
|
def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]:
|
||||||
# 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.
|
||||||
|
@ -154,7 +152,7 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i
|
||||||
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: Optional[List[ast.stmt]] = getattr(x, name, None)
|
val: list[ast.stmt] | None = 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)
|
||||||
|
@ -172,8 +170,8 @@ def getstatementrange_ast(
|
||||||
lineno: int,
|
lineno: int,
|
||||||
source: Source,
|
source: Source,
|
||||||
assertion: bool = False,
|
assertion: bool = False,
|
||||||
astnode: Optional[ast.AST] = None,
|
astnode: ast.AST | None = 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,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from .terminalwriter import get_terminal_width
|
from .terminalwriter import get_terminal_width
|
||||||
from .terminalwriter import TerminalWriter
|
from .terminalwriter import TerminalWriter
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
# tuples with fairly non-descriptive content. This is modeled very much
|
# tuples with fairly non-descriptive content. This is modeled very much
|
||||||
# after Lisp/Scheme - style pretty-printing of lists. If you find it
|
# after Lisp/Scheme - style pretty-printing of lists. If you find it
|
||||||
# useful, thank small children who sleep at night.
|
# useful, thank small children who sleep at night.
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import collections as _collections
|
import collections as _collections
|
||||||
import dataclasses as _dataclasses
|
import dataclasses as _dataclasses
|
||||||
from io import StringIO as _StringIO
|
from io import StringIO as _StringIO
|
||||||
|
@ -20,13 +22,8 @@ import re
|
||||||
import types as _types
|
import types as _types
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import Dict
|
|
||||||
from typing import IO
|
from typing import IO
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Set
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
|
|
||||||
class _safe_key:
|
class _safe_key:
|
||||||
|
@ -64,7 +61,7 @@ class PrettyPrinter:
|
||||||
self,
|
self,
|
||||||
indent: int = 4,
|
indent: int = 4,
|
||||||
width: int = 80,
|
width: int = 80,
|
||||||
depth: Optional[int] = None,
|
depth: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle pretty printing operations onto a stream using a set of
|
"""Handle pretty printing operations onto a stream using a set of
|
||||||
configured parameters.
|
configured parameters.
|
||||||
|
@ -100,7 +97,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
objid = id(object)
|
objid = id(object)
|
||||||
|
@ -136,7 +133,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
cls_name = object.__class__.__name__
|
cls_name = object.__class__.__name__
|
||||||
|
@ -149,9 +146,9 @@ class PrettyPrinter:
|
||||||
self._format_namespace_items(items, stream, indent, allowance, context, level)
|
self._format_namespace_items(items, stream, indent, allowance, context, level)
|
||||||
stream.write(")")
|
stream.write(")")
|
||||||
|
|
||||||
_dispatch: Dict[
|
_dispatch: dict[
|
||||||
Callable[..., str],
|
Callable[..., str],
|
||||||
Callable[["PrettyPrinter", Any, IO[str], int, int, Set[int], int], None],
|
Callable[[PrettyPrinter, Any, IO[str], int, int, set[int], int], None],
|
||||||
] = {}
|
] = {}
|
||||||
|
|
||||||
def _pprint_dict(
|
def _pprint_dict(
|
||||||
|
@ -160,7 +157,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
write = stream.write
|
write = stream.write
|
||||||
|
@ -177,7 +174,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not len(object):
|
if not len(object):
|
||||||
|
@ -196,7 +193,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
stream.write("[")
|
stream.write("[")
|
||||||
|
@ -211,7 +208,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
stream.write("(")
|
stream.write("(")
|
||||||
|
@ -226,7 +223,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not len(object):
|
if not len(object):
|
||||||
|
@ -252,7 +249,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
write = stream.write
|
write = stream.write
|
||||||
|
@ -311,7 +308,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
write = stream.write
|
write = stream.write
|
||||||
|
@ -340,7 +337,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
write = stream.write
|
write = stream.write
|
||||||
|
@ -358,7 +355,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
stream.write("mappingproxy(")
|
stream.write("mappingproxy(")
|
||||||
|
@ -373,7 +370,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
if type(object) is _types.SimpleNamespace:
|
if type(object) is _types.SimpleNamespace:
|
||||||
|
@ -391,11 +388,11 @@ class PrettyPrinter:
|
||||||
|
|
||||||
def _format_dict_items(
|
def _format_dict_items(
|
||||||
self,
|
self,
|
||||||
items: List[Tuple[Any, Any]],
|
items: list[tuple[Any, Any]],
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not items:
|
if not items:
|
||||||
|
@ -415,11 +412,11 @@ class PrettyPrinter:
|
||||||
|
|
||||||
def _format_namespace_items(
|
def _format_namespace_items(
|
||||||
self,
|
self,
|
||||||
items: List[Tuple[Any, Any]],
|
items: list[tuple[Any, Any]],
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not items:
|
if not items:
|
||||||
|
@ -452,11 +449,11 @@ class PrettyPrinter:
|
||||||
|
|
||||||
def _format_items(
|
def _format_items(
|
||||||
self,
|
self,
|
||||||
items: List[Any],
|
items: list[Any],
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not items:
|
if not items:
|
||||||
|
@ -473,7 +470,7 @@ class PrettyPrinter:
|
||||||
|
|
||||||
write("\n" + " " * indent)
|
write("\n" + " " * indent)
|
||||||
|
|
||||||
def _repr(self, object: Any, context: Set[int], level: int) -> str:
|
def _repr(self, object: Any, context: set[int], level: int) -> str:
|
||||||
return self._safe_repr(object, context.copy(), self._depth, level)
|
return self._safe_repr(object, context.copy(), self._depth, level)
|
||||||
|
|
||||||
def _pprint_default_dict(
|
def _pprint_default_dict(
|
||||||
|
@ -482,7 +479,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
rdf = self._repr(object.default_factory, context, level)
|
rdf = self._repr(object.default_factory, context, level)
|
||||||
|
@ -498,7 +495,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
stream.write(object.__class__.__name__ + "(")
|
stream.write(object.__class__.__name__ + "(")
|
||||||
|
@ -519,7 +516,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])):
|
if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])):
|
||||||
|
@ -538,7 +535,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
stream.write(object.__class__.__name__ + "(")
|
stream.write(object.__class__.__name__ + "(")
|
||||||
|
@ -557,7 +554,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._format(object.data, stream, indent, allowance, context, level - 1)
|
self._format(object.data, stream, indent, allowance, context, level - 1)
|
||||||
|
@ -570,7 +567,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._format(object.data, stream, indent, allowance, context, level - 1)
|
self._format(object.data, stream, indent, allowance, context, level - 1)
|
||||||
|
@ -583,7 +580,7 @@ class PrettyPrinter:
|
||||||
stream: IO[str],
|
stream: IO[str],
|
||||||
indent: int,
|
indent: int,
|
||||||
allowance: int,
|
allowance: int,
|
||||||
context: Set[int],
|
context: set[int],
|
||||||
level: int,
|
level: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._format(object.data, stream, indent, allowance, context, level - 1)
|
self._format(object.data, stream, indent, allowance, context, level - 1)
|
||||||
|
@ -591,7 +588,7 @@ class PrettyPrinter:
|
||||||
_dispatch[_collections.UserString.__repr__] = _pprint_user_string
|
_dispatch[_collections.UserString.__repr__] = _pprint_user_string
|
||||||
|
|
||||||
def _safe_repr(
|
def _safe_repr(
|
||||||
self, object: Any, context: Set[int], maxlevels: Optional[int], level: int
|
self, object: Any, context: set[int], maxlevels: int | None, level: int
|
||||||
) -> str:
|
) -> str:
|
||||||
typ = type(object)
|
typ = type(object)
|
||||||
if typ in _builtin_scalars:
|
if typ in _builtin_scalars:
|
||||||
|
@ -608,7 +605,7 @@ class PrettyPrinter:
|
||||||
if objid in context:
|
if objid in context:
|
||||||
return _recursion(object)
|
return _recursion(object)
|
||||||
context.add(objid)
|
context.add(objid)
|
||||||
components: List[str] = []
|
components: list[str] = []
|
||||||
append = components.append
|
append = components.append
|
||||||
level += 1
|
level += 1
|
||||||
for k, v in sorted(object.items(), key=_safe_tuple):
|
for k, v in sorted(object.items(), key=_safe_tuple):
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pprint
|
import pprint
|
||||||
import reprlib
|
import reprlib
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
def _try_repr_or_str(obj: object) -> str:
|
def _try_repr_or_str(obj: object) -> str:
|
||||||
|
@ -38,7 +39,7 @@ class SafeRepr(reprlib.Repr):
|
||||||
information on exceptions raised during the call.
|
information on exceptions raised during the call.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None:
|
def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
:param maxsize:
|
:param maxsize:
|
||||||
If not None, will truncate the resulting repr to that specific size, using ellipsis
|
If not None, will truncate the resulting repr to that specific size, using ellipsis
|
||||||
|
@ -59,7 +60,6 @@ class SafeRepr(reprlib.Repr):
|
||||||
s = ascii(x)
|
s = ascii(x)
|
||||||
else:
|
else:
|
||||||
s = super().repr(x)
|
s = super().repr(x)
|
||||||
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
|
@ -97,7 +97,7 @@ DEFAULT_REPR_MAX_SIZE = 240
|
||||||
|
|
||||||
|
|
||||||
def saferepr(
|
def saferepr(
|
||||||
obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False
|
obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Return a size-limited safe repr-string for the given object.
|
"""Return a size-limited safe repr-string for the given object.
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
"""Helper functions for writing to terminals and files."""
|
"""Helper functions for writing to terminals and files."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from typing import final
|
from typing import final
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
from typing import Optional
|
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
from typing import TextIO
|
from typing import TextIO
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
@ -71,7 +72,7 @@ class TerminalWriter:
|
||||||
invert=7,
|
invert=7,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, file: Optional[TextIO] = None) -> None:
|
def __init__(self, file: TextIO | None = None) -> None:
|
||||||
if file is None:
|
if file is None:
|
||||||
file = sys.stdout
|
file = sys.stdout
|
||||||
if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32":
|
if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32":
|
||||||
|
@ -85,7 +86,7 @@ class TerminalWriter:
|
||||||
self._file = file
|
self._file = file
|
||||||
self.hasmarkup = should_do_markup(file)
|
self.hasmarkup = should_do_markup(file)
|
||||||
self._current_line = ""
|
self._current_line = ""
|
||||||
self._terminal_width: Optional[int] = None
|
self._terminal_width: int | None = None
|
||||||
self.code_highlight = True
|
self.code_highlight = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -116,8 +117,8 @@ class TerminalWriter:
|
||||||
def sep(
|
def sep(
|
||||||
self,
|
self,
|
||||||
sepchar: str,
|
sepchar: str,
|
||||||
title: Optional[str] = None,
|
title: str | None = None,
|
||||||
fullwidth: Optional[int] = None,
|
fullwidth: int | None = None,
|
||||||
**markup: bool,
|
**markup: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
if fullwidth is None:
|
if fullwidth is None:
|
||||||
|
@ -200,9 +201,7 @@ class TerminalWriter:
|
||||||
for indent, new_line in zip(indents, new_lines):
|
for indent, new_line in zip(indents, new_lines):
|
||||||
self.line(indent + new_line)
|
self.line(indent + new_line)
|
||||||
|
|
||||||
def _get_pygments_lexer(
|
def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer | None:
|
||||||
self, lexer: Literal["python", "diff"]
|
|
||||||
) -> Optional["Lexer"]:
|
|
||||||
try:
|
try:
|
||||||
if lexer == "python":
|
if lexer == "python":
|
||||||
from pygments.lexers.python import PythonLexer
|
from pygments.lexers.python import PythonLexer
|
||||||
|
@ -217,7 +216,7 @@ class TerminalWriter:
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_pygments_formatter(self) -> Optional["Formatter"]:
|
def _get_pygments_formatter(self) -> Formatter | None:
|
||||||
try:
|
try:
|
||||||
import pygments.util
|
import pygments.util
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
"""Support for presenting detailed information in failing assertions."""
|
"""Support for presenting detailed information in failing assertions."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from _pytest.assertion import rewrite
|
from _pytest.assertion import rewrite
|
||||||
|
@ -94,7 +94,7 @@ class AssertionState:
|
||||||
def __init__(self, config: Config, mode) -> None:
|
def __init__(self, config: Config, mode) -> None:
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.trace = config.trace.root.get("assertion")
|
self.trace = config.trace.root.get("assertion")
|
||||||
self.hook: Optional[rewrite.AssertionRewritingHook] = None
|
self.hook: rewrite.AssertionRewritingHook | None = None
|
||||||
|
|
||||||
|
|
||||||
def install_importhook(config: Config) -> rewrite.AssertionRewritingHook:
|
def install_importhook(config: Config) -> rewrite.AssertionRewritingHook:
|
||||||
|
@ -113,7 +113,7 @@ def install_importhook(config: Config) -> rewrite.AssertionRewritingHook:
|
||||||
return hook
|
return hook
|
||||||
|
|
||||||
|
|
||||||
def pytest_collection(session: "Session") -> None:
|
def pytest_collection(session: Session) -> None:
|
||||||
# This hook is only called when test modules are collected
|
# This hook is only called when test modules are collected
|
||||||
# so for example not in the managing process of pytest-xdist
|
# so for example not in the managing process of pytest-xdist
|
||||||
# (which does not collect test modules).
|
# (which does not collect test modules).
|
||||||
|
@ -133,7 +133,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
||||||
"""
|
"""
|
||||||
ihook = item.ihook
|
ihook = item.ihook
|
||||||
|
|
||||||
def callbinrepr(op, left: object, right: object) -> Optional[str]:
|
def callbinrepr(op, left: object, right: object) -> str | None:
|
||||||
"""Call the pytest_assertrepr_compare hook and prepare the result.
|
"""Call the pytest_assertrepr_compare hook and prepare the result.
|
||||||
|
|
||||||
This uses the first result from the hook and then ensures the
|
This uses the first result from the hook and then ensures the
|
||||||
|
@ -179,7 +179,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
||||||
util._config = None
|
util._config = None
|
||||||
|
|
||||||
|
|
||||||
def pytest_sessionfinish(session: "Session") -> None:
|
def pytest_sessionfinish(session: Session) -> None:
|
||||||
assertstate = session.config.stash.get(assertstate_key, None)
|
assertstate = session.config.stash.get(assertstate_key, None)
|
||||||
if assertstate:
|
if assertstate:
|
||||||
if assertstate.hook is not None:
|
if assertstate.hook is not None:
|
||||||
|
@ -188,5 +188,5 @@ def pytest_sessionfinish(session: "Session") -> None:
|
||||||
|
|
||||||
def pytest_assertrepr_compare(
|
def pytest_assertrepr_compare(
|
||||||
config: Config, op: str, left: Any, right: Any
|
config: Config, op: str, left: Any, right: Any
|
||||||
) -> Optional[List[str]]:
|
) -> list[str] | None:
|
||||||
return util.assertrepr_compare(config=config, op=op, left=left, right=right)
|
return util.assertrepr_compare(config=config, op=op, left=left, right=right)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
"""Rewrite assertion AST to produce nice error messages."""
|
"""Rewrite assertion AST to produce nice error messages."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import errno
|
import errno
|
||||||
|
@ -18,17 +20,11 @@ import sys
|
||||||
import tokenize
|
import tokenize
|
||||||
import types
|
import types
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import Dict
|
|
||||||
from typing import IO
|
from typing import IO
|
||||||
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 Sequence
|
from typing import Sequence
|
||||||
from typing import Set
|
|
||||||
from typing import Tuple
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
|
from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
|
@ -73,17 +69,17 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||||
self.fnpats = config.getini("python_files")
|
self.fnpats = config.getini("python_files")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.fnpats = ["test_*.py", "*_test.py"]
|
self.fnpats = ["test_*.py", "*_test.py"]
|
||||||
self.session: Optional[Session] = None
|
self.session: Session | None = None
|
||||||
self._rewritten_names: Dict[str, Path] = {}
|
self._rewritten_names: dict[str, Path] = {}
|
||||||
self._must_rewrite: Set[str] = set()
|
self._must_rewrite: set[str] = set()
|
||||||
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
|
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
|
||||||
# which might result in infinite recursion (#3506)
|
# which might result in infinite recursion (#3506)
|
||||||
self._writing_pyc = False
|
self._writing_pyc = False
|
||||||
self._basenames_to_check_rewrite = {"conftest"}
|
self._basenames_to_check_rewrite = {"conftest"}
|
||||||
self._marked_for_rewrite_cache: Dict[str, bool] = {}
|
self._marked_for_rewrite_cache: dict[str, bool] = {}
|
||||||
self._session_paths_checked = False
|
self._session_paths_checked = False
|
||||||
|
|
||||||
def set_session(self, session: Optional[Session]) -> None:
|
def set_session(self, session: Session | None) -> None:
|
||||||
self.session = session
|
self.session = session
|
||||||
self._session_paths_checked = False
|
self._session_paths_checked = False
|
||||||
|
|
||||||
|
@ -93,9 +89,9 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||||
def find_spec(
|
def find_spec(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
path: Optional[Sequence[Union[str, bytes]]] = None,
|
path: Sequence[str | bytes] | None = None,
|
||||||
target: Optional[types.ModuleType] = None,
|
target: types.ModuleType | None = None,
|
||||||
) -> Optional[importlib.machinery.ModuleSpec]:
|
) -> importlib.machinery.ModuleSpec | None:
|
||||||
if self._writing_pyc:
|
if self._writing_pyc:
|
||||||
return None
|
return None
|
||||||
state = self.config.stash[assertstate_key]
|
state = self.config.stash[assertstate_key]
|
||||||
|
@ -132,7 +128,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||||
|
|
||||||
def create_module(
|
def create_module(
|
||||||
self, spec: importlib.machinery.ModuleSpec
|
self, spec: importlib.machinery.ModuleSpec
|
||||||
) -> Optional[types.ModuleType]:
|
) -> types.ModuleType | None:
|
||||||
return None # default behaviour is fine
|
return None # default behaviour is fine
|
||||||
|
|
||||||
def exec_module(self, module: types.ModuleType) -> None:
|
def exec_module(self, module: types.ModuleType) -> None:
|
||||||
|
@ -177,7 +173,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||||
state.trace(f"found cached rewritten pyc for {fn}")
|
state.trace(f"found cached rewritten pyc for {fn}")
|
||||||
exec(co, module.__dict__)
|
exec(co, module.__dict__)
|
||||||
|
|
||||||
def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool:
|
def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool:
|
||||||
"""A fast way to get out of rewriting modules.
|
"""A fast way to get out of rewriting modules.
|
||||||
|
|
||||||
Profiling has shown that the call to PathFinder.find_spec (inside of
|
Profiling has shown that the call to PathFinder.find_spec (inside of
|
||||||
|
@ -216,7 +212,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||||
state.trace(f"early skip of rewriting module: {name}")
|
state.trace(f"early skip of rewriting module: {name}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool:
|
def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool:
|
||||||
# always rewrite conftest files
|
# always rewrite conftest files
|
||||||
if os.path.basename(fn) == "conftest.py":
|
if os.path.basename(fn) == "conftest.py":
|
||||||
state.trace(f"rewriting conftest file: {fn!r}")
|
state.trace(f"rewriting conftest file: {fn!r}")
|
||||||
|
@ -237,7 +233,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||||
|
|
||||||
return self._is_marked_for_rewrite(name, state)
|
return self._is_marked_for_rewrite(name, state)
|
||||||
|
|
||||||
def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool:
|
def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool:
|
||||||
try:
|
try:
|
||||||
return self._marked_for_rewrite_cache[name]
|
return self._marked_for_rewrite_cache[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -278,7 +274,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||||
stacklevel=5,
|
stacklevel=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_data(self, pathname: Union[str, bytes]) -> bytes:
|
def get_data(self, pathname: str | bytes) -> bytes:
|
||||||
"""Optional PEP302 get_data API."""
|
"""Optional PEP302 get_data API."""
|
||||||
with open(pathname, "rb") as f:
|
with open(pathname, "rb") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
@ -317,7 +313,7 @@ def _write_pyc_fp(
|
||||||
|
|
||||||
|
|
||||||
def _write_pyc(
|
def _write_pyc(
|
||||||
state: "AssertionState",
|
state: AssertionState,
|
||||||
co: types.CodeType,
|
co: types.CodeType,
|
||||||
source_stat: os.stat_result,
|
source_stat: os.stat_result,
|
||||||
pyc: Path,
|
pyc: Path,
|
||||||
|
@ -341,7 +337,7 @@ def _write_pyc(
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]:
|
def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]:
|
||||||
"""Read and rewrite *fn* and return the code object."""
|
"""Read and rewrite *fn* and return the code object."""
|
||||||
stat = os.stat(fn)
|
stat = os.stat(fn)
|
||||||
source = fn.read_bytes()
|
source = fn.read_bytes()
|
||||||
|
@ -354,7 +350,7 @@ def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeT
|
||||||
|
|
||||||
def _read_pyc(
|
def _read_pyc(
|
||||||
source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None
|
source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None
|
||||||
) -> Optional[types.CodeType]:
|
) -> types.CodeType | None:
|
||||||
"""Possibly read a pytest pyc containing rewritten code.
|
"""Possibly read a pytest pyc containing rewritten code.
|
||||||
|
|
||||||
Return rewritten code if successful or None if not.
|
Return rewritten code if successful or None if not.
|
||||||
|
@ -404,8 +400,8 @@ def _read_pyc(
|
||||||
def rewrite_asserts(
|
def rewrite_asserts(
|
||||||
mod: ast.Module,
|
mod: ast.Module,
|
||||||
source: bytes,
|
source: bytes,
|
||||||
module_path: Optional[str] = None,
|
module_path: str | None = None,
|
||||||
config: Optional[Config] = None,
|
config: Config | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Rewrite the assert statements in mod."""
|
"""Rewrite the assert statements in mod."""
|
||||||
AssertionRewriter(module_path, config, source).run(mod)
|
AssertionRewriter(module_path, config, source).run(mod)
|
||||||
|
@ -421,11 +417,15 @@ def _saferepr(obj: object) -> str:
|
||||||
sequences, especially '\n{' and '\n}' are likely to be present in
|
sequences, especially '\n{' and '\n}' are likely to be present in
|
||||||
JSON reprs.
|
JSON reprs.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(obj, types.MethodType):
|
||||||
|
# for bound methods, skip redundant <bound method ...> information
|
||||||
|
return obj.__name__
|
||||||
|
|
||||||
maxsize = _get_maxsize_for_saferepr(util._config)
|
maxsize = _get_maxsize_for_saferepr(util._config)
|
||||||
return saferepr(obj, maxsize=maxsize).replace("\n", "\\n")
|
return saferepr(obj, maxsize=maxsize).replace("\n", "\\n")
|
||||||
|
|
||||||
|
|
||||||
def _get_maxsize_for_saferepr(config: Optional[Config]) -> Optional[int]:
|
def _get_maxsize_for_saferepr(config: Config | None) -> int | None:
|
||||||
"""Get `maxsize` configuration for saferepr based on the given config object."""
|
"""Get `maxsize` configuration for saferepr based on the given config object."""
|
||||||
if config is None:
|
if config is None:
|
||||||
verbosity = 0
|
verbosity = 0
|
||||||
|
@ -543,14 +543,14 @@ def traverse_node(node: ast.AST) -> Iterator[ast.AST]:
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=1)
|
@functools.lru_cache(maxsize=1)
|
||||||
def _get_assertion_exprs(src: bytes) -> Dict[int, str]:
|
def _get_assertion_exprs(src: bytes) -> dict[int, str]:
|
||||||
"""Return a mapping from {lineno: "assertion test expression"}."""
|
"""Return a mapping from {lineno: "assertion test expression"}."""
|
||||||
ret: Dict[int, str] = {}
|
ret: dict[int, str] = {}
|
||||||
|
|
||||||
depth = 0
|
depth = 0
|
||||||
lines: List[str] = []
|
lines: list[str] = []
|
||||||
assert_lineno: Optional[int] = None
|
assert_lineno: int | None = None
|
||||||
seen_lines: Set[int] = set()
|
seen_lines: set[int] = set()
|
||||||
|
|
||||||
def _write_and_reset() -> None:
|
def _write_and_reset() -> None:
|
||||||
nonlocal depth, lines, assert_lineno, seen_lines
|
nonlocal depth, lines, assert_lineno, seen_lines
|
||||||
|
@ -657,7 +657,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, module_path: Optional[str], config: Optional[Config], source: bytes
|
self, module_path: str | None, config: Config | None, source: bytes
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.module_path = module_path
|
self.module_path = module_path
|
||||||
|
@ -670,7 +670,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
self.enable_assertion_pass_hook = False
|
self.enable_assertion_pass_hook = False
|
||||||
self.source = source
|
self.source = source
|
||||||
self.scope: tuple[ast.AST, ...] = ()
|
self.scope: tuple[ast.AST, ...] = ()
|
||||||
self.variables_overwrite: defaultdict[tuple[ast.AST, ...], Dict[str, str]] = (
|
self.variables_overwrite: defaultdict[tuple[ast.AST, ...], dict[str, str]] = (
|
||||||
defaultdict(dict)
|
defaultdict(dict)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -737,7 +737,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
|
|
||||||
# Collect asserts.
|
# Collect asserts.
|
||||||
self.scope = (mod,)
|
self.scope = (mod,)
|
||||||
nodes: List[Union[ast.AST, Sentinel]] = [mod]
|
nodes: list[ast.AST | Sentinel] = [mod]
|
||||||
while nodes:
|
while nodes:
|
||||||
node = nodes.pop()
|
node = nodes.pop()
|
||||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
||||||
|
@ -749,7 +749,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
assert isinstance(node, ast.AST)
|
assert isinstance(node, ast.AST)
|
||||||
for name, field in ast.iter_fields(node):
|
for name, field in ast.iter_fields(node):
|
||||||
if isinstance(field, list):
|
if isinstance(field, list):
|
||||||
new: List[ast.AST] = []
|
new: list[ast.AST] = []
|
||||||
for i, child in enumerate(field):
|
for i, child in enumerate(field):
|
||||||
if isinstance(child, ast.Assert):
|
if isinstance(child, ast.Assert):
|
||||||
# Transform assert.
|
# Transform assert.
|
||||||
|
@ -821,7 +821,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
to format a string of %-formatted values as added by
|
to format a string of %-formatted values as added by
|
||||||
.explanation_param().
|
.explanation_param().
|
||||||
"""
|
"""
|
||||||
self.explanation_specifiers: Dict[str, ast.expr] = {}
|
self.explanation_specifiers: dict[str, ast.expr] = {}
|
||||||
self.stack.append(self.explanation_specifiers)
|
self.stack.append(self.explanation_specifiers)
|
||||||
|
|
||||||
def pop_format_context(self, expl_expr: ast.expr) -> ast.Name:
|
def pop_format_context(self, expl_expr: ast.expr) -> ast.Name:
|
||||||
|
@ -835,7 +835,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
current = self.stack.pop()
|
current = self.stack.pop()
|
||||||
if self.stack:
|
if self.stack:
|
||||||
self.explanation_specifiers = self.stack[-1]
|
self.explanation_specifiers = self.stack[-1]
|
||||||
keys: List[Optional[ast.expr]] = [ast.Constant(key) for key in current.keys()]
|
keys: list[ast.expr | None] = [ast.Constant(key) for key in current.keys()]
|
||||||
format_dict = ast.Dict(keys, list(current.values()))
|
format_dict = ast.Dict(keys, list(current.values()))
|
||||||
form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
|
form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
|
||||||
name = "@py_format" + str(next(self.variable_counter))
|
name = "@py_format" + str(next(self.variable_counter))
|
||||||
|
@ -844,13 +844,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form))
|
self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form))
|
||||||
return ast.Name(name, ast.Load())
|
return ast.Name(name, ast.Load())
|
||||||
|
|
||||||
def generic_visit(self, node: ast.AST) -> Tuple[ast.Name, str]:
|
def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]:
|
||||||
"""Handle expressions we don't have custom code for."""
|
"""Handle expressions we don't have custom code for."""
|
||||||
assert isinstance(node, ast.expr)
|
assert isinstance(node, ast.expr)
|
||||||
res = self.assign(node)
|
res = self.assign(node)
|
||||||
return res, self.explanation_param(self.display(res))
|
return res, self.explanation_param(self.display(res))
|
||||||
|
|
||||||
def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]:
|
def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]:
|
||||||
"""Return the AST statements to replace the ast.Assert instance.
|
"""Return the AST statements to replace the ast.Assert instance.
|
||||||
|
|
||||||
This rewrites the test of an assertion to provide
|
This rewrites the test of an assertion to provide
|
||||||
|
@ -874,15 +874,15 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
lineno=assert_.lineno,
|
lineno=assert_.lineno,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.statements: List[ast.stmt] = []
|
self.statements: list[ast.stmt] = []
|
||||||
self.variables: List[str] = []
|
self.variables: list[str] = []
|
||||||
self.variable_counter = itertools.count()
|
self.variable_counter = itertools.count()
|
||||||
|
|
||||||
if self.enable_assertion_pass_hook:
|
if self.enable_assertion_pass_hook:
|
||||||
self.format_variables: List[str] = []
|
self.format_variables: list[str] = []
|
||||||
|
|
||||||
self.stack: List[Dict[str, ast.expr]] = []
|
self.stack: list[dict[str, ast.expr]] = []
|
||||||
self.expl_stmts: List[ast.stmt] = []
|
self.expl_stmts: list[ast.stmt] = []
|
||||||
self.push_format_context()
|
self.push_format_context()
|
||||||
# Rewrite assert into a bunch of statements.
|
# Rewrite assert into a bunch of statements.
|
||||||
top_condition, explanation = self.visit(assert_.test)
|
top_condition, explanation = self.visit(assert_.test)
|
||||||
|
@ -926,13 +926,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
[*self.expl_stmts, hook_call_pass],
|
[*self.expl_stmts, hook_call_pass],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
statements_pass: List[ast.stmt] = [hook_impl_test]
|
statements_pass: list[ast.stmt] = [hook_impl_test]
|
||||||
|
|
||||||
# Test for assertion condition
|
# Test for assertion condition
|
||||||
main_test = ast.If(negation, statements_fail, statements_pass)
|
main_test = ast.If(negation, statements_fail, statements_pass)
|
||||||
self.statements.append(main_test)
|
self.statements.append(main_test)
|
||||||
if self.format_variables:
|
if self.format_variables:
|
||||||
variables: List[ast.expr] = [
|
variables: list[ast.expr] = [
|
||||||
ast.Name(name, ast.Store()) for name in self.format_variables
|
ast.Name(name, ast.Store()) for name in self.format_variables
|
||||||
]
|
]
|
||||||
clear_format = ast.Assign(variables, ast.Constant(None))
|
clear_format = ast.Assign(variables, ast.Constant(None))
|
||||||
|
@ -968,7 +968,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
ast.copy_location(node, assert_)
|
ast.copy_location(node, assert_)
|
||||||
return self.statements
|
return self.statements
|
||||||
|
|
||||||
def visit_NamedExpr(self, name: ast.NamedExpr) -> Tuple[ast.NamedExpr, str]:
|
def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.NamedExpr, str]:
|
||||||
# This method handles the 'walrus operator' repr of the target
|
# This method handles the 'walrus operator' repr of the target
|
||||||
# name if it's a local variable or _should_repr_global_name()
|
# name if it's a local variable or _should_repr_global_name()
|
||||||
# thinks it's acceptable.
|
# thinks it's acceptable.
|
||||||
|
@ -980,7 +980,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
expr = ast.IfExp(test, self.display(name), ast.Constant(target_id))
|
expr = ast.IfExp(test, self.display(name), ast.Constant(target_id))
|
||||||
return name, self.explanation_param(expr)
|
return name, self.explanation_param(expr)
|
||||||
|
|
||||||
def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]:
|
def visit_Name(self, name: ast.Name) -> tuple[ast.Name, str]:
|
||||||
# Display the repr of the name if it's a local variable or
|
# Display the repr of the name if it's a local variable or
|
||||||
# _should_repr_global_name() thinks it's acceptable.
|
# _should_repr_global_name() thinks it's acceptable.
|
||||||
locs = ast.Call(self.builtin("locals"), [], [])
|
locs = ast.Call(self.builtin("locals"), [], [])
|
||||||
|
@ -990,7 +990,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
expr = ast.IfExp(test, self.display(name), ast.Constant(name.id))
|
expr = ast.IfExp(test, self.display(name), ast.Constant(name.id))
|
||||||
return name, self.explanation_param(expr)
|
return name, self.explanation_param(expr)
|
||||||
|
|
||||||
def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]:
|
def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]:
|
||||||
res_var = self.variable()
|
res_var = self.variable()
|
||||||
expl_list = self.assign(ast.List([], ast.Load()))
|
expl_list = self.assign(ast.List([], ast.Load()))
|
||||||
app = ast.Attribute(expl_list, "append", ast.Load())
|
app = ast.Attribute(expl_list, "append", ast.Load())
|
||||||
|
@ -1002,7 +1002,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
# Process each operand, short-circuiting if needed.
|
# Process each operand, short-circuiting if needed.
|
||||||
for i, v in enumerate(boolop.values):
|
for i, v in enumerate(boolop.values):
|
||||||
if i:
|
if i:
|
||||||
fail_inner: List[ast.stmt] = []
|
fail_inner: list[ast.stmt] = []
|
||||||
# cond is set in a prior loop iteration below
|
# cond is set in a prior loop iteration below
|
||||||
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821
|
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821
|
||||||
self.expl_stmts = fail_inner
|
self.expl_stmts = fail_inner
|
||||||
|
@ -1030,7 +1030,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
cond: ast.expr = res
|
cond: ast.expr = res
|
||||||
if is_or:
|
if is_or:
|
||||||
cond = ast.UnaryOp(ast.Not(), cond)
|
cond = ast.UnaryOp(ast.Not(), cond)
|
||||||
inner: List[ast.stmt] = []
|
inner: list[ast.stmt] = []
|
||||||
self.statements.append(ast.If(cond, inner, []))
|
self.statements.append(ast.If(cond, inner, []))
|
||||||
self.statements = body = inner
|
self.statements = body = inner
|
||||||
self.statements = save
|
self.statements = save
|
||||||
|
@ -1039,13 +1039,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
expl = self.pop_format_context(expl_template)
|
expl = self.pop_format_context(expl_template)
|
||||||
return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
|
return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
|
||||||
|
|
||||||
def visit_UnaryOp(self, unary: ast.UnaryOp) -> Tuple[ast.Name, str]:
|
def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]:
|
||||||
pattern = UNARY_MAP[unary.op.__class__]
|
pattern = UNARY_MAP[unary.op.__class__]
|
||||||
operand_res, operand_expl = self.visit(unary.operand)
|
operand_res, operand_expl = self.visit(unary.operand)
|
||||||
res = self.assign(ast.UnaryOp(unary.op, operand_res))
|
res = self.assign(ast.UnaryOp(unary.op, operand_res))
|
||||||
return res, pattern % (operand_expl,)
|
return res, pattern % (operand_expl,)
|
||||||
|
|
||||||
def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]:
|
def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]:
|
||||||
symbol = BINOP_MAP[binop.op.__class__]
|
symbol = BINOP_MAP[binop.op.__class__]
|
||||||
left_expr, left_expl = self.visit(binop.left)
|
left_expr, left_expl = self.visit(binop.left)
|
||||||
right_expr, right_expl = self.visit(binop.right)
|
right_expr, right_expl = self.visit(binop.right)
|
||||||
|
@ -1053,7 +1053,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
|
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
|
||||||
return res, explanation
|
return res, explanation
|
||||||
|
|
||||||
def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]:
|
def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]:
|
||||||
new_func, func_expl = self.visit(call.func)
|
new_func, func_expl = self.visit(call.func)
|
||||||
arg_expls = []
|
arg_expls = []
|
||||||
new_args = []
|
new_args = []
|
||||||
|
@ -1085,13 +1085,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}"
|
outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}"
|
||||||
return res, outer_expl
|
return res, outer_expl
|
||||||
|
|
||||||
def visit_Starred(self, starred: ast.Starred) -> Tuple[ast.Starred, str]:
|
def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]:
|
||||||
# A Starred node can appear in a function call.
|
# A Starred node can appear in a function call.
|
||||||
res, expl = self.visit(starred.value)
|
res, expl = self.visit(starred.value)
|
||||||
new_starred = ast.Starred(res, starred.ctx)
|
new_starred = ast.Starred(res, starred.ctx)
|
||||||
return new_starred, "*" + expl
|
return new_starred, "*" + expl
|
||||||
|
|
||||||
def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]:
|
def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]:
|
||||||
if not isinstance(attr.ctx, ast.Load):
|
if not isinstance(attr.ctx, ast.Load):
|
||||||
return self.generic_visit(attr)
|
return self.generic_visit(attr)
|
||||||
value, value_expl = self.visit(attr.value)
|
value, value_expl = self.visit(attr.value)
|
||||||
|
@ -1101,7 +1101,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
expl = pat % (res_expl, res_expl, value_expl, attr.attr)
|
expl = pat % (res_expl, res_expl, value_expl, attr.attr)
|
||||||
return res, expl
|
return res, expl
|
||||||
|
|
||||||
def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]:
|
def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]:
|
||||||
self.push_format_context()
|
self.push_format_context()
|
||||||
# We first check if we have overwritten a variable in the previous assert
|
# We first check if we have overwritten a variable in the previous assert
|
||||||
if isinstance(
|
if isinstance(
|
||||||
|
@ -1114,11 +1114,11 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
||||||
left_expl = f"({left_expl})"
|
left_expl = f"({left_expl})"
|
||||||
res_variables = [self.variable() for i in range(len(comp.ops))]
|
res_variables = [self.variable() for i in range(len(comp.ops))]
|
||||||
load_names: List[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables]
|
load_names: list[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables]
|
||||||
store_names = [ast.Name(v, ast.Store()) for v in res_variables]
|
store_names = [ast.Name(v, ast.Store()) for v in res_variables]
|
||||||
it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
|
it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
|
||||||
expls: List[ast.expr] = []
|
expls: list[ast.expr] = []
|
||||||
syms: List[ast.expr] = []
|
syms: list[ast.expr] = []
|
||||||
results = [left_res]
|
results = [left_res]
|
||||||
for i, op, next_operand in it:
|
for i, op, next_operand in it:
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -4,8 +4,7 @@ Current default behaviour is to truncate assertion explanations at
|
||||||
terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI.
|
terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List
|
from __future__ import annotations
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from _pytest.assertion import util
|
from _pytest.assertion import util
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
|
@ -18,8 +17,8 @@ USAGE_MSG = "use '-vv' to show"
|
||||||
|
|
||||||
|
|
||||||
def truncate_if_required(
|
def truncate_if_required(
|
||||||
explanation: List[str], item: Item, max_length: Optional[int] = None
|
explanation: list[str], item: Item, max_length: int | None = None
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
"""Truncate this assertion explanation if the given test item is eligible."""
|
"""Truncate this assertion explanation if the given test item is eligible."""
|
||||||
if _should_truncate_item(item):
|
if _should_truncate_item(item):
|
||||||
return _truncate_explanation(explanation)
|
return _truncate_explanation(explanation)
|
||||||
|
@ -33,10 +32,10 @@ def _should_truncate_item(item: Item) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def _truncate_explanation(
|
def _truncate_explanation(
|
||||||
input_lines: List[str],
|
input_lines: list[str],
|
||||||
max_lines: Optional[int] = None,
|
max_lines: int | None = None,
|
||||||
max_chars: Optional[int] = None,
|
max_chars: int | None = None,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
"""Truncate given list of strings that makes up the assertion explanation.
|
"""Truncate given list of strings that makes up the assertion explanation.
|
||||||
|
|
||||||
Truncates to either 8 lines, or 640 characters - whichever the input reaches
|
Truncates to either 8 lines, or 640 characters - whichever the input reaches
|
||||||
|
@ -100,7 +99,7 @@ def _truncate_explanation(
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]:
|
def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]:
|
||||||
# Find point at which input length exceeds total allowed length
|
# Find point at which input length exceeds total allowed length
|
||||||
iterated_char_count = 0
|
iterated_char_count = 0
|
||||||
for iterated_index, input_line in enumerate(input_lines):
|
for iterated_index, input_line in enumerate(input_lines):
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
"""Utilities for assertion debugging."""
|
"""Utilities for assertion debugging."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import collections.abc
|
import collections.abc
|
||||||
import os
|
import os
|
||||||
import pprint
|
import pprint
|
||||||
|
@ -8,10 +10,8 @@ from typing import AbstractSet
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
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 Protocol
|
from typing import Protocol
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
from unicodedata import normalize
|
from unicodedata import normalize
|
||||||
|
@ -28,14 +28,14 @@ from _pytest.config import Config
|
||||||
# interpretation code and assertion rewriter to detect this plugin was
|
# interpretation code and assertion rewriter to detect this plugin was
|
||||||
# loaded and in turn call the hooks defined here as part of the
|
# loaded and in turn call the hooks defined here as part of the
|
||||||
# DebugInterpreter.
|
# DebugInterpreter.
|
||||||
_reprcompare: Optional[Callable[[str, object, object], Optional[str]]] = None
|
_reprcompare: Callable[[str, object, object], str | None] | None = None
|
||||||
|
|
||||||
# Works similarly as _reprcompare attribute. Is populated with the hook call
|
# Works similarly as _reprcompare attribute. Is populated with the hook call
|
||||||
# when pytest_runtest_setup is called.
|
# when pytest_runtest_setup is called.
|
||||||
_assertion_pass: Optional[Callable[[int, str, str], None]] = None
|
_assertion_pass: Callable[[int, str, str], None] | None = None
|
||||||
|
|
||||||
# Config object which is assigned during pytest_runtest_protocol.
|
# Config object which is assigned during pytest_runtest_protocol.
|
||||||
_config: Optional[Config] = None
|
_config: Config | None = None
|
||||||
|
|
||||||
|
|
||||||
class _HighlightFunc(Protocol):
|
class _HighlightFunc(Protocol):
|
||||||
|
@ -58,7 +58,7 @@ def format_explanation(explanation: str) -> str:
|
||||||
return "\n".join(result)
|
return "\n".join(result)
|
||||||
|
|
||||||
|
|
||||||
def _split_explanation(explanation: str) -> List[str]:
|
def _split_explanation(explanation: str) -> list[str]:
|
||||||
r"""Return a list of individual lines in the explanation.
|
r"""Return a list of individual lines in the explanation.
|
||||||
|
|
||||||
This will return a list of lines split on '\n{', '\n}' and '\n~'.
|
This will return a list of lines split on '\n{', '\n}' and '\n~'.
|
||||||
|
@ -75,7 +75,7 @@ def _split_explanation(explanation: str) -> List[str]:
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def _format_lines(lines: Sequence[str]) -> List[str]:
|
def _format_lines(lines: Sequence[str]) -> list[str]:
|
||||||
"""Format the individual lines.
|
"""Format the individual lines.
|
||||||
|
|
||||||
This will replace the '{', '}' and '~' characters of our mini formatting
|
This will replace the '{', '}' and '~' characters of our mini formatting
|
||||||
|
@ -169,7 +169,7 @@ def has_default_eq(
|
||||||
|
|
||||||
def assertrepr_compare(
|
def assertrepr_compare(
|
||||||
config, op: str, left: Any, right: Any, use_ascii: bool = False
|
config, op: str, left: Any, right: Any, use_ascii: bool = False
|
||||||
) -> Optional[List[str]]:
|
) -> list[str] | None:
|
||||||
"""Return specialised explanations for some operators/operands."""
|
"""Return specialised explanations for some operators/operands."""
|
||||||
verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
|
verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ def assertrepr_compare(
|
||||||
|
|
||||||
def _compare_eq_any(
|
def _compare_eq_any(
|
||||||
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0
|
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
explanation = []
|
explanation = []
|
||||||
if istext(left) and istext(right):
|
if istext(left) and istext(right):
|
||||||
explanation = _diff_text(left, right, verbose)
|
explanation = _diff_text(left, right, verbose)
|
||||||
|
@ -274,7 +274,7 @@ def _compare_eq_any(
|
||||||
return explanation
|
return explanation
|
||||||
|
|
||||||
|
|
||||||
def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
|
def _diff_text(left: str, right: str, verbose: int = 0) -> list[str]:
|
||||||
"""Return the explanation for the diff between text.
|
"""Return the explanation for the diff between text.
|
||||||
|
|
||||||
Unless --verbose is used this will skip leading and trailing
|
Unless --verbose is used this will skip leading and trailing
|
||||||
|
@ -282,7 +282,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
|
||||||
"""
|
"""
|
||||||
from difflib import ndiff
|
from difflib import ndiff
|
||||||
|
|
||||||
explanation: List[str] = []
|
explanation: list[str] = []
|
||||||
|
|
||||||
if verbose < 1:
|
if verbose < 1:
|
||||||
i = 0 # just in case left or right has zero length
|
i = 0 # just in case left or right has zero length
|
||||||
|
@ -327,7 +327,7 @@ def _compare_eq_iterable(
|
||||||
right: Iterable[Any],
|
right: Iterable[Any],
|
||||||
highlighter: _HighlightFunc,
|
highlighter: _HighlightFunc,
|
||||||
verbose: int = 0,
|
verbose: int = 0,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
if verbose <= 0 and not running_on_ci():
|
if verbose <= 0 and not running_on_ci():
|
||||||
return ["Use -v to get more diff"]
|
return ["Use -v to get more diff"]
|
||||||
# dynamic import to speedup pytest
|
# dynamic import to speedup pytest
|
||||||
|
@ -356,9 +356,9 @@ def _compare_eq_sequence(
|
||||||
right: Sequence[Any],
|
right: Sequence[Any],
|
||||||
highlighter: _HighlightFunc,
|
highlighter: _HighlightFunc,
|
||||||
verbose: int = 0,
|
verbose: int = 0,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
|
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
|
||||||
explanation: List[str] = []
|
explanation: list[str] = []
|
||||||
len_left = len(left)
|
len_left = len(left)
|
||||||
len_right = len(right)
|
len_right = len(right)
|
||||||
for i in range(min(len_left, len_right)):
|
for i in range(min(len_left, len_right)):
|
||||||
|
@ -417,7 +417,7 @@ def _compare_eq_set(
|
||||||
right: AbstractSet[Any],
|
right: AbstractSet[Any],
|
||||||
highlighter: _HighlightFunc,
|
highlighter: _HighlightFunc,
|
||||||
verbose: int = 0,
|
verbose: int = 0,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
explanation = []
|
explanation = []
|
||||||
explanation.extend(_set_one_sided_diff("left", left, right, highlighter))
|
explanation.extend(_set_one_sided_diff("left", left, right, highlighter))
|
||||||
explanation.extend(_set_one_sided_diff("right", right, left, highlighter))
|
explanation.extend(_set_one_sided_diff("right", right, left, highlighter))
|
||||||
|
@ -429,7 +429,7 @@ def _compare_gt_set(
|
||||||
right: AbstractSet[Any],
|
right: AbstractSet[Any],
|
||||||
highlighter: _HighlightFunc,
|
highlighter: _HighlightFunc,
|
||||||
verbose: int = 0,
|
verbose: int = 0,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
explanation = _compare_gte_set(left, right, highlighter)
|
explanation = _compare_gte_set(left, right, highlighter)
|
||||||
if not explanation:
|
if not explanation:
|
||||||
return ["Both sets are equal"]
|
return ["Both sets are equal"]
|
||||||
|
@ -441,7 +441,7 @@ def _compare_lt_set(
|
||||||
right: AbstractSet[Any],
|
right: AbstractSet[Any],
|
||||||
highlighter: _HighlightFunc,
|
highlighter: _HighlightFunc,
|
||||||
verbose: int = 0,
|
verbose: int = 0,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
explanation = _compare_lte_set(left, right, highlighter)
|
explanation = _compare_lte_set(left, right, highlighter)
|
||||||
if not explanation:
|
if not explanation:
|
||||||
return ["Both sets are equal"]
|
return ["Both sets are equal"]
|
||||||
|
@ -453,7 +453,7 @@ def _compare_gte_set(
|
||||||
right: AbstractSet[Any],
|
right: AbstractSet[Any],
|
||||||
highlighter: _HighlightFunc,
|
highlighter: _HighlightFunc,
|
||||||
verbose: int = 0,
|
verbose: int = 0,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
return _set_one_sided_diff("right", right, left, highlighter)
|
return _set_one_sided_diff("right", right, left, highlighter)
|
||||||
|
|
||||||
|
|
||||||
|
@ -462,7 +462,7 @@ def _compare_lte_set(
|
||||||
right: AbstractSet[Any],
|
right: AbstractSet[Any],
|
||||||
highlighter: _HighlightFunc,
|
highlighter: _HighlightFunc,
|
||||||
verbose: int = 0,
|
verbose: int = 0,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
return _set_one_sided_diff("left", left, right, highlighter)
|
return _set_one_sided_diff("left", left, right, highlighter)
|
||||||
|
|
||||||
|
|
||||||
|
@ -471,7 +471,7 @@ def _set_one_sided_diff(
|
||||||
set1: AbstractSet[Any],
|
set1: AbstractSet[Any],
|
||||||
set2: AbstractSet[Any],
|
set2: AbstractSet[Any],
|
||||||
highlighter: _HighlightFunc,
|
highlighter: _HighlightFunc,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
explanation = []
|
explanation = []
|
||||||
diff = set1 - set2
|
diff = set1 - set2
|
||||||
if diff:
|
if diff:
|
||||||
|
@ -486,8 +486,8 @@ def _compare_eq_dict(
|
||||||
right: Mapping[Any, Any],
|
right: Mapping[Any, Any],
|
||||||
highlighter: _HighlightFunc,
|
highlighter: _HighlightFunc,
|
||||||
verbose: int = 0,
|
verbose: int = 0,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
explanation: List[str] = []
|
explanation: list[str] = []
|
||||||
set_left = set(left)
|
set_left = set(left)
|
||||||
set_right = set(right)
|
set_right = set(right)
|
||||||
common = set_left.intersection(set_right)
|
common = set_left.intersection(set_right)
|
||||||
|
@ -531,7 +531,7 @@ def _compare_eq_dict(
|
||||||
|
|
||||||
def _compare_eq_cls(
|
def _compare_eq_cls(
|
||||||
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int
|
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
if not has_default_eq(left):
|
if not has_default_eq(left):
|
||||||
return []
|
return []
|
||||||
if isdatacls(left):
|
if isdatacls(left):
|
||||||
|
@ -584,7 +584,7 @@ def _compare_eq_cls(
|
||||||
return explanation
|
return explanation
|
||||||
|
|
||||||
|
|
||||||
def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]:
|
def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]:
|
||||||
index = text.find(term)
|
index = text.find(term)
|
||||||
head = text[:index]
|
head = text[:index]
|
||||||
tail = text[index + len(term) :]
|
tail = text[index + len(term) :]
|
||||||
|
|
|
@ -3,20 +3,17 @@
|
||||||
|
|
||||||
# This plugin was not named "cache" to avoid conflicts with the external
|
# This plugin was not named "cache" to avoid conflicts with the external
|
||||||
# pytest-cache version.
|
# pytest-cache version.
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import errno
|
import errno
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import Dict
|
|
||||||
from typing import final
|
from typing import final
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Set
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from .pathlib import resolve_from_str
|
from .pathlib import resolve_from_str
|
||||||
from .pathlib import rm_rf
|
from .pathlib import rm_rf
|
||||||
|
@ -77,7 +74,7 @@ class Cache:
|
||||||
self._config = config
|
self._config = config
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache":
|
def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache:
|
||||||
"""Create the Cache instance for a Config.
|
"""Create the Cache instance for a Config.
|
||||||
|
|
||||||
:meta private:
|
:meta private:
|
||||||
|
@ -249,7 +246,7 @@ class Cache:
|
||||||
|
|
||||||
|
|
||||||
class LFPluginCollWrapper:
|
class LFPluginCollWrapper:
|
||||||
def __init__(self, lfplugin: "LFPlugin") -> None:
|
def __init__(self, lfplugin: LFPlugin) -> None:
|
||||||
self.lfplugin = lfplugin
|
self.lfplugin = lfplugin
|
||||||
self._collected_at_least_one_failure = False
|
self._collected_at_least_one_failure = False
|
||||||
|
|
||||||
|
@ -263,7 +260,7 @@ class LFPluginCollWrapper:
|
||||||
lf_paths = self.lfplugin._last_failed_paths
|
lf_paths = self.lfplugin._last_failed_paths
|
||||||
|
|
||||||
# Use stable sort to prioritize last failed.
|
# Use stable sort to prioritize last failed.
|
||||||
def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool:
|
def sort_key(node: nodes.Item | nodes.Collector) -> bool:
|
||||||
return node.path in lf_paths
|
return node.path in lf_paths
|
||||||
|
|
||||||
res.result = sorted(
|
res.result = sorted(
|
||||||
|
@ -301,13 +298,13 @@ class LFPluginCollWrapper:
|
||||||
|
|
||||||
|
|
||||||
class LFPluginCollSkipfiles:
|
class LFPluginCollSkipfiles:
|
||||||
def __init__(self, lfplugin: "LFPlugin") -> None:
|
def __init__(self, lfplugin: LFPlugin) -> None:
|
||||||
self.lfplugin = lfplugin
|
self.lfplugin = lfplugin
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def pytest_make_collect_report(
|
def pytest_make_collect_report(
|
||||||
self, collector: nodes.Collector
|
self, collector: nodes.Collector
|
||||||
) -> Optional[CollectReport]:
|
) -> CollectReport | None:
|
||||||
if isinstance(collector, File):
|
if isinstance(collector, File):
|
||||||
if collector.path not in self.lfplugin._last_failed_paths:
|
if collector.path not in self.lfplugin._last_failed_paths:
|
||||||
self.lfplugin._skipped_files += 1
|
self.lfplugin._skipped_files += 1
|
||||||
|
@ -326,9 +323,9 @@ class LFPlugin:
|
||||||
active_keys = "lf", "failedfirst"
|
active_keys = "lf", "failedfirst"
|
||||||
self.active = any(config.getoption(key) for key in active_keys)
|
self.active = any(config.getoption(key) for key in active_keys)
|
||||||
assert config.cache
|
assert config.cache
|
||||||
self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {})
|
self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {})
|
||||||
self._previously_failed_count: Optional[int] = None
|
self._previously_failed_count: int | None = None
|
||||||
self._report_status: Optional[str] = None
|
self._report_status: str | None = None
|
||||||
self._skipped_files = 0 # count skipped files during collection due to --lf
|
self._skipped_files = 0 # count skipped files during collection due to --lf
|
||||||
|
|
||||||
if config.getoption("lf"):
|
if config.getoption("lf"):
|
||||||
|
@ -337,7 +334,7 @@ class LFPlugin:
|
||||||
LFPluginCollWrapper(self), "lfplugin-collwrapper"
|
LFPluginCollWrapper(self), "lfplugin-collwrapper"
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_last_failed_paths(self) -> Set[Path]:
|
def get_last_failed_paths(self) -> set[Path]:
|
||||||
"""Return a set with all Paths of the previously failed nodeids and
|
"""Return a set with all Paths of the previously failed nodeids and
|
||||||
their parents."""
|
their parents."""
|
||||||
rootpath = self.config.rootpath
|
rootpath = self.config.rootpath
|
||||||
|
@ -348,7 +345,7 @@ class LFPlugin:
|
||||||
result.update(path.parents)
|
result.update(path.parents)
|
||||||
return {x for x in result if x.exists()}
|
return {x for x in result if x.exists()}
|
||||||
|
|
||||||
def pytest_report_collectionfinish(self) -> Optional[str]:
|
def pytest_report_collectionfinish(self) -> str | None:
|
||||||
if self.active and self.config.getoption("verbose") >= 0:
|
if self.active and self.config.getoption("verbose") >= 0:
|
||||||
return f"run-last-failure: {self._report_status}"
|
return f"run-last-failure: {self._report_status}"
|
||||||
return None
|
return None
|
||||||
|
@ -370,7 +367,7 @@ class LFPlugin:
|
||||||
|
|
||||||
@hookimpl(wrapper=True, tryfirst=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_collection_modifyitems(
|
def pytest_collection_modifyitems(
|
||||||
self, config: Config, items: List[nodes.Item]
|
self, config: Config, items: list[nodes.Item]
|
||||||
) -> Generator[None, None, None]:
|
) -> Generator[None, None, None]:
|
||||||
res = yield
|
res = yield
|
||||||
|
|
||||||
|
@ -442,13 +439,13 @@ class NFPlugin:
|
||||||
|
|
||||||
@hookimpl(wrapper=True, tryfirst=True)
|
@hookimpl(wrapper=True, tryfirst=True)
|
||||||
def pytest_collection_modifyitems(
|
def pytest_collection_modifyitems(
|
||||||
self, items: List[nodes.Item]
|
self, items: list[nodes.Item]
|
||||||
) -> Generator[None, None, None]:
|
) -> Generator[None, None, None]:
|
||||||
res = yield
|
res = yield
|
||||||
|
|
||||||
if self.active:
|
if self.active:
|
||||||
new_items: Dict[str, nodes.Item] = {}
|
new_items: dict[str, nodes.Item] = {}
|
||||||
other_items: Dict[str, nodes.Item] = {}
|
other_items: dict[str, nodes.Item] = {}
|
||||||
for item in items:
|
for item in items:
|
||||||
if item.nodeid not in self.cached_nodeids:
|
if item.nodeid not in self.cached_nodeids:
|
||||||
new_items[item.nodeid] = item
|
new_items[item.nodeid] = item
|
||||||
|
@ -464,7 +461,7 @@ class NFPlugin:
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]:
|
def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]:
|
||||||
return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True)
|
return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True)
|
||||||
|
|
||||||
def pytest_sessionfinish(self) -> None:
|
def pytest_sessionfinish(self) -> None:
|
||||||
|
@ -541,7 +538,7 @@ def pytest_addoption(parser: Parser) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
|
||||||
if config.option.cacheshow and not config.option.help:
|
if config.option.cacheshow and not config.option.help:
|
||||||
from _pytest.main import wrap_session
|
from _pytest.main import wrap_session
|
||||||
|
|
||||||
|
@ -572,7 +569,7 @@ def cache(request: FixtureRequest) -> Cache:
|
||||||
return request.config.cache
|
return request.config.cache
|
||||||
|
|
||||||
|
|
||||||
def pytest_report_header(config: Config) -> Optional[str]:
|
def pytest_report_header(config: Config) -> str | None:
|
||||||
"""Display cachedir with --cache-show and if non-default."""
|
"""Display cachedir with --cache-show and if non-default."""
|
||||||
if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache":
|
if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache":
|
||||||
assert config.cache is not None
|
assert config.cache is not None
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
"""Per-test stdout/stderr capturing mechanism."""
|
"""Per-test stdout/stderr capturing mechanism."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import collections
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
|
@ -19,15 +21,14 @@ from typing import Generator
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from typing import List
|
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
from typing import Optional
|
|
||||||
from typing import TextIO
|
from typing import TextIO
|
||||||
from typing import Tuple
|
|
||||||
from typing import Type
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
|
@ -213,7 +214,7 @@ class DontReadFromInput(TextIO):
|
||||||
def __next__(self) -> str:
|
def __next__(self) -> str:
|
||||||
return self.readline()
|
return self.readline()
|
||||||
|
|
||||||
def readlines(self, hint: Optional[int] = -1) -> List[str]:
|
def readlines(self, hint: int | None = -1) -> list[str]:
|
||||||
raise OSError(
|
raise OSError(
|
||||||
"pytest: reading from stdin while output is captured! Consider using `-s`."
|
"pytest: reading from stdin while output is captured! Consider using `-s`."
|
||||||
)
|
)
|
||||||
|
@ -245,7 +246,7 @@ class DontReadFromInput(TextIO):
|
||||||
def tell(self) -> int:
|
def tell(self) -> int:
|
||||||
raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()")
|
raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()")
|
||||||
|
|
||||||
def truncate(self, size: Optional[int] = None) -> int:
|
def truncate(self, size: int | None = None) -> int:
|
||||||
raise UnsupportedOperation("cannot truncate stdin")
|
raise UnsupportedOperation("cannot truncate stdin")
|
||||||
|
|
||||||
def write(self, data: str) -> int:
|
def write(self, data: str) -> int:
|
||||||
|
@ -257,14 +258,14 @@ class DontReadFromInput(TextIO):
|
||||||
def writable(self) -> bool:
|
def writable(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __enter__(self) -> "DontReadFromInput":
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(
|
def __exit__(
|
||||||
self,
|
self,
|
||||||
type: Optional[Type[BaseException]],
|
type: type[BaseException] | None,
|
||||||
value: Optional[BaseException],
|
value: BaseException | None,
|
||||||
traceback: Optional[TracebackType],
|
traceback: TracebackType | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -339,7 +340,7 @@ class NoCapture(CaptureBase[str]):
|
||||||
|
|
||||||
class SysCaptureBase(CaptureBase[AnyStr]):
|
class SysCaptureBase(CaptureBase[AnyStr]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False
|
self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
name = patchsysdict[fd]
|
name = patchsysdict[fd]
|
||||||
self._old: TextIO = getattr(sys, name)
|
self._old: TextIO = getattr(sys, name)
|
||||||
|
@ -370,7 +371,7 @@ class SysCaptureBase(CaptureBase[AnyStr]):
|
||||||
self.tmpfile,
|
self.tmpfile,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _assert_state(self, op: str, states: Tuple[str, ...]) -> None:
|
def _assert_state(self, op: str, states: tuple[str, ...]) -> None:
|
||||||
assert (
|
assert (
|
||||||
self._state in states
|
self._state in states
|
||||||
), "cannot {} in state {!r}: expected one of {}".format(
|
), "cannot {} in state {!r}: expected one of {}".format(
|
||||||
|
@ -457,7 +458,7 @@ class FDCaptureBase(CaptureBase[AnyStr]):
|
||||||
# Further complications are the need to support suspend() and the
|
# Further complications are the need to support suspend() and the
|
||||||
# possibility of FD reuse (e.g. the tmpfile getting the very same
|
# possibility of FD reuse (e.g. the tmpfile getting the very same
|
||||||
# target FD). The following approach is robust, I believe.
|
# target FD). The following approach is robust, I believe.
|
||||||
self.targetfd_invalid: Optional[int] = os.open(os.devnull, os.O_RDWR)
|
self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR)
|
||||||
os.dup2(self.targetfd_invalid, targetfd)
|
os.dup2(self.targetfd_invalid, targetfd)
|
||||||
else:
|
else:
|
||||||
self.targetfd_invalid = None
|
self.targetfd_invalid = None
|
||||||
|
@ -487,7 +488,7 @@ class FDCaptureBase(CaptureBase[AnyStr]):
|
||||||
f"_state={self._state!r} tmpfile={self.tmpfile!r}>"
|
f"_state={self._state!r} tmpfile={self.tmpfile!r}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _assert_state(self, op: str, states: Tuple[str, ...]) -> None:
|
def _assert_state(self, op: str, states: tuple[str, ...]) -> None:
|
||||||
assert (
|
assert (
|
||||||
self._state in states
|
self._state in states
|
||||||
), "cannot {} in state {!r}: expected one of {}".format(
|
), "cannot {} in state {!r}: expected one of {}".format(
|
||||||
|
@ -609,13 +610,13 @@ class MultiCapture(Generic[AnyStr]):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
in_: Optional[CaptureBase[AnyStr]],
|
in_: CaptureBase[AnyStr] | None,
|
||||||
out: Optional[CaptureBase[AnyStr]],
|
out: CaptureBase[AnyStr] | None,
|
||||||
err: Optional[CaptureBase[AnyStr]],
|
err: CaptureBase[AnyStr] | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.in_: Optional[CaptureBase[AnyStr]] = in_
|
self.in_: CaptureBase[AnyStr] | None = in_
|
||||||
self.out: Optional[CaptureBase[AnyStr]] = out
|
self.out: CaptureBase[AnyStr] | None = out
|
||||||
self.err: Optional[CaptureBase[AnyStr]] = err
|
self.err: CaptureBase[AnyStr] | None = err
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
@ -632,7 +633,7 @@ class MultiCapture(Generic[AnyStr]):
|
||||||
if self.err:
|
if self.err:
|
||||||
self.err.start()
|
self.err.start()
|
||||||
|
|
||||||
def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]:
|
def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]:
|
||||||
"""Pop current snapshot out/err capture and flush to orig streams."""
|
"""Pop current snapshot out/err capture and flush to orig streams."""
|
||||||
out, err = self.readouterr()
|
out, err = self.readouterr()
|
||||||
if out:
|
if out:
|
||||||
|
@ -725,8 +726,8 @@ class CaptureManager:
|
||||||
|
|
||||||
def __init__(self, method: _CaptureMethod) -> None:
|
def __init__(self, method: _CaptureMethod) -> None:
|
||||||
self._method: Final = method
|
self._method: Final = method
|
||||||
self._global_capturing: Optional[MultiCapture[str]] = None
|
self._global_capturing: MultiCapture[str] | None = None
|
||||||
self._capture_fixture: Optional[CaptureFixture[Any]] = None
|
self._capture_fixture: CaptureFixture[Any] | None = None
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
@ -734,7 +735,7 @@ class CaptureManager:
|
||||||
f"_capture_fixture={self._capture_fixture!r}>"
|
f"_capture_fixture={self._capture_fixture!r}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_capturing(self) -> Union[str, bool]:
|
def is_capturing(self) -> str | bool:
|
||||||
if self.is_globally_capturing():
|
if self.is_globally_capturing():
|
||||||
return "global"
|
return "global"
|
||||||
if self._capture_fixture:
|
if self._capture_fixture:
|
||||||
|
@ -782,7 +783,7 @@ class CaptureManager:
|
||||||
|
|
||||||
# Fixture Control
|
# Fixture Control
|
||||||
|
|
||||||
def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None:
|
def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None:
|
||||||
if self._capture_fixture:
|
if self._capture_fixture:
|
||||||
current_fixture = self._capture_fixture.request.fixturename
|
current_fixture = self._capture_fixture.request.fixturename
|
||||||
requested_fixture = capture_fixture.request.fixturename
|
requested_fixture = capture_fixture.request.fixturename
|
||||||
|
@ -897,15 +898,15 @@ class CaptureFixture(Generic[AnyStr]):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
captureclass: Type[CaptureBase[AnyStr]],
|
captureclass: type[CaptureBase[AnyStr]],
|
||||||
request: SubRequest,
|
request: SubRequest,
|
||||||
*,
|
*,
|
||||||
_ispytest: bool = False,
|
_ispytest: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
check_ispytest(_ispytest)
|
check_ispytest(_ispytest)
|
||||||
self.captureclass: Type[CaptureBase[AnyStr]] = captureclass
|
self.captureclass: type[CaptureBase[AnyStr]] = captureclass
|
||||||
self.request = request
|
self.request = request
|
||||||
self._capture: Optional[MultiCapture[AnyStr]] = None
|
self._capture: MultiCapture[AnyStr] | None = None
|
||||||
self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER
|
self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER
|
||||||
self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER
|
self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
"""Command line options, ini-file and conftest.py processing."""
|
"""Command line options, ini-file and conftest.py processing."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import collections.abc
|
import collections.abc
|
||||||
import copy
|
import copy
|
||||||
|
@ -11,7 +13,7 @@ import glob
|
||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
@ -21,22 +23,16 @@ from types import FunctionType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from typing import Dict
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
from typing import final
|
from typing import final
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
from typing import IO
|
from typing import IO
|
||||||
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 Sequence
|
from typing import Sequence
|
||||||
from typing import Set
|
|
||||||
from typing import TextIO
|
from typing import TextIO
|
||||||
from typing import Tuple
|
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Union
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pluggy
|
import pluggy
|
||||||
|
@ -118,7 +114,7 @@ class ExitCode(enum.IntEnum):
|
||||||
class ConftestImportFailure(Exception):
|
class ConftestImportFailure(Exception):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
path: Path,
|
path: pathlib.Path,
|
||||||
*,
|
*,
|
||||||
cause: Exception,
|
cause: Exception,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -141,9 +137,9 @@ def filter_traceback_for_conftest_import_failure(
|
||||||
|
|
||||||
|
|
||||||
def main(
|
def main(
|
||||||
args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
|
args: list[str] | os.PathLike[str] | None = None,
|
||||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
plugins: Sequence[str | _PluggyPlugin] | None = None,
|
||||||
) -> Union[int, ExitCode]:
|
) -> int | ExitCode:
|
||||||
"""Perform an in-process test run.
|
"""Perform an in-process test run.
|
||||||
|
|
||||||
:param args:
|
:param args:
|
||||||
|
@ -176,9 +172,7 @@ def main(
|
||||||
return ExitCode.USAGE_ERROR
|
return ExitCode.USAGE_ERROR
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
|
ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
|
||||||
config=config
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
return ExitCode(ret)
|
return ExitCode(ret)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -286,9 +280,9 @@ builtin_plugins.add("pytester_assertions")
|
||||||
|
|
||||||
|
|
||||||
def get_config(
|
def get_config(
|
||||||
args: Optional[List[str]] = None,
|
args: list[str] | None = None,
|
||||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
plugins: Sequence[str | _PluggyPlugin] | None = None,
|
||||||
) -> "Config":
|
) -> Config:
|
||||||
# subsequent calls to main will create a fresh instance
|
# subsequent calls to main will create a fresh instance
|
||||||
pluginmanager = PytestPluginManager()
|
pluginmanager = PytestPluginManager()
|
||||||
config = Config(
|
config = Config(
|
||||||
|
@ -296,7 +290,7 @@ def get_config(
|
||||||
invocation_params=Config.InvocationParams(
|
invocation_params=Config.InvocationParams(
|
||||||
args=args or (),
|
args=args or (),
|
||||||
plugins=plugins,
|
plugins=plugins,
|
||||||
dir=Path.cwd(),
|
dir=pathlib.Path.cwd(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -310,7 +304,7 @@ def get_config(
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_manager() -> "PytestPluginManager":
|
def get_plugin_manager() -> PytestPluginManager:
|
||||||
"""Obtain a new instance of the
|
"""Obtain a new instance of the
|
||||||
:py:class:`pytest.PytestPluginManager`, with default plugins
|
:py:class:`pytest.PytestPluginManager`, with default plugins
|
||||||
already loaded.
|
already loaded.
|
||||||
|
@ -322,9 +316,9 @@ def get_plugin_manager() -> "PytestPluginManager":
|
||||||
|
|
||||||
|
|
||||||
def _prepareconfig(
|
def _prepareconfig(
|
||||||
args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
|
args: list[str] | os.PathLike[str] | None = None,
|
||||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
plugins: Sequence[str | _PluggyPlugin] | None = None,
|
||||||
) -> "Config":
|
) -> Config:
|
||||||
if args is None:
|
if args is None:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
elif isinstance(args, os.PathLike):
|
elif isinstance(args, os.PathLike):
|
||||||
|
@ -353,7 +347,7 @@ def _prepareconfig(
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def _get_directory(path: Path) -> Path:
|
def _get_directory(path: pathlib.Path) -> pathlib.Path:
|
||||||
"""Get the directory of a path - itself if already a directory."""
|
"""Get the directory of a path - itself if already a directory."""
|
||||||
if path.is_file():
|
if path.is_file():
|
||||||
return path.parent
|
return path.parent
|
||||||
|
@ -364,14 +358,14 @@ def _get_directory(path: Path) -> Path:
|
||||||
def _get_legacy_hook_marks(
|
def _get_legacy_hook_marks(
|
||||||
method: Any,
|
method: Any,
|
||||||
hook_type: str,
|
hook_type: str,
|
||||||
opt_names: Tuple[str, ...],
|
opt_names: tuple[str, ...],
|
||||||
) -> Dict[str, bool]:
|
) -> dict[str, bool]:
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# abuse typeguard from importlib to avoid massive method type union thats lacking a alias
|
# abuse typeguard from importlib to avoid massive method type union thats lacking a alias
|
||||||
assert inspect.isroutine(method)
|
assert inspect.isroutine(method)
|
||||||
known_marks: Set[str] = {m.name for m in getattr(method, "pytestmark", [])}
|
known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])}
|
||||||
must_warn: List[str] = []
|
must_warn: list[str] = []
|
||||||
opts: Dict[str, bool] = {}
|
opts: dict[str, bool] = {}
|
||||||
for opt_name in opt_names:
|
for opt_name in opt_names:
|
||||||
opt_attr = getattr(method, opt_name, AttributeError)
|
opt_attr = getattr(method, opt_name, AttributeError)
|
||||||
if opt_attr is not AttributeError:
|
if opt_attr is not AttributeError:
|
||||||
|
@ -410,13 +404,13 @@ class PytestPluginManager(PluginManager):
|
||||||
|
|
||||||
# -- State related to local conftest plugins.
|
# -- State related to local conftest plugins.
|
||||||
# All loaded conftest modules.
|
# All loaded conftest modules.
|
||||||
self._conftest_plugins: Set[types.ModuleType] = set()
|
self._conftest_plugins: set[types.ModuleType] = set()
|
||||||
# All conftest modules applicable for a directory.
|
# All conftest modules applicable for a directory.
|
||||||
# This includes the directory's own conftest modules as well
|
# This includes the directory's own conftest modules as well
|
||||||
# as those of its parent directories.
|
# as those of its parent directories.
|
||||||
self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {}
|
self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {}
|
||||||
# Cutoff directory above which conftests are no longer discovered.
|
# Cutoff directory above which conftests are no longer discovered.
|
||||||
self._confcutdir: Optional[Path] = None
|
self._confcutdir: pathlib.Path | None = None
|
||||||
# If set, conftest loading is skipped.
|
# If set, conftest loading is skipped.
|
||||||
self._noconftest = False
|
self._noconftest = False
|
||||||
|
|
||||||
|
@ -430,7 +424,7 @@ class PytestPluginManager(PluginManager):
|
||||||
# previously we would issue a warning when a plugin was skipped, but
|
# previously we would issue a warning when a plugin was skipped, but
|
||||||
# since we refactored warnings as first citizens of Config, they are
|
# since we refactored warnings as first citizens of Config, they are
|
||||||
# just stored here to be used later.
|
# just stored here to be used later.
|
||||||
self.skipped_plugins: List[Tuple[str, str]] = []
|
self.skipped_plugins: list[tuple[str, str]] = []
|
||||||
|
|
||||||
self.add_hookspecs(_pytest.hookspec)
|
self.add_hookspecs(_pytest.hookspec)
|
||||||
self.register(self)
|
self.register(self)
|
||||||
|
@ -456,7 +450,7 @@ class PytestPluginManager(PluginManager):
|
||||||
|
|
||||||
def parse_hookimpl_opts(
|
def parse_hookimpl_opts(
|
||||||
self, plugin: _PluggyPlugin, name: str
|
self, plugin: _PluggyPlugin, name: str
|
||||||
) -> Optional[HookimplOpts]:
|
) -> HookimplOpts | None:
|
||||||
""":meta private:"""
|
""":meta private:"""
|
||||||
# pytest hooks are always prefixed with "pytest_",
|
# pytest hooks are always prefixed with "pytest_",
|
||||||
# so we avoid accessing possibly non-readable attributes
|
# so we avoid accessing possibly non-readable attributes
|
||||||
|
@ -480,7 +474,7 @@ class PytestPluginManager(PluginManager):
|
||||||
method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
|
method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
|
||||||
)
|
)
|
||||||
|
|
||||||
def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOpts]:
|
def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None:
|
||||||
""":meta private:"""
|
""":meta private:"""
|
||||||
opts = super().parse_hookspec_opts(module_or_class, name)
|
opts = super().parse_hookspec_opts(module_or_class, name)
|
||||||
if opts is None:
|
if opts is None:
|
||||||
|
@ -493,9 +487,7 @@ class PytestPluginManager(PluginManager):
|
||||||
)
|
)
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
def register(
|
def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None:
|
||||||
self, plugin: _PluggyPlugin, name: Optional[str] = None
|
|
||||||
) -> Optional[str]:
|
|
||||||
if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
|
if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
PytestConfigWarning(
|
PytestConfigWarning(
|
||||||
|
@ -522,14 +514,14 @@ class PytestPluginManager(PluginManager):
|
||||||
|
|
||||||
def getplugin(self, name: str):
|
def getplugin(self, name: str):
|
||||||
# Support deprecated naming because plugins (xdist e.g.) use it.
|
# Support deprecated naming because plugins (xdist e.g.) use it.
|
||||||
plugin: Optional[_PluggyPlugin] = self.get_plugin(name)
|
plugin: _PluggyPlugin | None = self.get_plugin(name)
|
||||||
return plugin
|
return plugin
|
||||||
|
|
||||||
def hasplugin(self, name: str) -> bool:
|
def hasplugin(self, name: str) -> bool:
|
||||||
"""Return whether a plugin with the given name is registered."""
|
"""Return whether a plugin with the given name is registered."""
|
||||||
return bool(self.get_plugin(name))
|
return bool(self.get_plugin(name))
|
||||||
|
|
||||||
def pytest_configure(self, config: "Config") -> None:
|
def pytest_configure(self, config: Config) -> None:
|
||||||
""":meta private:"""
|
""":meta private:"""
|
||||||
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
|
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
|
||||||
# we should remove tryfirst/trylast as markers.
|
# we should remove tryfirst/trylast as markers.
|
||||||
|
@ -552,13 +544,13 @@ class PytestPluginManager(PluginManager):
|
||||||
#
|
#
|
||||||
def _set_initial_conftests(
|
def _set_initial_conftests(
|
||||||
self,
|
self,
|
||||||
args: Sequence[Union[str, Path]],
|
args: Sequence[str | pathlib.Path],
|
||||||
pyargs: bool,
|
pyargs: bool,
|
||||||
noconftest: bool,
|
noconftest: bool,
|
||||||
rootpath: Path,
|
rootpath: pathlib.Path,
|
||||||
confcutdir: Optional[Path],
|
confcutdir: pathlib.Path | None,
|
||||||
invocation_dir: Path,
|
invocation_dir: pathlib.Path,
|
||||||
importmode: Union[ImportMode, str],
|
importmode: ImportMode | str,
|
||||||
*,
|
*,
|
||||||
consider_namespace_packages: bool,
|
consider_namespace_packages: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -601,7 +593,7 @@ class PytestPluginManager(PluginManager):
|
||||||
consider_namespace_packages=consider_namespace_packages,
|
consider_namespace_packages=consider_namespace_packages,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _is_in_confcutdir(self, path: Path) -> bool:
|
def _is_in_confcutdir(self, path: pathlib.Path) -> bool:
|
||||||
"""Whether to consider the given path to load conftests from."""
|
"""Whether to consider the given path to load conftests from."""
|
||||||
if self._confcutdir is None:
|
if self._confcutdir is None:
|
||||||
return True
|
return True
|
||||||
|
@ -618,9 +610,9 @@ class PytestPluginManager(PluginManager):
|
||||||
|
|
||||||
def _try_load_conftest(
|
def _try_load_conftest(
|
||||||
self,
|
self,
|
||||||
anchor: Path,
|
anchor: pathlib.Path,
|
||||||
importmode: Union[str, ImportMode],
|
importmode: str | ImportMode,
|
||||||
rootpath: Path,
|
rootpath: pathlib.Path,
|
||||||
*,
|
*,
|
||||||
consider_namespace_packages: bool,
|
consider_namespace_packages: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -643,9 +635,9 @@ class PytestPluginManager(PluginManager):
|
||||||
|
|
||||||
def _loadconftestmodules(
|
def _loadconftestmodules(
|
||||||
self,
|
self,
|
||||||
path: Path,
|
path: pathlib.Path,
|
||||||
importmode: Union[str, ImportMode],
|
importmode: str | ImportMode,
|
||||||
rootpath: Path,
|
rootpath: pathlib.Path,
|
||||||
*,
|
*,
|
||||||
consider_namespace_packages: bool,
|
consider_namespace_packages: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -673,15 +665,15 @@ class PytestPluginManager(PluginManager):
|
||||||
clist.append(mod)
|
clist.append(mod)
|
||||||
self._dirpath2confmods[directory] = clist
|
self._dirpath2confmods[directory] = clist
|
||||||
|
|
||||||
def _getconftestmodules(self, path: Path) -> Sequence[types.ModuleType]:
|
def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]:
|
||||||
directory = self._get_directory(path)
|
directory = self._get_directory(path)
|
||||||
return self._dirpath2confmods.get(directory, ())
|
return self._dirpath2confmods.get(directory, ())
|
||||||
|
|
||||||
def _rget_with_confmod(
|
def _rget_with_confmod(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
path: Path,
|
path: pathlib.Path,
|
||||||
) -> Tuple[types.ModuleType, Any]:
|
) -> tuple[types.ModuleType, Any]:
|
||||||
modules = self._getconftestmodules(path)
|
modules = self._getconftestmodules(path)
|
||||||
for mod in reversed(modules):
|
for mod in reversed(modules):
|
||||||
try:
|
try:
|
||||||
|
@ -692,9 +684,9 @@ class PytestPluginManager(PluginManager):
|
||||||
|
|
||||||
def _importconftest(
|
def _importconftest(
|
||||||
self,
|
self,
|
||||||
conftestpath: Path,
|
conftestpath: pathlib.Path,
|
||||||
importmode: Union[str, ImportMode],
|
importmode: str | ImportMode,
|
||||||
rootpath: Path,
|
rootpath: pathlib.Path,
|
||||||
*,
|
*,
|
||||||
consider_namespace_packages: bool,
|
consider_namespace_packages: bool,
|
||||||
) -> types.ModuleType:
|
) -> types.ModuleType:
|
||||||
|
@ -746,7 +738,7 @@ class PytestPluginManager(PluginManager):
|
||||||
def _check_non_top_pytest_plugins(
|
def _check_non_top_pytest_plugins(
|
||||||
self,
|
self,
|
||||||
mod: types.ModuleType,
|
mod: types.ModuleType,
|
||||||
conftestpath: Path,
|
conftestpath: pathlib.Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
if (
|
if (
|
||||||
hasattr(mod, "pytest_plugins")
|
hasattr(mod, "pytest_plugins")
|
||||||
|
@ -832,7 +824,7 @@ class PytestPluginManager(PluginManager):
|
||||||
self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
|
self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
|
||||||
|
|
||||||
def _import_plugin_specs(
|
def _import_plugin_specs(
|
||||||
self, spec: Union[None, types.ModuleType, str, Sequence[str]]
|
self, spec: None | types.ModuleType | str | Sequence[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
plugins = _get_plugin_specs_as_list(spec)
|
plugins = _get_plugin_specs_as_list(spec)
|
||||||
for import_spec in plugins:
|
for import_spec in plugins:
|
||||||
|
@ -877,8 +869,8 @@ class PytestPluginManager(PluginManager):
|
||||||
|
|
||||||
|
|
||||||
def _get_plugin_specs_as_list(
|
def _get_plugin_specs_as_list(
|
||||||
specs: Union[None, types.ModuleType, str, Sequence[str]],
|
specs: None | types.ModuleType | str | Sequence[str],
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
"""Parse a plugins specification into a list of plugin names."""
|
"""Parse a plugins specification into a list of plugin names."""
|
||||||
# None means empty.
|
# None means empty.
|
||||||
if specs is None:
|
if specs is None:
|
||||||
|
@ -999,19 +991,19 @@ class Config:
|
||||||
Plugins accessing ``InvocationParams`` must be aware of that.
|
Plugins accessing ``InvocationParams`` must be aware of that.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
args: Tuple[str, ...]
|
args: tuple[str, ...]
|
||||||
"""The command-line arguments as passed to :func:`pytest.main`."""
|
"""The command-line arguments as passed to :func:`pytest.main`."""
|
||||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]]
|
plugins: Sequence[str | _PluggyPlugin] | None
|
||||||
"""Extra plugins, might be `None`."""
|
"""Extra plugins, might be `None`."""
|
||||||
dir: Path
|
dir: pathlib.Path
|
||||||
"""The directory from which :func:`pytest.main` was invoked."""
|
"""The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
args: Iterable[str],
|
args: Iterable[str],
|
||||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]],
|
plugins: Sequence[str | _PluggyPlugin] | None,
|
||||||
dir: Path,
|
dir: pathlib.Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
object.__setattr__(self, "args", tuple(args))
|
object.__setattr__(self, "args", tuple(args))
|
||||||
object.__setattr__(self, "plugins", plugins)
|
object.__setattr__(self, "plugins", plugins)
|
||||||
|
@ -1032,20 +1024,20 @@ class Config:
|
||||||
TESTPATHS = enum.auto()
|
TESTPATHS = enum.auto()
|
||||||
|
|
||||||
# Set by cacheprovider plugin.
|
# Set by cacheprovider plugin.
|
||||||
cache: Optional["Cache"]
|
cache: Cache
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
pluginmanager: PytestPluginManager,
|
pluginmanager: PytestPluginManager,
|
||||||
*,
|
*,
|
||||||
invocation_params: Optional[InvocationParams] = None,
|
invocation_params: InvocationParams | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
from .argparsing import FILE_OR_DIR
|
from .argparsing import FILE_OR_DIR
|
||||||
from .argparsing import Parser
|
from .argparsing import Parser
|
||||||
|
|
||||||
if invocation_params is None:
|
if invocation_params is None:
|
||||||
invocation_params = self.InvocationParams(
|
invocation_params = self.InvocationParams(
|
||||||
args=(), plugins=None, dir=Path.cwd()
|
args=(), plugins=None, dir=pathlib.Path.cwd()
|
||||||
)
|
)
|
||||||
|
|
||||||
self.option = argparse.Namespace()
|
self.option = argparse.Namespace()
|
||||||
|
@ -1083,20 +1075,20 @@ class Config:
|
||||||
|
|
||||||
self.trace = self.pluginmanager.trace.root.get("config")
|
self.trace = self.pluginmanager.trace.root.get("config")
|
||||||
self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment]
|
self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment]
|
||||||
self._inicache: Dict[str, Any] = {}
|
self._inicache: dict[str, Any] = {}
|
||||||
self._override_ini: Sequence[str] = ()
|
self._override_ini: Sequence[str] = ()
|
||||||
self._opt2dest: Dict[str, str] = {}
|
self._opt2dest: dict[str, str] = {}
|
||||||
self._cleanup: List[Callable[[], None]] = []
|
self._cleanup: list[Callable[[], None]] = []
|
||||||
self.pluginmanager.register(self, "pytestconfig")
|
self.pluginmanager.register(self, "pytestconfig")
|
||||||
self._configured = False
|
self._configured = False
|
||||||
self.hook.pytest_addoption.call_historic(
|
self.hook.pytest_addoption.call_historic(
|
||||||
kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
|
kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
|
||||||
)
|
)
|
||||||
self.args_source = Config.ArgsSource.ARGS
|
self.args_source = Config.ArgsSource.ARGS
|
||||||
self.args: List[str] = []
|
self.args: list[str] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rootpath(self) -> Path:
|
def rootpath(self) -> pathlib.Path:
|
||||||
"""The path to the :ref:`rootdir <rootdir>`.
|
"""The path to the :ref:`rootdir <rootdir>`.
|
||||||
|
|
||||||
:type: pathlib.Path
|
:type: pathlib.Path
|
||||||
|
@ -1106,11 +1098,9 @@ class Config:
|
||||||
return self._rootpath
|
return self._rootpath
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inipath(self) -> Optional[Path]:
|
def inipath(self) -> pathlib.Path | None:
|
||||||
"""The path to the :ref:`configfile <configfiles>`.
|
"""The path to the :ref:`configfile <configfiles>`.
|
||||||
|
|
||||||
:type: Optional[pathlib.Path]
|
|
||||||
|
|
||||||
.. versionadded:: 6.1
|
.. versionadded:: 6.1
|
||||||
"""
|
"""
|
||||||
return self._inipath
|
return self._inipath
|
||||||
|
@ -1137,15 +1127,15 @@ class Config:
|
||||||
fin()
|
fin()
|
||||||
|
|
||||||
def get_terminal_writer(self) -> TerminalWriter:
|
def get_terminal_writer(self) -> TerminalWriter:
|
||||||
terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin(
|
terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin(
|
||||||
"terminalreporter"
|
"terminalreporter"
|
||||||
)
|
)
|
||||||
assert terminalreporter is not None
|
assert terminalreporter is not None
|
||||||
return terminalreporter._tw
|
return terminalreporter._tw
|
||||||
|
|
||||||
def pytest_cmdline_parse(
|
def pytest_cmdline_parse(
|
||||||
self, pluginmanager: PytestPluginManager, args: List[str]
|
self, pluginmanager: PytestPluginManager, args: list[str]
|
||||||
) -> "Config":
|
) -> Config:
|
||||||
try:
|
try:
|
||||||
self.parse(args)
|
self.parse(args)
|
||||||
except UsageError:
|
except UsageError:
|
||||||
|
@ -1171,7 +1161,7 @@ class Config:
|
||||||
def notify_exception(
|
def notify_exception(
|
||||||
self,
|
self,
|
||||||
excinfo: ExceptionInfo[BaseException],
|
excinfo: ExceptionInfo[BaseException],
|
||||||
option: Optional[argparse.Namespace] = None,
|
option: argparse.Namespace | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if option and getattr(option, "fulltrace", False):
|
if option and getattr(option, "fulltrace", False):
|
||||||
style: TracebackStyle = "long"
|
style: TracebackStyle = "long"
|
||||||
|
@ -1194,7 +1184,7 @@ class Config:
|
||||||
return nodeid
|
return nodeid
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromdictargs(cls, option_dict, args) -> "Config":
|
def fromdictargs(cls, option_dict, args) -> Config:
|
||||||
"""Constructor usable for subprocesses."""
|
"""Constructor usable for subprocesses."""
|
||||||
config = get_config(args)
|
config = get_config(args)
|
||||||
config.option.__dict__.update(option_dict)
|
config.option.__dict__.update(option_dict)
|
||||||
|
@ -1203,7 +1193,7 @@ class Config:
|
||||||
config.pluginmanager.consider_pluginarg(x)
|
config.pluginmanager.consider_pluginarg(x)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def _processopt(self, opt: "Argument") -> None:
|
def _processopt(self, opt: Argument) -> None:
|
||||||
for name in opt._short_opts + opt._long_opts:
|
for name in opt._short_opts + opt._long_opts:
|
||||||
self._opt2dest[name] = opt.dest
|
self._opt2dest[name] = opt.dest
|
||||||
|
|
||||||
|
@ -1212,7 +1202,7 @@ class Config:
|
||||||
setattr(self.option, opt.dest, opt.default)
|
setattr(self.option, opt.dest, opt.default)
|
||||||
|
|
||||||
@hookimpl(trylast=True)
|
@hookimpl(trylast=True)
|
||||||
def pytest_load_initial_conftests(self, early_config: "Config") -> None:
|
def pytest_load_initial_conftests(self, early_config: Config) -> None:
|
||||||
# We haven't fully parsed the command line arguments yet, so
|
# We haven't fully parsed the command line arguments yet, so
|
||||||
# early_config.args it not set yet. But we need it for
|
# early_config.args it not set yet. But we need it for
|
||||||
# discovering the initial conftests. So "pre-run" the logic here.
|
# discovering the initial conftests. So "pre-run" the logic here.
|
||||||
|
@ -1290,7 +1280,8 @@ class Config:
|
||||||
self.pluginmanager.rewrite_hook = hook
|
self.pluginmanager.rewrite_hook = hook
|
||||||
|
|
||||||
if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
|
if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
|
||||||
# We don't autoload from setuptools entry points, no need to continue.
|
# We don't autoload from distribution package entry points,
|
||||||
|
# no need to continue.
|
||||||
return
|
return
|
||||||
|
|
||||||
package_files = (
|
package_files = (
|
||||||
|
@ -1303,7 +1294,7 @@ class Config:
|
||||||
for name in _iter_rewritable_modules(package_files):
|
for name in _iter_rewritable_modules(package_files):
|
||||||
hook.mark_rewrite(name)
|
hook.mark_rewrite(name)
|
||||||
|
|
||||||
def _validate_args(self, args: List[str], via: str) -> List[str]:
|
def _validate_args(self, args: list[str], via: str) -> list[str]:
|
||||||
"""Validate known args."""
|
"""Validate known args."""
|
||||||
self._parser._config_source_hint = via # type: ignore
|
self._parser._config_source_hint = via # type: ignore
|
||||||
try:
|
try:
|
||||||
|
@ -1318,13 +1309,13 @@ class Config:
|
||||||
def _decide_args(
|
def _decide_args(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
args: List[str],
|
args: list[str],
|
||||||
pyargs: bool,
|
pyargs: bool,
|
||||||
testpaths: List[str],
|
testpaths: list[str],
|
||||||
invocation_dir: Path,
|
invocation_dir: pathlib.Path,
|
||||||
rootpath: Path,
|
rootpath: pathlib.Path,
|
||||||
warn: bool,
|
warn: bool,
|
||||||
) -> Tuple[List[str], ArgsSource]:
|
) -> tuple[list[str], ArgsSource]:
|
||||||
"""Decide the args (initial paths/nodeids) to use given the relevant inputs.
|
"""Decide the args (initial paths/nodeids) to use given the relevant inputs.
|
||||||
|
|
||||||
:param warn: Whether can issue warnings.
|
:param warn: Whether can issue warnings.
|
||||||
|
@ -1360,7 +1351,7 @@ class Config:
|
||||||
result = [str(invocation_dir)]
|
result = [str(invocation_dir)]
|
||||||
return result, source
|
return result, source
|
||||||
|
|
||||||
def _preparse(self, args: List[str], addopts: bool = True) -> None:
|
def _preparse(self, args: list[str], addopts: bool = True) -> None:
|
||||||
if addopts:
|
if addopts:
|
||||||
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
||||||
if len(env_addopts):
|
if len(env_addopts):
|
||||||
|
@ -1381,8 +1372,8 @@ class Config:
|
||||||
self._consider_importhook(args)
|
self._consider_importhook(args)
|
||||||
self.pluginmanager.consider_preparse(args, exclude_only=False)
|
self.pluginmanager.consider_preparse(args, exclude_only=False)
|
||||||
if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
|
if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
|
||||||
# Don't autoload from setuptools entry point. Only explicitly specified
|
# Don't autoload from distribution package entry point. Only
|
||||||
# plugins are going to be loaded.
|
# explicitly specified plugins are going to be loaded.
|
||||||
self.pluginmanager.load_setuptools_entrypoints("pytest11")
|
self.pluginmanager.load_setuptools_entrypoints("pytest11")
|
||||||
self.pluginmanager.consider_env()
|
self.pluginmanager.consider_env()
|
||||||
|
|
||||||
|
@ -1484,11 +1475,11 @@ class Config:
|
||||||
|
|
||||||
self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
|
self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
|
||||||
|
|
||||||
def _get_unknown_ini_keys(self) -> List[str]:
|
def _get_unknown_ini_keys(self) -> list[str]:
|
||||||
parser_inicfg = self._parser._inidict
|
parser_inicfg = self._parser._inidict
|
||||||
return [name for name in self.inicfg if name not in parser_inicfg]
|
return [name for name in self.inicfg if name not in parser_inicfg]
|
||||||
|
|
||||||
def parse(self, args: List[str], addopts: bool = True) -> None:
|
def parse(self, args: list[str], addopts: bool = True) -> None:
|
||||||
# Parse given cmdline arguments into this config object.
|
# Parse given cmdline arguments into this config object.
|
||||||
assert (
|
assert (
|
||||||
self.args == []
|
self.args == []
|
||||||
|
@ -1592,7 +1583,7 @@ class Config:
|
||||||
|
|
||||||
# Meant for easy monkeypatching by legacypath plugin.
|
# Meant for easy monkeypatching by legacypath plugin.
|
||||||
# Can be inlined back (with no cover removed) once legacypath is gone.
|
# Can be inlined back (with no cover removed) once legacypath is gone.
|
||||||
def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]):
|
def _getini_unknown_type(self, name: str, type: str, value: str | list[str]):
|
||||||
msg = f"unknown configuration type: {type}"
|
msg = f"unknown configuration type: {type}"
|
||||||
raise ValueError(msg, value) # pragma: no cover
|
raise ValueError(msg, value) # pragma: no cover
|
||||||
|
|
||||||
|
@ -1648,24 +1639,26 @@ class Config:
|
||||||
else:
|
else:
|
||||||
return self._getini_unknown_type(name, type, value)
|
return self._getini_unknown_type(name, type, value)
|
||||||
|
|
||||||
def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]:
|
def _getconftest_pathlist(
|
||||||
|
self, name: str, path: pathlib.Path
|
||||||
|
) -> list[pathlib.Path] | None:
|
||||||
try:
|
try:
|
||||||
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
|
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
assert mod.__file__ is not None
|
assert mod.__file__ is not None
|
||||||
modpath = Path(mod.__file__).parent
|
modpath = pathlib.Path(mod.__file__).parent
|
||||||
values: List[Path] = []
|
values: list[pathlib.Path] = []
|
||||||
for relroot in relroots:
|
for relroot in relroots:
|
||||||
if isinstance(relroot, os.PathLike):
|
if isinstance(relroot, os.PathLike):
|
||||||
relroot = Path(relroot)
|
relroot = pathlib.Path(relroot)
|
||||||
else:
|
else:
|
||||||
relroot = relroot.replace("/", os.sep)
|
relroot = relroot.replace("/", os.sep)
|
||||||
relroot = absolutepath(modpath / relroot)
|
relroot = absolutepath(modpath / relroot)
|
||||||
values.append(relroot)
|
values.append(relroot)
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def _get_override_ini_value(self, name: str) -> Optional[str]:
|
def _get_override_ini_value(self, name: str) -> str | None:
|
||||||
value = None
|
value = None
|
||||||
# override_ini is a list of "ini=value" options.
|
# override_ini is a list of "ini=value" options.
|
||||||
# Always use the last item if multiple values are set for same ini-name,
|
# Always use the last item if multiple values are set for same ini-name,
|
||||||
|
@ -1720,7 +1713,7 @@ class Config:
|
||||||
VERBOSITY_TEST_CASES: Final = "test_cases"
|
VERBOSITY_TEST_CASES: Final = "test_cases"
|
||||||
_VERBOSITY_INI_DEFAULT: Final = "auto"
|
_VERBOSITY_INI_DEFAULT: Final = "auto"
|
||||||
|
|
||||||
def get_verbosity(self, verbosity_type: Optional[str] = None) -> int:
|
def get_verbosity(self, verbosity_type: str | None = None) -> int:
|
||||||
r"""Retrieve the verbosity level for a fine-grained verbosity type.
|
r"""Retrieve the verbosity level for a fine-grained verbosity type.
|
||||||
|
|
||||||
:param verbosity_type: Verbosity type to get level for. If a level is
|
:param verbosity_type: Verbosity type to get level for. If a level is
|
||||||
|
@ -1771,7 +1764,7 @@ class Config:
|
||||||
return f"verbosity_{verbosity_type}"
|
return f"verbosity_{verbosity_type}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _add_verbosity_ini(parser: "Parser", verbosity_type: str, help: str) -> None:
|
def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None:
|
||||||
"""Add a output verbosity configuration option for the given output type.
|
"""Add a output verbosity configuration option for the given output type.
|
||||||
|
|
||||||
:param parser: Parser for command line arguments and ini-file values.
|
:param parser: Parser for command line arguments and ini-file values.
|
||||||
|
@ -1827,7 +1820,7 @@ def _assertion_supported() -> bool:
|
||||||
|
|
||||||
|
|
||||||
def create_terminal_writer(
|
def create_terminal_writer(
|
||||||
config: Config, file: Optional[TextIO] = None
|
config: Config, file: TextIO | None = None
|
||||||
) -> TerminalWriter:
|
) -> TerminalWriter:
|
||||||
"""Create a TerminalWriter instance configured according to the options
|
"""Create a TerminalWriter instance configured according to the options
|
||||||
in the config object.
|
in the config object.
|
||||||
|
@ -1871,7 +1864,7 @@ def _strtobool(val: str) -> bool:
|
||||||
@lru_cache(maxsize=50)
|
@lru_cache(maxsize=50)
|
||||||
def parse_warning_filter(
|
def parse_warning_filter(
|
||||||
arg: str, *, escape: bool
|
arg: str, *, escape: bool
|
||||||
) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]:
|
) -> tuple[warnings._ActionKind, str, type[Warning], str, int]:
|
||||||
"""Parse a warnings filter string.
|
"""Parse a warnings filter string.
|
||||||
|
|
||||||
This is copied from warnings._setoption with the following changes:
|
This is copied from warnings._setoption with the following changes:
|
||||||
|
@ -1917,7 +1910,7 @@ def parse_warning_filter(
|
||||||
except warnings._OptionError as e:
|
except warnings._OptionError as e:
|
||||||
raise UsageError(error_template.format(error=str(e))) from None
|
raise UsageError(error_template.format(error=str(e))) from None
|
||||||
try:
|
try:
|
||||||
category: Type[Warning] = _resolve_warning_category(category_)
|
category: type[Warning] = _resolve_warning_category(category_)
|
||||||
except Exception:
|
except Exception:
|
||||||
exc_info = ExceptionInfo.from_current()
|
exc_info = ExceptionInfo.from_current()
|
||||||
exception_text = exc_info.getrepr(style="native")
|
exception_text = exc_info.getrepr(style="native")
|
||||||
|
@ -1940,7 +1933,7 @@ def parse_warning_filter(
|
||||||
return action, message, category, module, lineno
|
return action, message, category, module, lineno
|
||||||
|
|
||||||
|
|
||||||
def _resolve_warning_category(category: str) -> Type[Warning]:
|
def _resolve_warning_category(category: str) -> type[Warning]:
|
||||||
"""
|
"""
|
||||||
Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors)
|
Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors)
|
||||||
propagate so we can get access to their tracebacks (#9218).
|
propagate so we can get access to their tracebacks (#9218).
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from gettext import gettext
|
from gettext import gettext
|
||||||
import os
|
import os
|
||||||
|
@ -6,16 +8,12 @@ import sys
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from typing import Dict
|
|
||||||
from typing import final
|
from typing import final
|
||||||
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 NoReturn
|
from typing import NoReturn
|
||||||
from typing import Optional
|
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
from typing import Tuple
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
import _pytest._io
|
import _pytest._io
|
||||||
from _pytest.config.exceptions import UsageError
|
from _pytest.config.exceptions import UsageError
|
||||||
|
@ -41,32 +39,32 @@ class Parser:
|
||||||
there's an error processing the command line arguments.
|
there's an error processing the command line arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prog: Optional[str] = None
|
prog: str | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
usage: Optional[str] = None,
|
usage: str | None = None,
|
||||||
processopt: Optional[Callable[["Argument"], None]] = None,
|
processopt: Callable[[Argument], None] | None = None,
|
||||||
*,
|
*,
|
||||||
_ispytest: bool = False,
|
_ispytest: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
check_ispytest(_ispytest)
|
check_ispytest(_ispytest)
|
||||||
self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True)
|
self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True)
|
||||||
self._groups: List[OptionGroup] = []
|
self._groups: list[OptionGroup] = []
|
||||||
self._processopt = processopt
|
self._processopt = processopt
|
||||||
self._usage = usage
|
self._usage = usage
|
||||||
self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {}
|
self._inidict: dict[str, tuple[str, str | None, Any]] = {}
|
||||||
self._ininames: List[str] = []
|
self._ininames: list[str] = []
|
||||||
self.extra_info: Dict[str, Any] = {}
|
self.extra_info: dict[str, Any] = {}
|
||||||
|
|
||||||
def processoption(self, option: "Argument") -> None:
|
def processoption(self, option: Argument) -> None:
|
||||||
if self._processopt:
|
if self._processopt:
|
||||||
if option.dest:
|
if option.dest:
|
||||||
self._processopt(option)
|
self._processopt(option)
|
||||||
|
|
||||||
def getgroup(
|
def getgroup(
|
||||||
self, name: str, description: str = "", after: Optional[str] = None
|
self, name: str, description: str = "", after: str | None = None
|
||||||
) -> "OptionGroup":
|
) -> OptionGroup:
|
||||||
"""Get (or create) a named option Group.
|
"""Get (or create) a named option Group.
|
||||||
|
|
||||||
:param name: Name of the option group.
|
:param name: Name of the option group.
|
||||||
|
@ -108,8 +106,8 @@ class Parser:
|
||||||
|
|
||||||
def parse(
|
def parse(
|
||||||
self,
|
self,
|
||||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
args: Sequence[str | os.PathLike[str]],
|
||||||
namespace: Optional[argparse.Namespace] = None,
|
namespace: argparse.Namespace | None = None,
|
||||||
) -> argparse.Namespace:
|
) -> argparse.Namespace:
|
||||||
from _pytest._argcomplete import try_argcomplete
|
from _pytest._argcomplete import try_argcomplete
|
||||||
|
|
||||||
|
@ -118,7 +116,7 @@ class Parser:
|
||||||
strargs = [os.fspath(x) for x in args]
|
strargs = [os.fspath(x) for x in args]
|
||||||
return self.optparser.parse_args(strargs, namespace=namespace)
|
return self.optparser.parse_args(strargs, namespace=namespace)
|
||||||
|
|
||||||
def _getparser(self) -> "MyOptionParser":
|
def _getparser(self) -> MyOptionParser:
|
||||||
from _pytest._argcomplete import filescompleter
|
from _pytest._argcomplete import filescompleter
|
||||||
|
|
||||||
optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
|
optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
|
||||||
|
@ -139,10 +137,10 @@ class Parser:
|
||||||
|
|
||||||
def parse_setoption(
|
def parse_setoption(
|
||||||
self,
|
self,
|
||||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
args: Sequence[str | os.PathLike[str]],
|
||||||
option: argparse.Namespace,
|
option: argparse.Namespace,
|
||||||
namespace: Optional[argparse.Namespace] = None,
|
namespace: argparse.Namespace | None = None,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
parsedoption = self.parse(args, namespace=namespace)
|
parsedoption = self.parse(args, namespace=namespace)
|
||||||
for name, value in parsedoption.__dict__.items():
|
for name, value in parsedoption.__dict__.items():
|
||||||
setattr(option, name, value)
|
setattr(option, name, value)
|
||||||
|
@ -150,8 +148,8 @@ class Parser:
|
||||||
|
|
||||||
def parse_known_args(
|
def parse_known_args(
|
||||||
self,
|
self,
|
||||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
args: Sequence[str | os.PathLike[str]],
|
||||||
namespace: Optional[argparse.Namespace] = None,
|
namespace: argparse.Namespace | None = None,
|
||||||
) -> argparse.Namespace:
|
) -> argparse.Namespace:
|
||||||
"""Parse the known arguments at this point.
|
"""Parse the known arguments at this point.
|
||||||
|
|
||||||
|
@ -161,9 +159,9 @@ class Parser:
|
||||||
|
|
||||||
def parse_known_and_unknown_args(
|
def parse_known_and_unknown_args(
|
||||||
self,
|
self,
|
||||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
args: Sequence[str | os.PathLike[str]],
|
||||||
namespace: Optional[argparse.Namespace] = None,
|
namespace: argparse.Namespace | None = None,
|
||||||
) -> Tuple[argparse.Namespace, List[str]]:
|
) -> tuple[argparse.Namespace, list[str]]:
|
||||||
"""Parse the known arguments at this point, and also return the
|
"""Parse the known arguments at this point, and also return the
|
||||||
remaining unknown arguments.
|
remaining unknown arguments.
|
||||||
|
|
||||||
|
@ -179,9 +177,8 @@ class Parser:
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
help: str,
|
help: str,
|
||||||
type: Optional[
|
type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
|
||||||
Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
|
| None = None,
|
||||||
] = None,
|
|
||||||
default: Any = NOT_SET,
|
default: Any = NOT_SET,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Register an ini-file option.
|
"""Register an ini-file option.
|
||||||
|
@ -224,7 +221,7 @@ class Parser:
|
||||||
|
|
||||||
|
|
||||||
def get_ini_default_for_type(
|
def get_ini_default_for_type(
|
||||||
type: Optional[Literal["string", "paths", "pathlist", "args", "linelist", "bool"]],
|
type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] | None,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
Used by addini to get the default value for a given ini-option type, when
|
Used by addini to get the default value for a given ini-option type, when
|
||||||
|
@ -244,7 +241,7 @@ class ArgumentError(Exception):
|
||||||
"""Raised if an Argument instance is created with invalid or
|
"""Raised if an Argument instance is created with invalid or
|
||||||
inconsistent arguments."""
|
inconsistent arguments."""
|
||||||
|
|
||||||
def __init__(self, msg: str, option: Union["Argument", str]) -> None:
|
def __init__(self, msg: str, option: Argument | str) -> None:
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.option_id = str(option)
|
self.option_id = str(option)
|
||||||
|
|
||||||
|
@ -267,8 +264,8 @@ class Argument:
|
||||||
def __init__(self, *names: str, **attrs: Any) -> None:
|
def __init__(self, *names: str, **attrs: Any) -> None:
|
||||||
"""Store params in private vars for use in add_argument."""
|
"""Store params in private vars for use in add_argument."""
|
||||||
self._attrs = attrs
|
self._attrs = attrs
|
||||||
self._short_opts: List[str] = []
|
self._short_opts: list[str] = []
|
||||||
self._long_opts: List[str] = []
|
self._long_opts: list[str] = []
|
||||||
try:
|
try:
|
||||||
self.type = attrs["type"]
|
self.type = attrs["type"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -279,7 +276,7 @@ class Argument:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
self._set_opt_strings(names)
|
self._set_opt_strings(names)
|
||||||
dest: Optional[str] = attrs.get("dest")
|
dest: str | None = attrs.get("dest")
|
||||||
if dest:
|
if dest:
|
||||||
self.dest = dest
|
self.dest = dest
|
||||||
elif self._long_opts:
|
elif self._long_opts:
|
||||||
|
@ -291,7 +288,7 @@ class Argument:
|
||||||
self.dest = "???" # Needed for the error repr.
|
self.dest = "???" # Needed for the error repr.
|
||||||
raise ArgumentError("need a long or short option", self) from e
|
raise ArgumentError("need a long or short option", self) from e
|
||||||
|
|
||||||
def names(self) -> List[str]:
|
def names(self) -> list[str]:
|
||||||
return self._short_opts + self._long_opts
|
return self._short_opts + self._long_opts
|
||||||
|
|
||||||
def attrs(self) -> Mapping[str, Any]:
|
def attrs(self) -> Mapping[str, Any]:
|
||||||
|
@ -335,7 +332,7 @@ class Argument:
|
||||||
self._long_opts.append(opt)
|
self._long_opts.append(opt)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
args: List[str] = []
|
args: list[str] = []
|
||||||
if self._short_opts:
|
if self._short_opts:
|
||||||
args += ["_short_opts: " + repr(self._short_opts)]
|
args += ["_short_opts: " + repr(self._short_opts)]
|
||||||
if self._long_opts:
|
if self._long_opts:
|
||||||
|
@ -355,14 +352,14 @@ class OptionGroup:
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
description: str = "",
|
description: str = "",
|
||||||
parser: Optional[Parser] = None,
|
parser: Parser | None = None,
|
||||||
*,
|
*,
|
||||||
_ispytest: bool = False,
|
_ispytest: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
check_ispytest(_ispytest)
|
check_ispytest(_ispytest)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
self.options: List[Argument] = []
|
self.options: list[Argument] = []
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
|
|
||||||
def addoption(self, *opts: str, **attrs: Any) -> None:
|
def addoption(self, *opts: str, **attrs: Any) -> None:
|
||||||
|
@ -391,7 +388,7 @@ class OptionGroup:
|
||||||
option = Argument(*opts, **attrs)
|
option = Argument(*opts, **attrs)
|
||||||
self._addoption_instance(option, shortupper=True)
|
self._addoption_instance(option, shortupper=True)
|
||||||
|
|
||||||
def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None:
|
def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None:
|
||||||
if not shortupper:
|
if not shortupper:
|
||||||
for opt in option._short_opts:
|
for opt in option._short_opts:
|
||||||
if opt[0] == "-" and opt[1].islower():
|
if opt[0] == "-" and opt[1].islower():
|
||||||
|
@ -405,8 +402,8 @@ class MyOptionParser(argparse.ArgumentParser):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
parser: Parser,
|
parser: Parser,
|
||||||
extra_info: Optional[Dict[str, Any]] = None,
|
extra_info: dict[str, Any] | None = None,
|
||||||
prog: Optional[str] = None,
|
prog: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._parser = parser
|
self._parser = parser
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
@ -433,8 +430,8 @@ class MyOptionParser(argparse.ArgumentParser):
|
||||||
# Type ignored because typeshed has a very complex type in the superclass.
|
# Type ignored because typeshed has a very complex type in the superclass.
|
||||||
def parse_args( # type: ignore
|
def parse_args( # type: ignore
|
||||||
self,
|
self,
|
||||||
args: Optional[Sequence[str]] = None,
|
args: Sequence[str] | None = None,
|
||||||
namespace: Optional[argparse.Namespace] = None,
|
namespace: argparse.Namespace | None = None,
|
||||||
) -> argparse.Namespace:
|
) -> argparse.Namespace:
|
||||||
"""Allow splitting of positional arguments."""
|
"""Allow splitting of positional arguments."""
|
||||||
parsed, unrecognized = self.parse_known_args(args, namespace)
|
parsed, unrecognized = self.parse_known_args(args, namespace)
|
||||||
|
@ -455,7 +452,7 @@ class MyOptionParser(argparse.ArgumentParser):
|
||||||
# disable long --argument abbreviations without breaking short flags.
|
# disable long --argument abbreviations without breaking short flags.
|
||||||
def _parse_optional(
|
def _parse_optional(
|
||||||
self, arg_string: str
|
self, arg_string: str
|
||||||
) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
|
) -> tuple[argparse.Action | None, str, str | None] | None:
|
||||||
if not arg_string:
|
if not arg_string:
|
||||||
return None
|
return None
|
||||||
if arg_string[0] not in self.prefix_chars:
|
if arg_string[0] not in self.prefix_chars:
|
||||||
|
@ -507,7 +504,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
||||||
orgstr = super()._format_action_invocation(action)
|
orgstr = super()._format_action_invocation(action)
|
||||||
if orgstr and orgstr[0] != "-": # only optional arguments
|
if orgstr and orgstr[0] != "-": # only optional arguments
|
||||||
return orgstr
|
return orgstr
|
||||||
res: Optional[str] = getattr(action, "_formatted_action_invocation", None)
|
res: str | None = getattr(action, "_formatted_action_invocation", None)
|
||||||
if res:
|
if res:
|
||||||
return res
|
return res
|
||||||
options = orgstr.split(", ")
|
options = orgstr.split(", ")
|
||||||
|
@ -516,7 +513,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
||||||
action._formatted_action_invocation = orgstr # type: ignore
|
action._formatted_action_invocation = orgstr # type: ignore
|
||||||
return orgstr
|
return orgstr
|
||||||
return_list = []
|
return_list = []
|
||||||
short_long: Dict[str, str] = {}
|
short_long: dict[str, str] = {}
|
||||||
for option in options:
|
for option in options:
|
||||||
if len(option) == 2 or option[2] == " ":
|
if len(option) == 2 or option[2] == " ":
|
||||||
continue
|
continue
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue