Merge branch 'main' into add-timezone-to-junit-xml-timestamp
This commit is contained in:
commit
05c85b0182
|
@ -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
|
||||
|
||||
- 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
|
||||
run: |
|
||||
|
|
|
@ -14,6 +14,11 @@ on:
|
|||
branches:
|
||||
- main
|
||||
- "[0-9]+.[0-9]+.x"
|
||||
types:
|
||||
- opened # default
|
||||
- synchronize # default
|
||||
- reopened # default
|
||||
- ready_for_review # used in PRs created from the release workflow
|
||||
|
||||
env:
|
||||
PYTEST_ADDOPTS: "--color=yes"
|
||||
|
@ -182,6 +187,26 @@ jobs:
|
|||
tox_env: "doctesting"
|
||||
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:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
@ -222,8 +247,22 @@ jobs:
|
|||
- name: Upload coverage to Codecov
|
||||
if: "matrix.use_coverage"
|
||||
uses: codecov/codecov-action@v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
fail_ci_if_error: false
|
||||
files: ./coverage.xml
|
||||
token: 1eca3b1f-31a2-4fb8-a8c3-138b441b50a7 #repo token; cfg read fails
|
||||
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,6 +46,7 @@ jobs:
|
|||
run: python scripts/update-plugin-list.py
|
||||
|
||||
- name: Create Pull Request
|
||||
id: pr
|
||||
uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e
|
||||
with:
|
||||
commit-message: '[automated] Update plugin list'
|
||||
|
@ -55,3 +56,13 @@ jobs:
|
|||
branch-suffix: short-commit-hash
|
||||
title: '[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/*/.doctrees
|
||||
doc/*/_changelog_towncrier_draft.rst
|
||||
build/
|
||||
dist/
|
||||
*.egg-info
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: "v0.4.8"
|
||||
rev: "v0.4.9"
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["--fix"]
|
||||
|
@ -66,8 +66,41 @@ repos:
|
|||
- id: changelogs-rst
|
||||
name: changelog filenames
|
||||
language: fail
|
||||
entry: 'changelog files must be named ####.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst'
|
||||
exclude: changelog/(\d+\.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst|README.rst|_template.rst)
|
||||
entry: >-
|
||||
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/
|
||||
- id: py-deprecated
|
||||
name: py library is deprecated
|
||||
|
|
|
@ -14,11 +14,16 @@ sphinx:
|
|||
fail_on_warning: true
|
||||
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-24.04
|
||||
tools:
|
||||
python: "3.9"
|
||||
python: >-
|
||||
3.12
|
||||
apt_packages:
|
||||
- inkscape
|
||||
jobs:
|
||||
post_checkout:
|
||||
- git fetch --unshallow || true
|
||||
- git fetch --tags || true
|
||||
|
||||
formats:
|
||||
- epub
|
3
AUTHORS
3
AUTHORS
|
@ -149,6 +149,7 @@ Evgeny Seliverstov
|
|||
Fabian Sturm
|
||||
Fabien Zarifian
|
||||
Fabio Zadrozny
|
||||
Farbod Ahmadian
|
||||
faph
|
||||
Felix Hofstätter
|
||||
Felix Nieuwenhuizen
|
||||
|
@ -245,6 +246,7 @@ Levon Saldamli
|
|||
Lewis Cowles
|
||||
Llandy Riveron Del Risco
|
||||
Loic Esteve
|
||||
lovetheguitar
|
||||
Lukas Bednar
|
||||
Luke Murphy
|
||||
Maciek Fijalkowski
|
||||
|
@ -393,6 +395,7 @@ Stefano Taschini
|
|||
Steffen Allner
|
||||
Stephan Obermann
|
||||
Sven-Hendrik Haase
|
||||
Sviatoslav Sydorenko
|
||||
Sylvain Marié
|
||||
Tadek Teleżyński
|
||||
Takafumi Arakaki
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
# 2.7.5 3.3.2
|
||||
# FilesCompleter 75.1109 69.2116
|
||||
# FastFilesCompleter 0.7383 1.0760
|
||||
from __future__ import annotations
|
||||
|
||||
import timeit
|
||||
|
||||
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
for i in range(1000):
|
||||
exec("def test_func_%d(): pass" % i)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from unittest import TestCase # noqa: F401
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
for i in range(5000):
|
||||
exec(
|
||||
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,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 ===========================
|
|
@ -20,10 +20,22 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
|||
* ``deprecation``: feature deprecation.
|
||||
* ``breaking``: a change which may break existing suites, such as feature removal or behavior change.
|
||||
* ``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``.
|
||||
|
||||
.. 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,
|
||||
then after you submit the PR and get the PR number you can add a
|
||||
changelog using that instead.
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
# reference: https://docs.codecov.io/docs/codecovyml-reference
|
||||
---
|
||||
|
||||
codecov:
|
||||
token: 1eca3b1f-31a2-4fb8-a8c3-138b441b50a7 #repo token
|
||||
|
||||
coverage:
|
||||
status:
|
||||
patch: true
|
||||
|
|
|
@ -45,7 +45,7 @@ The py.test Development Team
|
|||
**New Features**
|
||||
|
||||
* 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.
|
||||
Thanks :user:`jab` and :user:`nicoddemus` for the PR.
|
||||
|
|
|
@ -44,7 +44,7 @@ The py.test Development Team
|
|||
Thanks :user:`nicoddemus` for the PR.
|
||||
|
||||
* 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
|
||||
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.
|
||||
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
|
||||
in ``pytest.main("-c your_absolute_path")``.
|
||||
|
||||
* Fix maximum recursion depth detection when raised error class is not aware
|
||||
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.
|
||||
Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for
|
||||
|
|
|
@ -19,12 +19,15 @@ with advance notice in the **Deprecations** section of releases.
|
|||
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',
|
||||
but not on readthedocs.
|
||||
To be included in v\ |release| (if present)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. include:: _changelog_towncrier_draft.rst
|
||||
.. towncrier-draft-entries:: |release| [UNRELEASED DRAFT]
|
||||
|
||||
Released versions
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. 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``.
|
||||
|
||||
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.
|
||||
|
@ -1419,7 +1422,7 @@ Bug Fixes
|
|||
tests/link -> tests/real
|
||||
|
||||
running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``.
|
||||
This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details).
|
||||
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.
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
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:`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.
|
||||
|
||||
|
@ -3041,7 +3044,7 @@ pytest 5.4.1 (2020-03-13)
|
|||
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.
|
||||
|
||||
|
@ -4064,7 +4067,7 @@ Bug Fixes
|
|||
(``--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.
|
||||
|
@ -4265,7 +4268,7 @@ Bug Fixes
|
|||
(``--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.
|
||||
|
@ -7226,10 +7229,10 @@ New Features
|
|||
* 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.
|
||||
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
|
||||
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
|
||||
with the ``match`` keyword argument. Thanks :user:`Kriechi` for the PR.
|
||||
|
@ -7257,7 +7260,7 @@ Changes
|
|||
the failure. (:issue:`2228`) Thanks to :user:`kkoukiou` for the PR.
|
||||
|
||||
* 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
|
||||
the truncation message to make it clearer that pytest truncates all
|
||||
|
@ -7266,7 +7269,7 @@ Changes
|
|||
|
||||
* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use
|
||||
``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``,
|
||||
to give it a comprehensible repr while preventing unwarranted modification.
|
||||
|
@ -7520,7 +7523,7 @@ Bug Fixes
|
|||
a sequence of strings) when modules are considered for assertion rewriting.
|
||||
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`).
|
||||
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
|
||||
``unittest.TestCase`` subclasses with ``--pdb``
|
||||
|
@ -7575,12 +7578,12 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
* ``--nomagic``: use ``--assert=plain`` 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
|
||||
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
|
||||
were never documented and a leftover from a pre-virtualenv era. These entry
|
||||
|
@ -7591,19 +7594,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,
|
||||
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`).
|
||||
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
|
||||
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.
|
||||
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.
|
||||
|
||||
* Raise a helpful failure message when requesting a parametrized fixture at runtime,
|
||||
e.g. with ``request.getfixturevalue``. Previously these parameters were simply
|
||||
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.
|
||||
|
||||
* ``_pytest.monkeypatch.monkeypatch`` class has been renamed to ``_pytest.monkeypatch.MonkeyPatch``
|
||||
|
@ -7621,7 +7624,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
|
||||
* New ``doctest_namespace`` fixture for injecting names into the
|
||||
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
|
||||
when running (failing) doctests (implements :issue:`1749`).
|
||||
|
@ -7629,23 +7632,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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
friendly strings for custom types.
|
||||
|
@ -7663,7 +7666,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``
|
||||
still works and is not scheduled for removal. Closes proposal
|
||||
:issue:`1629`. Thanks :user:`obestwalter` and :user:`davehunt` for the complete PR
|
||||
(:pull:`1633`).
|
||||
(:pr:`1633`).
|
||||
|
||||
* New cli flags:
|
||||
|
||||
|
@ -7707,19 +7710,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
|
||||
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
|
||||
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.
|
||||
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
|
||||
those marked with the ``@pytest.yield_fixture`` decorator. This change renders
|
||||
``@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.
|
||||
|
||||
* Explicitly passed parametrize ids do not get escaped to ascii (:issue:`1351`).
|
||||
|
@ -7730,11 +7733,11 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
Thanks :user:`nicoddemus` for the PR.
|
||||
|
||||
* ``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
|
||||
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``,
|
||||
``setup_module``, etc.) is now optional and may be omitted.
|
||||
|
@ -7742,32 +7745,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
|
||||
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
|
||||
``--disable-pytest-warnings`` to explicitly disable the warnings summary (:issue:`1668`).
|
||||
|
||||
* Make ImportError during collection more explicit by reminding
|
||||
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
|
||||
:user:`mikofski` for the report and :user:`tomviner` for the PR (:issue:`1544`).
|
||||
|
||||
* ``pytest.raises`` in the context manager form accepts a custom
|
||||
``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
|
||||
was only available for test modules. Thanks :user:`flub`, :user:`sober7` and
|
||||
:user:`nicoddemus` for the PR (:issue:`1619`).
|
||||
|
||||
* 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
|
||||
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
|
||||
sub-expressions that happened to be ``False``, assuming this was redundant information.
|
||||
|
@ -7783,20 +7786,20 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
Thanks :user:`nicoddemus` for the PR.
|
||||
|
||||
* ``[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.
|
||||
Thanks :user:`nicoddemus` for the PR.
|
||||
|
||||
* 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.
|
||||
|
||||
* 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
|
||||
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`).
|
||||
|
||||
|
@ -7854,11 +7857,11 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
:user:`tomviner` for the PR.
|
||||
|
||||
* ``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.
|
||||
|
||||
* 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.
|
||||
Fixes :issue:`1579`. Thanks to :user:`Stranger6667` for the PR.
|
||||
|
@ -7866,7 +7869,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
* Fixed scope overriding inside metafunc.parametrize (:issue:`634`).
|
||||
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.
|
||||
|
||||
* Fixed off-by-one error with lines from ``request.node.warn``.
|
||||
|
@ -7883,14 +7886,14 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
|
||||
* Fix Xfail does not work with condition keyword argument.
|
||||
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
|
||||
in ``pytest.main("-c your_absolute_path")``.
|
||||
|
||||
* Fix maximum recursion depth detection when raised error class is not aware
|
||||
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.
|
||||
Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for
|
||||
|
@ -7917,7 +7920,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
Thanks :user:`nicoddemus` for the PR.
|
||||
|
||||
* 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
|
||||
containing non-ascii lines at the point of failure generated an internal
|
||||
|
@ -7938,7 +7941,7 @@ time or change existing behaviors in order to make them less surprising/more use
|
|||
**New Features**
|
||||
|
||||
* 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.
|
||||
Thanks :user:`jab` and :user:`nicoddemus` for the PR.
|
||||
|
@ -7949,14 +7952,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``
|
||||
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.
|
||||
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``.
|
||||
Thanks :user:`nicoddemus` for the PR.
|
||||
|
||||
* New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings
|
||||
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.
|
||||
Fixes :issue:`1366`.
|
||||
|
@ -7988,7 +7991,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,
|
||||
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
|
||||
found in the environment, even when ``-vv`` isn't used.
|
||||
|
|
|
@ -15,17 +15,33 @@
|
|||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# The short X.Y version.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from textwrap import dedent
|
||||
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:
|
||||
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])
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
|
@ -72,6 +88,8 @@ extensions = [
|
|||
"sphinx.ext.viewcode",
|
||||
"sphinx_removed_in",
|
||||
"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
|
||||
|
@ -153,13 +171,8 @@ linkcheck_ignore = [
|
|||
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"),
|
||||
}
|
||||
|
||||
|
||||
|
@ -176,6 +189,7 @@ nitpick_ignore = [
|
|||
("py:class", "SubRequest"),
|
||||
("py:class", "TerminalReporter"),
|
||||
("py:class", "_pytest._code.code.TerminalRepr"),
|
||||
("py:class", "TerminalRepr"),
|
||||
("py:class", "_pytest.fixtures.FixtureFunctionMarker"),
|
||||
("py:class", "_pytest.logging.LogCaptureHandler"),
|
||||
("py:class", "_pytest.mark.structures.ParameterSet"),
|
||||
|
@ -197,13 +211,16 @@ nitpick_ignore = [
|
|||
("py:class", "_PluggyPlugin"),
|
||||
# TypeVars
|
||||
("py:class", "_pytest._code.code.E"),
|
||||
("py:class", "E"), # due to delayed annotation
|
||||
("py:class", "_pytest.fixtures.FixtureFunction"),
|
||||
("py:class", "_pytest.nodes._NodeType"),
|
||||
("py:class", "_NodeType"), # due to delayed annotation
|
||||
("py:class", "_pytest.python_api.E"),
|
||||
("py:class", "_pytest.recwarn.T"),
|
||||
("py:class", "_pytest.runner.TResult"),
|
||||
("py:obj", "_pytest.fixtures.FixtureValue"),
|
||||
("py:obj", "_pytest.stash.T"),
|
||||
("py:class", "_ScopeName"),
|
||||
]
|
||||
|
||||
|
||||
|
@ -422,6 +439,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 = {
|
||||
"pluggy": ("https://pluggy.readthedocs.io/en/stable", None),
|
||||
|
@ -435,31 +464,7 @@ intersphinx_mapping = {
|
|||
}
|
||||
|
||||
|
||||
def configure_logging(app: "sphinx.application.Sphinx") -> None:
|
||||
"""Configure Sphinx's WarningHandler to handle (expected) missing include."""
|
||||
import logging
|
||||
|
||||
import sphinx.util.logging
|
||||
|
||||
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:
|
||||
def setup(app: sphinx.application.Sphinx) -> None:
|
||||
app.add_crossref_type(
|
||||
"fixture",
|
||||
"fixture",
|
||||
|
@ -488,8 +493,6 @@ def setup(app: "sphinx.application.Sphinx") -> None:
|
|||
indextemplate="pair: %s; hook",
|
||||
)
|
||||
|
||||
configure_logging(app)
|
||||
|
||||
# legacypath.py monkey-patches pytest.Testdir in. Import the file so
|
||||
# that autodoc can discover references to it.
|
||||
import _pytest.legacypath # noqa: F401
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
collect_ignore = ["conf.py"]
|
||||
|
|
|
@ -22,9 +22,9 @@ Contact channels
|
|||
requests to GitHub.
|
||||
|
||||
- ``#pytest`` `on irc.libera.chat <ircs://irc.libera.chat:6697/#pytest>`_ IRC
|
||||
channel for random questions (using an IRC client, `via webchat
|
||||
<https://web.libera.chat/#pytest>`_, or `via Matrix
|
||||
<https://matrix.to/#/%23pytest:libera.chat>`_).
|
||||
channel for random questions (using an IRC client, or `via webchat
|
||||
<https://web.libera.chat/#pytest>`)
|
||||
- ``#pytest`` `on Matrix https://matrix.to/#/#pytest:matrix.org>`.
|
||||
|
||||
|
||||
.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pytest import raises
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
hello = "world"
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import shutil
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
module.TestStateFullThing.classcount = 0
|
||||
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
collect_ignore = ["nonpython", "customdirectory"]
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# content of conftest.py
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# content of test_first.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# content of test_second.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_2():
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# content of test_third.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_3():
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.device(serial="123")
|
||||
def test_something_quick():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.device(serial="abc")
|
||||
def test_another():
|
||||
pass
|
||||
|
||||
|
@ -71,6 +73,28 @@ Or the inverse, running all tests except the webtest ones:
|
|||
|
||||
===================== 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
|
||||
--------------------------------------
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Module containing a parametrized tests testing cross-python serialization
|
||||
via the pickle module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# content of conftest.py
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# run this with $ pytest --collect-only test_collectonly.py
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_function():
|
||||
|
|
|
@ -212,7 +212,7 @@ the command line arguments before they get processed:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
# setuptools plugin
|
||||
# installable external plugin
|
||||
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
|
||||
argument handling during program startup. This allows you to
|
||||
have a single executable, which is usually more convenient.
|
||||
Please note that the mechanism for plugin discovery used by pytest
|
||||
(setuptools entry points) doesn't work with frozen executables so pytest
|
||||
Please note that the mechanism for plugin discovery used by pytest (:ref:`entry
|
||||
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
|
||||
like ``pytest-timeout`` they must be imported explicitly and passed on to pytest.main.
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ Here is a non-exhaustive list of issues fixed by the new implementation:
|
|||
|
||||
* Marker transfer incompatible with inheritance (:issue:`535`).
|
||||
|
||||
More details can be found in the :pull:`original PR <3317>`.
|
||||
More details can be found in the :pr:`original PR <3317>`.
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
|
@ -1931,7 +1931,7 @@ The same applies for the test folder level obviously.
|
|||
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.
|
||||
|
||||
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**
|
||||
|
||||
To run all tests which are decorated with the ``@pytest.mark.slow`` decorator:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
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>`.
|
||||
|
||||
|
@ -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:
|
||||
|
||||
* 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::
|
||||
|
||||
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.
|
||||
|
||||
* :ref:`external plugins <extplugins>`: modules discovered through
|
||||
`setuptools entry points`_
|
||||
* :ref:`external plugins <extplugins>`: installed third-party modules discovered
|
||||
through :ref:`entry points <pip-installable plugins>` in their packaging metadata
|
||||
|
||||
* `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
|
||||
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.
|
||||
|
||||
|
@ -142,7 +143,8 @@ Making your plugin installable by others
|
|||
If you want to make your plugin externally available, you
|
||||
may define a so-called entry point for your distribution so
|
||||
that ``pytest`` finds your plugin module. Entry points are
|
||||
a feature that is provided by :std:doc:`setuptools <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
|
||||
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.
|
||||
|
||||
This mechanism makes it easy to share fixtures within applications or even
|
||||
external applications without the need to create external plugins using
|
||||
the ``setuptools``'s entry point technique.
|
||||
external applications without the need to create external plugins using the
|
||||
:std:doc:`entry point packaging metadata
|
||||
<packaging:guides/creating-and-discovering-plugins>` technique.
|
||||
|
||||
Plugins imported by :globalvar:`pytest_plugins` will also automatically be marked
|
||||
for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
|
||||
|
|
|
@ -90,7 +90,7 @@ and can also be used to hold pytest configuration if they have a ``[pytest]`` se
|
|||
setup.cfg
|
||||
~~~~~~~~~
|
||||
|
||||
``setup.cfg`` files are general purpose configuration files, used originally by ``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.
|
||||
|
||||
.. 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
|
||||
page.
|
||||
|
||||
This list contains 1476 plugins.
|
||||
This list contains 1479 plugins.
|
||||
|
||||
.. 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-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-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-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)
|
||||
|
@ -100,6 +101,7 @@ This list contains 1476 plugins.
|
|||
:pypi:`pytest-assertions` Pytest Assertions Apr 27, 2022 N/A N/A
|
||||
:pypi:`pytest-assertutil` pytest-assertutil May 10, 2019 N/A N/A
|
||||
:pypi:`pytest-assert-utils` Useful assertion utilities for use with pytest Apr 14, 2022 3 - Alpha N/A
|
||||
:pypi:`pytest-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-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
|
||||
|
@ -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-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-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-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
|
||||
|
@ -150,7 +152,7 @@ This list contains 1476 plugins.
|
|||
: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-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-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
|
||||
|
@ -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-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-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-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)
|
||||
|
@ -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-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-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-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
|
||||
|
@ -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-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-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-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
|
||||
|
@ -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-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-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-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-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-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
|
||||
|
@ -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-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-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-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
|
||||
|
@ -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-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-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-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)
|
||||
|
@ -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-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-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-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-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-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-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-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
|
||||
|
@ -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-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-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-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)
|
||||
|
@ -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-exasol-itde` Feb 15, 2024 N/A pytest (>=7,<9)
|
||||
: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-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
|
||||
|
@ -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-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-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-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
|
||||
|
@ -678,6 +680,7 @@ This list contains 1476 plugins.
|
|||
: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-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-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
|
||||
|
@ -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-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-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-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-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-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-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
|
||||
|
@ -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-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-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-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)
|
||||
|
@ -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-mockito` Base fixtures for mockito Jul 11, 2018 4 - Beta N/A
|
||||
:pypi:`pytest-mockredis` An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A
|
||||
:pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. 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-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
|
||||
|
@ -877,7 +879,7 @@ This list contains 1476 plugins.
|
|||
: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-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-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
|
||||
|
@ -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-ngrok` Jan 20, 2022 3 - Alpha pytest
|
||||
:pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0)
|
||||
:pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies 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-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
|
||||
|
@ -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-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-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-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
|
||||
|
@ -1016,7 +1018,7 @@ This list contains 1476 plugins.
|
|||
: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-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-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
|
||||
|
@ -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-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-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-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)
|
||||
|
@ -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-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0)
|
||||
:pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Feb 14, 2023 4 - Beta pytest (>=6.2.0)
|
||||
:pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins 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-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
|
||||
|
@ -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-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-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-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)
|
||||
|
@ -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-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-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-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
|
||||
|
@ -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-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-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-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0)
|
||||
:pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5)
|
||||
:pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A
|
||||
:pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons 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-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
|
||||
|
@ -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-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-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-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
|
||||
|
@ -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-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-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-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)
|
||||
|
@ -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-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-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-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
|
||||
|
@ -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-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-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-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
|
||||
|
@ -1879,6 +1882,13 @@ This list contains 1476 plugins.
|
|||
|
||||
apistellar plugin for pytest.
|
||||
|
||||
:pypi:`pytest-apiver`
|
||||
*last release*: Jun 21, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: pytest
|
||||
|
||||
|
||||
|
||||
:pypi:`pytest-appengine`
|
||||
*last release*: Feb 27, 2017,
|
||||
*status*: N/A,
|
||||
|
@ -1977,6 +1987,13 @@ This list contains 1476 plugins.
|
|||
|
||||
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`
|
||||
*last release*: Jun 24, 2021,
|
||||
*status*: N/A,
|
||||
|
@ -2153,7 +2170,7 @@ This list contains 1476 plugins.
|
|||
This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2.
|
||||
|
||||
:pypi:`pytest-aux`
|
||||
*last release*: May 31, 2024,
|
||||
*last release*: Jun 18, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: N/A
|
||||
|
||||
|
@ -2328,7 +2345,7 @@ This list contains 1476 plugins.
|
|||
Pytest plugin to run your tests with beartype checking enabled.
|
||||
|
||||
:pypi:`pytest-bec-e2e`
|
||||
*last release*: Jun 07, 2024,
|
||||
*last release*: Jun 21, 2024,
|
||||
*status*: 3 - Alpha,
|
||||
*requires*: pytest
|
||||
|
||||
|
@ -2398,7 +2415,7 @@ This list contains 1476 plugins.
|
|||
Provides a mock fixture for python bigquery client
|
||||
|
||||
:pypi:`pytest-bisect-tests`
|
||||
*last release*: Mar 25, 2024,
|
||||
*last release*: Jun 09, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: N/A
|
||||
|
||||
|
@ -2832,7 +2849,7 @@ This list contains 1476 plugins.
|
|||
Check links in files
|
||||
|
||||
:pypi:`pytest-checklist`
|
||||
*last release*: Mar 12, 2024,
|
||||
*last release*: Jun 10, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: N/A
|
||||
|
||||
|
@ -2930,7 +2947,7 @@ This list contains 1476 plugins.
|
|||
Easy quality control for CLDF datasets using pytest
|
||||
|
||||
:pypi:`pytest-cleanslate`
|
||||
*last release*: May 30, 2024,
|
||||
*last release*: Jun 17, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: pytest
|
||||
|
||||
|
@ -3104,13 +3121,6 @@ This list contains 1476 plugins.
|
|||
|
||||
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`
|
||||
*last release*: Aug 02, 2022,
|
||||
*status*: N/A,
|
||||
|
@ -3140,7 +3150,7 @@ This list contains 1476 plugins.
|
|||
An interactive GUI test runner for PyTest
|
||||
|
||||
:pypi:`pytest-common-subject`
|
||||
*last release*: Jun 03, 2024,
|
||||
*last release*: Jun 12, 2024,
|
||||
*status*: N/A,
|
||||
*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
|
||||
|
||||
:pypi:`pytest-custom-outputs`
|
||||
*last release*: May 30, 2024,
|
||||
*last release*: Jun 17, 2024,
|
||||
*status*: 4 - Beta,
|
||||
*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`
|
||||
*last release*: Jan 30, 2019,
|
||||
|
@ -3476,7 +3486,7 @@ This list contains 1476 plugins.
|
|||
Useful functions for managing data for pytest fixtures
|
||||
|
||||
:pypi:`pytest-databases`
|
||||
*last release*: May 25, 2024,
|
||||
*last release*: Jun 11, 2024,
|
||||
*status*: 4 - Beta,
|
||||
*requires*: pytest
|
||||
|
||||
|
@ -3840,7 +3850,7 @@ This list contains 1476 plugins.
|
|||
Pytest plugin to record discovered tests in a file
|
||||
|
||||
:pypi:`pytest-ditto`
|
||||
*last release*: May 29, 2024,
|
||||
*last release*: Jun 09, 2024,
|
||||
*status*: 4 - Beta,
|
||||
*requires*: pytest>=3.5.0
|
||||
|
||||
|
@ -3853,6 +3863,13 @@ This list contains 1476 plugins.
|
|||
|
||||
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`
|
||||
*last release*: Jan 30, 2024,
|
||||
*status*: 5 - Production/Stable,
|
||||
|
@ -3896,7 +3913,7 @@ This list contains 1476 plugins.
|
|||
A pytest plugin for running django in class-scoped fixtures
|
||||
|
||||
:pypi:`pytest-django-docker-pg`
|
||||
*last release*: May 21, 2024,
|
||||
*last release*: Jun 13, 2024,
|
||||
*status*: 5 - Production/Stable,
|
||||
*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
|
||||
|
||||
:pypi:`pytest-edit`
|
||||
*last release*: Jun 07, 2024,
|
||||
*last release*: Jun 09, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: pytest
|
||||
|
||||
|
@ -4687,9 +4704,9 @@ This list contains 1476 plugins.
|
|||
|
||||
|
||||
:pypi:`pytest-excel`
|
||||
*last release*: Sep 14, 2023,
|
||||
*last release*: Jun 18, 2024,
|
||||
*status*: 5 - Production/Stable,
|
||||
*requires*: N/A
|
||||
*requires*: pytest>3.6
|
||||
|
||||
pytest plugin for generating excel reports
|
||||
|
||||
|
@ -5730,7 +5747,7 @@ This list contains 1476 plugins.
|
|||
A pytest plugin for use with homeassistant custom components.
|
||||
|
||||
:pypi:`pytest-homeassistant-custom-component`
|
||||
*last release*: Jun 08, 2024,
|
||||
*last release*: Jun 22, 2024,
|
||||
*status*: 3 - Alpha,
|
||||
*requires*: pytest==8.2.0
|
||||
|
||||
|
@ -6023,6 +6040,13 @@ This list contains 1476 plugins.
|
|||
|
||||
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`
|
||||
*last release*: Apr 20, 2021,
|
||||
*status*: N/A,
|
||||
|
@ -6339,7 +6363,7 @@ This list contains 1476 plugins.
|
|||
Generate JSON test reports
|
||||
|
||||
:pypi:`pytest-json-ctrf`
|
||||
*last release*: May 21, 2024,
|
||||
*last release*: Jun 15, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: pytest>6.0.0
|
||||
|
||||
|
@ -6380,13 +6404,6 @@ This list contains 1476 plugins.
|
|||
|
||||
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`
|
||||
*last release*: Jun 04, 2024,
|
||||
*status*: N/A,
|
||||
|
@ -7046,7 +7063,7 @@ This list contains 1476 plugins.
|
|||
Custom metrics report for pytest
|
||||
|
||||
:pypi:`pytest-mh`
|
||||
*last release*: May 28, 2024,
|
||||
*last release*: Jun 13, 2024,
|
||||
*status*: N/A,
|
||||
*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.
|
||||
|
||||
:pypi:`pytest-mock-resources`
|
||||
*last release*: Apr 11, 2024,
|
||||
*last release*: Jun 20, 2024,
|
||||
*status*: N/A,
|
||||
*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.
|
||||
|
||||
:pypi:`pytest-neos`
|
||||
*last release*: May 01, 2024,
|
||||
*last release*: Jun 11, 2024,
|
||||
*status*: 1 - Planning,
|
||||
*requires*: N/A
|
||||
|
||||
|
@ -7480,7 +7497,7 @@ This list contains 1476 plugins.
|
|||
pytest ngs fixtures
|
||||
|
||||
:pypi:`pytest-nhsd-apim`
|
||||
*last release*: May 20, 2024,
|
||||
*last release*: Jun 18, 2024,
|
||||
*status*: N/A,
|
||||
*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
|
||||
|
||||
:pypi:`pytest-patterns`
|
||||
*last release*: Nov 17, 2023,
|
||||
*last release*: Jun 14, 2024,
|
||||
*status*: 4 - Beta,
|
||||
*requires*: N/A
|
||||
|
||||
|
@ -8390,9 +8407,9 @@ This list contains 1476 plugins.
|
|||
Profiling plugin for py.test
|
||||
|
||||
:pypi:`pytest-progress`
|
||||
*last release*: Jan 31, 2022,
|
||||
*last release*: Jun 18, 2024,
|
||||
*status*: 5 - Production/Stable,
|
||||
*requires*: N/A
|
||||
*requires*: pytest>=2.7
|
||||
|
||||
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.
|
||||
|
||||
:pypi:`pytest-pyodide`
|
||||
*last release*: Apr 30, 2024,
|
||||
*last release*: Jun 12, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: pytest
|
||||
|
||||
|
@ -8691,9 +8708,9 @@ This list contains 1476 plugins.
|
|||
Pytest plugin for uploading test results to your QA Touch Testrun.
|
||||
|
||||
:pypi:`pytest-qgis`
|
||||
*last release*: Nov 29, 2023,
|
||||
*last release*: Jun 14, 2024,
|
||||
*status*: 5 - Production/Stable,
|
||||
*requires*: pytest >=6.0
|
||||
*requires*: pytest>=6.0
|
||||
|
||||
A pytest plugin for testing QGIS python plugins
|
||||
|
||||
|
@ -8880,7 +8897,7 @@ This list contains 1476 plugins.
|
|||
Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal
|
||||
|
||||
:pypi:`pytest-redis`
|
||||
*last release*: Jun 05, 2024,
|
||||
*last release*: Jun 19, 2024,
|
||||
*status*: 5 - Production/Stable,
|
||||
*requires*: pytest>=6.2
|
||||
|
||||
|
@ -9664,7 +9681,7 @@ This list contains 1476 plugins.
|
|||
Automatically mocks resources from serverless.yml in pytest using moto.
|
||||
|
||||
:pypi:`pytest-servers`
|
||||
*last release*: May 09, 2024,
|
||||
*last release*: Jun 17, 2024,
|
||||
*status*: 3 - Alpha,
|
||||
*requires*: pytest>=6.2
|
||||
|
||||
|
@ -10105,9 +10122,9 @@ This list contains 1476 plugins.
|
|||
Pytest plugin for the splinter automation library
|
||||
|
||||
:pypi:`pytest-split`
|
||||
*last release*: Jan 29, 2024,
|
||||
*last release*: Jun 19, 2024,
|
||||
*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.
|
||||
|
||||
|
@ -10147,7 +10164,7 @@ This list contains 1476 plugins.
|
|||
A Dynamic test tool for Splunk Apps and Add-ons
|
||||
|
||||
:pypi:`pytest-splunk-addon-ui-smartx`
|
||||
*last release*: Jun 05, 2024,
|
||||
*last release*: Jun 14, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: N/A
|
||||
|
||||
|
@ -10238,9 +10255,9 @@ This list contains 1476 plugins.
|
|||
A package to prevent Dependency Confusion attacks against Yandex.
|
||||
|
||||
:pypi:`pytest-static`
|
||||
*last release*: Jan 15, 2024,
|
||||
*last release*: Jun 20, 2024,
|
||||
*status*: 1 - Planning,
|
||||
*requires*: pytest (>=7.4.3,<8.0.0)
|
||||
*requires*: pytest<8.0.0,>=7.4.3
|
||||
|
||||
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.
|
||||
|
||||
:pypi:`pytest-structlog`
|
||||
*last release*: Jun 08, 2024,
|
||||
*last release*: Jun 09, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: pytest
|
||||
|
||||
|
@ -11505,9 +11522,9 @@ This list contains 1476 plugins.
|
|||
Selenium webdriver fixture for py.test
|
||||
|
||||
:pypi:`pytest-webtest-extras`
|
||||
*last release*: Nov 13, 2023,
|
||||
*last release*: Jun 08, 2024,
|
||||
*status*: N/A,
|
||||
*requires*: pytest >= 7.0.0
|
||||
*requires*: pytest>=7.0.0
|
||||
|
||||
Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources.
|
||||
|
||||
|
@ -11770,6 +11787,13 @@ This list contains 1476 plugins.
|
|||
|
||||
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`
|
||||
*last release*: Mar 26, 2021,
|
||||
*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 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
|
||||
.. autofunction:: pytest_load_initial_conftests
|
||||
|
@ -1147,8 +1147,9 @@ When set, pytest will print tracing and debug information.
|
|||
|
||||
.. envvar:: PYTEST_DISABLE_PLUGIN_AUTOLOAD
|
||||
|
||||
When set, disables plugin auto-loading through setuptools entrypoints. Only explicitly specified plugins will be
|
||||
loaded.
|
||||
When set, disables plugin auto-loading through :std:doc:`entry point packaging
|
||||
metadata <packaging:guides/creating-and-discovering-plugins>`. Only explicitly
|
||||
specified plugins will be loaded.
|
||||
|
||||
.. envvar:: PYTEST_PLUGINS
|
||||
|
||||
|
|
|
@ -9,3 +9,5 @@ sphinxcontrib-svg2pdfconverter
|
|||
# See https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045.
|
||||
packaging
|
||||
furo
|
||||
sphinxcontrib-towncrier
|
||||
sphinx-issues
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
|
|
@ -98,6 +98,7 @@ lint.select = [
|
|||
"D", # pydocstyle
|
||||
"E", # pycodestyle
|
||||
"F", # pyflakes
|
||||
"FA100", # add future annotations
|
||||
"I", # isort
|
||||
"PGH004", # pygrep-hooks - Use specific rule codes when using noqa
|
||||
"PIE", # flake8-pie
|
||||
|
@ -155,6 +156,10 @@ lint.per-file-ignores."src/_pytest/_version.py" = [
|
|||
lint.per-file-ignores."testing/python/approx.py" = [
|
||||
"B015",
|
||||
]
|
||||
lint.extend-safe-fixes = [
|
||||
"UP006",
|
||||
"UP007",
|
||||
]
|
||||
lint.isort.combine-as-imports = true
|
||||
lint.isort.force-single-line = true
|
||||
lint.isort.force-sort-within-sections = true
|
||||
|
@ -164,9 +169,13 @@ lint.isort.known-local-folder = [
|
|||
]
|
||||
lint.isort.lines-after-imports = 2
|
||||
lint.isort.order-by-type = false
|
||||
lint.isort.required-imports = [
|
||||
"from __future__ import annotations",
|
||||
]
|
||||
# In order to be able to format for 88 char in ruff format
|
||||
lint.pycodestyle.max-line-length = 120
|
||||
lint.pydocstyle.convention = "pep257"
|
||||
lint.pyupgrade.keep-runtime-typing = false
|
||||
|
||||
[tool.pylint.main]
|
||||
# Maximum number of characters on a single line.
|
||||
|
@ -349,6 +358,9 @@ markers = [
|
|||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
"number_mark",
|
||||
"builtin_matchers_mark",
|
||||
"str_mark",
|
||||
# conftest.py reorders tests moving slow ones to the end of the list
|
||||
"slow",
|
||||
# experimental mark for all tests using pexpect
|
||||
|
@ -363,44 +375,74 @@ directory = "changelog/"
|
|||
title_format = "pytest {version} ({project_date})"
|
||||
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]]
|
||||
# When something public gets removed in a breaking way. Could be
|
||||
# deprecated in an earlier release.
|
||||
directory = "breaking"
|
||||
name = "Breaking Changes"
|
||||
name = "Removals and backward incompatible breaking changes"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
# Declarations of future API removals and breaking changes in behavior.
|
||||
directory = "deprecation"
|
||||
name = "Deprecations"
|
||||
name = "Deprecations (removal in next major release)"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
# New behaviors, public APIs. That sort of stuff.
|
||||
directory = "feature"
|
||||
name = "Features"
|
||||
name = "New features"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
# New behaviors in existing features.
|
||||
directory = "improvement"
|
||||
name = "Improvements"
|
||||
name = "Improvements in existing functionality"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
# Something we deemed an improper undesired behavior that got corrected
|
||||
# in the release to match pre-agreed expectations.
|
||||
directory = "bugfix"
|
||||
name = "Bug Fixes"
|
||||
name = "Bug fixes"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
# Updates regarding bundling dependencies.
|
||||
directory = "vendor"
|
||||
name = "Vendored Libraries"
|
||||
name = "Vendored libraries"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
# Notable updates to the documentation structure or build process.
|
||||
directory = "doc"
|
||||
name = "Improved Documentation"
|
||||
name = "Improved documentation"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "trivial"
|
||||
name = "Trivial/Internal Changes"
|
||||
# Notes for downstreams about unobvious side effects and tooling. Changes
|
||||
# in the test invocation considerations and runtime assumptions.
|
||||
directory = "packaging"
|
||||
name = "Packaging updates and notes for downstreams"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
# Stuff that affects the contributor experience. e.g. Running tests,
|
||||
# building the docs, setting up the development environment.
|
||||
directory = "contrib"
|
||||
name = "Contributor-facing changes"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
# Changes that are hard to assign to any of the above categories.
|
||||
directory = "misc"
|
||||
name = "Miscellaneous internal changes"
|
||||
showcontent = true
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
@ -9,6 +9,8 @@ our CHANGELOG) into Markdown (which is required by GitHub Releases).
|
|||
Requires Python3.6+.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: disallow-untyped-defs
|
||||
"""Invoke development tasks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
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
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import pathlib
|
||||
import re
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
__all__ = ["__version__", "version_tuple"]
|
||||
|
||||
try:
|
||||
|
|
|
@ -62,13 +62,13 @@ If things do not work right away:
|
|||
global argcomplete script).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from glob import glob
|
||||
import os
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class FastFilesCompleter:
|
||||
|
@ -77,7 +77,7 @@ class FastFilesCompleter:
|
|||
def __init__(self, directories: bool = True) -> None:
|
||||
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.
|
||||
if os.sep in prefix[1:]:
|
||||
prefix_dir = len(os.path.dirname(prefix) + os.sep)
|
||||
|
@ -104,7 +104,7 @@ if os.environ.get("_ARGCOMPLETE"):
|
|||
import argcomplete.completers
|
||||
except ImportError:
|
||||
sys.exit(-1)
|
||||
filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter()
|
||||
filescompleter: FastFilesCompleter | None = FastFilesCompleter()
|
||||
|
||||
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
|
||||
argcomplete.autocomplete(parser, always_complete_options=False)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Python inspection/code generation API."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .code import Code
|
||||
from .code import ExceptionInfo
|
||||
from .code import filter_traceback
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import dataclasses
|
||||
import inspect
|
||||
|
@ -17,7 +19,6 @@ from types import TracebackType
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import ClassVar
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import final
|
||||
from typing import Generic
|
||||
|
@ -25,11 +26,9 @@ from typing import Iterable
|
|||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import SupportsIndex
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
|
@ -57,6 +56,8 @@ if sys.version_info < (3, 11):
|
|||
|
||||
TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
||||
|
||||
EXCEPTION_OR_MORE = Union[Type[Exception], Tuple[Type[Exception], ...]]
|
||||
|
||||
|
||||
class Code:
|
||||
"""Wrapper around Python code objects."""
|
||||
|
@ -67,7 +68,7 @@ class Code:
|
|||
self.raw = obj
|
||||
|
||||
@classmethod
|
||||
def from_function(cls, obj: object) -> "Code":
|
||||
def from_function(cls, obj: object) -> Code:
|
||||
return cls(getrawcode(obj))
|
||||
|
||||
def __eq__(self, other):
|
||||
|
@ -85,7 +86,7 @@ class Code:
|
|||
return self.raw.co_name
|
||||
|
||||
@property
|
||||
def path(self) -> Union[Path, str]:
|
||||
def path(self) -> Path | str:
|
||||
"""Return a path object pointing to source code, or an ``str`` in
|
||||
case of ``OSError`` / non-existing file."""
|
||||
if not self.raw.co_filename:
|
||||
|
@ -102,17 +103,17 @@ class Code:
|
|||
return self.raw.co_filename
|
||||
|
||||
@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."""
|
||||
full, _ = findsource(self.raw)
|
||||
return full
|
||||
|
||||
def source(self) -> "Source":
|
||||
def source(self) -> Source:
|
||||
"""Return a _pytest._code.Source object for the code object's source only."""
|
||||
# return source only for that part of code
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@property
|
||||
def f_globals(self) -> Dict[str, Any]:
|
||||
def f_globals(self) -> dict[str, Any]:
|
||||
return self.raw.f_globals
|
||||
|
||||
@property
|
||||
def f_locals(self) -> Dict[str, Any]:
|
||||
def f_locals(self) -> dict[str, Any]:
|
||||
return self.raw.f_locals
|
||||
|
||||
@property
|
||||
|
@ -153,7 +154,7 @@ class Frame:
|
|||
return Code(self.raw.f_code)
|
||||
|
||||
@property
|
||||
def statement(self) -> "Source":
|
||||
def statement(self) -> Source:
|
||||
"""Statement this frame is at."""
|
||||
if self.code.fullsource is None:
|
||||
return Source("")
|
||||
|
@ -197,14 +198,14 @@ class TracebackEntry:
|
|||
def __init__(
|
||||
self,
|
||||
rawentry: TracebackType,
|
||||
repr_style: Optional['Literal["short", "long"]'] = None,
|
||||
repr_style: Literal["short", "long"] | None = None,
|
||||
) -> None:
|
||||
self._rawentry: Final = rawentry
|
||||
self._repr_style: Final = repr_style
|
||||
|
||||
def with_repr_style(
|
||||
self, repr_style: Optional['Literal["short", "long"]']
|
||||
) -> "TracebackEntry":
|
||||
self, repr_style: Literal["short", "long"] | None
|
||||
) -> TracebackEntry:
|
||||
return TracebackEntry(self._rawentry, repr_style)
|
||||
|
||||
@property
|
||||
|
@ -223,19 +224,19 @@ class TracebackEntry:
|
|||
return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
|
||||
|
||||
@property
|
||||
def statement(self) -> "Source":
|
||||
def statement(self) -> Source:
|
||||
"""_pytest._code.Source object for the current statement."""
|
||||
source = self.frame.code.fullsource
|
||||
assert source is not None
|
||||
return source.getstatement(self.lineno)
|
||||
|
||||
@property
|
||||
def path(self) -> Union[Path, str]:
|
||||
def path(self) -> Path | str:
|
||||
"""Path to the source code."""
|
||||
return self.frame.code.path
|
||||
|
||||
@property
|
||||
def locals(self) -> Dict[str, Any]:
|
||||
def locals(self) -> dict[str, Any]:
|
||||
"""Locals of underlying frame."""
|
||||
return self.frame.f_locals
|
||||
|
||||
|
@ -243,8 +244,8 @@ class TracebackEntry:
|
|||
return self.frame.code.firstlineno
|
||||
|
||||
def getsource(
|
||||
self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None
|
||||
) -> Optional["Source"]:
|
||||
self, astcache: dict[str | Path, ast.AST] | None = None
|
||||
) -> Source | None:
|
||||
"""Return failing source code."""
|
||||
# we use the passed in astcache to not reparse asttrees
|
||||
# within exception info printing
|
||||
|
@ -270,7 +271,7 @@ class TracebackEntry:
|
|||
|
||||
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__
|
||||
resolving to True.
|
||||
|
||||
|
@ -279,9 +280,7 @@ class TracebackEntry:
|
|||
|
||||
Mostly for internal use.
|
||||
"""
|
||||
tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = (
|
||||
False
|
||||
)
|
||||
tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False
|
||||
for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals):
|
||||
# in normal cases, f_locals and f_globals are dictionaries
|
||||
# however via `exec(...)` / `eval(...)` they can be other types
|
||||
|
@ -326,13 +325,13 @@ class Traceback(List[TracebackEntry]):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
tb: Union[TracebackType, Iterable[TracebackEntry]],
|
||||
tb: TracebackType | Iterable[TracebackEntry],
|
||||
) -> None:
|
||||
"""Initialize from given python traceback object and ExceptionInfo."""
|
||||
if isinstance(tb, TracebackType):
|
||||
|
||||
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
|
||||
cur_: Optional[TracebackType] = cur
|
||||
cur_: TracebackType | None = cur
|
||||
while cur_ is not None:
|
||||
yield TracebackEntry(cur_)
|
||||
cur_ = cur_.tb_next
|
||||
|
@ -343,11 +342,11 @@ class Traceback(List[TracebackEntry]):
|
|||
|
||||
def cut(
|
||||
self,
|
||||
path: Optional[Union["os.PathLike[str]", str]] = None,
|
||||
lineno: Optional[int] = None,
|
||||
firstlineno: Optional[int] = None,
|
||||
excludepath: Optional["os.PathLike[str]"] = None,
|
||||
) -> "Traceback":
|
||||
path: os.PathLike[str] | str | None = None,
|
||||
lineno: int | None = None,
|
||||
firstlineno: int | None = None,
|
||||
excludepath: os.PathLike[str] | None = None,
|
||||
) -> Traceback:
|
||||
"""Return a Traceback instance wrapping part of this Traceback.
|
||||
|
||||
By providing any combination of path, lineno and firstlineno, the
|
||||
|
@ -378,14 +377,12 @@ class Traceback(List[TracebackEntry]):
|
|||
return self
|
||||
|
||||
@overload
|
||||
def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: ...
|
||||
def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ...
|
||||
|
||||
@overload
|
||||
def __getitem__(self, key: slice) -> "Traceback": ...
|
||||
def __getitem__(self, key: slice) -> Traceback: ...
|
||||
|
||||
def __getitem__(
|
||||
self, key: Union["SupportsIndex", slice]
|
||||
) -> Union[TracebackEntry, "Traceback"]:
|
||||
def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback:
|
||||
if isinstance(key, slice):
|
||||
return self.__class__(super().__getitem__(key))
|
||||
else:
|
||||
|
@ -393,12 +390,9 @@ class Traceback(List[TracebackEntry]):
|
|||
|
||||
def filter(
|
||||
self,
|
||||
excinfo_or_fn: Union[
|
||||
"ExceptionInfo[BaseException]",
|
||||
Callable[[TracebackEntry], bool],
|
||||
],
|
||||
excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool],
|
||||
/,
|
||||
) -> "Traceback":
|
||||
) -> Traceback:
|
||||
"""Return a Traceback instance with certain items removed.
|
||||
|
||||
If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s
|
||||
|
@ -414,10 +408,10 @@ class Traceback(List[TracebackEntry]):
|
|||
fn = excinfo_or_fn
|
||||
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
|
||||
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):
|
||||
# id for the code.raw is needed to work around
|
||||
# the strange metaprogramming in the decorator lib from pypi
|
||||
|
@ -445,15 +439,15 @@ class ExceptionInfo(Generic[E]):
|
|||
|
||||
_assert_start_repr: ClassVar = "AssertionError('assert "
|
||||
|
||||
_excinfo: Optional[Tuple[Type["E"], "E", TracebackType]]
|
||||
_excinfo: tuple[type[E], E, TracebackType] | None
|
||||
_striptext: str
|
||||
_traceback: Optional[Traceback]
|
||||
_traceback: Traceback | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
excinfo: Optional[Tuple[Type["E"], "E", TracebackType]],
|
||||
excinfo: tuple[type[E], E, TracebackType] | None,
|
||||
striptext: str = "",
|
||||
traceback: Optional[Traceback] = None,
|
||||
traceback: Traceback | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -469,8 +463,8 @@ class ExceptionInfo(Generic[E]):
|
|||
# This is OK to ignore because this class is (conceptually) readonly.
|
||||
# See https://github.com/python/mypy/issues/7049.
|
||||
exception: E, # type: ignore[misc]
|
||||
exprinfo: Optional[str] = None,
|
||||
) -> "ExceptionInfo[E]":
|
||||
exprinfo: str | None = None,
|
||||
) -> ExceptionInfo[E]:
|
||||
"""Return an ExceptionInfo for an existing exception.
|
||||
|
||||
The exception must have a non-``None`` ``__traceback__`` attribute,
|
||||
|
@ -495,9 +489,9 @@ class ExceptionInfo(Generic[E]):
|
|||
@classmethod
|
||||
def from_exc_info(
|
||||
cls,
|
||||
exc_info: Tuple[Type[E], E, TracebackType],
|
||||
exprinfo: Optional[str] = None,
|
||||
) -> "ExceptionInfo[E]":
|
||||
exc_info: tuple[type[E], E, TracebackType],
|
||||
exprinfo: str | None = None,
|
||||
) -> ExceptionInfo[E]:
|
||||
"""Like :func:`from_exception`, but using old-style exc_info tuple."""
|
||||
_striptext = ""
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def from_current(
|
||||
cls, exprinfo: Optional[str] = None
|
||||
) -> "ExceptionInfo[BaseException]":
|
||||
def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]:
|
||||
"""Return an ExceptionInfo matching the current traceback.
|
||||
|
||||
.. warning::
|
||||
|
@ -532,17 +524,17 @@ class ExceptionInfo(Generic[E]):
|
|||
return ExceptionInfo.from_exc_info(exc_info, exprinfo)
|
||||
|
||||
@classmethod
|
||||
def for_later(cls) -> "ExceptionInfo[E]":
|
||||
def for_later(cls) -> ExceptionInfo[E]:
|
||||
"""Return an unfilled ExceptionInfo."""
|
||||
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()``."""
|
||||
assert self._excinfo is None, "ExceptionInfo was already filled"
|
||||
self._excinfo = exc_info
|
||||
|
||||
@property
|
||||
def type(self) -> Type[E]:
|
||||
def type(self) -> type[E]:
|
||||
"""The exception class."""
|
||||
assert (
|
||||
self._excinfo is not None
|
||||
|
@ -605,16 +597,14 @@ class ExceptionInfo(Generic[E]):
|
|||
text = text[len(self._striptext) :]
|
||||
return text
|
||||
|
||||
def errisinstance(
|
||||
self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]]
|
||||
) -> bool:
|
||||
def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool:
|
||||
"""Return True if the exception is an instance of exc.
|
||||
|
||||
Consider using ``isinstance(excinfo.value, exc)`` instead.
|
||||
"""
|
||||
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
|
||||
# traceback, or None if all hidden.
|
||||
for i in range(-1, -len(self.traceback) - 1, -1):
|
||||
|
@ -630,14 +620,13 @@ class ExceptionInfo(Generic[E]):
|
|||
showlocals: bool = False,
|
||||
style: TracebackStyle = "long",
|
||||
abspath: bool = False,
|
||||
tbfilter: Union[
|
||||
bool, Callable[["ExceptionInfo[BaseException]"], Traceback]
|
||||
] = True,
|
||||
tbfilter: bool
|
||||
| Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True,
|
||||
funcargs: bool = False,
|
||||
truncate_locals: bool = True,
|
||||
truncate_args: bool = True,
|
||||
chain: bool = True,
|
||||
) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
|
||||
) -> ReprExceptionInfo | ExceptionChainRepr:
|
||||
"""Return str()able representation of this exception info.
|
||||
|
||||
: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
|
||||
representation of the exception using :func:`python:re.search`.
|
||||
|
||||
|
@ -737,9 +726,9 @@ class ExceptionInfo(Generic[E]):
|
|||
def _group_contains(
|
||||
self,
|
||||
exc_group: BaseExceptionGroup[BaseException],
|
||||
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
||||
match: Union[str, Pattern[str], None],
|
||||
target_depth: Optional[int] = None,
|
||||
expected_exception: EXCEPTION_OR_MORE,
|
||||
match: str | Pattern[str] | None,
|
||||
target_depth: int | None = None,
|
||||
current_depth: int = 1,
|
||||
) -> bool:
|
||||
"""Return `True` if a `BaseExceptionGroup` contains a matching exception."""
|
||||
|
@ -766,10 +755,10 @@ class ExceptionInfo(Generic[E]):
|
|||
|
||||
def group_contains(
|
||||
self,
|
||||
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
||||
expected_exception: EXCEPTION_OR_MORE,
|
||||
*,
|
||||
match: Union[str, Pattern[str], None] = None,
|
||||
depth: Optional[int] = None,
|
||||
match: str | Pattern[str] | None = None,
|
||||
depth: int | None = None,
|
||||
) -> bool:
|
||||
"""Check whether a captured exception group contains a matching exception.
|
||||
|
||||
|
@ -811,16 +800,16 @@ class FormattedExcinfo:
|
|||
showlocals: bool = False
|
||||
style: TracebackStyle = "long"
|
||||
abspath: bool = True
|
||||
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
|
||||
tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True
|
||||
funcargs: bool = False
|
||||
truncate_locals: bool = True
|
||||
truncate_args: 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
|
||||
)
|
||||
|
||||
def _getindent(self, source: "Source") -> int:
|
||||
def _getindent(self, source: Source) -> int:
|
||||
# Figure out indent for the given source.
|
||||
try:
|
||||
s = str(source.getstatement(len(source) - 1))
|
||||
|
@ -835,13 +824,13 @@ class FormattedExcinfo:
|
|||
return 0
|
||||
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)
|
||||
if source is not None:
|
||||
source = source.deindent()
|
||||
return source
|
||||
|
||||
def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]:
|
||||
def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None:
|
||||
if self.funcargs:
|
||||
args = []
|
||||
for argname, argvalue in entry.frame.getargs(var=True):
|
||||
|
@ -855,11 +844,11 @@ class FormattedExcinfo:
|
|||
|
||||
def get_source(
|
||||
self,
|
||||
source: Optional["Source"],
|
||||
source: Source | None,
|
||||
line_index: int = -1,
|
||||
excinfo: Optional[ExceptionInfo[BaseException]] = None,
|
||||
excinfo: ExceptionInfo[BaseException] | None = None,
|
||||
short: bool = False,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
"""Return formatted and marked up source lines."""
|
||||
lines = []
|
||||
if source is not None and line_index < 0:
|
||||
|
@ -888,7 +877,7 @@ class FormattedExcinfo:
|
|||
excinfo: ExceptionInfo[BaseException],
|
||||
indent: int = 4,
|
||||
markall: bool = False,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
lines = []
|
||||
indentstr = " " * indent
|
||||
# Get the real exception information out.
|
||||
|
@ -900,7 +889,7 @@ class FormattedExcinfo:
|
|||
failindent = indentstr
|
||||
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:
|
||||
lines = []
|
||||
keys = [loc for loc in locals if loc[0] != "@"]
|
||||
|
@ -928,10 +917,10 @@ class FormattedExcinfo:
|
|||
|
||||
def repr_traceback_entry(
|
||||
self,
|
||||
entry: Optional[TracebackEntry],
|
||||
excinfo: Optional[ExceptionInfo[BaseException]] = None,
|
||||
) -> "ReprEntry":
|
||||
lines: List[str] = []
|
||||
entry: TracebackEntry | None,
|
||||
excinfo: ExceptionInfo[BaseException] | None = None,
|
||||
) -> ReprEntry:
|
||||
lines: list[str] = []
|
||||
style = (
|
||||
entry._repr_style
|
||||
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))
|
||||
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):
|
||||
try:
|
||||
np = bestrelpath(Path.cwd(), path)
|
||||
|
@ -976,7 +965,7 @@ class FormattedExcinfo:
|
|||
return np
|
||||
return str(path)
|
||||
|
||||
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback":
|
||||
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback:
|
||||
traceback = excinfo.traceback
|
||||
if callable(self.tbfilter):
|
||||
traceback = self.tbfilter(excinfo)
|
||||
|
@ -1007,7 +996,7 @@ class FormattedExcinfo:
|
|||
|
||||
def _truncate_recursive_traceback(
|
||||
self, traceback: Traceback
|
||||
) -> Tuple[Traceback, Optional[str]]:
|
||||
) -> tuple[Traceback, str | None]:
|
||||
"""Truncate the given recursive traceback trying to find the starting
|
||||
point of the recursion.
|
||||
|
||||
|
@ -1024,7 +1013,7 @@ class FormattedExcinfo:
|
|||
recursionindex = traceback.recursionindex()
|
||||
except Exception as e:
|
||||
max_frames = 10
|
||||
extraline: Optional[str] = (
|
||||
extraline: str | None = (
|
||||
"!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
|
||||
" The following exception happened when comparing locals in the stack frame:\n"
|
||||
f" {type(e).__name__}: {e!s}\n"
|
||||
|
@ -1042,16 +1031,12 @@ class FormattedExcinfo:
|
|||
|
||||
return traceback, extraline
|
||||
|
||||
def repr_excinfo(
|
||||
self, excinfo: ExceptionInfo[BaseException]
|
||||
) -> "ExceptionChainRepr":
|
||||
repr_chain: List[
|
||||
Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]
|
||||
] = []
|
||||
e: Optional[BaseException] = excinfo.value
|
||||
excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo
|
||||
def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr:
|
||||
repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = []
|
||||
e: BaseException | None = excinfo.value
|
||||
excinfo_: ExceptionInfo[BaseException] | None = excinfo
|
||||
descr = None
|
||||
seen: Set[int] = set()
|
||||
seen: set[int] = set()
|
||||
while e is not None and id(e) not in seen:
|
||||
seen.add(id(e))
|
||||
|
||||
|
@ -1060,7 +1045,7 @@ class FormattedExcinfo:
|
|||
# full support for exception groups added to ExceptionInfo.
|
||||
# See https://github.com/pytest-dev/pytest/issues/9159
|
||||
if isinstance(e, BaseExceptionGroup):
|
||||
reprtraceback: Union[ReprTracebackNative, ReprTraceback] = (
|
||||
reprtraceback: ReprTracebackNative | ReprTraceback = (
|
||||
ReprTracebackNative(
|
||||
traceback.format_exception(
|
||||
type(excinfo_.value),
|
||||
|
@ -1118,9 +1103,9 @@ class TerminalRepr:
|
|||
@dataclasses.dataclass(eq=False)
|
||||
class ExceptionRepr(TerminalRepr):
|
||||
# Provided by subclasses.
|
||||
reprtraceback: "ReprTraceback"
|
||||
reprcrash: Optional["ReprFileLocation"]
|
||||
sections: List[Tuple[str, str, str]] = dataclasses.field(
|
||||
reprtraceback: ReprTraceback
|
||||
reprcrash: ReprFileLocation | None
|
||||
sections: list[tuple[str, str, str]] = dataclasses.field(
|
||||
init=False, default_factory=list
|
||||
)
|
||||
|
||||
|
@ -1135,13 +1120,11 @@ class ExceptionRepr(TerminalRepr):
|
|||
|
||||
@dataclasses.dataclass(eq=False)
|
||||
class ExceptionChainRepr(ExceptionRepr):
|
||||
chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]]
|
||||
chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
chain: Sequence[
|
||||
Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
|
||||
],
|
||||
chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]],
|
||||
) -> None:
|
||||
# reprcrash and reprtraceback of the outermost (the newest) exception
|
||||
# in the chain.
|
||||
|
@ -1162,8 +1145,8 @@ class ExceptionChainRepr(ExceptionRepr):
|
|||
|
||||
@dataclasses.dataclass(eq=False)
|
||||
class ReprExceptionInfo(ExceptionRepr):
|
||||
reprtraceback: "ReprTraceback"
|
||||
reprcrash: Optional["ReprFileLocation"]
|
||||
reprtraceback: ReprTraceback
|
||||
reprcrash: ReprFileLocation | None
|
||||
|
||||
def toterminal(self, tw: TerminalWriter) -> None:
|
||||
self.reprtraceback.toterminal(tw)
|
||||
|
@ -1172,8 +1155,8 @@ class ReprExceptionInfo(ExceptionRepr):
|
|||
|
||||
@dataclasses.dataclass(eq=False)
|
||||
class ReprTraceback(TerminalRepr):
|
||||
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
|
||||
extraline: Optional[str]
|
||||
reprentries: Sequence[ReprEntry | ReprEntryNative]
|
||||
extraline: str | None
|
||||
style: TracebackStyle
|
||||
|
||||
entrysep: ClassVar = "_ "
|
||||
|
@ -1217,9 +1200,9 @@ class ReprEntryNative(TerminalRepr):
|
|||
@dataclasses.dataclass(eq=False)
|
||||
class ReprEntry(TerminalRepr):
|
||||
lines: Sequence[str]
|
||||
reprfuncargs: Optional["ReprFuncArgs"]
|
||||
reprlocals: Optional["ReprLocals"]
|
||||
reprfileloc: Optional["ReprFileLocation"]
|
||||
reprfuncargs: ReprFuncArgs | None
|
||||
reprlocals: ReprLocals | None
|
||||
reprfileloc: ReprFileLocation | None
|
||||
style: TracebackStyle
|
||||
|
||||
def _write_entry_lines(self, tw: TerminalWriter) -> None:
|
||||
|
@ -1243,9 +1226,9 @@ class ReprEntry(TerminalRepr):
|
|||
# such as "> assert 0"
|
||||
fail_marker = f"{FormattedExcinfo.fail_marker} "
|
||||
indent_size = len(fail_marker)
|
||||
indents: List[str] = []
|
||||
source_lines: List[str] = []
|
||||
failure_lines: List[str] = []
|
||||
indents: list[str] = []
|
||||
source_lines: list[str] = []
|
||||
failure_lines: list[str] = []
|
||||
for index, line in enumerate(self.lines):
|
||||
is_failure_line = line.startswith(fail_marker)
|
||||
if is_failure_line:
|
||||
|
@ -1324,7 +1307,7 @@ class ReprLocals(TerminalRepr):
|
|||
|
||||
@dataclasses.dataclass(eq=False)
|
||||
class ReprFuncArgs(TerminalRepr):
|
||||
args: Sequence[Tuple[str, object]]
|
||||
args: Sequence[tuple[str, object]]
|
||||
|
||||
def toterminal(self, tw: TerminalWriter) -> None:
|
||||
if self.args:
|
||||
|
@ -1345,7 +1328,7 @@ class ReprFuncArgs(TerminalRepr):
|
|||
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.
|
||||
|
||||
If the source cannot be determined return ("", -1).
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from bisect import bisect_right
|
||||
import inspect
|
||||
|
@ -7,11 +9,7 @@ import tokenize
|
|||
import types
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
|
||||
|
@ -23,7 +21,7 @@ class Source:
|
|||
|
||||
def __init__(self, obj: object = None) -> None:
|
||||
if not obj:
|
||||
self.lines: List[str] = []
|
||||
self.lines: list[str] = []
|
||||
elif isinstance(obj, Source):
|
||||
self.lines = obj.lines
|
||||
elif isinstance(obj, (tuple, list)):
|
||||
|
@ -50,9 +48,9 @@ class Source:
|
|||
def __getitem__(self, key: int) -> str: ...
|
||||
|
||||
@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):
|
||||
return self.lines[key]
|
||||
else:
|
||||
|
@ -68,7 +66,7 @@ class Source:
|
|||
def __len__(self) -> int:
|
||||
return len(self.lines)
|
||||
|
||||
def strip(self) -> "Source":
|
||||
def strip(self) -> Source:
|
||||
"""Return new Source object with trailing and leading blank lines removed."""
|
||||
start, end = 0, len(self)
|
||||
while start < end and not self.lines[start].strip():
|
||||
|
@ -79,20 +77,20 @@ class Source:
|
|||
source.lines[:] = self.lines[start:end]
|
||||
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
|
||||
given indent-string."""
|
||||
newsource = Source()
|
||||
newsource.lines = [(indent + line) for line in self.lines]
|
||||
return newsource
|
||||
|
||||
def getstatement(self, lineno: int) -> "Source":
|
||||
def getstatement(self, lineno: int) -> Source:
|
||||
"""Return Source statement which contains the given linenumber
|
||||
(counted from 0)."""
|
||||
start, end = self.getstatementrange(lineno)
|
||||
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
|
||||
which containing the given lineno."""
|
||||
if not (0 <= lineno < len(self)):
|
||||
|
@ -100,7 +98,7 @@ class Source:
|
|||
ast, start, end = getstatementrange_ast(lineno, self)
|
||||
return start, end
|
||||
|
||||
def deindent(self) -> "Source":
|
||||
def deindent(self) -> Source:
|
||||
"""Return a new Source object deindented."""
|
||||
newsource = Source()
|
||||
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:
|
||||
sourcelines, lineno = inspect.findsource(obj)
|
||||
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}")
|
||||
|
||||
|
||||
def deindent(lines: Iterable[str]) -> List[str]:
|
||||
def deindent(lines: Iterable[str]) -> list[str]:
|
||||
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.
|
||||
# AST's line numbers start indexing at 1.
|
||||
values: List[int] = []
|
||||
values: list[int] = []
|
||||
for x in ast.walk(node):
|
||||
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
|
||||
# The lineno points to the class/def, so need to include the decorators.
|
||||
|
@ -154,7 +152,7 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i
|
|||
values.append(d.lineno - 1)
|
||||
values.append(x.lineno - 1)
|
||||
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:
|
||||
# Treat the finally/orelse part as its own statement.
|
||||
values.append(val[0].lineno - 1 - 1)
|
||||
|
@ -172,8 +170,8 @@ def getstatementrange_ast(
|
|||
lineno: int,
|
||||
source: Source,
|
||||
assertion: bool = False,
|
||||
astnode: Optional[ast.AST] = None,
|
||||
) -> Tuple[ast.AST, int, int]:
|
||||
astnode: ast.AST | None = None,
|
||||
) -> tuple[ast.AST, int, int]:
|
||||
if astnode is None:
|
||||
content = str(source)
|
||||
# See #4260:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .terminalwriter import get_terminal_width
|
||||
from .terminalwriter import TerminalWriter
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
# tuples with fairly non-descriptive content. This is modeled very much
|
||||
# after Lisp/Scheme - style pretty-printing of lists. If you find it
|
||||
# useful, thank small children who sleep at night.
|
||||
from __future__ import annotations
|
||||
|
||||
import collections as _collections
|
||||
import dataclasses as _dataclasses
|
||||
from io import StringIO as _StringIO
|
||||
|
@ -20,13 +22,8 @@ import re
|
|||
import types as _types
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import IO
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class _safe_key:
|
||||
|
@ -64,7 +61,7 @@ class PrettyPrinter:
|
|||
self,
|
||||
indent: int = 4,
|
||||
width: int = 80,
|
||||
depth: Optional[int] = None,
|
||||
depth: int | None = None,
|
||||
) -> None:
|
||||
"""Handle pretty printing operations onto a stream using a set of
|
||||
configured parameters.
|
||||
|
@ -100,7 +97,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
objid = id(object)
|
||||
|
@ -136,7 +133,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
cls_name = object.__class__.__name__
|
||||
|
@ -149,9 +146,9 @@ class PrettyPrinter:
|
|||
self._format_namespace_items(items, stream, indent, allowance, context, level)
|
||||
stream.write(")")
|
||||
|
||||
_dispatch: Dict[
|
||||
_dispatch: dict[
|
||||
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(
|
||||
|
@ -160,7 +157,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -177,7 +174,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not len(object):
|
||||
|
@ -196,7 +193,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write("[")
|
||||
|
@ -211,7 +208,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write("(")
|
||||
|
@ -226,7 +223,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not len(object):
|
||||
|
@ -252,7 +249,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -311,7 +308,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -340,7 +337,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -358,7 +355,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write("mappingproxy(")
|
||||
|
@ -373,7 +370,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if type(object) is _types.SimpleNamespace:
|
||||
|
@ -391,11 +388,11 @@ class PrettyPrinter:
|
|||
|
||||
def _format_dict_items(
|
||||
self,
|
||||
items: List[Tuple[Any, Any]],
|
||||
items: list[tuple[Any, Any]],
|
||||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not items:
|
||||
|
@ -415,11 +412,11 @@ class PrettyPrinter:
|
|||
|
||||
def _format_namespace_items(
|
||||
self,
|
||||
items: List[Tuple[Any, Any]],
|
||||
items: list[tuple[Any, Any]],
|
||||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not items:
|
||||
|
@ -452,11 +449,11 @@ class PrettyPrinter:
|
|||
|
||||
def _format_items(
|
||||
self,
|
||||
items: List[Any],
|
||||
items: list[Any],
|
||||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not items:
|
||||
|
@ -473,7 +470,7 @@ class PrettyPrinter:
|
|||
|
||||
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)
|
||||
|
||||
def _pprint_default_dict(
|
||||
|
@ -482,7 +479,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
rdf = self._repr(object.default_factory, context, level)
|
||||
|
@ -498,7 +495,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write(object.__class__.__name__ + "(")
|
||||
|
@ -519,7 +516,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
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],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write(object.__class__.__name__ + "(")
|
||||
|
@ -557,7 +554,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
self._format(object.data, stream, indent, allowance, context, level - 1)
|
||||
|
@ -570,7 +567,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
self._format(object.data, stream, indent, allowance, context, level - 1)
|
||||
|
@ -583,7 +580,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
self._format(object.data, stream, indent, allowance, context, level - 1)
|
||||
|
@ -591,7 +588,7 @@ class PrettyPrinter:
|
|||
_dispatch[_collections.UserString.__repr__] = _pprint_user_string
|
||||
|
||||
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:
|
||||
typ = type(object)
|
||||
if typ in _builtin_scalars:
|
||||
|
@ -608,7 +605,7 @@ class PrettyPrinter:
|
|||
if objid in context:
|
||||
return _recursion(object)
|
||||
context.add(objid)
|
||||
components: List[str] = []
|
||||
components: list[str] = []
|
||||
append = components.append
|
||||
level += 1
|
||||
for k, v in sorted(object.items(), key=_safe_tuple):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pprint
|
||||
import reprlib
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def _try_repr_or_str(obj: object) -> str:
|
||||
|
@ -38,7 +39,7 @@ class SafeRepr(reprlib.Repr):
|
|||
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:
|
||||
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)
|
||||
else:
|
||||
s = super().repr(x)
|
||||
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
|
@ -97,7 +97,7 @@ DEFAULT_REPR_MAX_SIZE = 240
|
|||
|
||||
|
||||
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:
|
||||
"""Return a size-limited safe repr-string for the given object.
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
"""Helper functions for writing to terminals and files."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from typing import final
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import TextIO
|
||||
from typing import TYPE_CHECKING
|
||||
|
@ -71,7 +72,7 @@ class TerminalWriter:
|
|||
invert=7,
|
||||
)
|
||||
|
||||
def __init__(self, file: Optional[TextIO] = None) -> None:
|
||||
def __init__(self, file: TextIO | None = None) -> None:
|
||||
if file is None:
|
||||
file = sys.stdout
|
||||
if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32":
|
||||
|
@ -85,7 +86,7 @@ class TerminalWriter:
|
|||
self._file = file
|
||||
self.hasmarkup = should_do_markup(file)
|
||||
self._current_line = ""
|
||||
self._terminal_width: Optional[int] = None
|
||||
self._terminal_width: int | None = None
|
||||
self.code_highlight = True
|
||||
|
||||
@property
|
||||
|
@ -116,8 +117,8 @@ class TerminalWriter:
|
|||
def sep(
|
||||
self,
|
||||
sepchar: str,
|
||||
title: Optional[str] = None,
|
||||
fullwidth: Optional[int] = None,
|
||||
title: str | None = None,
|
||||
fullwidth: int | None = None,
|
||||
**markup: bool,
|
||||
) -> None:
|
||||
if fullwidth is None:
|
||||
|
@ -200,9 +201,7 @@ class TerminalWriter:
|
|||
for indent, new_line in zip(indents, new_lines):
|
||||
self.line(indent + new_line)
|
||||
|
||||
def _get_pygments_lexer(
|
||||
self, lexer: Literal["python", "diff"]
|
||||
) -> Optional["Lexer"]:
|
||||
def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer | None:
|
||||
try:
|
||||
if lexer == "python":
|
||||
from pygments.lexers.python import PythonLexer
|
||||
|
@ -217,7 +216,7 @@ class TerminalWriter:
|
|||
except ModuleNotFoundError:
|
||||
return None
|
||||
|
||||
def _get_pygments_formatter(self) -> Optional["Formatter"]:
|
||||
def _get_pygments_formatter(self) -> Formatter | None:
|
||||
try:
|
||||
import pygments.util
|
||||
except ModuleNotFoundError:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
import unicodedata
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Support for presenting detailed information in failing assertions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from _pytest.assertion import rewrite
|
||||
|
@ -94,7 +94,7 @@ class AssertionState:
|
|||
def __init__(self, config: Config, mode) -> None:
|
||||
self.mode = mode
|
||||
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:
|
||||
|
@ -113,7 +113,7 @@ def install_importhook(config: Config) -> rewrite.AssertionRewritingHook:
|
|||
return hook
|
||||
|
||||
|
||||
def pytest_collection(session: "Session") -> None:
|
||||
def pytest_collection(session: Session) -> None:
|
||||
# This hook is only called when test modules are collected
|
||||
# so for example not in the managing process of pytest-xdist
|
||||
# (which does not collect test modules).
|
||||
|
@ -133,7 +133,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
|||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
def pytest_sessionfinish(session: "Session") -> None:
|
||||
def pytest_sessionfinish(session: Session) -> None:
|
||||
assertstate = session.config.stash.get(assertstate_key, None)
|
||||
if assertstate:
|
||||
if assertstate.hook is not None:
|
||||
|
@ -188,5 +188,5 @@ def pytest_sessionfinish(session: "Session") -> None:
|
|||
|
||||
def pytest_assertrepr_compare(
|
||||
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)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Rewrite assertion AST to produce nice error messages."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from collections import defaultdict
|
||||
import errno
|
||||
|
@ -18,17 +20,11 @@ import sys
|
|||
import tokenize
|
||||
import types
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
@ -73,17 +69,17 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
self.fnpats = config.getini("python_files")
|
||||
except ValueError:
|
||||
self.fnpats = ["test_*.py", "*_test.py"]
|
||||
self.session: Optional[Session] = None
|
||||
self._rewritten_names: Dict[str, Path] = {}
|
||||
self._must_rewrite: Set[str] = set()
|
||||
self.session: Session | None = None
|
||||
self._rewritten_names: dict[str, Path] = {}
|
||||
self._must_rewrite: set[str] = set()
|
||||
# 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)
|
||||
self._writing_pyc = False
|
||||
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
|
||||
|
||||
def set_session(self, session: Optional[Session]) -> None:
|
||||
def set_session(self, session: Session | None) -> None:
|
||||
self.session = session
|
||||
self._session_paths_checked = False
|
||||
|
||||
|
@ -93,9 +89,9 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
def find_spec(
|
||||
self,
|
||||
name: str,
|
||||
path: Optional[Sequence[Union[str, bytes]]] = None,
|
||||
target: Optional[types.ModuleType] = None,
|
||||
) -> Optional[importlib.machinery.ModuleSpec]:
|
||||
path: Sequence[str | bytes] | None = None,
|
||||
target: types.ModuleType | None = None,
|
||||
) -> importlib.machinery.ModuleSpec | None:
|
||||
if self._writing_pyc:
|
||||
return None
|
||||
state = self.config.stash[assertstate_key]
|
||||
|
@ -132,7 +128,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
|
||||
def create_module(
|
||||
self, spec: importlib.machinery.ModuleSpec
|
||||
) -> Optional[types.ModuleType]:
|
||||
) -> types.ModuleType | None:
|
||||
return None # default behaviour is fine
|
||||
|
||||
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}")
|
||||
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.
|
||||
|
||||
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}")
|
||||
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
|
||||
if os.path.basename(fn) == "conftest.py":
|
||||
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)
|
||||
|
||||
def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool:
|
||||
def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool:
|
||||
try:
|
||||
return self._marked_for_rewrite_cache[name]
|
||||
except KeyError:
|
||||
|
@ -278,7 +274,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
stacklevel=5,
|
||||
)
|
||||
|
||||
def get_data(self, pathname: Union[str, bytes]) -> bytes:
|
||||
def get_data(self, pathname: str | bytes) -> bytes:
|
||||
"""Optional PEP302 get_data API."""
|
||||
with open(pathname, "rb") as f:
|
||||
return f.read()
|
||||
|
@ -317,7 +313,7 @@ def _write_pyc_fp(
|
|||
|
||||
|
||||
def _write_pyc(
|
||||
state: "AssertionState",
|
||||
state: AssertionState,
|
||||
co: types.CodeType,
|
||||
source_stat: os.stat_result,
|
||||
pyc: Path,
|
||||
|
@ -341,7 +337,7 @@ def _write_pyc(
|
|||
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."""
|
||||
stat = os.stat(fn)
|
||||
source = fn.read_bytes()
|
||||
|
@ -354,7 +350,7 @@ def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeT
|
|||
|
||||
def _read_pyc(
|
||||
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.
|
||||
|
||||
Return rewritten code if successful or None if not.
|
||||
|
@ -404,8 +400,8 @@ def _read_pyc(
|
|||
def rewrite_asserts(
|
||||
mod: ast.Module,
|
||||
source: bytes,
|
||||
module_path: Optional[str] = None,
|
||||
config: Optional[Config] = None,
|
||||
module_path: str | None = None,
|
||||
config: Config | None = None,
|
||||
) -> None:
|
||||
"""Rewrite the assert statements in 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
|
||||
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)
|
||||
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."""
|
||||
if config is None:
|
||||
verbosity = 0
|
||||
|
@ -543,14 +543,14 @@ def traverse_node(node: ast.AST) -> Iterator[ast.AST]:
|
|||
|
||||
|
||||
@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"}."""
|
||||
ret: Dict[int, str] = {}
|
||||
ret: dict[int, str] = {}
|
||||
|
||||
depth = 0
|
||||
lines: List[str] = []
|
||||
assert_lineno: Optional[int] = None
|
||||
seen_lines: Set[int] = set()
|
||||
lines: list[str] = []
|
||||
assert_lineno: int | None = None
|
||||
seen_lines: set[int] = set()
|
||||
|
||||
def _write_and_reset() -> None:
|
||||
nonlocal depth, lines, assert_lineno, seen_lines
|
||||
|
@ -657,7 +657,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, module_path: Optional[str], config: Optional[Config], source: bytes
|
||||
self, module_path: str | None, config: Config | None, source: bytes
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.module_path = module_path
|
||||
|
@ -670,7 +670,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
self.enable_assertion_pass_hook = False
|
||||
self.source = source
|
||||
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)
|
||||
)
|
||||
|
||||
|
@ -737,7 +737,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
|
||||
# Collect asserts.
|
||||
self.scope = (mod,)
|
||||
nodes: List[Union[ast.AST, Sentinel]] = [mod]
|
||||
nodes: list[ast.AST | Sentinel] = [mod]
|
||||
while nodes:
|
||||
node = nodes.pop()
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
||||
|
@ -749,7 +749,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
assert isinstance(node, ast.AST)
|
||||
for name, field in ast.iter_fields(node):
|
||||
if isinstance(field, list):
|
||||
new: List[ast.AST] = []
|
||||
new: list[ast.AST] = []
|
||||
for i, child in enumerate(field):
|
||||
if isinstance(child, ast.Assert):
|
||||
# Transform assert.
|
||||
|
@ -821,7 +821,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
to format a string of %-formatted values as added by
|
||||
.explanation_param().
|
||||
"""
|
||||
self.explanation_specifiers: Dict[str, ast.expr] = {}
|
||||
self.explanation_specifiers: dict[str, ast.expr] = {}
|
||||
self.stack.append(self.explanation_specifiers)
|
||||
|
||||
def pop_format_context(self, expl_expr: ast.expr) -> ast.Name:
|
||||
|
@ -835,7 +835,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
current = self.stack.pop()
|
||||
if self.stack:
|
||||
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()))
|
||||
form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
|
||||
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))
|
||||
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."""
|
||||
assert isinstance(node, ast.expr)
|
||||
res = self.assign(node)
|
||||
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.
|
||||
|
||||
This rewrites the test of an assertion to provide
|
||||
|
@ -874,15 +874,15 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
lineno=assert_.lineno,
|
||||
)
|
||||
|
||||
self.statements: List[ast.stmt] = []
|
||||
self.variables: List[str] = []
|
||||
self.statements: list[ast.stmt] = []
|
||||
self.variables: list[str] = []
|
||||
self.variable_counter = itertools.count()
|
||||
|
||||
if self.enable_assertion_pass_hook:
|
||||
self.format_variables: List[str] = []
|
||||
self.format_variables: list[str] = []
|
||||
|
||||
self.stack: List[Dict[str, ast.expr]] = []
|
||||
self.expl_stmts: List[ast.stmt] = []
|
||||
self.stack: list[dict[str, ast.expr]] = []
|
||||
self.expl_stmts: list[ast.stmt] = []
|
||||
self.push_format_context()
|
||||
# Rewrite assert into a bunch of statements.
|
||||
top_condition, explanation = self.visit(assert_.test)
|
||||
|
@ -926,13 +926,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
[*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
|
||||
main_test = ast.If(negation, statements_fail, statements_pass)
|
||||
self.statements.append(main_test)
|
||||
if self.format_variables:
|
||||
variables: List[ast.expr] = [
|
||||
variables: list[ast.expr] = [
|
||||
ast.Name(name, ast.Store()) for name in self.format_variables
|
||||
]
|
||||
clear_format = ast.Assign(variables, ast.Constant(None))
|
||||
|
@ -968,7 +968,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
ast.copy_location(node, assert_)
|
||||
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
|
||||
# name if it's a local variable or _should_repr_global_name()
|
||||
# thinks it's acceptable.
|
||||
|
@ -980,7 +980,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
expr = ast.IfExp(test, self.display(name), ast.Constant(target_id))
|
||||
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
|
||||
# _should_repr_global_name() thinks it's acceptable.
|
||||
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))
|
||||
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()
|
||||
expl_list = self.assign(ast.List([], 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.
|
||||
for i, v in enumerate(boolop.values):
|
||||
if i:
|
||||
fail_inner: List[ast.stmt] = []
|
||||
fail_inner: list[ast.stmt] = []
|
||||
# cond is set in a prior loop iteration below
|
||||
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821
|
||||
self.expl_stmts = fail_inner
|
||||
|
@ -1030,7 +1030,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
cond: ast.expr = res
|
||||
if is_or:
|
||||
cond = ast.UnaryOp(ast.Not(), cond)
|
||||
inner: List[ast.stmt] = []
|
||||
inner: list[ast.stmt] = []
|
||||
self.statements.append(ast.If(cond, inner, []))
|
||||
self.statements = body = inner
|
||||
self.statements = save
|
||||
|
@ -1039,13 +1039,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
expl = self.pop_format_context(expl_template)
|
||||
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__]
|
||||
operand_res, operand_expl = self.visit(unary.operand)
|
||||
res = self.assign(ast.UnaryOp(unary.op, operand_res))
|
||||
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__]
|
||||
left_expr, left_expl = self.visit(binop.left)
|
||||
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))
|
||||
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)
|
||||
arg_expls = []
|
||||
new_args = []
|
||||
|
@ -1085,13 +1085,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}"
|
||||
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.
|
||||
res, expl = self.visit(starred.value)
|
||||
new_starred = ast.Starred(res, starred.ctx)
|
||||
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):
|
||||
return self.generic_visit(attr)
|
||||
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)
|
||||
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()
|
||||
# We first check if we have overwritten a variable in the previous assert
|
||||
if isinstance(
|
||||
|
@ -1114,11 +1114,11 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
||||
left_expl = f"({left_expl})"
|
||||
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]
|
||||
it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
|
||||
expls: List[ast.expr] = []
|
||||
syms: List[ast.expr] = []
|
||||
expls: list[ast.expr] = []
|
||||
syms: list[ast.expr] = []
|
||||
results = [left_res]
|
||||
for i, op, next_operand in it:
|
||||
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.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from _pytest.assertion import util
|
||||
from _pytest.config import Config
|
||||
|
@ -18,8 +17,8 @@ USAGE_MSG = "use '-vv' to show"
|
|||
|
||||
|
||||
def truncate_if_required(
|
||||
explanation: List[str], item: Item, max_length: Optional[int] = None
|
||||
) -> List[str]:
|
||||
explanation: list[str], item: Item, max_length: int | None = None
|
||||
) -> list[str]:
|
||||
"""Truncate this assertion explanation if the given test item is eligible."""
|
||||
if _should_truncate_item(item):
|
||||
return _truncate_explanation(explanation)
|
||||
|
@ -33,10 +32,10 @@ def _should_truncate_item(item: Item) -> bool:
|
|||
|
||||
|
||||
def _truncate_explanation(
|
||||
input_lines: List[str],
|
||||
max_lines: Optional[int] = None,
|
||||
max_chars: Optional[int] = None,
|
||||
) -> List[str]:
|
||||
input_lines: list[str],
|
||||
max_lines: int | None = None,
|
||||
max_chars: int | None = None,
|
||||
) -> list[str]:
|
||||
"""Truncate given list of strings that makes up the assertion explanation.
|
||||
|
||||
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
|
||||
iterated_char_count = 0
|
||||
for iterated_index, input_line in enumerate(input_lines):
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Utilities for assertion debugging."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
import os
|
||||
import pprint
|
||||
|
@ -8,10 +10,8 @@ from typing import AbstractSet
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Protocol
|
||||
from typing import Sequence
|
||||
from unicodedata import normalize
|
||||
|
@ -28,14 +28,14 @@ from _pytest.config import Config
|
|||
# interpretation code and assertion rewriter to detect this plugin was
|
||||
# loaded and in turn call the hooks defined here as part of the
|
||||
# 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
|
||||
# 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: Optional[Config] = None
|
||||
_config: Config | None = None
|
||||
|
||||
|
||||
class _HighlightFunc(Protocol):
|
||||
|
@ -58,7 +58,7 @@ def format_explanation(explanation: str) -> str:
|
|||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
def _format_lines(lines: Sequence[str]) -> List[str]:
|
||||
def _format_lines(lines: Sequence[str]) -> list[str]:
|
||||
"""Format the individual lines.
|
||||
|
||||
This will replace the '{', '}' and '~' characters of our mini formatting
|
||||
|
@ -169,7 +169,7 @@ def has_default_eq(
|
|||
|
||||
def assertrepr_compare(
|
||||
config, op: str, left: Any, right: Any, use_ascii: bool = False
|
||||
) -> Optional[List[str]]:
|
||||
) -> list[str] | None:
|
||||
"""Return specialised explanations for some operators/operands."""
|
||||
verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
|
||||
|
||||
|
@ -239,7 +239,7 @@ def assertrepr_compare(
|
|||
|
||||
def _compare_eq_any(
|
||||
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = []
|
||||
if istext(left) and istext(right):
|
||||
explanation = _diff_text(left, right, verbose)
|
||||
|
@ -274,7 +274,7 @@ def _compare_eq_any(
|
|||
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.
|
||||
|
||||
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
|
||||
|
||||
explanation: List[str] = []
|
||||
explanation: list[str] = []
|
||||
|
||||
if verbose < 1:
|
||||
i = 0 # just in case left or right has zero length
|
||||
|
@ -327,7 +327,7 @@ def _compare_eq_iterable(
|
|||
right: Iterable[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
if verbose <= 0 and not running_on_ci():
|
||||
return ["Use -v to get more diff"]
|
||||
# dynamic import to speedup pytest
|
||||
|
@ -356,9 +356,9 @@ def _compare_eq_sequence(
|
|||
right: Sequence[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
|
||||
explanation: List[str] = []
|
||||
explanation: list[str] = []
|
||||
len_left = len(left)
|
||||
len_right = len(right)
|
||||
for i in range(min(len_left, len_right)):
|
||||
|
@ -417,7 +417,7 @@ def _compare_eq_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = []
|
||||
explanation.extend(_set_one_sided_diff("left", left, right, highlighter))
|
||||
explanation.extend(_set_one_sided_diff("right", right, left, highlighter))
|
||||
|
@ -429,7 +429,7 @@ def _compare_gt_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = _compare_gte_set(left, right, highlighter)
|
||||
if not explanation:
|
||||
return ["Both sets are equal"]
|
||||
|
@ -441,7 +441,7 @@ def _compare_lt_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = _compare_lte_set(left, right, highlighter)
|
||||
if not explanation:
|
||||
return ["Both sets are equal"]
|
||||
|
@ -453,7 +453,7 @@ def _compare_gte_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
return _set_one_sided_diff("right", right, left, highlighter)
|
||||
|
||||
|
||||
|
@ -462,7 +462,7 @@ def _compare_lte_set(
|
|||
right: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
return _set_one_sided_diff("left", left, right, highlighter)
|
||||
|
||||
|
||||
|
@ -471,7 +471,7 @@ def _set_one_sided_diff(
|
|||
set1: AbstractSet[Any],
|
||||
set2: AbstractSet[Any],
|
||||
highlighter: _HighlightFunc,
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
explanation = []
|
||||
diff = set1 - set2
|
||||
if diff:
|
||||
|
@ -486,8 +486,8 @@ def _compare_eq_dict(
|
|||
right: Mapping[Any, Any],
|
||||
highlighter: _HighlightFunc,
|
||||
verbose: int = 0,
|
||||
) -> List[str]:
|
||||
explanation: List[str] = []
|
||||
) -> list[str]:
|
||||
explanation: list[str] = []
|
||||
set_left = set(left)
|
||||
set_right = set(right)
|
||||
common = set_left.intersection(set_right)
|
||||
|
@ -531,7 +531,7 @@ def _compare_eq_dict(
|
|||
|
||||
def _compare_eq_cls(
|
||||
left: Any, right: Any, highlighter: _HighlightFunc, verbose: int
|
||||
) -> List[str]:
|
||||
) -> list[str]:
|
||||
if not has_default_eq(left):
|
||||
return []
|
||||
if isdatacls(left):
|
||||
|
@ -584,7 +584,7 @@ def _compare_eq_cls(
|
|||
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)
|
||||
head = text[:index]
|
||||
tail = text[index + len(term) :]
|
||||
|
|
|
@ -3,20 +3,17 @@
|
|||
|
||||
# This plugin was not named "cache" to avoid conflicts with the external
|
||||
# pytest-cache version.
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from typing import Dict
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
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 rm_rf
|
||||
|
@ -77,7 +74,7 @@ class Cache:
|
|||
self._config = config
|
||||
|
||||
@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.
|
||||
|
||||
:meta private:
|
||||
|
@ -249,7 +246,7 @@ class Cache:
|
|||
|
||||
|
||||
class LFPluginCollWrapper:
|
||||
def __init__(self, lfplugin: "LFPlugin") -> None:
|
||||
def __init__(self, lfplugin: LFPlugin) -> None:
|
||||
self.lfplugin = lfplugin
|
||||
self._collected_at_least_one_failure = False
|
||||
|
||||
|
@ -263,7 +260,7 @@ class LFPluginCollWrapper:
|
|||
lf_paths = self.lfplugin._last_failed_paths
|
||||
|
||||
# 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
|
||||
|
||||
res.result = sorted(
|
||||
|
@ -301,13 +298,13 @@ class LFPluginCollWrapper:
|
|||
|
||||
|
||||
class LFPluginCollSkipfiles:
|
||||
def __init__(self, lfplugin: "LFPlugin") -> None:
|
||||
def __init__(self, lfplugin: LFPlugin) -> None:
|
||||
self.lfplugin = lfplugin
|
||||
|
||||
@hookimpl
|
||||
def pytest_make_collect_report(
|
||||
self, collector: nodes.Collector
|
||||
) -> Optional[CollectReport]:
|
||||
) -> CollectReport | None:
|
||||
if isinstance(collector, File):
|
||||
if collector.path not in self.lfplugin._last_failed_paths:
|
||||
self.lfplugin._skipped_files += 1
|
||||
|
@ -326,9 +323,9 @@ class LFPlugin:
|
|||
active_keys = "lf", "failedfirst"
|
||||
self.active = any(config.getoption(key) for key in active_keys)
|
||||
assert config.cache
|
||||
self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {})
|
||||
self._previously_failed_count: Optional[int] = None
|
||||
self._report_status: Optional[str] = None
|
||||
self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {})
|
||||
self._previously_failed_count: int | None = None
|
||||
self._report_status: str | None = None
|
||||
self._skipped_files = 0 # count skipped files during collection due to --lf
|
||||
|
||||
if config.getoption("lf"):
|
||||
|
@ -337,7 +334,7 @@ class LFPlugin:
|
|||
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
|
||||
their parents."""
|
||||
rootpath = self.config.rootpath
|
||||
|
@ -348,7 +345,7 @@ class LFPlugin:
|
|||
result.update(path.parents)
|
||||
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:
|
||||
return f"run-last-failure: {self._report_status}"
|
||||
return None
|
||||
|
@ -370,7 +367,7 @@ class LFPlugin:
|
|||
|
||||
@hookimpl(wrapper=True, tryfirst=True)
|
||||
def pytest_collection_modifyitems(
|
||||
self, config: Config, items: List[nodes.Item]
|
||||
self, config: Config, items: list[nodes.Item]
|
||||
) -> Generator[None, None, None]:
|
||||
res = yield
|
||||
|
||||
|
@ -442,13 +439,13 @@ class NFPlugin:
|
|||
|
||||
@hookimpl(wrapper=True, tryfirst=True)
|
||||
def pytest_collection_modifyitems(
|
||||
self, items: List[nodes.Item]
|
||||
self, items: list[nodes.Item]
|
||||
) -> Generator[None, None, None]:
|
||||
res = yield
|
||||
|
||||
if self.active:
|
||||
new_items: Dict[str, nodes.Item] = {}
|
||||
other_items: Dict[str, nodes.Item] = {}
|
||||
new_items: dict[str, nodes.Item] = {}
|
||||
other_items: dict[str, nodes.Item] = {}
|
||||
for item in items:
|
||||
if item.nodeid not in self.cached_nodeids:
|
||||
new_items[item.nodeid] = item
|
||||
|
@ -464,7 +461,7 @@ class NFPlugin:
|
|||
|
||||
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)
|
||||
|
||||
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:
|
||||
from _pytest.main import wrap_session
|
||||
|
||||
|
@ -572,7 +569,7 @@ def cache(request: FixtureRequest) -> 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."""
|
||||
if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache":
|
||||
assert config.cache is not None
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Per-test stdout/stderr capturing mechanism."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import collections
|
||||
import contextlib
|
||||
|
@ -19,15 +21,14 @@ from typing import Generator
|
|||
from typing import Generic
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import TextIO
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import hookimpl
|
||||
|
@ -213,7 +214,7 @@ class DontReadFromInput(TextIO):
|
|||
def __next__(self) -> str:
|
||||
return self.readline()
|
||||
|
||||
def readlines(self, hint: Optional[int] = -1) -> List[str]:
|
||||
def readlines(self, hint: int | None = -1) -> list[str]:
|
||||
raise OSError(
|
||||
"pytest: reading from stdin while output is captured! Consider using `-s`."
|
||||
)
|
||||
|
@ -245,7 +246,7 @@ class DontReadFromInput(TextIO):
|
|||
def tell(self) -> int:
|
||||
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")
|
||||
|
||||
def write(self, data: str) -> int:
|
||||
|
@ -257,14 +258,14 @@ class DontReadFromInput(TextIO):
|
|||
def writable(self) -> bool:
|
||||
return False
|
||||
|
||||
def __enter__(self) -> "DontReadFromInput":
|
||||
def __enter__(self) -> Self:
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
type: Optional[Type[BaseException]],
|
||||
value: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
type: type[BaseException] | None,
|
||||
value: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
@ -339,7 +340,7 @@ class NoCapture(CaptureBase[str]):
|
|||
|
||||
class SysCaptureBase(CaptureBase[AnyStr]):
|
||||
def __init__(
|
||||
self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False
|
||||
self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False
|
||||
) -> None:
|
||||
name = patchsysdict[fd]
|
||||
self._old: TextIO = getattr(sys, name)
|
||||
|
@ -370,7 +371,7 @@ class SysCaptureBase(CaptureBase[AnyStr]):
|
|||
self.tmpfile,
|
||||
)
|
||||
|
||||
def _assert_state(self, op: str, states: Tuple[str, ...]) -> None:
|
||||
def _assert_state(self, op: str, states: tuple[str, ...]) -> None:
|
||||
assert (
|
||||
self._state in states
|
||||
), "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
|
||||
# possibility of FD reuse (e.g. the tmpfile getting the very same
|
||||
# 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)
|
||||
else:
|
||||
self.targetfd_invalid = None
|
||||
|
@ -487,7 +488,7 @@ class FDCaptureBase(CaptureBase[AnyStr]):
|
|||
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 (
|
||||
self._state in states
|
||||
), "cannot {} in state {!r}: expected one of {}".format(
|
||||
|
@ -609,13 +610,13 @@ class MultiCapture(Generic[AnyStr]):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
in_: Optional[CaptureBase[AnyStr]],
|
||||
out: Optional[CaptureBase[AnyStr]],
|
||||
err: Optional[CaptureBase[AnyStr]],
|
||||
in_: CaptureBase[AnyStr] | None,
|
||||
out: CaptureBase[AnyStr] | None,
|
||||
err: CaptureBase[AnyStr] | None,
|
||||
) -> None:
|
||||
self.in_: Optional[CaptureBase[AnyStr]] = in_
|
||||
self.out: Optional[CaptureBase[AnyStr]] = out
|
||||
self.err: Optional[CaptureBase[AnyStr]] = err
|
||||
self.in_: CaptureBase[AnyStr] | None = in_
|
||||
self.out: CaptureBase[AnyStr] | None = out
|
||||
self.err: CaptureBase[AnyStr] | None = err
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
|
@ -632,7 +633,7 @@ class MultiCapture(Generic[AnyStr]):
|
|||
if self.err:
|
||||
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."""
|
||||
out, err = self.readouterr()
|
||||
if out:
|
||||
|
@ -725,8 +726,8 @@ class CaptureManager:
|
|||
|
||||
def __init__(self, method: _CaptureMethod) -> None:
|
||||
self._method: Final = method
|
||||
self._global_capturing: Optional[MultiCapture[str]] = None
|
||||
self._capture_fixture: Optional[CaptureFixture[Any]] = None
|
||||
self._global_capturing: MultiCapture[str] | None = None
|
||||
self._capture_fixture: CaptureFixture[Any] | None = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
|
@ -734,7 +735,7 @@ class CaptureManager:
|
|||
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():
|
||||
return "global"
|
||||
if self._capture_fixture:
|
||||
|
@ -782,7 +783,7 @@ class CaptureManager:
|
|||
|
||||
# Fixture Control
|
||||
|
||||
def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None:
|
||||
def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None:
|
||||
if self._capture_fixture:
|
||||
current_fixture = self._capture_fixture.request.fixturename
|
||||
requested_fixture = capture_fixture.request.fixturename
|
||||
|
@ -897,15 +898,15 @@ class CaptureFixture(Generic[AnyStr]):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
captureclass: Type[CaptureBase[AnyStr]],
|
||||
captureclass: type[CaptureBase[AnyStr]],
|
||||
request: SubRequest,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
self.captureclass: Type[CaptureBase[AnyStr]] = captureclass
|
||||
self.captureclass: type[CaptureBase[AnyStr]] = captureclass
|
||||
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_err: AnyStr = self.captureclass.EMPTY_BUFFER
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Command line options, ini-file and conftest.py processing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import collections.abc
|
||||
import copy
|
||||
|
@ -11,7 +13,7 @@ import glob
|
|||
import importlib.metadata
|
||||
import inspect
|
||||
import os
|
||||
from pathlib import Path
|
||||
import pathlib
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
|
@ -21,22 +23,16 @@ from types import FunctionType
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import TextIO
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
|
@ -118,7 +114,7 @@ class ExitCode(enum.IntEnum):
|
|||
class ConftestImportFailure(Exception):
|
||||
def __init__(
|
||||
self,
|
||||
path: Path,
|
||||
path: pathlib.Path,
|
||||
*,
|
||||
cause: Exception,
|
||||
) -> None:
|
||||
|
@ -141,9 +137,9 @@ def filter_traceback_for_conftest_import_failure(
|
|||
|
||||
|
||||
def main(
|
||||
args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
||||
) -> Union[int, ExitCode]:
|
||||
args: list[str] | os.PathLike[str] | None = None,
|
||||
plugins: Sequence[str | _PluggyPlugin] | None = None,
|
||||
) -> int | ExitCode:
|
||||
"""Perform an in-process test run.
|
||||
|
||||
:param args:
|
||||
|
@ -176,9 +172,7 @@ def main(
|
|||
return ExitCode.USAGE_ERROR
|
||||
else:
|
||||
try:
|
||||
ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
|
||||
config=config
|
||||
)
|
||||
ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
|
||||
try:
|
||||
return ExitCode(ret)
|
||||
except ValueError:
|
||||
|
@ -286,9 +280,9 @@ builtin_plugins.add("pytester_assertions")
|
|||
|
||||
|
||||
def get_config(
|
||||
args: Optional[List[str]] = None,
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
||||
) -> "Config":
|
||||
args: list[str] | None = None,
|
||||
plugins: Sequence[str | _PluggyPlugin] | None = None,
|
||||
) -> Config:
|
||||
# subsequent calls to main will create a fresh instance
|
||||
pluginmanager = PytestPluginManager()
|
||||
config = Config(
|
||||
|
@ -296,7 +290,7 @@ def get_config(
|
|||
invocation_params=Config.InvocationParams(
|
||||
args=args or (),
|
||||
plugins=plugins,
|
||||
dir=Path.cwd(),
|
||||
dir=pathlib.Path.cwd(),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -310,7 +304,7 @@ def get_config(
|
|||
return config
|
||||
|
||||
|
||||
def get_plugin_manager() -> "PytestPluginManager":
|
||||
def get_plugin_manager() -> PytestPluginManager:
|
||||
"""Obtain a new instance of the
|
||||
:py:class:`pytest.PytestPluginManager`, with default plugins
|
||||
already loaded.
|
||||
|
@ -322,9 +316,9 @@ def get_plugin_manager() -> "PytestPluginManager":
|
|||
|
||||
|
||||
def _prepareconfig(
|
||||
args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
|
||||
) -> "Config":
|
||||
args: list[str] | os.PathLike[str] | None = None,
|
||||
plugins: Sequence[str | _PluggyPlugin] | None = None,
|
||||
) -> Config:
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
elif isinstance(args, os.PathLike):
|
||||
|
@ -353,7 +347,7 @@ def _prepareconfig(
|
|||
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."""
|
||||
if path.is_file():
|
||||
return path.parent
|
||||
|
@ -364,14 +358,14 @@ def _get_directory(path: Path) -> Path:
|
|||
def _get_legacy_hook_marks(
|
||||
method: Any,
|
||||
hook_type: str,
|
||||
opt_names: Tuple[str, ...],
|
||||
) -> Dict[str, bool]:
|
||||
opt_names: tuple[str, ...],
|
||||
) -> dict[str, bool]:
|
||||
if TYPE_CHECKING:
|
||||
# abuse typeguard from importlib to avoid massive method type union thats lacking a alias
|
||||
assert inspect.isroutine(method)
|
||||
known_marks: Set[str] = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
must_warn: List[str] = []
|
||||
opts: Dict[str, bool] = {}
|
||||
known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
must_warn: list[str] = []
|
||||
opts: dict[str, bool] = {}
|
||||
for opt_name in opt_names:
|
||||
opt_attr = getattr(method, opt_name, AttributeError)
|
||||
if opt_attr is not AttributeError:
|
||||
|
@ -410,13 +404,13 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
# -- State related to local conftest plugins.
|
||||
# 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.
|
||||
# This includes the directory's own conftest modules as well
|
||||
# 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.
|
||||
self._confcutdir: Optional[Path] = None
|
||||
self._confcutdir: pathlib.Path | None = None
|
||||
# If set, conftest loading is skipped.
|
||||
self._noconftest = False
|
||||
|
||||
|
@ -430,7 +424,7 @@ class PytestPluginManager(PluginManager):
|
|||
# previously we would issue a warning when a plugin was skipped, but
|
||||
# since we refactored warnings as first citizens of Config, they are
|
||||
# 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.register(self)
|
||||
|
@ -456,7 +450,7 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def parse_hookimpl_opts(
|
||||
self, plugin: _PluggyPlugin, name: str
|
||||
) -> Optional[HookimplOpts]:
|
||||
) -> HookimplOpts | None:
|
||||
""":meta private:"""
|
||||
# pytest hooks are always prefixed with "pytest_",
|
||||
# so we avoid accessing possibly non-readable attributes
|
||||
|
@ -480,7 +474,7 @@ class PytestPluginManager(PluginManager):
|
|||
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:"""
|
||||
opts = super().parse_hookspec_opts(module_or_class, name)
|
||||
if opts is None:
|
||||
|
@ -493,9 +487,7 @@ class PytestPluginManager(PluginManager):
|
|||
)
|
||||
return opts
|
||||
|
||||
def register(
|
||||
self, plugin: _PluggyPlugin, name: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None:
|
||||
if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
|
||||
warnings.warn(
|
||||
PytestConfigWarning(
|
||||
|
@ -522,14 +514,14 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def getplugin(self, name: str):
|
||||
# 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
|
||||
|
||||
def hasplugin(self, name: str) -> bool:
|
||||
"""Return whether a plugin with the given name is registered."""
|
||||
return bool(self.get_plugin(name))
|
||||
|
||||
def pytest_configure(self, config: "Config") -> None:
|
||||
def pytest_configure(self, config: Config) -> None:
|
||||
""":meta private:"""
|
||||
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
|
||||
# we should remove tryfirst/trylast as markers.
|
||||
|
@ -552,13 +544,13 @@ class PytestPluginManager(PluginManager):
|
|||
#
|
||||
def _set_initial_conftests(
|
||||
self,
|
||||
args: Sequence[Union[str, Path]],
|
||||
args: Sequence[str | pathlib.Path],
|
||||
pyargs: bool,
|
||||
noconftest: bool,
|
||||
rootpath: Path,
|
||||
confcutdir: Optional[Path],
|
||||
invocation_dir: Path,
|
||||
importmode: Union[ImportMode, str],
|
||||
rootpath: pathlib.Path,
|
||||
confcutdir: pathlib.Path | None,
|
||||
invocation_dir: pathlib.Path,
|
||||
importmode: ImportMode | str,
|
||||
*,
|
||||
consider_namespace_packages: bool,
|
||||
) -> None:
|
||||
|
@ -601,7 +593,7 @@ class PytestPluginManager(PluginManager):
|
|||
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."""
|
||||
if self._confcutdir is None:
|
||||
return True
|
||||
|
@ -618,9 +610,9 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def _try_load_conftest(
|
||||
self,
|
||||
anchor: Path,
|
||||
importmode: Union[str, ImportMode],
|
||||
rootpath: Path,
|
||||
anchor: pathlib.Path,
|
||||
importmode: str | ImportMode,
|
||||
rootpath: pathlib.Path,
|
||||
*,
|
||||
consider_namespace_packages: bool,
|
||||
) -> None:
|
||||
|
@ -643,9 +635,9 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def _loadconftestmodules(
|
||||
self,
|
||||
path: Path,
|
||||
importmode: Union[str, ImportMode],
|
||||
rootpath: Path,
|
||||
path: pathlib.Path,
|
||||
importmode: str | ImportMode,
|
||||
rootpath: pathlib.Path,
|
||||
*,
|
||||
consider_namespace_packages: bool,
|
||||
) -> None:
|
||||
|
@ -673,15 +665,15 @@ class PytestPluginManager(PluginManager):
|
|||
clist.append(mod)
|
||||
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)
|
||||
return self._dirpath2confmods.get(directory, ())
|
||||
|
||||
def _rget_with_confmod(
|
||||
self,
|
||||
name: str,
|
||||
path: Path,
|
||||
) -> Tuple[types.ModuleType, Any]:
|
||||
path: pathlib.Path,
|
||||
) -> tuple[types.ModuleType, Any]:
|
||||
modules = self._getconftestmodules(path)
|
||||
for mod in reversed(modules):
|
||||
try:
|
||||
|
@ -692,9 +684,9 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
def _importconftest(
|
||||
self,
|
||||
conftestpath: Path,
|
||||
importmode: Union[str, ImportMode],
|
||||
rootpath: Path,
|
||||
conftestpath: pathlib.Path,
|
||||
importmode: str | ImportMode,
|
||||
rootpath: pathlib.Path,
|
||||
*,
|
||||
consider_namespace_packages: bool,
|
||||
) -> types.ModuleType:
|
||||
|
@ -746,7 +738,7 @@ class PytestPluginManager(PluginManager):
|
|||
def _check_non_top_pytest_plugins(
|
||||
self,
|
||||
mod: types.ModuleType,
|
||||
conftestpath: Path,
|
||||
conftestpath: pathlib.Path,
|
||||
) -> None:
|
||||
if (
|
||||
hasattr(mod, "pytest_plugins")
|
||||
|
@ -832,7 +824,7 @@ class PytestPluginManager(PluginManager):
|
|||
self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
|
||||
|
||||
def _import_plugin_specs(
|
||||
self, spec: Union[None, types.ModuleType, str, Sequence[str]]
|
||||
self, spec: None | types.ModuleType | str | Sequence[str]
|
||||
) -> None:
|
||||
plugins = _get_plugin_specs_as_list(spec)
|
||||
for import_spec in plugins:
|
||||
|
@ -877,8 +869,8 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
|
||||
def _get_plugin_specs_as_list(
|
||||
specs: Union[None, types.ModuleType, str, Sequence[str]],
|
||||
) -> List[str]:
|
||||
specs: None | types.ModuleType | str | Sequence[str],
|
||||
) -> list[str]:
|
||||
"""Parse a plugins specification into a list of plugin names."""
|
||||
# None means empty.
|
||||
if specs is None:
|
||||
|
@ -999,19 +991,19 @@ class Config:
|
|||
Plugins accessing ``InvocationParams`` must be aware of that.
|
||||
"""
|
||||
|
||||
args: Tuple[str, ...]
|
||||
args: tuple[str, ...]
|
||||
"""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`."""
|
||||
dir: Path
|
||||
"""The directory from which :func:`pytest.main` was invoked."""
|
||||
dir: pathlib.Path
|
||||
"""The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
args: Iterable[str],
|
||||
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]],
|
||||
dir: Path,
|
||||
plugins: Sequence[str | _PluggyPlugin] | None,
|
||||
dir: pathlib.Path,
|
||||
) -> None:
|
||||
object.__setattr__(self, "args", tuple(args))
|
||||
object.__setattr__(self, "plugins", plugins)
|
||||
|
@ -1032,20 +1024,20 @@ class Config:
|
|||
TESTPATHS = enum.auto()
|
||||
|
||||
# Set by cacheprovider plugin.
|
||||
cache: Optional["Cache"]
|
||||
cache: Cache
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pluginmanager: PytestPluginManager,
|
||||
*,
|
||||
invocation_params: Optional[InvocationParams] = None,
|
||||
invocation_params: InvocationParams | None = None,
|
||||
) -> None:
|
||||
from .argparsing import FILE_OR_DIR
|
||||
from .argparsing import Parser
|
||||
|
||||
if invocation_params is None:
|
||||
invocation_params = self.InvocationParams(
|
||||
args=(), plugins=None, dir=Path.cwd()
|
||||
args=(), plugins=None, dir=pathlib.Path.cwd()
|
||||
)
|
||||
|
||||
self.option = argparse.Namespace()
|
||||
|
@ -1083,20 +1075,20 @@ class Config:
|
|||
|
||||
self.trace = self.pluginmanager.trace.root.get("config")
|
||||
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._opt2dest: Dict[str, str] = {}
|
||||
self._cleanup: List[Callable[[], None]] = []
|
||||
self._opt2dest: dict[str, str] = {}
|
||||
self._cleanup: list[Callable[[], None]] = []
|
||||
self.pluginmanager.register(self, "pytestconfig")
|
||||
self._configured = False
|
||||
self.hook.pytest_addoption.call_historic(
|
||||
kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
|
||||
)
|
||||
self.args_source = Config.ArgsSource.ARGS
|
||||
self.args: List[str] = []
|
||||
self.args: list[str] = []
|
||||
|
||||
@property
|
||||
def rootpath(self) -> Path:
|
||||
def rootpath(self) -> pathlib.Path:
|
||||
"""The path to the :ref:`rootdir <rootdir>`.
|
||||
|
||||
:type: pathlib.Path
|
||||
|
@ -1106,11 +1098,9 @@ class Config:
|
|||
return self._rootpath
|
||||
|
||||
@property
|
||||
def inipath(self) -> Optional[Path]:
|
||||
def inipath(self) -> pathlib.Path | None:
|
||||
"""The path to the :ref:`configfile <configfiles>`.
|
||||
|
||||
:type: Optional[pathlib.Path]
|
||||
|
||||
.. versionadded:: 6.1
|
||||
"""
|
||||
return self._inipath
|
||||
|
@ -1137,15 +1127,15 @@ class Config:
|
|||
fin()
|
||||
|
||||
def get_terminal_writer(self) -> TerminalWriter:
|
||||
terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin(
|
||||
terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin(
|
||||
"terminalreporter"
|
||||
)
|
||||
assert terminalreporter is not None
|
||||
return terminalreporter._tw
|
||||
|
||||
def pytest_cmdline_parse(
|
||||
self, pluginmanager: PytestPluginManager, args: List[str]
|
||||
) -> "Config":
|
||||
self, pluginmanager: PytestPluginManager, args: list[str]
|
||||
) -> Config:
|
||||
try:
|
||||
self.parse(args)
|
||||
except UsageError:
|
||||
|
@ -1171,7 +1161,7 @@ class Config:
|
|||
def notify_exception(
|
||||
self,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
option: Optional[argparse.Namespace] = None,
|
||||
option: argparse.Namespace | None = None,
|
||||
) -> None:
|
||||
if option and getattr(option, "fulltrace", False):
|
||||
style: TracebackStyle = "long"
|
||||
|
@ -1194,7 +1184,7 @@ class Config:
|
|||
return nodeid
|
||||
|
||||
@classmethod
|
||||
def fromdictargs(cls, option_dict, args) -> "Config":
|
||||
def fromdictargs(cls, option_dict, args) -> Config:
|
||||
"""Constructor usable for subprocesses."""
|
||||
config = get_config(args)
|
||||
config.option.__dict__.update(option_dict)
|
||||
|
@ -1203,7 +1193,7 @@ class Config:
|
|||
config.pluginmanager.consider_pluginarg(x)
|
||||
return config
|
||||
|
||||
def _processopt(self, opt: "Argument") -> None:
|
||||
def _processopt(self, opt: Argument) -> None:
|
||||
for name in opt._short_opts + opt._long_opts:
|
||||
self._opt2dest[name] = opt.dest
|
||||
|
||||
|
@ -1212,7 +1202,7 @@ class Config:
|
|||
setattr(self.option, opt.dest, opt.default)
|
||||
|
||||
@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
|
||||
# early_config.args it not set yet. But we need it for
|
||||
# discovering the initial conftests. So "pre-run" the logic here.
|
||||
|
@ -1290,7 +1280,8 @@ class Config:
|
|||
self.pluginmanager.rewrite_hook = hook
|
||||
|
||||
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
|
||||
|
||||
package_files = (
|
||||
|
@ -1303,7 +1294,7 @@ class Config:
|
|||
for name in _iter_rewritable_modules(package_files):
|
||||
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."""
|
||||
self._parser._config_source_hint = via # type: ignore
|
||||
try:
|
||||
|
@ -1318,13 +1309,13 @@ class Config:
|
|||
def _decide_args(
|
||||
self,
|
||||
*,
|
||||
args: List[str],
|
||||
args: list[str],
|
||||
pyargs: bool,
|
||||
testpaths: List[str],
|
||||
invocation_dir: Path,
|
||||
rootpath: Path,
|
||||
testpaths: list[str],
|
||||
invocation_dir: pathlib.Path,
|
||||
rootpath: pathlib.Path,
|
||||
warn: bool,
|
||||
) -> Tuple[List[str], ArgsSource]:
|
||||
) -> tuple[list[str], ArgsSource]:
|
||||
"""Decide the args (initial paths/nodeids) to use given the relevant inputs.
|
||||
|
||||
:param warn: Whether can issue warnings.
|
||||
|
@ -1360,7 +1351,7 @@ class Config:
|
|||
result = [str(invocation_dir)]
|
||||
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:
|
||||
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
|
||||
if len(env_addopts):
|
||||
|
@ -1381,8 +1372,8 @@ class Config:
|
|||
self._consider_importhook(args)
|
||||
self.pluginmanager.consider_preparse(args, exclude_only=False)
|
||||
if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
|
||||
# Don't autoload from setuptools entry point. Only explicitly specified
|
||||
# plugins are going to be loaded.
|
||||
# Don't autoload from distribution package entry point. Only
|
||||
# explicitly specified plugins are going to be loaded.
|
||||
self.pluginmanager.load_setuptools_entrypoints("pytest11")
|
||||
self.pluginmanager.consider_env()
|
||||
|
||||
|
@ -1484,11 +1475,11 @@ class Config:
|
|||
|
||||
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
|
||||
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.
|
||||
assert (
|
||||
self.args == []
|
||||
|
@ -1592,7 +1583,7 @@ class Config:
|
|||
|
||||
# Meant for easy monkeypatching by legacypath plugin.
|
||||
# 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}"
|
||||
raise ValueError(msg, value) # pragma: no cover
|
||||
|
||||
|
@ -1648,24 +1639,26 @@ class Config:
|
|||
else:
|
||||
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:
|
||||
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
|
||||
except KeyError:
|
||||
return None
|
||||
assert mod.__file__ is not None
|
||||
modpath = Path(mod.__file__).parent
|
||||
values: List[Path] = []
|
||||
modpath = pathlib.Path(mod.__file__).parent
|
||||
values: list[pathlib.Path] = []
|
||||
for relroot in relroots:
|
||||
if isinstance(relroot, os.PathLike):
|
||||
relroot = Path(relroot)
|
||||
relroot = pathlib.Path(relroot)
|
||||
else:
|
||||
relroot = relroot.replace("/", os.sep)
|
||||
relroot = absolutepath(modpath / relroot)
|
||||
values.append(relroot)
|
||||
return values
|
||||
|
||||
def _get_override_ini_value(self, name: str) -> Optional[str]:
|
||||
def _get_override_ini_value(self, name: str) -> str | None:
|
||||
value = None
|
||||
# override_ini is a list of "ini=value" options.
|
||||
# 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_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.
|
||||
|
||||
:param verbosity_type: Verbosity type to get level for. If a level is
|
||||
|
@ -1771,7 +1764,7 @@ class Config:
|
|||
return f"verbosity_{verbosity_type}"
|
||||
|
||||
@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.
|
||||
|
||||
:param parser: Parser for command line arguments and ini-file values.
|
||||
|
@ -1827,7 +1820,7 @@ def _assertion_supported() -> bool:
|
|||
|
||||
|
||||
def create_terminal_writer(
|
||||
config: Config, file: Optional[TextIO] = None
|
||||
config: Config, file: TextIO | None = None
|
||||
) -> TerminalWriter:
|
||||
"""Create a TerminalWriter instance configured according to the options
|
||||
in the config object.
|
||||
|
@ -1871,7 +1864,7 @@ def _strtobool(val: str) -> bool:
|
|||
@lru_cache(maxsize=50)
|
||||
def parse_warning_filter(
|
||||
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.
|
||||
|
||||
This is copied from warnings._setoption with the following changes:
|
||||
|
@ -1917,7 +1910,7 @@ def parse_warning_filter(
|
|||
except warnings._OptionError as e:
|
||||
raise UsageError(error_template.format(error=str(e))) from None
|
||||
try:
|
||||
category: Type[Warning] = _resolve_warning_category(category_)
|
||||
category: type[Warning] = _resolve_warning_category(category_)
|
||||
except Exception:
|
||||
exc_info = ExceptionInfo.from_current()
|
||||
exception_text = exc_info.getrepr(style="native")
|
||||
|
@ -1940,7 +1933,7 @@ def parse_warning_filter(
|
|||
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)
|
||||
propagate so we can get access to their tracebacks (#9218).
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from gettext import gettext
|
||||
import os
|
||||
|
@ -6,16 +8,12 @@ import sys
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import final
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
import _pytest._io
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
@ -41,32 +39,32 @@ class Parser:
|
|||
there's an error processing the command line arguments.
|
||||
"""
|
||||
|
||||
prog: Optional[str] = None
|
||||
prog: str | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
usage: Optional[str] = None,
|
||||
processopt: Optional[Callable[["Argument"], None]] = None,
|
||||
usage: str | None = None,
|
||||
processopt: Callable[[Argument], None] | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True)
|
||||
self._groups: List[OptionGroup] = []
|
||||
self._groups: list[OptionGroup] = []
|
||||
self._processopt = processopt
|
||||
self._usage = usage
|
||||
self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {}
|
||||
self._ininames: List[str] = []
|
||||
self.extra_info: Dict[str, Any] = {}
|
||||
self._inidict: dict[str, tuple[str, str | None, Any]] = {}
|
||||
self._ininames: list[str] = []
|
||||
self.extra_info: dict[str, Any] = {}
|
||||
|
||||
def processoption(self, option: "Argument") -> None:
|
||||
def processoption(self, option: Argument) -> None:
|
||||
if self._processopt:
|
||||
if option.dest:
|
||||
self._processopt(option)
|
||||
|
||||
def getgroup(
|
||||
self, name: str, description: str = "", after: Optional[str] = None
|
||||
) -> "OptionGroup":
|
||||
self, name: str, description: str = "", after: str | None = None
|
||||
) -> OptionGroup:
|
||||
"""Get (or create) a named option Group.
|
||||
|
||||
:param name: Name of the option group.
|
||||
|
@ -108,8 +106,8 @@ class Parser:
|
|||
|
||||
def parse(
|
||||
self,
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
args: Sequence[str | os.PathLike[str]],
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> argparse.Namespace:
|
||||
from _pytest._argcomplete import try_argcomplete
|
||||
|
||||
|
@ -118,7 +116,7 @@ class Parser:
|
|||
strargs = [os.fspath(x) for x in args]
|
||||
return self.optparser.parse_args(strargs, namespace=namespace)
|
||||
|
||||
def _getparser(self) -> "MyOptionParser":
|
||||
def _getparser(self) -> MyOptionParser:
|
||||
from _pytest._argcomplete import filescompleter
|
||||
|
||||
optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
|
||||
|
@ -139,10 +137,10 @@ class Parser:
|
|||
|
||||
def parse_setoption(
|
||||
self,
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
args: Sequence[str | os.PathLike[str]],
|
||||
option: argparse.Namespace,
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> List[str]:
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> list[str]:
|
||||
parsedoption = self.parse(args, namespace=namespace)
|
||||
for name, value in parsedoption.__dict__.items():
|
||||
setattr(option, name, value)
|
||||
|
@ -150,8 +148,8 @@ class Parser:
|
|||
|
||||
def parse_known_args(
|
||||
self,
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
args: Sequence[str | os.PathLike[str]],
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> argparse.Namespace:
|
||||
"""Parse the known arguments at this point.
|
||||
|
||||
|
@ -161,9 +159,9 @@ class Parser:
|
|||
|
||||
def parse_known_and_unknown_args(
|
||||
self,
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> Tuple[argparse.Namespace, List[str]]:
|
||||
args: Sequence[str | os.PathLike[str]],
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> tuple[argparse.Namespace, list[str]]:
|
||||
"""Parse the known arguments at this point, and also return the
|
||||
remaining unknown arguments.
|
||||
|
||||
|
@ -179,9 +177,8 @@ class Parser:
|
|||
self,
|
||||
name: str,
|
||||
help: str,
|
||||
type: Optional[
|
||||
Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
|
||||
] = None,
|
||||
type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
|
||||
| None = None,
|
||||
default: Any = NOT_SET,
|
||||
) -> None:
|
||||
"""Register an ini-file option.
|
||||
|
@ -224,7 +221,7 @@ class Parser:
|
|||
|
||||
|
||||
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:
|
||||
"""
|
||||
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
|
||||
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.option_id = str(option)
|
||||
|
||||
|
@ -267,8 +264,8 @@ class Argument:
|
|||
def __init__(self, *names: str, **attrs: Any) -> None:
|
||||
"""Store params in private vars for use in add_argument."""
|
||||
self._attrs = attrs
|
||||
self._short_opts: List[str] = []
|
||||
self._long_opts: List[str] = []
|
||||
self._short_opts: list[str] = []
|
||||
self._long_opts: list[str] = []
|
||||
try:
|
||||
self.type = attrs["type"]
|
||||
except KeyError:
|
||||
|
@ -279,7 +276,7 @@ class Argument:
|
|||
except KeyError:
|
||||
pass
|
||||
self._set_opt_strings(names)
|
||||
dest: Optional[str] = attrs.get("dest")
|
||||
dest: str | None = attrs.get("dest")
|
||||
if dest:
|
||||
self.dest = dest
|
||||
elif self._long_opts:
|
||||
|
@ -291,7 +288,7 @@ class Argument:
|
|||
self.dest = "???" # Needed for the error repr.
|
||||
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
|
||||
|
||||
def attrs(self) -> Mapping[str, Any]:
|
||||
|
@ -335,7 +332,7 @@ class Argument:
|
|||
self._long_opts.append(opt)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
args: List[str] = []
|
||||
args: list[str] = []
|
||||
if self._short_opts:
|
||||
args += ["_short_opts: " + repr(self._short_opts)]
|
||||
if self._long_opts:
|
||||
|
@ -355,14 +352,14 @@ class OptionGroup:
|
|||
self,
|
||||
name: str,
|
||||
description: str = "",
|
||||
parser: Optional[Parser] = None,
|
||||
parser: Parser | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
check_ispytest(_ispytest)
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.options: List[Argument] = []
|
||||
self.options: list[Argument] = []
|
||||
self.parser = parser
|
||||
|
||||
def addoption(self, *opts: str, **attrs: Any) -> None:
|
||||
|
@ -391,7 +388,7 @@ class OptionGroup:
|
|||
option = Argument(*opts, **attrs)
|
||||
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:
|
||||
for opt in option._short_opts:
|
||||
if opt[0] == "-" and opt[1].islower():
|
||||
|
@ -405,8 +402,8 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
def __init__(
|
||||
self,
|
||||
parser: Parser,
|
||||
extra_info: Optional[Dict[str, Any]] = None,
|
||||
prog: Optional[str] = None,
|
||||
extra_info: dict[str, Any] | None = None,
|
||||
prog: str | None = None,
|
||||
) -> None:
|
||||
self._parser = parser
|
||||
super().__init__(
|
||||
|
@ -433,8 +430,8 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
# Type ignored because typeshed has a very complex type in the superclass.
|
||||
def parse_args( # type: ignore
|
||||
self,
|
||||
args: Optional[Sequence[str]] = None,
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
args: Sequence[str] | None = None,
|
||||
namespace: argparse.Namespace | None = None,
|
||||
) -> argparse.Namespace:
|
||||
"""Allow splitting of positional arguments."""
|
||||
parsed, unrecognized = self.parse_known_args(args, namespace)
|
||||
|
@ -455,7 +452,7 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
# disable long --argument abbreviations without breaking short flags.
|
||||
def _parse_optional(
|
||||
self, arg_string: str
|
||||
) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
|
||||
) -> tuple[argparse.Action | None, str, str | None] | None:
|
||||
if not arg_string:
|
||||
return None
|
||||
if arg_string[0] not in self.prefix_chars:
|
||||
|
@ -507,7 +504,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
|||
orgstr = super()._format_action_invocation(action)
|
||||
if orgstr and orgstr[0] != "-": # only optional arguments
|
||||
return orgstr
|
||||
res: Optional[str] = getattr(action, "_formatted_action_invocation", None)
|
||||
res: str | None = getattr(action, "_formatted_action_invocation", None)
|
||||
if res:
|
||||
return res
|
||||
options = orgstr.split(", ")
|
||||
|
@ -516,7 +513,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
|||
action._formatted_action_invocation = orgstr # type: ignore
|
||||
return orgstr
|
||||
return_list = []
|
||||
short_long: Dict[str, str] = {}
|
||||
short_long: dict[str, str] = {}
|
||||
for option in options:
|
||||
if len(option) == 2 or option[2] == " ":
|
||||
continue
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import final
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
import iniconfig
|
||||
|
||||
|
@ -32,7 +29,7 @@ def _parse_ini_config(path: Path) -> iniconfig.IniConfig:
|
|||
|
||||
def load_config_dict_from_file(
|
||||
filepath: Path,
|
||||
) -> Optional[Dict[str, Union[str, List[str]]]]:
|
||||
) -> dict[str, str | list[str]] | None:
|
||||
"""Load pytest configuration from the given file path, if supported.
|
||||
|
||||
Return None if the file does not contain valid pytest configuration.
|
||||
|
@ -77,7 +74,7 @@ def load_config_dict_from_file(
|
|||
# TOML supports richer data types than ini files (strings, arrays, floats, ints, etc),
|
||||
# however we need to convert all scalar values to str for compatibility with the rest
|
||||
# of the configuration system, which expects strings only.
|
||||
def make_scalar(v: object) -> Union[str, List[str]]:
|
||||
def make_scalar(v: object) -> str | list[str]:
|
||||
return v if isinstance(v, list) else str(v)
|
||||
|
||||
return {k: make_scalar(v) for k, v in result.items()}
|
||||
|
@ -88,7 +85,7 @@ def load_config_dict_from_file(
|
|||
def locate_config(
|
||||
invocation_dir: Path,
|
||||
args: Iterable[Path],
|
||||
) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]:
|
||||
) -> tuple[Path | None, Path | None, dict[str, str | list[str]]]:
|
||||
"""Search in the list of arguments for a valid ini-file for pytest,
|
||||
and return a tuple of (rootdir, inifile, cfg-dict)."""
|
||||
config_names = [
|
||||
|
@ -101,7 +98,7 @@ def locate_config(
|
|||
args = [x for x in args if not str(x).startswith("-")]
|
||||
if not args:
|
||||
args = [invocation_dir]
|
||||
found_pyproject_toml: Optional[Path] = None
|
||||
found_pyproject_toml: Path | None = None
|
||||
for arg in args:
|
||||
argpath = absolutepath(arg)
|
||||
for base in (argpath, *argpath.parents):
|
||||
|
@ -122,7 +119,7 @@ def get_common_ancestor(
|
|||
invocation_dir: Path,
|
||||
paths: Iterable[Path],
|
||||
) -> Path:
|
||||
common_ancestor: Optional[Path] = None
|
||||
common_ancestor: Path | None = None
|
||||
for path in paths:
|
||||
if not path.exists():
|
||||
continue
|
||||
|
@ -144,7 +141,7 @@ def get_common_ancestor(
|
|||
return common_ancestor
|
||||
|
||||
|
||||
def get_dirs_from_args(args: Iterable[str]) -> List[Path]:
|
||||
def get_dirs_from_args(args: Iterable[str]) -> list[Path]:
|
||||
def is_option(x: str) -> bool:
|
||||
return x.startswith("-")
|
||||
|
||||
|
@ -171,11 +168,11 @@ CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supporte
|
|||
|
||||
def determine_setup(
|
||||
*,
|
||||
inifile: Optional[str],
|
||||
inifile: str | None,
|
||||
args: Sequence[str],
|
||||
rootdir_cmd_arg: Optional[str],
|
||||
rootdir_cmd_arg: str | None,
|
||||
invocation_dir: Path,
|
||||
) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]:
|
||||
) -> tuple[Path, Path | None, dict[str, str | list[str]]]:
|
||||
"""Determine the rootdir, inifile and ini configuration values from the
|
||||
command line arguments.
|
||||
|
||||
|
@ -192,7 +189,7 @@ def determine_setup(
|
|||
dirs = get_dirs_from_args(args)
|
||||
if inifile:
|
||||
inipath_ = absolutepath(inifile)
|
||||
inipath: Optional[Path] = inipath_
|
||||
inipath: Path | None = inipath_
|
||||
inicfg = load_config_dict_from_file(inipath_) or {}
|
||||
if rootdir_cmd_arg is None:
|
||||
rootdir = inipath_.parent
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
# ruff: noqa: T100
|
||||
"""Interactive debugging with PDB, the Python Debugger."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import sys
|
||||
|
@ -9,11 +11,6 @@ import types
|
|||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import Union
|
||||
import unittest
|
||||
|
||||
from _pytest import outcomes
|
||||
|
@ -30,7 +27,7 @@ from _pytest.reports import BaseReport
|
|||
from _pytest.runner import CallInfo
|
||||
|
||||
|
||||
def _validate_usepdb_cls(value: str) -> Tuple[str, str]:
|
||||
def _validate_usepdb_cls(value: str) -> tuple[str, str]:
|
||||
"""Validate syntax of --pdbcls option."""
|
||||
try:
|
||||
modname, classname = value.split(":")
|
||||
|
@ -95,22 +92,22 @@ def pytest_configure(config: Config) -> None:
|
|||
class pytestPDB:
|
||||
"""Pseudo PDB that defers to the real pdb."""
|
||||
|
||||
_pluginmanager: Optional[PytestPluginManager] = None
|
||||
_config: Optional[Config] = None
|
||||
_saved: List[
|
||||
Tuple[Callable[..., None], Optional[PytestPluginManager], Optional[Config]]
|
||||
_pluginmanager: PytestPluginManager | None = None
|
||||
_config: Config | None = None
|
||||
_saved: list[
|
||||
tuple[Callable[..., None], PytestPluginManager | None, Config | None]
|
||||
] = []
|
||||
_recursive_debug = 0
|
||||
_wrapped_pdb_cls: Optional[Tuple[Type[Any], Type[Any]]] = None
|
||||
_wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None
|
||||
|
||||
@classmethod
|
||||
def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]:
|
||||
def _is_capturing(cls, capman: CaptureManager | None) -> str | bool:
|
||||
if capman:
|
||||
return capman.is_capturing()
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _import_pdb_cls(cls, capman: Optional["CaptureManager"]):
|
||||
def _import_pdb_cls(cls, capman: CaptureManager | None):
|
||||
if not cls._config:
|
||||
import pdb
|
||||
|
||||
|
@ -149,7 +146,7 @@ class pytestPDB:
|
|||
return wrapped_cls
|
||||
|
||||
@classmethod
|
||||
def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]):
|
||||
def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None):
|
||||
import _pytest.config
|
||||
|
||||
class PytestPdbWrapper(pdb_cls):
|
||||
|
@ -238,7 +235,7 @@ class pytestPDB:
|
|||
import _pytest.config
|
||||
|
||||
if cls._pluginmanager is None:
|
||||
capman: Optional[CaptureManager] = None
|
||||
capman: CaptureManager | None = None
|
||||
else:
|
||||
capman = cls._pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
|
@ -281,7 +278,7 @@ class pytestPDB:
|
|||
|
||||
class PdbInvoke:
|
||||
def pytest_exception_interact(
|
||||
self, node: Node, call: "CallInfo[Any]", report: BaseReport
|
||||
self, node: Node, call: CallInfo[Any], report: BaseReport
|
||||
) -> None:
|
||||
capman = node.config.pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
|
|
|
@ -9,6 +9,8 @@ All constants defined in this module should be either instances of
|
|||
in case of warnings which need to format their messages.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from warnings import warn
|
||||
|
||||
from _pytest.warning_types import PytestDeprecationWarning
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Discover and run doctests in modules and test files."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import bdb
|
||||
from contextlib import contextmanager
|
||||
import functools
|
||||
|
@ -13,17 +15,11 @@ import traceback
|
|||
import types
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Generator
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
from _pytest import outcomes
|
||||
|
@ -67,7 +63,7 @@ DOCTEST_REPORT_CHOICES = (
|
|||
# Lazy definition of runner class
|
||||
RUNNER_CLASS = None
|
||||
# Lazy definition of output checker class
|
||||
CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None
|
||||
CHECKER_CLASS: type[doctest.OutputChecker] | None = None
|
||||
|
||||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
|
@ -129,7 +125,7 @@ def pytest_unconfigure() -> None:
|
|||
def pytest_collect_file(
|
||||
file_path: Path,
|
||||
parent: Collector,
|
||||
) -> Optional[Union["DoctestModule", "DoctestTextfile"]]:
|
||||
) -> DoctestModule | DoctestTextfile | None:
|
||||
config = parent.config
|
||||
if file_path.suffix == ".py":
|
||||
if config.option.doctestmodules and not any(
|
||||
|
@ -161,7 +157,7 @@ def _is_main_py(path: Path) -> bool:
|
|||
|
||||
class ReprFailDoctest(TerminalRepr):
|
||||
def __init__(
|
||||
self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]]
|
||||
self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]]
|
||||
) -> None:
|
||||
self.reprlocation_lines = reprlocation_lines
|
||||
|
||||
|
@ -173,12 +169,12 @@ class ReprFailDoctest(TerminalRepr):
|
|||
|
||||
|
||||
class MultipleDoctestFailures(Exception):
|
||||
def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None:
|
||||
def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None:
|
||||
super().__init__()
|
||||
self.failures = failures
|
||||
|
||||
|
||||
def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
||||
def _init_runner_class() -> type[doctest.DocTestRunner]:
|
||||
import doctest
|
||||
|
||||
class PytestDoctestRunner(doctest.DebugRunner):
|
||||
|
@ -190,8 +186,8 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
checker: Optional["doctest.OutputChecker"] = None,
|
||||
verbose: Optional[bool] = None,
|
||||
checker: doctest.OutputChecker | None = None,
|
||||
verbose: bool | None = None,
|
||||
optionflags: int = 0,
|
||||
continue_on_failure: bool = True,
|
||||
) -> None:
|
||||
|
@ -201,8 +197,8 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
def report_failure(
|
||||
self,
|
||||
out,
|
||||
test: "doctest.DocTest",
|
||||
example: "doctest.Example",
|
||||
test: doctest.DocTest,
|
||||
example: doctest.Example,
|
||||
got: str,
|
||||
) -> None:
|
||||
failure = doctest.DocTestFailure(test, example, got)
|
||||
|
@ -214,9 +210,9 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
def report_unexpected_exception(
|
||||
self,
|
||||
out,
|
||||
test: "doctest.DocTest",
|
||||
example: "doctest.Example",
|
||||
exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType],
|
||||
test: doctest.DocTest,
|
||||
example: doctest.Example,
|
||||
exc_info: tuple[type[BaseException], BaseException, types.TracebackType],
|
||||
) -> None:
|
||||
if isinstance(exc_info[1], OutcomeException):
|
||||
raise exc_info[1]
|
||||
|
@ -232,11 +228,11 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
|
||||
|
||||
def _get_runner(
|
||||
checker: Optional["doctest.OutputChecker"] = None,
|
||||
verbose: Optional[bool] = None,
|
||||
checker: doctest.OutputChecker | None = None,
|
||||
verbose: bool | None = None,
|
||||
optionflags: int = 0,
|
||||
continue_on_failure: bool = True,
|
||||
) -> "doctest.DocTestRunner":
|
||||
) -> doctest.DocTestRunner:
|
||||
# We need this in order to do a lazy import on doctest
|
||||
global RUNNER_CLASS
|
||||
if RUNNER_CLASS is None:
|
||||
|
@ -255,9 +251,9 @@ class DoctestItem(Item):
|
|||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
parent: "Union[DoctestTextfile, DoctestModule]",
|
||||
runner: "doctest.DocTestRunner",
|
||||
dtest: "doctest.DocTest",
|
||||
parent: DoctestTextfile | DoctestModule,
|
||||
runner: doctest.DocTestRunner,
|
||||
dtest: doctest.DocTest,
|
||||
) -> None:
|
||||
super().__init__(name, parent)
|
||||
self.runner = runner
|
||||
|
@ -274,18 +270,18 @@ class DoctestItem(Item):
|
|||
@classmethod
|
||||
def from_parent( # type: ignore[override]
|
||||
cls,
|
||||
parent: "Union[DoctestTextfile, DoctestModule]",
|
||||
parent: DoctestTextfile | DoctestModule,
|
||||
*,
|
||||
name: str,
|
||||
runner: "doctest.DocTestRunner",
|
||||
dtest: "doctest.DocTest",
|
||||
) -> "Self":
|
||||
runner: doctest.DocTestRunner,
|
||||
dtest: doctest.DocTest,
|
||||
) -> Self:
|
||||
# incompatible signature due to imposed limits on subclass
|
||||
"""The public named constructor."""
|
||||
return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
|
||||
|
||||
def _initrequest(self) -> None:
|
||||
self.funcargs: Dict[str, object] = {}
|
||||
self.funcargs: dict[str, object] = {}
|
||||
self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type]
|
||||
|
||||
def setup(self) -> None:
|
||||
|
@ -298,7 +294,7 @@ class DoctestItem(Item):
|
|||
def runtest(self) -> None:
|
||||
_check_all_skipped(self.dtest)
|
||||
self._disable_output_capturing_for_darwin()
|
||||
failures: List[doctest.DocTestFailure] = []
|
||||
failures: list[doctest.DocTestFailure] = []
|
||||
# Type ignored because we change the type of `out` from what
|
||||
# doctest expects.
|
||||
self.runner.run(self.dtest, out=failures) # type: ignore[arg-type]
|
||||
|
@ -320,12 +316,12 @@ class DoctestItem(Item):
|
|||
def repr_failure( # type: ignore[override]
|
||||
self,
|
||||
excinfo: ExceptionInfo[BaseException],
|
||||
) -> Union[str, TerminalRepr]:
|
||||
) -> str | TerminalRepr:
|
||||
import doctest
|
||||
|
||||
failures: Optional[
|
||||
Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]
|
||||
] = None
|
||||
failures: (
|
||||
Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None
|
||||
) = None
|
||||
if isinstance(
|
||||
excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
|
||||
):
|
||||
|
@ -381,11 +377,11 @@ class DoctestItem(Item):
|
|||
reprlocation_lines.append((reprlocation, lines))
|
||||
return ReprFailDoctest(reprlocation_lines)
|
||||
|
||||
def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
|
||||
def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
|
||||
return self.path, self.dtest.lineno, f"[doctest] {self.name}"
|
||||
|
||||
|
||||
def _get_flag_lookup() -> Dict[str, int]:
|
||||
def _get_flag_lookup() -> dict[str, int]:
|
||||
import doctest
|
||||
|
||||
return dict(
|
||||
|
@ -451,7 +447,7 @@ class DoctestTextfile(Module):
|
|||
)
|
||||
|
||||
|
||||
def _check_all_skipped(test: "doctest.DocTest") -> None:
|
||||
def _check_all_skipped(test: doctest.DocTest) -> None:
|
||||
"""Raise pytest.skip() if all examples in the given DocTest have the SKIP
|
||||
option set."""
|
||||
import doctest
|
||||
|
@ -477,7 +473,7 @@ def _patch_unwrap_mock_aware() -> Generator[None, None, None]:
|
|||
real_unwrap = inspect.unwrap
|
||||
|
||||
def _mock_aware_unwrap(
|
||||
func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None
|
||||
func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None
|
||||
) -> Any:
|
||||
try:
|
||||
if stop is None or stop is _is_mocked:
|
||||
|
@ -505,7 +501,13 @@ class DoctestModule(Module):
|
|||
import doctest
|
||||
|
||||
class MockAwareDocTestFinder(doctest.DocTestFinder):
|
||||
if sys.version_info < (3, 11):
|
||||
py_ver_info_minor = sys.version_info[:2]
|
||||
is_find_lineno_broken = (
|
||||
py_ver_info_minor < (3, 11)
|
||||
or (py_ver_info_minor == (3, 11) and sys.version_info.micro < 9)
|
||||
or (py_ver_info_minor == (3, 12) and sys.version_info.micro < 3)
|
||||
)
|
||||
if is_find_lineno_broken:
|
||||
|
||||
def _find_lineno(self, obj, source_lines):
|
||||
"""On older Pythons, doctest code does not take into account
|
||||
|
@ -588,7 +590,7 @@ class DoctestModule(Module):
|
|||
)
|
||||
|
||||
|
||||
def _init_checker_class() -> Type["doctest.OutputChecker"]:
|
||||
def _init_checker_class() -> type[doctest.OutputChecker]:
|
||||
import doctest
|
||||
import re
|
||||
|
||||
|
@ -656,8 +658,8 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
|
|||
return got
|
||||
offset = 0
|
||||
for w, g in zip(wants, gots):
|
||||
fraction: Optional[str] = w.group("fraction")
|
||||
exponent: Optional[str] = w.group("exponent1")
|
||||
fraction: str | None = w.group("fraction")
|
||||
exponent: str | None = w.group("exponent1")
|
||||
if exponent is None:
|
||||
exponent = w.group("exponent2")
|
||||
precision = 0 if fraction is None else len(fraction)
|
||||
|
@ -676,7 +678,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
|
|||
return LiteralsOutputChecker
|
||||
|
||||
|
||||
def _get_checker() -> "doctest.OutputChecker":
|
||||
def _get_checker() -> doctest.OutputChecker:
|
||||
"""Return a doctest.OutputChecker subclass that supports some
|
||||
additional options:
|
||||
|
||||
|
@ -735,7 +737,7 @@ def _get_report_choice(key: str) -> int:
|
|||
|
||||
|
||||
@fixture(scope="session")
|
||||
def doctest_namespace() -> Dict[str, Any]:
|
||||
def doctest_namespace() -> dict[str, Any]:
|
||||
"""Fixture that returns a :py:class:`dict` that will be injected into the
|
||||
namespace of doctests.
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Generator
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: allow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
from collections import defaultdict
|
||||
from collections import deque
|
||||
|
@ -20,7 +22,6 @@ from typing import Generator
|
|||
from typing import Generic
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import MutableMapping
|
||||
from typing import NoReturn
|
||||
|
@ -28,7 +29,6 @@ from typing import Optional
|
|||
from typing import OrderedDict
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
|
@ -113,18 +113,18 @@ _FixtureCachedResult = Union[
|
|||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class PseudoFixtureDef(Generic[FixtureValue]):
|
||||
cached_result: "_FixtureCachedResult[FixtureValue]"
|
||||
cached_result: _FixtureCachedResult[FixtureValue]
|
||||
_scope: Scope
|
||||
|
||||
|
||||
def pytest_sessionstart(session: "Session") -> None:
|
||||
def pytest_sessionstart(session: Session) -> None:
|
||||
session._fixturemanager = FixtureManager(session)
|
||||
|
||||
|
||||
def get_scope_package(
|
||||
node: nodes.Item,
|
||||
fixturedef: "FixtureDef[object]",
|
||||
) -> Optional[nodes.Node]:
|
||||
fixturedef: FixtureDef[object],
|
||||
) -> nodes.Node | None:
|
||||
from _pytest.python import Package
|
||||
|
||||
for parent in node.iter_parents():
|
||||
|
@ -133,7 +133,7 @@ def get_scope_package(
|
|||
return node.session
|
||||
|
||||
|
||||
def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]:
|
||||
def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None:
|
||||
import _pytest.python
|
||||
|
||||
if scope is Scope.Function:
|
||||
|
@ -152,7 +152,7 @@ def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]:
|
|||
assert_never(scope)
|
||||
|
||||
|
||||
def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
||||
def getfixturemarker(obj: object) -> FixtureFunctionMarker | None:
|
||||
"""Return fixturemarker or None if it doesn't exist or raised
|
||||
exceptions."""
|
||||
return cast(
|
||||
|
@ -171,8 +171,8 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
|||
class FixtureArgKey:
|
||||
argname: str
|
||||
param_index: int
|
||||
scoped_item_path: Optional[Path]
|
||||
item_cls: Optional[type]
|
||||
scoped_item_path: Path | None
|
||||
item_cls: type | None
|
||||
|
||||
|
||||
_V = TypeVar("_V")
|
||||
|
@ -212,10 +212,10 @@ def get_parametrized_fixture_argkeys(
|
|||
yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls)
|
||||
|
||||
|
||||
def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
||||
argkeys_by_item: Dict[Scope, Dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {}
|
||||
items_by_argkey: Dict[
|
||||
Scope, Dict[FixtureArgKey, OrderedDict[nodes.Item, None]]
|
||||
def reorder_items(items: Sequence[nodes.Item]) -> list[nodes.Item]:
|
||||
argkeys_by_item: dict[Scope, dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {}
|
||||
items_by_argkey: dict[
|
||||
Scope, dict[FixtureArgKey, OrderedDict[nodes.Item, None]]
|
||||
] = {}
|
||||
for scope in HIGH_SCOPES:
|
||||
scoped_argkeys_by_item = argkeys_by_item[scope] = {}
|
||||
|
@ -249,7 +249,7 @@ def reorder_items_atscope(
|
|||
scoped_items_by_argkey = items_by_argkey[scope]
|
||||
scoped_argkeys_by_item = argkeys_by_item[scope]
|
||||
|
||||
ignore: Set[FixtureArgKey] = set()
|
||||
ignore: set[FixtureArgKey] = set()
|
||||
items_deque = deque(items)
|
||||
items_done: OrderedSet[nodes.Item] = {}
|
||||
while items_deque:
|
||||
|
@ -309,19 +309,19 @@ class FuncFixtureInfo:
|
|||
__slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs")
|
||||
|
||||
# Fixture names that the item requests directly by function parameters.
|
||||
argnames: Tuple[str, ...]
|
||||
argnames: tuple[str, ...]
|
||||
# Fixture names that the item immediately requires. These include
|
||||
# argnames + fixture names specified via usefixtures and via autouse=True in
|
||||
# fixture definitions.
|
||||
initialnames: Tuple[str, ...]
|
||||
initialnames: tuple[str, ...]
|
||||
# The transitive closure of the fixture names that the item requires.
|
||||
# Note: can't include dynamic dependencies (`request.getfixturevalue` calls).
|
||||
names_closure: List[str]
|
||||
names_closure: list[str]
|
||||
# A map from a fixture name in the transitive closure to the FixtureDefs
|
||||
# matching the name which are applicable to this function.
|
||||
# There may be multiple overriding fixtures with the same name. The
|
||||
# sequence is ordered from furthest to closes to the function.
|
||||
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
|
||||
name2fixturedefs: dict[str, Sequence[FixtureDef[Any]]]
|
||||
|
||||
def prune_dependency_tree(self) -> None:
|
||||
"""Recompute names_closure from initialnames and name2fixturedefs.
|
||||
|
@ -334,7 +334,7 @@ class FuncFixtureInfo:
|
|||
tree. In this way the dependency tree can get pruned, and the closure
|
||||
of argnames may get reduced.
|
||||
"""
|
||||
closure: Set[str] = set()
|
||||
closure: set[str] = set()
|
||||
working_set = set(self.initialnames)
|
||||
while working_set:
|
||||
argname = working_set.pop()
|
||||
|
@ -360,10 +360,10 @@ class FixtureRequest(abc.ABC):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
pyfuncitem: "Function",
|
||||
fixturename: Optional[str],
|
||||
arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]],
|
||||
fixture_defs: Dict[str, "FixtureDef[Any]"],
|
||||
pyfuncitem: Function,
|
||||
fixturename: str | None,
|
||||
arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]],
|
||||
fixture_defs: dict[str, FixtureDef[Any]],
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -390,7 +390,7 @@ class FixtureRequest(abc.ABC):
|
|||
self.param: Any
|
||||
|
||||
@property
|
||||
def _fixturemanager(self) -> "FixtureManager":
|
||||
def _fixturemanager(self) -> FixtureManager:
|
||||
return self._pyfuncitem.session._fixturemanager
|
||||
|
||||
@property
|
||||
|
@ -406,13 +406,13 @@ class FixtureRequest(abc.ABC):
|
|||
@abc.abstractmethod
|
||||
def _check_scope(
|
||||
self,
|
||||
requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]],
|
||||
requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
|
||||
requested_scope: Scope,
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def fixturenames(self) -> List[str]:
|
||||
def fixturenames(self) -> list[str]:
|
||||
"""Names of all active fixtures in this request."""
|
||||
result = list(self._pyfuncitem.fixturenames)
|
||||
result.extend(set(self._fixture_defs).difference(result))
|
||||
|
@ -477,7 +477,7 @@ class FixtureRequest(abc.ABC):
|
|||
return node.keywords
|
||||
|
||||
@property
|
||||
def session(self) -> "Session":
|
||||
def session(self) -> Session:
|
||||
"""Pytest session object."""
|
||||
return self._pyfuncitem.session
|
||||
|
||||
|
@ -487,7 +487,7 @@ class FixtureRequest(abc.ABC):
|
|||
the last test within the requesting test context finished execution."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
|
||||
def applymarker(self, marker: str | MarkDecorator) -> None:
|
||||
"""Apply a marker to a single test function invocation.
|
||||
|
||||
This method is useful if you don't want to have a keyword/marker
|
||||
|
@ -498,7 +498,7 @@ class FixtureRequest(abc.ABC):
|
|||
"""
|
||||
self.node.add_marker(marker)
|
||||
|
||||
def raiseerror(self, msg: Optional[str]) -> NoReturn:
|
||||
def raiseerror(self, msg: str | None) -> NoReturn:
|
||||
"""Raise a FixtureLookupError exception.
|
||||
|
||||
:param msg:
|
||||
|
@ -535,7 +535,7 @@ class FixtureRequest(abc.ABC):
|
|||
)
|
||||
return fixturedef.cached_result[0]
|
||||
|
||||
def _iter_chain(self) -> Iterator["SubRequest"]:
|
||||
def _iter_chain(self) -> Iterator[SubRequest]:
|
||||
"""Yield all SubRequests in the chain, from self up.
|
||||
|
||||
Note: does *not* yield the TopRequest.
|
||||
|
@ -547,7 +547,7 @@ class FixtureRequest(abc.ABC):
|
|||
|
||||
def _get_active_fixturedef(
|
||||
self, argname: str
|
||||
) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
|
||||
) -> FixtureDef[object] | PseudoFixtureDef[object]:
|
||||
if argname == "request":
|
||||
cached_result = (self, [0], None)
|
||||
return PseudoFixtureDef(cached_result, Scope.Function)
|
||||
|
@ -618,7 +618,7 @@ class FixtureRequest(abc.ABC):
|
|||
self._fixture_defs[argname] = fixturedef
|
||||
return fixturedef
|
||||
|
||||
def _check_fixturedef_without_param(self, fixturedef: "FixtureDef[object]") -> None:
|
||||
def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None:
|
||||
"""Check that this request is allowed to execute this fixturedef without
|
||||
a param."""
|
||||
funcitem = self._pyfuncitem
|
||||
|
@ -651,7 +651,7 @@ class FixtureRequest(abc.ABC):
|
|||
)
|
||||
fail(msg, pytrace=False)
|
||||
|
||||
def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
|
||||
def _get_fixturestack(self) -> list[FixtureDef[Any]]:
|
||||
values = [request._fixturedef for request in self._iter_chain()]
|
||||
values.reverse()
|
||||
return values
|
||||
|
@ -661,7 +661,7 @@ class FixtureRequest(abc.ABC):
|
|||
class TopRequest(FixtureRequest):
|
||||
"""The type of the ``request`` fixture in a test function."""
|
||||
|
||||
def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
|
||||
def __init__(self, pyfuncitem: Function, *, _ispytest: bool = False) -> None:
|
||||
super().__init__(
|
||||
fixturename=None,
|
||||
pyfuncitem=pyfuncitem,
|
||||
|
@ -676,7 +676,7 @@ class TopRequest(FixtureRequest):
|
|||
|
||||
def _check_scope(
|
||||
self,
|
||||
requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]],
|
||||
requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
|
||||
requested_scope: Scope,
|
||||
) -> None:
|
||||
# TopRequest always has function scope so always valid.
|
||||
|
@ -710,7 +710,7 @@ class SubRequest(FixtureRequest):
|
|||
scope: Scope,
|
||||
param: Any,
|
||||
param_index: int,
|
||||
fixturedef: "FixtureDef[object]",
|
||||
fixturedef: FixtureDef[object],
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -740,7 +740,7 @@ class SubRequest(FixtureRequest):
|
|||
scope = self._scope
|
||||
if scope is Scope.Function:
|
||||
# This might also be a non-function Item despite its attribute name.
|
||||
node: Optional[nodes.Node] = self._pyfuncitem
|
||||
node: nodes.Node | None = self._pyfuncitem
|
||||
elif scope is Scope.Package:
|
||||
node = get_scope_package(self._pyfuncitem, self._fixturedef)
|
||||
else:
|
||||
|
@ -753,7 +753,7 @@ class SubRequest(FixtureRequest):
|
|||
|
||||
def _check_scope(
|
||||
self,
|
||||
requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]],
|
||||
requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
|
||||
requested_scope: Scope,
|
||||
) -> None:
|
||||
if isinstance(requested_fixturedef, PseudoFixtureDef):
|
||||
|
@ -774,7 +774,7 @@ class SubRequest(FixtureRequest):
|
|||
pytrace=False,
|
||||
)
|
||||
|
||||
def _format_fixturedef_line(self, fixturedef: "FixtureDef[object]") -> str:
|
||||
def _format_fixturedef_line(self, fixturedef: FixtureDef[object]) -> str:
|
||||
factory = fixturedef.func
|
||||
path, lineno = getfslineno(factory)
|
||||
if isinstance(path, Path):
|
||||
|
@ -791,15 +791,15 @@ class FixtureLookupError(LookupError):
|
|||
"""Could not return a requested fixture (missing or invalid)."""
|
||||
|
||||
def __init__(
|
||||
self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None
|
||||
self, argname: str | None, request: FixtureRequest, msg: str | None = None
|
||||
) -> None:
|
||||
self.argname = argname
|
||||
self.request = request
|
||||
self.fixturestack = request._get_fixturestack()
|
||||
self.msg = msg
|
||||
|
||||
def formatrepr(self) -> "FixtureLookupErrorRepr":
|
||||
tblines: List[str] = []
|
||||
def formatrepr(self) -> FixtureLookupErrorRepr:
|
||||
tblines: list[str] = []
|
||||
addline = tblines.append
|
||||
stack = [self.request._pyfuncitem.obj]
|
||||
stack.extend(map(lambda x: x.func, self.fixturestack))
|
||||
|
@ -847,11 +847,11 @@ class FixtureLookupError(LookupError):
|
|||
class FixtureLookupErrorRepr(TerminalRepr):
|
||||
def __init__(
|
||||
self,
|
||||
filename: Union[str, "os.PathLike[str]"],
|
||||
filename: str | os.PathLike[str],
|
||||
firstlineno: int,
|
||||
tblines: Sequence[str],
|
||||
errorstring: str,
|
||||
argname: Optional[str],
|
||||
argname: str | None,
|
||||
) -> None:
|
||||
self.tblines = tblines
|
||||
self.errorstring = errorstring
|
||||
|
@ -879,7 +879,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
|
|||
|
||||
|
||||
def call_fixture_func(
|
||||
fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs
|
||||
fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs
|
||||
) -> FixtureValue:
|
||||
if is_generator(fixturefunc):
|
||||
fixturefunc = cast(
|
||||
|
@ -950,14 +950,12 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
baseid: Optional[str],
|
||||
baseid: str | None,
|
||||
argname: str,
|
||||
func: "_FixtureFunc[FixtureValue]",
|
||||
scope: Union[Scope, _ScopeName, Callable[[str, Config], _ScopeName], None],
|
||||
params: Optional[Sequence[object]],
|
||||
ids: Optional[
|
||||
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
||||
] = None,
|
||||
func: _FixtureFunc[FixtureValue],
|
||||
scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None,
|
||||
params: Sequence[object] | None,
|
||||
ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -1003,8 +1001,8 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
self.argnames: Final = getfuncargnames(func, name=argname)
|
||||
# If the fixture was executed, the current value of the fixture.
|
||||
# Can change if the fixture is executed with different parameters.
|
||||
self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
|
||||
self._finalizers: Final[List[Callable[[], object]]] = []
|
||||
self.cached_result: _FixtureCachedResult[FixtureValue] | None = None
|
||||
self._finalizers: Final[list[Callable[[], object]]] = []
|
||||
|
||||
@property
|
||||
def scope(self) -> _ScopeName:
|
||||
|
@ -1015,7 +1013,7 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
self._finalizers.append(finalizer)
|
||||
|
||||
def finish(self, request: SubRequest) -> None:
|
||||
exceptions: List[BaseException] = []
|
||||
exceptions: list[BaseException] = []
|
||||
while self._finalizers:
|
||||
fin = self._finalizers.pop()
|
||||
try:
|
||||
|
@ -1099,7 +1097,7 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
|
||||
def resolve_fixture_function(
|
||||
fixturedef: FixtureDef[FixtureValue], request: FixtureRequest
|
||||
) -> "_FixtureFunc[FixtureValue]":
|
||||
) -> _FixtureFunc[FixtureValue]:
|
||||
"""Get the actual callable that can be called to obtain the fixture
|
||||
value."""
|
||||
fixturefunc = fixturedef.func
|
||||
|
@ -1147,7 +1145,7 @@ def pytest_fixture_setup(
|
|||
|
||||
def wrap_function_to_error_out_if_called_directly(
|
||||
function: FixtureFunction,
|
||||
fixture_marker: "FixtureFunctionMarker",
|
||||
fixture_marker: FixtureFunctionMarker,
|
||||
) -> FixtureFunction:
|
||||
"""Wrap the given fixture function so we can raise an error about it being called directly,
|
||||
instead of used as an argument in a test function."""
|
||||
|
@ -1173,13 +1171,11 @@ def wrap_function_to_error_out_if_called_directly(
|
|||
@final
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class FixtureFunctionMarker:
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]"
|
||||
params: Optional[Tuple[object, ...]]
|
||||
scope: _ScopeName | Callable[[str, Config], _ScopeName]
|
||||
params: tuple[object, ...] | None
|
||||
autouse: bool = False
|
||||
ids: Optional[
|
||||
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
||||
] = None
|
||||
name: Optional[str] = None
|
||||
ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None
|
||||
name: str | None = None
|
||||
|
||||
_ispytest: dataclasses.InitVar[bool] = False
|
||||
|
||||
|
@ -1217,13 +1213,11 @@ class FixtureFunctionMarker:
|
|||
def fixture(
|
||||
fixture_function: FixtureFunction,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
|
||||
params: Optional[Iterable[object]] = ...,
|
||||
scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
|
||||
params: Iterable[object] | None = ...,
|
||||
autouse: bool = ...,
|
||||
ids: Optional[
|
||||
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
|
||||
] = ...,
|
||||
name: Optional[str] = ...,
|
||||
ids: Sequence[object | None] | Callable[[Any], object | None] | None = ...,
|
||||
name: str | None = ...,
|
||||
) -> FixtureFunction: ...
|
||||
|
||||
|
||||
|
@ -1231,27 +1225,23 @@ def fixture(
|
|||
def fixture(
|
||||
fixture_function: None = ...,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
|
||||
params: Optional[Iterable[object]] = ...,
|
||||
scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
|
||||
params: Iterable[object] | None = ...,
|
||||
autouse: bool = ...,
|
||||
ids: Optional[
|
||||
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
|
||||
] = ...,
|
||||
name: Optional[str] = None,
|
||||
ids: Sequence[object | None] | Callable[[Any], object | None] | None = ...,
|
||||
name: str | None = None,
|
||||
) -> FixtureFunctionMarker: ...
|
||||
|
||||
|
||||
def fixture(
|
||||
fixture_function: Optional[FixtureFunction] = None,
|
||||
fixture_function: FixtureFunction | None = None,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function",
|
||||
params: Optional[Iterable[object]] = None,
|
||||
scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function",
|
||||
params: Iterable[object] | None = None,
|
||||
autouse: bool = False,
|
||||
ids: Optional[
|
||||
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
|
||||
] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> Union[FixtureFunctionMarker, FixtureFunction]:
|
||||
ids: Sequence[object | None] | Callable[[Any], object | None] | None = None,
|
||||
name: str | None = None,
|
||||
) -> FixtureFunctionMarker | FixtureFunction:
|
||||
"""Decorator to mark a fixture factory function.
|
||||
|
||||
This decorator can be used, with or without parameters, to define a
|
||||
|
@ -1385,7 +1375,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.showfixtures:
|
||||
showfixtures(config)
|
||||
return 0
|
||||
|
@ -1395,7 +1385,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
|||
return None
|
||||
|
||||
|
||||
def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
|
||||
def _get_direct_parametrize_args(node: nodes.Node) -> set[str]:
|
||||
"""Return all direct parametrization arguments of a node, so we don't
|
||||
mistake them for fixtures.
|
||||
|
||||
|
@ -1404,7 +1394,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
|
|||
These things are done later as well when dealing with parametrization
|
||||
so this could be improved.
|
||||
"""
|
||||
parametrize_argnames: Set[str] = set()
|
||||
parametrize_argnames: set[str] = set()
|
||||
for marker in node.iter_markers(name="parametrize"):
|
||||
if not marker.kwargs.get("indirect", False):
|
||||
p_argnames, _ = ParameterSet._parse_parametrize_args(
|
||||
|
@ -1414,7 +1404,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]:
|
|||
return parametrize_argnames
|
||||
|
||||
|
||||
def deduplicate_names(*seqs: Iterable[str]) -> Tuple[str, ...]:
|
||||
def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]:
|
||||
"""De-duplicate the sequence of names while keeping the original order."""
|
||||
# Ideally we would use a set, but it does not preserve insertion order.
|
||||
return tuple(dict.fromkeys(name for seq in seqs for name in seq))
|
||||
|
@ -1451,17 +1441,17 @@ class FixtureManager:
|
|||
by a lookup of their FuncFixtureInfo.
|
||||
"""
|
||||
|
||||
def __init__(self, session: "Session") -> None:
|
||||
def __init__(self, session: Session) -> None:
|
||||
self.session = session
|
||||
self.config: Config = session.config
|
||||
# Maps a fixture name (argname) to all of the FixtureDefs in the test
|
||||
# suite/plugins defined with this name. Populated by parsefactories().
|
||||
# TODO: The order of the FixtureDefs list of each arg is significant,
|
||||
# explain.
|
||||
self._arg2fixturedefs: Final[Dict[str, List[FixtureDef[Any]]]] = {}
|
||||
self._holderobjseen: Final[Set[object]] = set()
|
||||
self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {}
|
||||
self._holderobjseen: Final[set[object]] = set()
|
||||
# A mapping from a nodeid to a list of autouse fixtures it defines.
|
||||
self._nodeid_autousenames: Final[Dict[str, List[str]]] = {
|
||||
self._nodeid_autousenames: Final[dict[str, list[str]]] = {
|
||||
"": self.config.getini("usefixtures"),
|
||||
}
|
||||
session.config.pluginmanager.register(self, "funcmanage")
|
||||
|
@ -1469,8 +1459,8 @@ class FixtureManager:
|
|||
def getfixtureinfo(
|
||||
self,
|
||||
node: nodes.Item,
|
||||
func: Optional[Callable[..., object]],
|
||||
cls: Optional[type],
|
||||
func: Callable[..., object] | None,
|
||||
cls: type | None,
|
||||
) -> FuncFixtureInfo:
|
||||
"""Calculate the :class:`FuncFixtureInfo` for an item.
|
||||
|
||||
|
@ -1541,9 +1531,9 @@ class FixtureManager:
|
|||
def getfixtureclosure(
|
||||
self,
|
||||
parentnode: nodes.Node,
|
||||
initialnames: Tuple[str, ...],
|
||||
initialnames: tuple[str, ...],
|
||||
ignore_args: AbstractSet[str],
|
||||
) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
|
||||
) -> tuple[list[str], dict[str, Sequence[FixtureDef[Any]]]]:
|
||||
# Collect the closure of all fixtures, starting with the given
|
||||
# fixturenames as the initial set. As we have to visit all
|
||||
# factory definitions anyway, we also return an arg2fixturedefs
|
||||
|
@ -1553,7 +1543,7 @@ class FixtureManager:
|
|||
|
||||
fixturenames_closure = list(initialnames)
|
||||
|
||||
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
|
||||
arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {}
|
||||
lastlen = -1
|
||||
while lastlen != len(fixturenames_closure):
|
||||
lastlen = len(fixturenames_closure)
|
||||
|
@ -1580,7 +1570,7 @@ class FixtureManager:
|
|||
fixturenames_closure.sort(key=sort_by_scope, reverse=True)
|
||||
return fixturenames_closure, arg2fixturedefs
|
||||
|
||||
def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
|
||||
def pytest_generate_tests(self, metafunc: Metafunc) -> None:
|
||||
"""Generate new tests based on parametrized fixtures used by the given metafunc"""
|
||||
|
||||
def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]:
|
||||
|
@ -1625,7 +1615,7 @@ class FixtureManager:
|
|||
|
||||
# Try next super fixture, if any.
|
||||
|
||||
def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None:
|
||||
def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> None:
|
||||
# Separate parametrized setups.
|
||||
items[:] = reorder_items(items)
|
||||
|
||||
|
@ -1633,15 +1623,11 @@ class FixtureManager:
|
|||
self,
|
||||
*,
|
||||
name: str,
|
||||
func: "_FixtureFunc[object]",
|
||||
nodeid: Optional[str],
|
||||
scope: Union[
|
||||
Scope, _ScopeName, Callable[[str, Config], _ScopeName]
|
||||
] = "function",
|
||||
params: Optional[Sequence[object]] = None,
|
||||
ids: Optional[
|
||||
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
||||
] = None,
|
||||
func: _FixtureFunc[object],
|
||||
nodeid: str | None,
|
||||
scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] = "function",
|
||||
params: Sequence[object] | None = None,
|
||||
ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
|
||||
autouse: bool = False,
|
||||
) -> None:
|
||||
"""Register a fixture
|
||||
|
@ -1699,14 +1685,14 @@ class FixtureManager:
|
|||
def parsefactories(
|
||||
self,
|
||||
node_or_obj: object,
|
||||
nodeid: Optional[str],
|
||||
nodeid: str | None,
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def parsefactories(
|
||||
self,
|
||||
node_or_obj: Union[nodes.Node, object],
|
||||
nodeid: Union[str, NotSetType, None] = NOTSET,
|
||||
node_or_obj: nodes.Node | object,
|
||||
nodeid: str | NotSetType | None = NOTSET,
|
||||
) -> None:
|
||||
"""Collect fixtures from a collection node or object.
|
||||
|
||||
|
@ -1764,7 +1750,7 @@ class FixtureManager:
|
|||
|
||||
def getfixturedefs(
|
||||
self, argname: str, node: nodes.Node
|
||||
) -> Optional[Sequence[FixtureDef[Any]]]:
|
||||
) -> Sequence[FixtureDef[Any]] | None:
|
||||
"""Get FixtureDefs for a fixture name which are applicable
|
||||
to a given node.
|
||||
|
||||
|
@ -1791,7 +1777,7 @@ class FixtureManager:
|
|||
yield fixturedef
|
||||
|
||||
|
||||
def show_fixtures_per_test(config: Config) -> Union[int, ExitCode]:
|
||||
def show_fixtures_per_test(config: Config) -> int | ExitCode:
|
||||
from _pytest.main import wrap_session
|
||||
|
||||
return wrap_session(config, _show_fixtures_per_test)
|
||||
|
@ -1809,7 +1795,7 @@ def _pretty_fixture_path(invocation_dir: Path, func) -> str:
|
|||
return bestrelpath(invocation_dir, loc)
|
||||
|
||||
|
||||
def _show_fixtures_per_test(config: Config, session: "Session") -> None:
|
||||
def _show_fixtures_per_test(config: Config, session: Session) -> None:
|
||||
import _pytest.config
|
||||
|
||||
session.perform_collect()
|
||||
|
@ -1842,7 +1828,7 @@ def _show_fixtures_per_test(config: Config, session: "Session") -> None:
|
|||
|
||||
def write_item(item: nodes.Item) -> None:
|
||||
# Not all items have _fixtureinfo attribute.
|
||||
info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None)
|
||||
info: FuncFixtureInfo | None = getattr(item, "_fixtureinfo", None)
|
||||
if info is None or not info.name2fixturedefs:
|
||||
# This test item does not use any fixtures.
|
||||
return
|
||||
|
@ -1862,13 +1848,13 @@ def _show_fixtures_per_test(config: Config, session: "Session") -> None:
|
|||
write_item(session_item)
|
||||
|
||||
|
||||
def showfixtures(config: Config) -> Union[int, ExitCode]:
|
||||
def showfixtures(config: Config) -> int | ExitCode:
|
||||
from _pytest.main import wrap_session
|
||||
|
||||
return wrap_session(config, _showfixtures_main)
|
||||
|
||||
|
||||
def _showfixtures_main(config: Config, session: "Session") -> None:
|
||||
def _showfixtures_main(config: Config, session: Session) -> None:
|
||||
import _pytest.config
|
||||
|
||||
session.perform_collect()
|
||||
|
@ -1879,7 +1865,7 @@ def _showfixtures_main(config: Config, session: "Session") -> None:
|
|||
fm = session._fixturemanager
|
||||
|
||||
available = []
|
||||
seen: Set[Tuple[str, str]] = set()
|
||||
seen: set[tuple[str, str]] = set()
|
||||
|
||||
for argname, fixturedefs in fm._arg2fixturedefs.items():
|
||||
assert fixturedefs is not None
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue