Merge remote-tracking branch 'origin/main' into teardown_fixture_order
This commit is contained in:
commit
5eddb50b09
|
@ -23,6 +23,11 @@ afc607cfd81458d4e4f3b1f3cf8cc931b933907e
|
|||
5f95dce95602921a70bfbc7d8de2f7712c5e4505
|
||||
# ran pyupgrade-docs again
|
||||
75d0b899bbb56d6849e9d69d83a9426ed3f43f8b
|
||||
|
||||
# move argument parser to own file
|
||||
c9df77cbd6a365dcb73c39618e4842711817e871
|
||||
# Replace reorder-python-imports by isort due to black incompatibility (#11896)
|
||||
8b54596639f41dfac070030ef20394b9001fe63c
|
||||
# Run blacken-docs with black's 2024's style
|
||||
4546d5445aaefe6a03957db028c263521dfb5c4b
|
||||
# Migration to ruff / ruff format
|
||||
4588653b2497ed25976b7aaff225b889fb476756
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: Build and Check Package
|
||||
uses: hynek/build-and-inspect-python-package@v2.0.0
|
||||
uses: hynek/build-and-inspect-python-package@v2.0.1
|
||||
|
||||
deploy:
|
||||
if: github.repository == 'pytest-dev/pytest'
|
||||
|
|
|
@ -35,7 +35,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Build and Check Package
|
||||
uses: hynek/build-and-inspect-python-package@v2.0.0
|
||||
uses: hynek/build-and-inspect-python-package@v2.0.1
|
||||
|
||||
build:
|
||||
needs: [package]
|
||||
|
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
python-version: "3.11"
|
||||
cache: pip
|
||||
- name: requests-cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pytest-plugin-list/
|
||||
key: plugins-http-cache-${{ github.run_id }} # Can use time based key as well
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.12.1
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: 1.16.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==23.7.0]
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: "v0.2.1"
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["--fix"]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
|
@ -20,37 +16,11 @@ repos:
|
|||
- id: debug-statements
|
||||
exclude: _pytest/(debugging|hookspec).py
|
||||
language_version: python3
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.2.1
|
||||
- repo: https://github.com/adamchainz/blacken-docs
|
||||
rev: 1.16.0
|
||||
hooks:
|
||||
- id: autoflake
|
||||
name: autoflake
|
||||
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
|
||||
language: python
|
||||
files: \.py$
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
additional_dependencies:
|
||||
- flake8-typing-imports==1.12.0
|
||||
- flake8-docstrings==1.5.0
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.12.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src', --py38-plus]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: ["--max-py-version=3.12", "--include-version-classifiers"]
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==24.1.1]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.10.0
|
||||
hooks:
|
||||
|
@ -64,7 +34,7 @@ repos:
|
|||
additional_dependencies:
|
||||
- iniconfig>=1.1.0
|
||||
- attrs>=19.2.0
|
||||
- pluggy
|
||||
- pluggy>=1.4.0
|
||||
- packaging
|
||||
- tomli
|
||||
- types-pkg_resources
|
||||
|
@ -72,6 +42,12 @@ repos:
|
|||
# for mypy running on python>=3.11 since exceptiongroup is only a dependency
|
||||
# on <3.11
|
||||
- exceptiongroup>=1.0.0rc8
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "1.7.0"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
|
||||
additional_dependencies: ["tox>=4.9"]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rst
|
||||
|
|
3
AUTHORS
3
AUTHORS
|
@ -93,6 +93,7 @@ Christopher Dignam
|
|||
Christopher Gilling
|
||||
Claire Cecil
|
||||
Claudio Madotto
|
||||
Clément M.T. Robert
|
||||
CrazyMerlyn
|
||||
Cristian Vera
|
||||
Cyrus Maden
|
||||
|
@ -340,6 +341,7 @@ Ronny Pfannschmidt
|
|||
Ross Lawley
|
||||
Ruaridh Williamson
|
||||
Russel Winder
|
||||
Russell Martin
|
||||
Ryan Puddephatt
|
||||
Ryan Wooden
|
||||
Sadra Barikbin
|
||||
|
@ -414,6 +416,7 @@ Vivaan Verma
|
|||
Vlad Dragos
|
||||
Vlad Radziuk
|
||||
Vladyslav Rachek
|
||||
Volodymyr Kochetkov
|
||||
Volodymyr Piskun
|
||||
Wei Lin
|
||||
Wil Cooley
|
||||
|
|
|
@ -27,9 +27,6 @@
|
|||
:target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/main
|
||||
:alt: pre-commit.ci status
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
|
||||
:target: https://www.codetriage.com/pytest-dev/pytest
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import sys
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import cProfile
|
||||
import pytest # NOQA
|
||||
import pstats
|
||||
|
||||
import pytest # noqa: F401
|
||||
|
||||
script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
|
||||
cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
|
||||
p = pstats.Stats("prof")
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# FastFilesCompleter 0.7383 1.0760
|
||||
import timeit
|
||||
|
||||
|
||||
imports = [
|
||||
"from argcomplete.completers import FilesCompleter as completer",
|
||||
"from _pytest._argcomplete import FastFilesCompleter as completer",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import pytest
|
||||
|
||||
|
||||
SKIP = True
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from unittest import TestCase # noqa: F401
|
||||
|
||||
|
||||
for i in range(15000):
|
||||
exec(
|
||||
f"""
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
:func:`pytest.warns` now validates that warning object's ``message`` is of type `str` -- currently in Python it is possible to pass other types than `str` when creating `Warning` instances, however this causes an exception when :func:`warnings.filterwarnings` is used to filter those warnings. See `CPython #103577 <https://github.com/python/cpython/issues/103577>`__ for a discussion.
|
||||
While this can be considered a bug in CPython, we decided to put guards in pytest as the error message produced without this check in place is confusing.
|
|
@ -0,0 +1 @@
|
|||
Correctly handle errors from :func:`getpass.getuser` in Python 3.13.
|
|
@ -0,0 +1 @@
|
|||
Fix an edge case where ``ExceptionInfo._stringify_exception`` could crash :func:`pytest.raises`.
|
|
@ -0,0 +1 @@
|
|||
Fix a regression in pytest 8.0.0 whereby autouse fixtures defined in a module get ignored by the doctests in the module.
|
|
@ -0,0 +1 @@
|
|||
Fix a regression in pytest 8.0.0 whereby items would be collected in reverse order in some circumstances.
|
|
@ -6,6 +6,7 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-8.0.0
|
||||
release-8.0.0rc2
|
||||
release-8.0.0rc1
|
||||
release-7.4.4
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
pytest-8.0.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 8.0.0 release!
|
||||
|
||||
This release contains new features, improvements, bug fixes, and breaking changes, so users
|
||||
are encouraged to take a look at the CHANGELOG carefully:
|
||||
|
||||
https://docs.pytest.org/en/stable/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/stable/
|
||||
|
||||
As usual, you can upgrade from PyPI via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -129,7 +129,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
|||
if pytestconfig.getoption("verbose") > 0:
|
||||
...
|
||||
|
||||
record_property -- .../_pytest/junitxml.py:282
|
||||
record_property -- .../_pytest/junitxml.py:284
|
||||
Add extra properties to the calling test.
|
||||
|
||||
User properties become part of the test report and are available to the
|
||||
|
@ -143,13 +143,13 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
|||
def test_function(record_property):
|
||||
record_property("example_key", 1)
|
||||
|
||||
record_xml_attribute -- .../_pytest/junitxml.py:305
|
||||
record_xml_attribute -- .../_pytest/junitxml.py:307
|
||||
Add extra xml attributes to the tag for the calling test.
|
||||
|
||||
The fixture is callable with ``name, value``. The value is
|
||||
automatically XML-encoded.
|
||||
|
||||
record_testsuite_property [session scope] -- .../_pytest/junitxml.py:343
|
||||
record_testsuite_property [session scope] -- .../_pytest/junitxml.py:345
|
||||
Record a new ``<property>`` tag as child of the root ``<testsuite>``.
|
||||
|
||||
This is suitable to writing global information regarding the entire test
|
||||
|
@ -196,7 +196,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
|||
|
||||
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
|
||||
|
||||
caplog -- .../_pytest/logging.py:593
|
||||
caplog -- .../_pytest/logging.py:594
|
||||
Access and control log capturing.
|
||||
|
||||
Captured logs are available through the following properties/methods::
|
||||
|
|
|
@ -28,6 +28,18 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 8.0.0 (2024-01-27)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#11842 <https://github.com/pytest-dev/pytest/issues/11842>`_: Properly escape the ``reason`` of a :ref:`skip <pytest.mark.skip ref>` mark when writing JUnit XML files.
|
||||
|
||||
|
||||
- `#11861 <https://github.com/pytest-dev/pytest/issues/11861>`_: Avoid microsecond exceeds ``1_000_000`` when using ``log-date-format`` with ``%f`` specifier, which might cause the test suite to crash.
|
||||
|
||||
|
||||
pytest 8.0.0rc2 (2024-01-17)
|
||||
============================
|
||||
|
||||
|
@ -254,6 +266,10 @@ These are breaking changes where deprecation was not possible.
|
|||
therefore fail on the newly-re-emitted warnings.
|
||||
|
||||
|
||||
- The internal ``FixtureManager.getfixtureclosure`` method has changed. Plugins which use this method or
|
||||
which subclass ``FixtureManager`` and overwrite that method will need to adapt to the change.
|
||||
|
||||
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
|
|
@ -23,6 +23,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from _pytest import __version__ as version
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import sphinx.application
|
||||
|
||||
|
@ -440,9 +441,10 @@ intersphinx_mapping = {
|
|||
|
||||
def configure_logging(app: "sphinx.application.Sphinx") -> None:
|
||||
"""Configure Sphinx's WarningHandler to handle (expected) missing include."""
|
||||
import sphinx.util.logging
|
||||
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.
|
||||
|
|
|
@ -33,13 +33,11 @@ have been available since years and should be used instead.
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.tryfirst
|
||||
def pytest_runtest_call():
|
||||
...
|
||||
def pytest_runtest_call(): ...
|
||||
|
||||
|
||||
# or
|
||||
def pytest_runtest_call():
|
||||
...
|
||||
def pytest_runtest_call(): ...
|
||||
|
||||
|
||||
pytest_runtest_call.tryfirst = True
|
||||
|
@ -49,8 +47,7 @@ should be changed to:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_runtest_call():
|
||||
...
|
||||
def pytest_runtest_call(): ...
|
||||
|
||||
Changed ``hookimpl`` attributes:
|
||||
|
||||
|
@ -146,8 +143,7 @@ Applying a mark to a fixture function never had any effect, but it is a common u
|
|||
|
||||
@pytest.mark.usefixtures("clean_database")
|
||||
@pytest.fixture
|
||||
def user() -> User:
|
||||
...
|
||||
def user() -> User: ...
|
||||
|
||||
Users expected in this case that the ``usefixtures`` mark would have its intended effect of using the ``clean_database`` fixture when ``user`` was invoked, when in fact it has no effect at all.
|
||||
|
||||
|
@ -308,11 +304,9 @@ they are in fact part of the ``nose`` support.
|
|||
def teardown(self):
|
||||
self.resource.close()
|
||||
|
||||
def test_foo(self):
|
||||
...
|
||||
def test_foo(self): ...
|
||||
|
||||
def test_bar(self):
|
||||
...
|
||||
def test_bar(self): ...
|
||||
|
||||
|
||||
|
||||
|
@ -327,11 +321,9 @@ Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`x
|
|||
def teardown_method(self):
|
||||
self.resource.close()
|
||||
|
||||
def test_foo(self):
|
||||
...
|
||||
def test_foo(self): ...
|
||||
|
||||
def test_bar(self):
|
||||
...
|
||||
def test_bar(self): ...
|
||||
|
||||
|
||||
This is easy to do in an entire code base by doing a simple find/replace.
|
||||
|
@ -346,17 +338,14 @@ Code using `@with_setup <with-setup-nose>`_ such as this:
|
|||
from nose.tools import with_setup
|
||||
|
||||
|
||||
def setup_some_resource():
|
||||
...
|
||||
def setup_some_resource(): ...
|
||||
|
||||
|
||||
def teardown_some_resource():
|
||||
...
|
||||
def teardown_some_resource(): ...
|
||||
|
||||
|
||||
@with_setup(setup_some_resource, teardown_some_resource)
|
||||
def test_foo():
|
||||
...
|
||||
def test_foo(): ...
|
||||
|
||||
Will also need to be ported to a supported pytest style. One way to do it is using a fixture:
|
||||
|
||||
|
@ -365,12 +354,10 @@ Will also need to be ported to a supported pytest style. One way to do it is usi
|
|||
import pytest
|
||||
|
||||
|
||||
def setup_some_resource():
|
||||
...
|
||||
def setup_some_resource(): ...
|
||||
|
||||
|
||||
def teardown_some_resource():
|
||||
...
|
||||
def teardown_some_resource(): ...
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -380,8 +367,7 @@ Will also need to be ported to a supported pytest style. One way to do it is usi
|
|||
teardown_some_resource()
|
||||
|
||||
|
||||
def test_foo(some_resource):
|
||||
...
|
||||
def test_foo(some_resource): ...
|
||||
|
||||
|
||||
.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup
|
||||
|
@ -500,8 +486,7 @@ Implement the :hook:`pytest_load_initial_conftests` hook instead.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_cmdline_preparse(config: Config, args: List[str]) -> None:
|
||||
...
|
||||
def pytest_cmdline_preparse(config: Config, args: List[str]) -> None: ...
|
||||
|
||||
|
||||
# becomes:
|
||||
|
@ -509,8 +494,7 @@ Implement the :hook:`pytest_load_initial_conftests` hook instead.
|
|||
|
||||
def pytest_load_initial_conftests(
|
||||
early_config: Config, parser: Parser, args: List[str]
|
||||
) -> None:
|
||||
...
|
||||
) -> None: ...
|
||||
|
||||
|
||||
Collection changes in pytest 8
|
||||
|
@ -925,8 +909,7 @@ Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated
|
|||
(50, 500),
|
||||
],
|
||||
)
|
||||
def test_foo(a, b):
|
||||
...
|
||||
def test_foo(a, b): ...
|
||||
|
||||
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
|
||||
call.
|
||||
|
@ -949,8 +932,7 @@ To update the code, use ``pytest.param``:
|
|||
(50, 500),
|
||||
],
|
||||
)
|
||||
def test_foo(a, b):
|
||||
...
|
||||
def test_foo(a, b): ...
|
||||
|
||||
|
||||
.. _pytest_funcarg__ prefix deprecated:
|
||||
|
@ -1101,15 +1083,13 @@ This is just a matter of renaming the fixture as the API is the same:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_xml_property):
|
||||
...
|
||||
def test_foo(record_xml_property): ...
|
||||
|
||||
Change to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_property):
|
||||
...
|
||||
def test_foo(record_property): ...
|
||||
|
||||
|
||||
.. _passing command-line string to pytest.main deprecated:
|
||||
|
@ -1271,8 +1251,7 @@ Example of usage:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
class MySymbol:
|
||||
...
|
||||
class MySymbol: ...
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
|
|
|
@ -172,7 +172,7 @@ class TestRaises:
|
|||
raise ValueError("demo error")
|
||||
|
||||
def test_tupleerror(self):
|
||||
a, b = [1] # NOQA
|
||||
a, b = [1] # noqa: F841
|
||||
|
||||
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
|
||||
items = [1, 2, 3]
|
||||
|
@ -180,7 +180,7 @@ class TestRaises:
|
|||
a, b = items.pop()
|
||||
|
||||
def test_some_error(self):
|
||||
if namenotexi: # NOQA
|
||||
if namenotexi: # noqa: F821
|
||||
pass
|
||||
|
||||
def func1(self):
|
||||
|
|
|
@ -2,6 +2,7 @@ import os.path
|
|||
|
||||
import pytest
|
||||
|
||||
|
||||
mydir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import os.path
|
||||
import shutil
|
||||
|
||||
|
||||
failure_demo = os.path.join(os.path.dirname(__file__), "failure_demo.py")
|
||||
pytest_plugins = ("pytester",)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Module containing a parametrized tests testing cross-python serialization
|
||||
via the pickle module."""
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
|
@ -32,14 +33,12 @@ class Python:
|
|||
dumpfile = self.picklefile.with_name("dump.py")
|
||||
dumpfile.write_text(
|
||||
textwrap.dedent(
|
||||
r"""
|
||||
rf"""
|
||||
import pickle
|
||||
f = open({!r}, 'wb')
|
||||
s = pickle.dump({!r}, f, protocol=2)
|
||||
f = open({str(self.picklefile)!r}, 'wb')
|
||||
s = pickle.dump({obj!r}, f, protocol=2)
|
||||
f.close()
|
||||
""".format(
|
||||
str(self.picklefile), obj
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
subprocess.run((self.pythonpath, str(dumpfile)), check=True)
|
||||
|
@ -48,17 +47,15 @@ class Python:
|
|||
loadfile = self.picklefile.with_name("load.py")
|
||||
loadfile.write_text(
|
||||
textwrap.dedent(
|
||||
r"""
|
||||
rf"""
|
||||
import pickle
|
||||
f = open({!r}, 'rb')
|
||||
f = open({str(self.picklefile)!r}, 'rb')
|
||||
obj = pickle.load(f)
|
||||
f.close()
|
||||
res = eval({!r})
|
||||
res = eval({expression!r})
|
||||
if not res:
|
||||
raise SystemExit(1)
|
||||
""".format(
|
||||
str(self.picklefile), expression
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
print(loadfile)
|
||||
|
|
|
@ -162,7 +162,7 @@ objects, they are still using the default pytest representation:
|
|||
rootdir: /home/sweet/project
|
||||
collected 8 items
|
||||
|
||||
<Dir parametrize.rst-192>
|
||||
<Dir parametrize.rst-193>
|
||||
<Module test_time.py>
|
||||
<Function test_timedistance_v0[a0-b0-expected0]>
|
||||
<Function test_timedistance_v0[a1-b1-expected1]>
|
||||
|
@ -239,7 +239,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
|||
rootdir: /home/sweet/project
|
||||
collected 4 items
|
||||
|
||||
<Dir parametrize.rst-192>
|
||||
<Dir parametrize.rst-193>
|
||||
<Module test_scenarios.py>
|
||||
<Class TestSampleWithScenarios>
|
||||
<Function test_demo1[basic]>
|
||||
|
@ -318,7 +318,7 @@ Let's first see how it looks like at collection time:
|
|||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
<Dir parametrize.rst-192>
|
||||
<Dir parametrize.rst-193>
|
||||
<Module test_backends.py>
|
||||
<Function test_db_initialized[d1]>
|
||||
<Function test_db_initialized[d2]>
|
||||
|
|
|
@ -152,7 +152,7 @@ The test collection would look like this:
|
|||
configfile: pytest.ini
|
||||
collected 2 items
|
||||
|
||||
<Dir pythoncollection.rst-193>
|
||||
<Dir pythoncollection.rst-194>
|
||||
<Module check_myapp.py>
|
||||
<Class CheckMyApp>
|
||||
<Function simple_check>
|
||||
|
@ -215,7 +215,7 @@ You can always peek at the collection tree without running tests like this:
|
|||
configfile: pytest.ini
|
||||
collected 3 items
|
||||
|
||||
<Dir pythoncollection.rst-193>
|
||||
<Dir pythoncollection.rst-194>
|
||||
<Dir CWD>
|
||||
<Module pythoncollection.py>
|
||||
<Function test_function>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import pytest
|
||||
|
||||
|
||||
xfail = pytest.mark.xfail
|
||||
|
||||
|
||||
|
|
|
@ -99,8 +99,7 @@ sets. pytest-2.3 introduces a decorator for use on the factory itself:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.fixture(params=["mysql", "pg"])
|
||||
def db(request):
|
||||
... # use request.param
|
||||
def db(request): ... # use request.param
|
||||
|
||||
Here the factory will be invoked twice (with the respective "mysql"
|
||||
and "pg" values set as ``request.param`` attributes) and all of
|
||||
|
@ -141,8 +140,7 @@ argument:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.fixture()
|
||||
def db(request):
|
||||
...
|
||||
def db(request): ...
|
||||
|
||||
The name under which the funcarg resource can be requested is ``db``.
|
||||
|
||||
|
@ -151,8 +149,7 @@ aka:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_funcarg__db(request):
|
||||
...
|
||||
def pytest_funcarg__db(request): ...
|
||||
|
||||
|
||||
But it is then not possible to define scoping and parametrization.
|
||||
|
|
|
@ -22,7 +22,7 @@ Install ``pytest``
|
|||
.. code-block:: bash
|
||||
|
||||
$ pytest --version
|
||||
pytest 8.0.0rc2
|
||||
pytest 8.0.0
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
|
|
|
@ -227,8 +227,7 @@ to use strings:
|
|||
|
||||
|
||||
@pytest.mark.skipif("sys.version_info >= (3,3)")
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
During test function setup the skipif condition is evaluated by calling
|
||||
``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains
|
||||
|
@ -262,8 +261,7 @@ configuration value which you might have added:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif("not config.getvalue('db')")
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
The equivalent with "boolean conditions" is:
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ method to test for exceptions returned as part of an :class:`ExceptionGroup`:
|
|||
.. code-block:: python
|
||||
|
||||
def test_exception_in_group():
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
with pytest.raises(ExceptionGroup) as excinfo:
|
||||
raise ExceptionGroup(
|
||||
"Group message",
|
||||
[
|
||||
|
@ -176,7 +176,7 @@ exception at a specific level; exceptions contained directly in the top
|
|||
.. code-block:: python
|
||||
|
||||
def test_exception_in_group_at_given_depth():
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
with pytest.raises(ExceptionGroup) as excinfo:
|
||||
raise ExceptionGroup(
|
||||
"Group message",
|
||||
[
|
||||
|
|
|
@ -1418,7 +1418,7 @@ Running the above tests results in the following test IDs being used:
|
|||
rootdir: /home/sweet/project
|
||||
collected 12 items
|
||||
|
||||
<Dir fixtures.rst-211>
|
||||
<Dir fixtures.rst-212>
|
||||
<Module test_anothersmtp.py>
|
||||
<Function test_showhelo[smtp.gmail.com]>
|
||||
<Function test_showhelo[mail.python.org]>
|
||||
|
@ -1721,8 +1721,7 @@ You can specify multiple fixtures like this:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.usefixtures("cleandir", "anotherfixture")
|
||||
def test():
|
||||
...
|
||||
def test(): ...
|
||||
|
||||
and you may specify fixture usage at the test module level using :globalvar:`pytestmark`:
|
||||
|
||||
|
@ -1750,8 +1749,7 @@ into an ini-file:
|
|||
|
||||
@pytest.mark.usefixtures("my_other_fixture")
|
||||
@pytest.fixture
|
||||
def my_fixture_that_sadly_wont_use_my_other_fixture():
|
||||
...
|
||||
def my_fixture_that_sadly_wont_use_my_other_fixture(): ...
|
||||
|
||||
This generates a deprecation warning, and will become an error in Pytest 8.
|
||||
|
||||
|
|
|
@ -47,8 +47,7 @@ which may be passed an optional ``reason``:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skip(reason="no way of currently testing this")
|
||||
def test_the_unknown():
|
||||
...
|
||||
def test_the_unknown(): ...
|
||||
|
||||
|
||||
Alternatively, it is also possible to skip imperatively during test execution or setup
|
||||
|
@ -93,8 +92,7 @@ when run on an interpreter earlier than Python3.10:
|
|||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
If the condition evaluates to ``True`` during collection, the test function will be skipped,
|
||||
with the specified reason appearing in the summary when using ``-rs``.
|
||||
|
@ -112,8 +110,7 @@ You can share ``skipif`` markers between modules. Consider this test module:
|
|||
|
||||
|
||||
@minversion
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
You can import the marker and reuse it in another test module:
|
||||
|
||||
|
@ -124,8 +121,7 @@ You can import the marker and reuse it in another test module:
|
|||
|
||||
|
||||
@minversion
|
||||
def test_anotherfunction():
|
||||
...
|
||||
def test_anotherfunction(): ...
|
||||
|
||||
For larger test suites it's usually a good idea to have one file
|
||||
where you define the markers which you then consistently apply
|
||||
|
@ -232,8 +228,7 @@ expect a test to fail:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
This test will run but no traceback will be reported when it fails. Instead, terminal
|
||||
reporting will list it in the "expected to fail" (``XFAIL``) or "unexpectedly
|
||||
|
@ -275,8 +270,7 @@ that condition as the first parameter:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library")
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
Note that you have to pass a reason as well (see the parameter description at
|
||||
:ref:`pytest.mark.xfail ref`).
|
||||
|
@ -289,8 +283,7 @@ You can specify the motive of an expected failure with the ``reason`` parameter:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(reason="known parser issue")
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
|
||||
``raises`` parameter
|
||||
|
@ -302,8 +295,7 @@ a single exception, or a tuple of exceptions, in the ``raises`` argument.
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(raises=RuntimeError)
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
Then the test will be reported as a regular failure if it fails with an
|
||||
exception not mentioned in ``raises``.
|
||||
|
@ -317,8 +309,7 @@ even executed, use the ``run`` parameter as ``False``:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(run=False)
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
This is specially useful for xfailing tests that are crashing the interpreter and should be
|
||||
investigated later.
|
||||
|
@ -334,8 +325,7 @@ You can change this by setting the ``strict`` keyword-only parameter to ``True``
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(strict=True)
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
|
||||
This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite.
|
||||
|
|
|
@ -46,24 +46,18 @@ Plugin discovery order at tool startup
|
|||
|
||||
5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable.
|
||||
|
||||
6. by loading all :file:`conftest.py` files as inferred by the command line
|
||||
invocation:
|
||||
6. by loading all "initial ":file:`conftest.py` files:
|
||||
|
||||
- if no test paths are specified, use the current dir as a test path
|
||||
- if exists, load ``conftest.py`` and ``test*/conftest.py`` relative
|
||||
to the directory part of the first test path. After the ``conftest.py``
|
||||
file is loaded, load all plugins specified in its
|
||||
:globalvar:`pytest_plugins` variable if present.
|
||||
|
||||
Note that pytest does not find ``conftest.py`` files in deeper nested
|
||||
sub directories at tool startup. It is usually a good idea to keep
|
||||
your ``conftest.py`` file in the top level test or project root directory.
|
||||
|
||||
7. by recursively loading all plugins specified by the
|
||||
:globalvar:`pytest_plugins` variable in ``conftest.py`` files.
|
||||
- determine the test paths: specified on the command line, otherwise in
|
||||
:confval:`testpaths` if defined and running from the rootdir, otherwise the
|
||||
current dir
|
||||
- for each test path, load ``conftest.py`` and ``test*/conftest.py`` relative
|
||||
to the directory part of the test path, if exist. Before a ``conftest.py``
|
||||
file is loaded, load ``conftest.py`` files in all of its parent directories.
|
||||
After a ``conftest.py`` file is loaded, recursively load all plugins specified
|
||||
in its :globalvar:`pytest_plugins` variable if present.
|
||||
|
||||
|
||||
.. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/
|
||||
.. _`conftest.py plugins`:
|
||||
.. _`localplugin`:
|
||||
.. _`local conftest plugins`:
|
||||
|
@ -108,9 +102,9 @@ Here is how you might run it::
|
|||
See also: :ref:`pythonpath`.
|
||||
|
||||
.. note::
|
||||
Some hooks should be implemented only in plugins or conftest.py files situated at the
|
||||
tests root directory due to how pytest discovers plugins during startup,
|
||||
see the documentation of each hook for details.
|
||||
Some hooks cannot be implemented in conftest.py files which are not
|
||||
:ref:`initial <pluginorder>` due to how pytest discovers plugins during
|
||||
startup. See the documentation of each hook for details.
|
||||
|
||||
Writing your own plugin
|
||||
-----------------------
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -164,8 +164,7 @@ Add warning filters to marked test items.
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:.*usage will be deprecated.*:DeprecationWarning")
|
||||
def test_foo():
|
||||
...
|
||||
def test_foo(): ...
|
||||
|
||||
|
||||
.. _`pytest.mark.parametrize ref`:
|
||||
|
@ -276,8 +275,7 @@ For example:
|
|||
.. code-block:: python
|
||||
|
||||
@pytest.mark.timeout(10, "slow", method="thread")
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
Will create and attach a :class:`Mark <pytest.Mark>` object to the collected
|
||||
:class:`Item <pytest.Item>`, which can then be accessed by fixtures or hooks with
|
||||
|
@ -294,8 +292,7 @@ Example for using multiple custom markers:
|
|||
|
||||
@pytest.mark.timeout(10, "slow", method="thread")
|
||||
@pytest.mark.slow
|
||||
def test_function():
|
||||
...
|
||||
def test_function(): ...
|
||||
|
||||
When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers_with_node <_pytest.nodes.Node.iter_markers_with_node>` is used with multiple markers, the marker closest to the function will be iterated over first. The above example will result in ``@pytest.mark.slow`` followed by ``@pytest.mark.timeout(...)``.
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ from pathlib import Path
|
|||
|
||||
import requests
|
||||
|
||||
|
||||
issues_url = "https://api.github.com/repos/pytest-dev/pytest/issues"
|
||||
|
||||
|
||||
|
|
194
pyproject.toml
194
pyproject.toml
|
@ -1,13 +1,178 @@
|
|||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=45.0",
|
||||
"setuptools-scm[toml]>=6.2.3",
|
||||
[project]
|
||||
name = "pytest"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
readme = "README.rst"
|
||||
keywords = [
|
||||
"test",
|
||||
"unittest",
|
||||
]
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "Holger Krekel"},
|
||||
{name = "Bruno Oliveira"},
|
||||
{name = "Ronny Pfannschmidt"},
|
||||
{name = "Floris Bruynooghe"},
|
||||
{name = "Brianna Laugher"},
|
||||
{name = "Florian Bruhin"},
|
||||
{name = "Others (See AUTHORS)"},
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Development Status :: 6 - Mature",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: Unix",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Software Development :: Testing",
|
||||
"Topic :: Utilities",
|
||||
]
|
||||
dynamic = [
|
||||
"version",
|
||||
]
|
||||
dependencies = [
|
||||
'colorama; sys_platform == "win32"',
|
||||
'exceptiongroup>=1.0.0rc8; python_version < "3.11"',
|
||||
"iniconfig",
|
||||
"packaging",
|
||||
"pluggy<2.0,>=1.4",
|
||||
'tomli>=1; python_version < "3.11"',
|
||||
]
|
||||
[project.optional-dependencies]
|
||||
testing = [
|
||||
"argcomplete",
|
||||
"attrs>=19.2",
|
||||
"hypothesis>=3.56",
|
||||
"mock",
|
||||
"pygments>=2.7.2",
|
||||
"requests",
|
||||
"setuptools",
|
||||
"xmlschema",
|
||||
]
|
||||
[project.urls]
|
||||
Changelog = "https://docs.pytest.org/en/stable/changelog.html"
|
||||
Homepage = "https://docs.pytest.org/en/latest/"
|
||||
Source = "https://github.com/pytest-dev/pytest"
|
||||
Tracker = "https://github.com/pytest-dev/pytest/issues"
|
||||
Twitter = "https://twitter.com/pytestdotorg"
|
||||
[project.scripts]
|
||||
"py.test" = "pytest:console_main"
|
||||
pytest = "pytest:console_main"
|
||||
|
||||
[build-system]
|
||||
build-backend = "setuptools.build_meta"
|
||||
requires = [
|
||||
"setuptools>=61",
|
||||
"setuptools-scm[toml]>=6.2.3",
|
||||
]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"_pytest" = ["py.typed"]
|
||||
"pytest" = ["py.typed"]
|
||||
|
||||
[tool.setuptools_scm]
|
||||
write_to = "src/_pytest/_version.py"
|
||||
|
||||
[tool.black]
|
||||
target-version = ['py38']
|
||||
|
||||
[tool.ruff]
|
||||
src = ["src"]
|
||||
line-length = 88
|
||||
|
||||
[tool.ruff.format]
|
||||
docstring-code-format = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"B", # bugbear
|
||||
"D", # pydocstyle
|
||||
"E", # pycodestyle
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"PYI", # flake8-pyi
|
||||
"UP", # pyupgrade
|
||||
"RUF", # ruff
|
||||
"W", # pycodestyle
|
||||
"PIE", # flake8-pie
|
||||
"PGH004", # pygrep-hooks - Use specific rule codes when using noqa
|
||||
"PLE", # pylint error
|
||||
"PLW", # pylint warning
|
||||
"PLR1714", # Consider merging multiple comparisons
|
||||
]
|
||||
ignore = [
|
||||
# bugbear ignore
|
||||
"B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable.
|
||||
"B007", # Loop control variable `i` not used within loop body
|
||||
"B009", # Do not call `getattr` with a constant attribute value
|
||||
"B010", # [*] Do not call `setattr` with a constant attribute value.
|
||||
"B011", # Do not `assert False` (`python -O` removes these calls)
|
||||
"B028", # No explicit `stacklevel` keyword argument found
|
||||
# pycodestyle ignore
|
||||
# pytest can do weird low-level things, and we usually know
|
||||
# what we're doing when we use type(..) is ...
|
||||
"E721", # Do not compare types, use `isinstance()`
|
||||
# pydocstyle ignore
|
||||
"D100", # Missing docstring in public module
|
||||
"D101", # Missing docstring in public class
|
||||
"D102", # Missing docstring in public method
|
||||
"D103", # Missing docstring in public function
|
||||
"D104", # Missing docstring in public package
|
||||
"D105", # Missing docstring in magic method
|
||||
"D106", # Missing docstring in public nested class
|
||||
"D107", # Missing docstring in `__init__`
|
||||
"D209", # [*] Multi-line docstring closing quotes should be on a separate line
|
||||
"D205", # 1 blank line required between summary line and description
|
||||
"D400", # First line should end with a period
|
||||
"D401", # First line of docstring should be in imperative mood
|
||||
"D402", # First line should not be the function's signature
|
||||
"D404", # First word of the docstring should not be "This"
|
||||
"D415", # First line should end with a period, question mark, or exclamation point
|
||||
# ruff ignore
|
||||
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
|
||||
# pylint ignore
|
||||
"PLW0603", # Using the global statement
|
||||
"PLW0120", # remove the else and dedent its contents
|
||||
"PLW2901", # for loop variable overwritten by assignment target
|
||||
"PLR5501", # Use `elif` instead of `else` then `if`
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pycodestyle]
|
||||
# In order to be able to format for 88 char in ruff format
|
||||
max-line-length = 120
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "pep257"
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
force-single-line = true
|
||||
combine-as-imports = true
|
||||
force-sort-within-sections = true
|
||||
order-by-type = false
|
||||
known-local-folder = ["pytest", "_pytest"]
|
||||
lines-after-imports = 2
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"src/_pytest/_py/**/*.py" = ["B", "PYI"]
|
||||
"src/_pytest/_version.py" = ["I001"]
|
||||
"testing/python/approx.py" = ["B015"]
|
||||
|
||||
[tool.check-wheel-contents]
|
||||
# check-wheel-contents is executed by the build-and-inspect-python-package action.
|
||||
# W009: Wheel contains multiple toplevel library entries
|
||||
ignore = "W009"
|
||||
|
||||
[tool.pyproject-fmt]
|
||||
indent = 4
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "2.0"
|
||||
addopts = "-rfEX -p pytester --strict-markers"
|
||||
|
@ -67,7 +232,6 @@ markers = [
|
|||
"uses_pexpect",
|
||||
]
|
||||
|
||||
|
||||
[tool.towncrier]
|
||||
package = "pytest"
|
||||
package_dir = "src"
|
||||
|
@ -116,10 +280,16 @@ template = "changelog/_template.rst"
|
|||
name = "Trivial/Internal Changes"
|
||||
showcontent = true
|
||||
|
||||
[tool.black]
|
||||
target-version = ['py38']
|
||||
|
||||
# check-wheel-contents is executed by the build-and-inspect-python-package action.
|
||||
[tool.check-wheel-contents]
|
||||
# W009: Wheel contains multiple toplevel library entries
|
||||
ignore = "W009"
|
||||
[tool.mypy]
|
||||
mypy_path = ["src"]
|
||||
check_untyped_defs = true
|
||||
disallow_any_generics = true
|
||||
disallow_untyped_defs = true
|
||||
ignore_missing_imports = true
|
||||
show_error_codes = true
|
||||
strict_equality = true
|
||||
warn_redundant_casts = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
warn_unused_configs = true
|
||||
no_implicit_reexport = true
|
||||
|
|
|
@ -8,9 +8,9 @@ our CHANGELOG) into Markdown (which is required by GitHub Releases).
|
|||
|
||||
Requires Python3.6+.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
import pypandoc
|
||||
|
|
|
@ -14,8 +14,8 @@ After that, it will create a release using the `release` tox environment, and pu
|
|||
`pytest bot <pytestbot@gmail.com>` commit author.
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
import re
|
||||
from subprocess import check_call
|
||||
from subprocess import check_output
|
||||
from subprocess import run
|
||||
|
@ -79,7 +79,7 @@ def prepare_release_pr(
|
|||
)
|
||||
except InvalidFeatureRelease as e:
|
||||
print(f"{Fore.RED}{e}")
|
||||
raise SystemExit(1)
|
||||
raise SystemExit(1) from None
|
||||
|
||||
print(f"Version: {Fore.CYAN}{version}")
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ def pre_release(
|
|||
|
||||
def changelog(version: str, write_out: bool = False) -> None:
|
||||
addopts = [] if write_out else ["--draft"]
|
||||
check_call(["towncrier", "--yes", "--version", version] + addopts)
|
||||
check_call(["towncrier", "--yes", "--version", version, *addopts])
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# mypy: disallow-untyped-defs
|
||||
import sys
|
||||
from subprocess import call
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> int:
|
||||
|
|
|
@ -11,13 +11,13 @@ from typing import TypedDict
|
|||
|
||||
import packaging.version
|
||||
import platformdirs
|
||||
import tabulate
|
||||
import wcwidth
|
||||
from requests_cache import CachedResponse
|
||||
from requests_cache import CachedSession
|
||||
from requests_cache import OriginalResponse
|
||||
from requests_cache import SQLiteCache
|
||||
import tabulate
|
||||
from tqdm import tqdm
|
||||
import wcwidth
|
||||
|
||||
|
||||
FILE_HEAD = r"""
|
||||
|
@ -86,7 +86,6 @@ def project_response_with_refresh(
|
|||
|
||||
force refresh in case of last serial mismatch
|
||||
"""
|
||||
|
||||
response = session.get(f"https://pypi.org/pypi/{name}/json")
|
||||
if int(response.headers.get("X-PyPI-Last-Serial", -1)) != last_serial:
|
||||
response = session.get(f"https://pypi.org/pypi/{name}/json", refresh=True)
|
||||
|
@ -185,7 +184,6 @@ def iter_plugins() -> Iterator[PluginInfo]:
|
|||
|
||||
def plugin_definitions(plugins: Iterable[PluginInfo]) -> Iterator[str]:
|
||||
"""Return RST for the plugin list that fits better on a vertical page."""
|
||||
|
||||
for plugin in plugins:
|
||||
yield dedent(
|
||||
f"""
|
||||
|
@ -210,7 +208,7 @@ def main() -> None:
|
|||
f.write(f"This list contains {len(plugins)} plugins.\n\n")
|
||||
f.write(".. only:: not latex\n\n")
|
||||
|
||||
wcwidth # reference library that must exist for tabulate to work
|
||||
_ = wcwidth # reference library that must exist for tabulate to work
|
||||
plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst")
|
||||
f.write(indent(plugin_table, " "))
|
||||
f.write("\n\n")
|
||||
|
|
104
setup.cfg
104
setup.cfg
|
@ -1,104 +0,0 @@
|
|||
[metadata]
|
||||
name = pytest
|
||||
description = pytest: simple powerful testing with Python
|
||||
long_description = file: README.rst
|
||||
long_description_content_type = text/x-rst
|
||||
url = https://docs.pytest.org/en/latest/
|
||||
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
|
||||
license = MIT
|
||||
license_files = LICENSE
|
||||
platforms = unix, linux, osx, cygwin, win32
|
||||
classifiers =
|
||||
Development Status :: 6 - Mature
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: MacOS :: MacOS X
|
||||
Operating System :: Microsoft :: Windows
|
||||
Operating System :: POSIX
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: 3.12
|
||||
Topic :: Software Development :: Libraries
|
||||
Topic :: Software Development :: Testing
|
||||
Topic :: Utilities
|
||||
keywords = test, unittest
|
||||
project_urls =
|
||||
Changelog=https://docs.pytest.org/en/stable/changelog.html
|
||||
Twitter=https://twitter.com/pytestdotorg
|
||||
Source=https://github.com/pytest-dev/pytest
|
||||
Tracker=https://github.com/pytest-dev/pytest/issues
|
||||
|
||||
[options]
|
||||
packages =
|
||||
_pytest
|
||||
_pytest._code
|
||||
_pytest._io
|
||||
_pytest._py
|
||||
_pytest.assertion
|
||||
_pytest.config
|
||||
_pytest.mark
|
||||
pytest
|
||||
py_modules = py
|
||||
install_requires =
|
||||
iniconfig
|
||||
packaging
|
||||
pluggy>=1.3.0,<2.0
|
||||
colorama;sys_platform=="win32"
|
||||
exceptiongroup>=1.0.0rc8;python_version<"3.11"
|
||||
tomli>=1.0.0;python_version<"3.11"
|
||||
python_requires = >=3.8
|
||||
package_dir =
|
||||
=src
|
||||
setup_requires =
|
||||
setuptools
|
||||
setuptools-scm>=6.0
|
||||
zip_safe = no
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
pytest=pytest:console_main
|
||||
py.test=pytest:console_main
|
||||
|
||||
[options.extras_require]
|
||||
testing =
|
||||
argcomplete
|
||||
attrs>=19.2.0
|
||||
hypothesis>=3.56
|
||||
mock
|
||||
pygments>=2.7.2
|
||||
requests
|
||||
setuptools
|
||||
xmlschema
|
||||
|
||||
[options.package_data]
|
||||
_pytest = py.typed
|
||||
pytest = py.typed
|
||||
|
||||
[build_sphinx]
|
||||
source_dir = doc/en/
|
||||
build_dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[check-manifest]
|
||||
ignore =
|
||||
src/_pytest/_version.py
|
||||
|
||||
[devpi:upload]
|
||||
formats = sdist.tgz,bdist_wheel
|
||||
|
||||
[mypy]
|
||||
mypy_path = src
|
||||
check_untyped_defs = True
|
||||
disallow_any_generics = True
|
||||
ignore_missing_imports = True
|
||||
show_error_codes = True
|
||||
strict_equality = True
|
||||
warn_redundant_casts = True
|
||||
warn_return_any = True
|
||||
warn_unreachable = True
|
||||
warn_unused_configs = True
|
||||
no_implicit_reexport = True
|
|
@ -1,7 +1,8 @@
|
|||
__all__ = ["__version__", "version_tuple"]
|
||||
|
||||
try:
|
||||
from ._version import version as __version__, version_tuple
|
||||
from ._version import version as __version__
|
||||
from ._version import version_tuple
|
||||
except ImportError: # pragma: no cover
|
||||
# broken installation, we don't even try
|
||||
# unknown only works because we do poor mans version compare
|
||||
|
|
|
@ -61,10 +61,11 @@ If things do not work right away:
|
|||
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
||||
global argcomplete script).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from glob import glob
|
||||
import os
|
||||
import sys
|
||||
from glob import glob
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Python inspection/code generation API."""
|
||||
|
||||
from .code import Code
|
||||
from .code import ExceptionInfo
|
||||
from .code import filter_traceback
|
||||
|
@ -9,6 +10,7 @@ from .code import TracebackEntry
|
|||
from .source import getrawcode
|
||||
from .source import Source
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Code",
|
||||
"ExceptionInfo",
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
# mypy: allow-untyped-defs
|
||||
import ast
|
||||
import dataclasses
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
from inspect import CO_VARARGS
|
||||
from inspect import CO_VARKEYWORDS
|
||||
from io import StringIO
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
from traceback import format_exception_only
|
||||
from types import CodeType
|
||||
from types import FrameType
|
||||
|
@ -50,6 +51,7 @@ from _pytest.deprecated import check_ispytest
|
|||
from _pytest.pathlib import absolutepath
|
||||
from _pytest.pathlib import bestrelpath
|
||||
|
||||
|
||||
if sys.version_info[:2] < (3, 11):
|
||||
from exceptiongroup import BaseExceptionGroup
|
||||
|
||||
|
@ -486,9 +488,10 @@ class ExceptionInfo(Generic[E]):
|
|||
|
||||
.. versionadded:: 7.4
|
||||
"""
|
||||
assert (
|
||||
exception.__traceback__
|
||||
), "Exceptions passed to ExcInfo.from_exception(...) must have a non-None __traceback__."
|
||||
assert exception.__traceback__, (
|
||||
"Exceptions passed to ExcInfo.from_exception(...)"
|
||||
" must have a non-None __traceback__."
|
||||
)
|
||||
exc_info = (type(exception), exception, exception.__traceback__)
|
||||
return cls.from_exc_info(exc_info, exprinfo)
|
||||
|
||||
|
@ -587,9 +590,7 @@ class ExceptionInfo(Generic[E]):
|
|||
def __repr__(self) -> str:
|
||||
if self._excinfo is None:
|
||||
return "<ExceptionInfo for raises contextmanager>"
|
||||
return "<{} {} tblen={}>".format(
|
||||
self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback)
|
||||
)
|
||||
return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>"
|
||||
|
||||
def exconly(self, tryshort: bool = False) -> str:
|
||||
"""Return the exception as a string.
|
||||
|
@ -698,10 +699,21 @@ class ExceptionInfo(Generic[E]):
|
|||
return fmt.repr_excinfo(self)
|
||||
|
||||
def _stringify_exception(self, exc: BaseException) -> str:
|
||||
try:
|
||||
notes = getattr(exc, "__notes__", [])
|
||||
except KeyError:
|
||||
# Workaround for https://github.com/python/cpython/issues/98778 on
|
||||
# Python <= 3.9, and some 3.10 and 3.11 patch versions.
|
||||
HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ())
|
||||
if sys.version_info[:2] <= (3, 11) and isinstance(exc, HTTPError):
|
||||
notes = []
|
||||
else:
|
||||
raise
|
||||
|
||||
return "\n".join(
|
||||
[
|
||||
str(exc),
|
||||
*getattr(exc, "__notes__", []),
|
||||
*notes,
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -1006,13 +1018,8 @@ class FormattedExcinfo:
|
|||
extraline: Optional[str] = (
|
||||
"!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
|
||||
" The following exception happened when comparing locals in the stack frame:\n"
|
||||
" {exc_type}: {exc_msg}\n"
|
||||
" Displaying first and last {max_frames} stack frames out of {total}."
|
||||
).format(
|
||||
exc_type=type(e).__name__,
|
||||
exc_msg=str(e),
|
||||
max_frames=max_frames,
|
||||
total=len(traceback),
|
||||
f" {type(e).__name__}: {e!s}\n"
|
||||
f" Displaying first and last {max_frames} stack frames out of {len(traceback)}."
|
||||
)
|
||||
# Type ignored because adding two instances of a List subtype
|
||||
# currently incorrectly has type List instead of the subtype.
|
||||
|
@ -1219,7 +1226,6 @@ class ReprEntry(TerminalRepr):
|
|||
the "E" prefix) using syntax highlighting, taking care to not highlighting the ">"
|
||||
character, as doing so might break line continuations.
|
||||
"""
|
||||
|
||||
if not self.lines:
|
||||
return
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# mypy: allow-untyped-defs
|
||||
import ast
|
||||
from bisect import bisect_right
|
||||
import inspect
|
||||
import textwrap
|
||||
import tokenize
|
||||
import types
|
||||
import warnings
|
||||
from bisect import bisect_right
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
|
@ -12,6 +12,7 @@ from typing import Optional
|
|||
from typing import overload
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
|
||||
class Source:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# mypy: allow-untyped-defs
|
||||
# This module was imported from the cpython standard library
|
||||
# (https://github.com/python/cpython/) at commit
|
||||
# c5140945c723ae6c4b7ee81ff720ac8ea4b52cfd (python3.12).
|
||||
|
@ -14,9 +15,9 @@
|
|||
# useful, thank small children who sleep at night.
|
||||
import collections as _collections
|
||||
import dataclasses as _dataclasses
|
||||
from io import StringIO as _StringIO
|
||||
import re
|
||||
import types as _types
|
||||
from io import StringIO as _StringIO
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
|
|
|
@ -19,8 +19,8 @@ def _format_repr_exception(exc: BaseException, obj: object) -> str:
|
|||
raise
|
||||
except BaseException as exc:
|
||||
exc_info = f"unpresentable exception ({_try_repr_or_str(exc)})"
|
||||
return "<[{} raised in repr()] {} object at 0x{:x}>".format(
|
||||
exc_info, type(obj).__name__, id(obj)
|
||||
return (
|
||||
f"<[{exc_info} raised in repr()] {type(obj).__name__} object at 0x{id(obj):x}>"
|
||||
)
|
||||
|
||||
|
||||
|
@ -108,7 +108,6 @@ def saferepr(
|
|||
This function is a wrapper around the Repr/reprlib functionality of the
|
||||
stdlib.
|
||||
"""
|
||||
|
||||
return SafeRepr(maxsize, use_ascii).repr(obj)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Helper functions for writing to terminals and files."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
@ -183,9 +184,7 @@ class TerminalWriter:
|
|||
"""
|
||||
if indents and len(indents) != len(lines):
|
||||
raise ValueError(
|
||||
"indents size ({}) should have same size as lines ({})".format(
|
||||
len(indents), len(lines)
|
||||
)
|
||||
f"indents size ({len(indents)}) should have same size as lines ({len(lines)})"
|
||||
)
|
||||
if not indents:
|
||||
indents = [""] * len(lines)
|
||||
|
@ -233,17 +232,17 @@ class TerminalWriter:
|
|||
# which may lead to the previous color being propagated to the
|
||||
# start of the expression, so reset first.
|
||||
return "\x1b[0m" + highlighted
|
||||
except pygments.util.ClassNotFound:
|
||||
except pygments.util.ClassNotFound as e:
|
||||
raise UsageError(
|
||||
"PYTEST_THEME environment variable had an invalid value: '{}'. "
|
||||
"Only valid pygment styles are allowed.".format(
|
||||
os.getenv("PYTEST_THEME")
|
||||
)
|
||||
)
|
||||
except pygments.util.OptionError:
|
||||
) from e
|
||||
except pygments.util.OptionError as e:
|
||||
raise UsageError(
|
||||
"PYTEST_THEME_MODE environment variable had an invalid value: '{}'. "
|
||||
"The only allowed values are 'dark' and 'light'.".format(
|
||||
os.getenv("PYTEST_THEME_MODE")
|
||||
)
|
||||
)
|
||||
) from e
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import unicodedata
|
||||
from functools import lru_cache
|
||||
import unicodedata
|
||||
|
||||
|
||||
@lru_cache(100)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""create errno-specific classes for IO or os calls."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import errno
|
||||
|
@ -8,6 +9,7 @@ from typing import Callable
|
|||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""local path implementation."""
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
from contextlib import contextmanager
|
||||
import fnmatch
|
||||
import importlib.util
|
||||
import io
|
||||
import os
|
||||
import posixpath
|
||||
import sys
|
||||
import uuid
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from os.path import abspath
|
||||
from os.path import dirname
|
||||
from os.path import exists
|
||||
|
@ -19,18 +16,23 @@ from os.path import isdir
|
|||
from os.path import isfile
|
||||
from os.path import islink
|
||||
from os.path import normpath
|
||||
import posixpath
|
||||
from stat import S_ISDIR
|
||||
from stat import S_ISLNK
|
||||
from stat import S_ISREG
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Literal
|
||||
from typing import overload
|
||||
from typing import TYPE_CHECKING
|
||||
import uuid
|
||||
import warnings
|
||||
|
||||
from . import error
|
||||
|
||||
|
||||
# Moved from local.py.
|
||||
iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt")
|
||||
|
||||
|
@ -450,7 +452,7 @@ class LocalPath:
|
|||
|
||||
def ensure_dir(self, *args):
|
||||
"""Ensure the path joined with args is a directory."""
|
||||
return self.ensure(*args, **{"dir": True})
|
||||
return self.ensure(*args, dir=True)
|
||||
|
||||
def bestrelpath(self, dest):
|
||||
"""Return a string which is a relative path from self
|
||||
|
@ -675,7 +677,7 @@ class LocalPath:
|
|||
else:
|
||||
kw.setdefault("dirname", dirname)
|
||||
kw.setdefault("sep", self.sep)
|
||||
obj.strpath = normpath("%(dirname)s%(sep)s%(basename)s" % kw)
|
||||
obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw))
|
||||
return obj
|
||||
|
||||
def _getbyspec(self, spec: str) -> list[str]:
|
||||
|
@ -760,7 +762,10 @@ class LocalPath:
|
|||
# expected "Callable[[str, Any, Any], TextIOWrapper]" [arg-type]
|
||||
# Which seems incorrect, given io.open supports the given argument types.
|
||||
return error.checked_call(
|
||||
io.open, self.strpath, mode, encoding=encoding # type:ignore[arg-type]
|
||||
io.open,
|
||||
self.strpath,
|
||||
mode,
|
||||
encoding=encoding, # type:ignore[arg-type]
|
||||
)
|
||||
return error.checked_call(open, self.strpath, mode)
|
||||
|
||||
|
@ -779,11 +784,11 @@ class LocalPath:
|
|||
|
||||
valid checkers::
|
||||
|
||||
file=1 # is a file
|
||||
file=0 # is not a file (may not even exist)
|
||||
dir=1 # is a dir
|
||||
link=1 # is a link
|
||||
exists=1 # exists
|
||||
file = 1 # is a file
|
||||
file = 0 # is not a file (may not even exist)
|
||||
dir = 1 # is a dir
|
||||
link = 1 # is a link
|
||||
exists = 1 # exists
|
||||
|
||||
You can specify multiple checker definitions, for example::
|
||||
|
||||
|
@ -1100,9 +1105,7 @@ class LocalPath:
|
|||
modname = self.purebasename
|
||||
spec = importlib.util.spec_from_file_location(modname, str(self))
|
||||
if spec is None or spec.loader is None:
|
||||
raise ImportError(
|
||||
f"Can't find module {modname} at location {str(self)}"
|
||||
)
|
||||
raise ImportError(f"Can't find module {modname} at location {self!s}")
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
@ -1167,7 +1170,8 @@ class LocalPath:
|
|||
where the 'self' path points to executable.
|
||||
The process is directly invoked and not through a system shell.
|
||||
"""
|
||||
from subprocess import Popen, PIPE
|
||||
from subprocess import PIPE
|
||||
from subprocess import Popen
|
||||
|
||||
popen_opts.pop("stdout", None)
|
||||
popen_opts.pop("stderr", None)
|
||||
|
@ -1277,7 +1281,8 @@ class LocalPath:
|
|||
# error: Argument 1 has incompatible type overloaded function; expected "Callable[[str], str]" [arg-type]
|
||||
# Which seems incorrect, given tempfile.mkdtemp supports the given argument types.
|
||||
path = error.checked_call(
|
||||
tempfile.mkdtemp, dir=str(rootdir) # type:ignore[arg-type]
|
||||
tempfile.mkdtemp,
|
||||
dir=str(rootdir), # type:ignore[arg-type]
|
||||
)
|
||||
return cls(path)
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Support for presenting detailed information in failing assertions."""
|
||||
import sys
|
||||
from typing import Any
|
||||
|
@ -15,6 +16,7 @@ from _pytest.config import hookimpl
|
|||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.nodes import Item
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.main import Session
|
||||
|
||||
|
@ -128,7 +130,6 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
|||
reporting via the pytest_assertrepr_compare hook. This sets up this custom
|
||||
comparison for the test.
|
||||
"""
|
||||
|
||||
ihook = item.ihook
|
||||
|
||||
def callbinrepr(op, left: object, right: object) -> Optional[str]:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
"""Rewrite assertion AST to produce nice error messages."""
|
||||
|
||||
import ast
|
||||
from collections import defaultdict
|
||||
import errno
|
||||
import functools
|
||||
import importlib.abc
|
||||
|
@ -9,13 +11,12 @@ import io
|
|||
import itertools
|
||||
import marshal
|
||||
import os
|
||||
from pathlib import Path
|
||||
from pathlib import PurePath
|
||||
import struct
|
||||
import sys
|
||||
import tokenize
|
||||
import types
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from pathlib import PurePath
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import IO
|
||||
|
@ -33,15 +34,17 @@ from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
|
|||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest._version import version
|
||||
from _pytest.assertion import util
|
||||
from _pytest.assertion.util import ( # noqa: F401
|
||||
format_explanation as _format_explanation,
|
||||
)
|
||||
from _pytest.config import Config
|
||||
from _pytest.main import Session
|
||||
from _pytest.pathlib import absolutepath
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
from _pytest.stash import StashKey
|
||||
|
||||
|
||||
# fmt: off
|
||||
from _pytest.assertion.util import format_explanation as _format_explanation # noqa:F401, isort:skip
|
||||
# fmt:on
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.assertion import AssertionState
|
||||
|
||||
|
@ -858,9 +861,10 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
the expression is false.
|
||||
"""
|
||||
if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1:
|
||||
from _pytest.warning_types import PytestAssertRewriteWarning
|
||||
import warnings
|
||||
|
||||
from _pytest.warning_types import PytestAssertRewriteWarning
|
||||
|
||||
# TODO: This assert should not be needed.
|
||||
assert self.module_path is not None
|
||||
warnings.warn_explicit(
|
||||
|
@ -921,7 +925,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
# If any hooks implement assert_pass hook
|
||||
hook_impl_test = ast.If(
|
||||
self.helper("_check_if_assertion_pass_impl"),
|
||||
self.expl_stmts + [hook_call_pass],
|
||||
[*self.expl_stmts, hook_call_pass],
|
||||
[],
|
||||
)
|
||||
statements_pass = [hook_impl_test]
|
||||
|
@ -1002,7 +1006,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
if i:
|
||||
fail_inner: List[ast.stmt] = []
|
||||
# cond is set in a prior loop iteration below
|
||||
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa
|
||||
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821
|
||||
self.expl_stmts = fail_inner
|
||||
# Check if the left operand is a ast.NamedExpr and the value has already been visited
|
||||
if (
|
||||
|
@ -1016,9 +1020,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
]
|
||||
):
|
||||
pytest_temp = self.variable()
|
||||
self.variables_overwrite[self.scope][
|
||||
v.left.target.id
|
||||
] = v.left # type:ignore[assignment]
|
||||
self.variables_overwrite[self.scope][v.left.target.id] = v.left # type:ignore[assignment]
|
||||
v.left.target.id = pytest_temp
|
||||
self.push_format_context()
|
||||
res, expl = self.visit(v)
|
||||
|
@ -1062,9 +1064,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get(
|
||||
self.scope, {}
|
||||
):
|
||||
arg = self.variables_overwrite[self.scope][
|
||||
arg.id
|
||||
] # type:ignore[assignment]
|
||||
arg = self.variables_overwrite[self.scope][arg.id] # type:ignore[assignment]
|
||||
res, expl = self.visit(arg)
|
||||
arg_expls.append(expl)
|
||||
new_args.append(res)
|
||||
|
@ -1072,9 +1072,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
if isinstance(
|
||||
keyword.value, ast.Name
|
||||
) and keyword.value.id in self.variables_overwrite.get(self.scope, {}):
|
||||
keyword.value = self.variables_overwrite[self.scope][
|
||||
keyword.value.id
|
||||
] # type:ignore[assignment]
|
||||
keyword.value = self.variables_overwrite[self.scope][keyword.value.id] # type:ignore[assignment]
|
||||
res, expl = self.visit(keyword.value)
|
||||
new_kwargs.append(ast.keyword(keyword.arg, res))
|
||||
if keyword.arg:
|
||||
|
@ -1111,13 +1109,9 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
if isinstance(
|
||||
comp.left, ast.Name
|
||||
) and comp.left.id in self.variables_overwrite.get(self.scope, {}):
|
||||
comp.left = self.variables_overwrite[self.scope][
|
||||
comp.left.id
|
||||
] # type:ignore[assignment]
|
||||
comp.left = self.variables_overwrite[self.scope][comp.left.id] # type:ignore[assignment]
|
||||
if isinstance(comp.left, ast.NamedExpr):
|
||||
self.variables_overwrite[self.scope][
|
||||
comp.left.target.id
|
||||
] = comp.left # type:ignore[assignment]
|
||||
self.variables_overwrite[self.scope][comp.left.target.id] = comp.left # type:ignore[assignment]
|
||||
left_res, left_expl = self.visit(comp.left)
|
||||
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
||||
left_expl = f"({left_expl})"
|
||||
|
@ -1135,9 +1129,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
and next_operand.target.id == left_res.id
|
||||
):
|
||||
next_operand.target.id = self.variable()
|
||||
self.variables_overwrite[self.scope][
|
||||
left_res.id
|
||||
] = next_operand # type:ignore[assignment]
|
||||
self.variables_overwrite[self.scope][left_res.id] = next_operand # type:ignore[assignment]
|
||||
next_res, next_expl = self.visit(next_operand)
|
||||
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
|
||||
next_expl = f"({next_expl})"
|
||||
|
|
|
@ -3,6 +3,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
|
||||
|
||||
|
@ -91,7 +92,8 @@ def _truncate_explanation(
|
|||
else:
|
||||
# Add proper ellipsis when we were able to fit a full line exactly
|
||||
truncated_explanation[-1] = "..."
|
||||
return truncated_explanation + [
|
||||
return [
|
||||
*truncated_explanation,
|
||||
"",
|
||||
f"...Full output truncated ({truncated_line_count} line"
|
||||
f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Utilities for assertion debugging."""
|
||||
import collections.abc
|
||||
import os
|
||||
|
@ -14,13 +15,14 @@ from typing import Protocol
|
|||
from typing import Sequence
|
||||
from unicodedata import normalize
|
||||
|
||||
import _pytest._code
|
||||
from _pytest import outcomes
|
||||
import _pytest._code
|
||||
from _pytest._io.pprint import PrettyPrinter
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest._io.saferepr import saferepr_unlimited
|
||||
from _pytest.config import Config
|
||||
|
||||
|
||||
# The _reprcompare attribute on the util module is used by the new assertion
|
||||
# interpretation code and assertion rewriter to detect this plugin was
|
||||
# loaded and in turn call the hooks defined here as part of the
|
||||
|
@ -231,8 +233,8 @@ def assertrepr_compare(
|
|||
return None
|
||||
|
||||
if explanation[0] != "":
|
||||
explanation = [""] + explanation
|
||||
return [summary] + explanation
|
||||
explanation = ["", *explanation]
|
||||
return [summary, *explanation]
|
||||
|
||||
|
||||
def _compare_eq_any(
|
||||
|
@ -301,8 +303,8 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
|
|||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation += [
|
||||
"Skipping {} identical trailing "
|
||||
"characters in diff, use -v to show".format(i)
|
||||
f"Skipping {i} identical trailing "
|
||||
"characters in diff, use -v to show"
|
||||
]
|
||||
left = left[:-i]
|
||||
right = right[:-i]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Implementation of the cache provider."""
|
||||
# This plugin was not named "cache" to avoid conflicts with the external
|
||||
# pytest-cache version.
|
||||
|
@ -31,6 +32,7 @@ from _pytest.nodes import Directory
|
|||
from _pytest.nodes import File
|
||||
from _pytest.reports import TestReport
|
||||
|
||||
|
||||
README_CONTENT = """\
|
||||
# pytest cache directory #
|
||||
|
||||
|
@ -111,6 +113,7 @@ class Cache:
|
|||
"""
|
||||
check_ispytest(_ispytest)
|
||||
import warnings
|
||||
|
||||
from _pytest.warning_types import PytestCacheWarning
|
||||
|
||||
warnings.warn(
|
||||
|
@ -366,15 +369,13 @@ class LFPlugin:
|
|||
|
||||
noun = "failure" if self._previously_failed_count == 1 else "failures"
|
||||
suffix = " first" if self.config.getoption("failedfirst") else ""
|
||||
self._report_status = "rerun previous {count} {noun}{suffix}".format(
|
||||
count=self._previously_failed_count, suffix=suffix, noun=noun
|
||||
self._report_status = (
|
||||
f"rerun previous {self._previously_failed_count} {noun}{suffix}"
|
||||
)
|
||||
|
||||
if self._skipped_files > 0:
|
||||
files_noun = "file" if self._skipped_files == 1 else "files"
|
||||
self._report_status += " (skipped {files} {files_noun})".format(
|
||||
files=self._skipped_files, files_noun=files_noun
|
||||
)
|
||||
self._report_status += f" (skipped {self._skipped_files} {files_noun})"
|
||||
else:
|
||||
self._report_status = "no previously failed tests, "
|
||||
if self.config.getoption("last_failed_no_failures") == "none":
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Per-test stdout/stderr capturing mechanism."""
|
||||
import abc
|
||||
import collections
|
||||
import contextlib
|
||||
import io
|
||||
from io import UnsupportedOperation
|
||||
import os
|
||||
import sys
|
||||
from io import UnsupportedOperation
|
||||
from tempfile import TemporaryFile
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
|
@ -38,6 +39,7 @@ from _pytest.nodes import File
|
|||
from _pytest.nodes import Item
|
||||
from _pytest.reports import CollectReport
|
||||
|
||||
|
||||
_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"]
|
||||
|
||||
|
||||
|
@ -596,7 +598,8 @@ if sys.version_info >= (3, 11) or TYPE_CHECKING:
|
|||
else:
|
||||
|
||||
class CaptureResult(
|
||||
collections.namedtuple("CaptureResult", ["out", "err"]), Generic[AnyStr]
|
||||
collections.namedtuple("CaptureResult", ["out", "err"]), # noqa: PYI024
|
||||
Generic[AnyStr],
|
||||
):
|
||||
"""The result of :method:`caplog.readouterr() <pytest.CaptureFixture.readouterr>`."""
|
||||
|
||||
|
@ -789,9 +792,7 @@ class CaptureManager:
|
|||
current_fixture = self._capture_fixture.request.fixturename
|
||||
requested_fixture = capture_fixture.request.fixturename
|
||||
capture_fixture.request.raiseerror(
|
||||
"cannot use {} and {} at the same time".format(
|
||||
requested_fixture, current_fixture
|
||||
)
|
||||
f"cannot use {requested_fixture} and {current_fixture} at the same time"
|
||||
)
|
||||
self._capture_fixture = capture_fixture
|
||||
|
||||
|
@ -987,7 +988,6 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
|
|||
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_output(capsys):
|
||||
|
@ -1015,7 +1015,6 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None,
|
|||
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_output(capsysbinary):
|
||||
|
@ -1043,7 +1042,6 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
|
|||
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_system_echo(capfd):
|
||||
|
@ -1071,7 +1069,6 @@ def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, N
|
|||
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_system_echo(capfdbinary):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Python version compatibility code."""
|
||||
from __future__ import annotations
|
||||
|
||||
|
@ -5,20 +6,15 @@ import dataclasses
|
|||
import enum
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
from inspect import Parameter
|
||||
from inspect import signature
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Final
|
||||
from typing import NoReturn
|
||||
from typing import TypeVar
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_S = TypeVar("_S")
|
||||
|
||||
|
||||
# fmt: off
|
||||
|
@ -26,7 +22,7 @@ _S = TypeVar("_S")
|
|||
# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions
|
||||
class NotSetType(enum.Enum):
|
||||
token = 0
|
||||
NOTSET: Final = NotSetType.token # noqa: E305
|
||||
NOTSET: Final = NotSetType.token
|
||||
# fmt: on
|
||||
|
||||
|
||||
|
@ -176,25 +172,13 @@ _non_printable_ascii_translate_table.update(
|
|||
)
|
||||
|
||||
|
||||
def _translate_non_printable(s: str) -> str:
|
||||
return s.translate(_non_printable_ascii_translate_table)
|
||||
|
||||
|
||||
STRING_TYPES = bytes, str
|
||||
|
||||
|
||||
def _bytes_to_ascii(val: bytes) -> str:
|
||||
return val.decode("ascii", "backslashreplace")
|
||||
|
||||
|
||||
def ascii_escaped(val: bytes | str) -> str:
|
||||
r"""If val is pure ASCII, return it as an str, otherwise, escape
|
||||
bytes objects into a sequence of escaped bytes:
|
||||
|
||||
b'\xc3\xb4\xc5\xd6' -> r'\xc3\xb4\xc5\xd6'
|
||||
|
||||
and escapes unicode objects into a sequence of escaped unicode
|
||||
ids, e.g.:
|
||||
and escapes strings into a sequence of escaped unicode ids, e.g.:
|
||||
|
||||
r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944'
|
||||
|
||||
|
@ -205,10 +189,10 @@ def ascii_escaped(val: bytes | str) -> str:
|
|||
a UTF-8 string.
|
||||
"""
|
||||
if isinstance(val, bytes):
|
||||
ret = _bytes_to_ascii(val)
|
||||
ret = val.decode("ascii", "backslashreplace")
|
||||
else:
|
||||
ret = val.encode("unicode_escape").decode("ascii")
|
||||
return _translate_non_printable(ret)
|
||||
return ret.translate(_non_printable_ascii_translate_table)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
@ -243,9 +227,7 @@ def get_real_func(obj):
|
|||
from _pytest._io.saferepr import saferepr
|
||||
|
||||
raise ValueError(
|
||||
("could not find real function of {start}\nstopped at {current}").format(
|
||||
start=saferepr(start_obj), current=saferepr(obj)
|
||||
)
|
||||
f"could not find real function of {saferepr(start_obj)}\nstopped at {saferepr(obj)}"
|
||||
)
|
||||
if isinstance(obj, functools.partial):
|
||||
obj = obj.func
|
||||
|
@ -307,7 +289,7 @@ def get_user_id() -> int | None:
|
|||
# mypy follows the version and platform checking expectation of PEP 484:
|
||||
# https://mypy.readthedocs.io/en/stable/common_issues.html?highlight=platform#python-version-and-system-platform-checks
|
||||
# Containment checks are too complex for mypy v1.5.0 and cause failure.
|
||||
if sys.platform == "win32" or sys.platform == "emscripten":
|
||||
if sys.platform in {"win32", "emscripten"}:
|
||||
# win32 does not have a getuid() function.
|
||||
# Emscripten has a return 0 stub.
|
||||
return None
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Command line options, ini-file and conftest.py processing."""
|
||||
import argparse
|
||||
import collections.abc
|
||||
import copy
|
||||
import dataclasses
|
||||
import enum
|
||||
from functools import lru_cache
|
||||
import glob
|
||||
import importlib.metadata
|
||||
import inspect
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
import types
|
||||
from types import FunctionType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
|
@ -36,6 +36,7 @@ from typing import Tuple
|
|||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
from pluggy import HookimplMarker
|
||||
from pluggy import HookimplOpts
|
||||
|
@ -43,15 +44,15 @@ from pluggy import HookspecMarker
|
|||
from pluggy import HookspecOpts
|
||||
from pluggy import PluginManager
|
||||
|
||||
import _pytest._code
|
||||
import _pytest.deprecated
|
||||
import _pytest.hookspec
|
||||
from .exceptions import PrintHelp as PrintHelp
|
||||
from .exceptions import UsageError as UsageError
|
||||
from .findpaths import determine_setup
|
||||
import _pytest._code
|
||||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest._io import TerminalWriter
|
||||
import _pytest.deprecated
|
||||
import _pytest.hookspec
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.pathlib import absolutepath
|
||||
|
@ -64,10 +65,12 @@ from _pytest.stash import Stash
|
|||
from _pytest.warning_types import PytestConfigWarning
|
||||
from _pytest.warning_types import warn_explicit_for
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .argparsing import Argument
|
||||
from .argparsing import Parser
|
||||
from _pytest._code.code import _TracebackStyle
|
||||
from _pytest.terminal import TerminalReporter
|
||||
from .argparsing import Argument, Parser
|
||||
|
||||
|
||||
_PluggyPlugin = object
|
||||
|
@ -235,7 +238,8 @@ essential_plugins = (
|
|||
"helpconfig", # Provides -p.
|
||||
)
|
||||
|
||||
default_plugins = essential_plugins + (
|
||||
default_plugins = (
|
||||
*essential_plugins,
|
||||
"python",
|
||||
"terminal",
|
||||
"debugging",
|
||||
|
@ -541,6 +545,7 @@ class PytestPluginManager(PluginManager):
|
|||
noconftest: bool,
|
||||
rootpath: Path,
|
||||
confcutdir: Optional[Path],
|
||||
invocation_dir: Path,
|
||||
importmode: Union[ImportMode, str],
|
||||
) -> None:
|
||||
"""Load initial conftest files given a preparsed "namespace".
|
||||
|
@ -550,8 +555,9 @@ class PytestPluginManager(PluginManager):
|
|||
All builtin and 3rd party plugins will have been loaded, however, so
|
||||
common options will not confuse our logic here.
|
||||
"""
|
||||
current = Path.cwd()
|
||||
self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None
|
||||
self._confcutdir = (
|
||||
absolutepath(invocation_dir / confcutdir) if confcutdir else None
|
||||
)
|
||||
self._noconftest = noconftest
|
||||
self._using_pyargs = pyargs
|
||||
foundanchor = False
|
||||
|
@ -561,7 +567,7 @@ class PytestPluginManager(PluginManager):
|
|||
i = path.find("::")
|
||||
if i != -1:
|
||||
path = path[:i]
|
||||
anchor = absolutepath(current / path)
|
||||
anchor = absolutepath(invocation_dir / path)
|
||||
|
||||
# Ensure we do not break if what appears to be an anchor
|
||||
# is in fact a very long option (#10169, #11394).
|
||||
|
@ -569,7 +575,7 @@ class PytestPluginManager(PluginManager):
|
|||
self._try_load_conftest(anchor, importmode, rootpath)
|
||||
foundanchor = True
|
||||
if not foundanchor:
|
||||
self._try_load_conftest(current, importmode, rootpath)
|
||||
self._try_load_conftest(invocation_dir, importmode, rootpath)
|
||||
|
||||
def _is_in_confcutdir(self, path: Path) -> bool:
|
||||
"""Whether a path is within the confcutdir.
|
||||
|
@ -666,7 +672,7 @@ class PytestPluginManager(PluginManager):
|
|||
if dirpath in path.parents or path == dirpath:
|
||||
if mod in mods:
|
||||
raise AssertionError(
|
||||
f"While trying to load conftest path {str(conftestpath)}, "
|
||||
f"While trying to load conftest path {conftestpath!s}, "
|
||||
f"found that the module {mod} is already loaded with path {mod.__file__}. "
|
||||
"This is not supposed to happen. Please report this issue to pytest."
|
||||
)
|
||||
|
@ -743,13 +749,10 @@ class PytestPluginManager(PluginManager):
|
|||
self.set_blocked("pytest_" + name)
|
||||
else:
|
||||
name = arg
|
||||
# Unblock the plugin. None indicates that it has been blocked.
|
||||
# There is no interface with pluggy for this.
|
||||
if self._name2plugin.get(name, -1) is None:
|
||||
del self._name2plugin[name]
|
||||
# Unblock the plugin.
|
||||
self.unblock(name)
|
||||
if not name.startswith("pytest_"):
|
||||
if self._name2plugin.get("pytest_" + name, -1) is None:
|
||||
del self._name2plugin["pytest_" + name]
|
||||
self.unblock("pytest_" + name)
|
||||
self.import_plugin(arg, consider_entry_points=True)
|
||||
|
||||
def consider_conftest(
|
||||
|
@ -812,7 +815,7 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
|
||||
def _get_plugin_specs_as_list(
|
||||
specs: Union[None, types.ModuleType, str, Sequence[str]]
|
||||
specs: Union[None, types.ModuleType, str, Sequence[str]],
|
||||
) -> List[str]:
|
||||
"""Parse a plugins specification into a list of plugin names."""
|
||||
# None means empty.
|
||||
|
@ -973,7 +976,8 @@ class Config:
|
|||
*,
|
||||
invocation_params: Optional[InvocationParams] = None,
|
||||
) -> None:
|
||||
from .argparsing import Parser, FILE_OR_DIR
|
||||
from .argparsing import FILE_OR_DIR
|
||||
from .argparsing import Parser
|
||||
|
||||
if invocation_params is None:
|
||||
invocation_params = self.InvocationParams(
|
||||
|
@ -1168,6 +1172,7 @@ class Config:
|
|||
noconftest=early_config.known_args_namespace.noconftest,
|
||||
rootpath=early_config.rootpath,
|
||||
confcutdir=early_config.known_args_namespace.confcutdir,
|
||||
invocation_dir=early_config.invocation_params.dir,
|
||||
importmode=early_config.known_args_namespace.importmode,
|
||||
)
|
||||
|
||||
|
@ -1176,8 +1181,8 @@ class Config:
|
|||
args, namespace=copy.copy(self.option)
|
||||
)
|
||||
rootpath, inipath, inicfg = determine_setup(
|
||||
ns.inifilename,
|
||||
ns.file_or_dir + unknown_args,
|
||||
inifile=ns.inifilename,
|
||||
args=ns.file_or_dir + unknown_args,
|
||||
rootdir_cmd_arg=ns.rootdir or None,
|
||||
invocation_dir=self.invocation_params.dir,
|
||||
)
|
||||
|
@ -1261,6 +1266,8 @@ class Config:
|
|||
"""Decide the args (initial paths/nodeids) to use given the relevant inputs.
|
||||
|
||||
:param warn: Whether can issue warnings.
|
||||
|
||||
:returns: The args and the args source. Guaranteed to be non-empty.
|
||||
"""
|
||||
if args:
|
||||
source = Config.ArgsSource.ARGS
|
||||
|
@ -1369,12 +1376,7 @@ class Config:
|
|||
|
||||
if Version(minver) > Version(pytest.__version__):
|
||||
raise pytest.UsageError(
|
||||
"%s: 'minversion' requires pytest-%s, actual pytest-%s'"
|
||||
% (
|
||||
self.inipath,
|
||||
minver,
|
||||
pytest.__version__,
|
||||
)
|
||||
f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'"
|
||||
)
|
||||
|
||||
def _validate_config_options(self) -> None:
|
||||
|
@ -1387,8 +1389,9 @@ class Config:
|
|||
return
|
||||
|
||||
# Imported lazily to improve start-up time.
|
||||
from packaging.requirements import InvalidRequirement
|
||||
from packaging.requirements import Requirement
|
||||
from packaging.version import Version
|
||||
from packaging.requirements import InvalidRequirement, Requirement
|
||||
|
||||
plugin_info = self.pluginmanager.list_plugin_distinfo()
|
||||
plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info}
|
||||
|
@ -1608,9 +1611,7 @@ class Config:
|
|||
key, user_ini_value = ini_config.split("=", 1)
|
||||
except ValueError as e:
|
||||
raise UsageError(
|
||||
"-o/--override-ini expects option=value style (got: {!r}).".format(
|
||||
ini_config
|
||||
)
|
||||
f"-o/--override-ini expects option=value style (got: {ini_config!r})."
|
||||
) from e
|
||||
else:
|
||||
if key == name:
|
||||
|
@ -1668,7 +1669,6 @@ class Config:
|
|||
can be used to explicitly use the global verbosity level.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# content of pytest.ini
|
||||
|
@ -1848,13 +1848,13 @@ def parse_warning_filter(
|
|||
try:
|
||||
action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined]
|
||||
except warnings._OptionError as e:
|
||||
raise UsageError(error_template.format(error=str(e)))
|
||||
raise UsageError(error_template.format(error=str(e))) from None
|
||||
try:
|
||||
category: Type[Warning] = _resolve_warning_category(category_)
|
||||
except Exception:
|
||||
exc_info = ExceptionInfo.from_current()
|
||||
exception_text = exc_info.getrepr(style="native")
|
||||
raise UsageError(error_template.format(error=exception_text))
|
||||
raise UsageError(error_template.format(error=exception_text)) from None
|
||||
if message and escape:
|
||||
message = re.escape(message)
|
||||
if module and escape:
|
||||
|
@ -1867,7 +1867,7 @@ def parse_warning_filter(
|
|||
except ValueError as e:
|
||||
raise UsageError(
|
||||
error_template.format(error=f"invalid lineno {lineno_!r}: {e}")
|
||||
)
|
||||
) from None
|
||||
else:
|
||||
lineno = 0
|
||||
return action, message, category, module, lineno
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
import argparse
|
||||
from gettext import gettext
|
||||
import os
|
||||
import sys
|
||||
from gettext import gettext
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
|
@ -20,6 +21,7 @@ import _pytest._io
|
|||
from _pytest.config.exceptions import UsageError
|
||||
from _pytest.deprecated import check_ispytest
|
||||
|
||||
|
||||
FILE_OR_DIR = "file_or_dir"
|
||||
|
||||
|
||||
|
@ -120,7 +122,7 @@ class Parser:
|
|||
from _pytest._argcomplete import filescompleter
|
||||
|
||||
optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
|
||||
groups = self._groups + [self._anonymous]
|
||||
groups = [*self._groups, self._anonymous]
|
||||
for group in groups:
|
||||
if group.options:
|
||||
desc = group.description or group.name
|
||||
|
@ -215,7 +217,7 @@ class Parser:
|
|||
|
||||
|
||||
def get_ini_default_for_type(
|
||||
type: Optional[Literal["string", "paths", "pathlist", "args", "linelist", "bool"]]
|
||||
type: Optional[Literal["string", "paths", "pathlist", "args", "linelist", "bool"]],
|
||||
) -> Any:
|
||||
"""
|
||||
Used by addini to get the default value for a given ini-option type, when
|
||||
|
@ -447,7 +449,7 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
|
||||
if not arg_string:
|
||||
return None
|
||||
if not arg_string[0] in self.prefix_chars:
|
||||
if arg_string[0] not in self.prefix_chars:
|
||||
return None
|
||||
if arg_string in self._option_string_actions:
|
||||
action = self._option_string_actions[arg_string]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
|
@ -37,7 +37,6 @@ def load_config_dict_from_file(
|
|||
|
||||
Return None if the file does not contain valid pytest configuration.
|
||||
"""
|
||||
|
||||
# Configuration from ini files are obtained from the [pytest] section, if present.
|
||||
if filepath.suffix == ".ini":
|
||||
iniconfig = _parse_ini_config(filepath)
|
||||
|
@ -87,6 +86,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]]]]:
|
||||
"""Search in the list of arguments for a valid ini-file for pytest,
|
||||
|
@ -100,7 +100,7 @@ def locate_config(
|
|||
]
|
||||
args = [x for x in args if not str(x).startswith("-")]
|
||||
if not args:
|
||||
args = [Path.cwd()]
|
||||
args = [invocation_dir]
|
||||
for arg in args:
|
||||
argpath = absolutepath(arg)
|
||||
for base in (argpath, *argpath.parents):
|
||||
|
@ -113,7 +113,10 @@ def locate_config(
|
|||
return None, None, {}
|
||||
|
||||
|
||||
def get_common_ancestor(paths: Iterable[Path]) -> Path:
|
||||
def get_common_ancestor(
|
||||
invocation_dir: Path,
|
||||
paths: Iterable[Path],
|
||||
) -> Path:
|
||||
common_ancestor: Optional[Path] = None
|
||||
for path in paths:
|
||||
if not path.exists():
|
||||
|
@ -130,7 +133,7 @@ def get_common_ancestor(paths: Iterable[Path]) -> Path:
|
|||
if shared is not None:
|
||||
common_ancestor = shared
|
||||
if common_ancestor is None:
|
||||
common_ancestor = Path.cwd()
|
||||
common_ancestor = invocation_dir
|
||||
elif common_ancestor.is_file():
|
||||
common_ancestor = common_ancestor.parent
|
||||
return common_ancestor
|
||||
|
@ -162,10 +165,11 @@ CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supporte
|
|||
|
||||
|
||||
def determine_setup(
|
||||
*,
|
||||
inifile: Optional[str],
|
||||
args: Sequence[str],
|
||||
rootdir_cmd_arg: Optional[str] = None,
|
||||
invocation_dir: Optional[Path] = None,
|
||||
rootdir_cmd_arg: Optional[str],
|
||||
invocation_dir: Path,
|
||||
) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]:
|
||||
"""Determine the rootdir, inifile and ini configuration values from the
|
||||
command line arguments.
|
||||
|
@ -177,8 +181,7 @@ def determine_setup(
|
|||
:param rootdir_cmd_arg:
|
||||
The `--rootdir` command line argument, if given.
|
||||
:param invocation_dir:
|
||||
The working directory when pytest was invoked, if known.
|
||||
If not known, the current working directory is used.
|
||||
The working directory when pytest was invoked.
|
||||
"""
|
||||
rootdir = None
|
||||
dirs = get_dirs_from_args(args)
|
||||
|
@ -189,8 +192,8 @@ def determine_setup(
|
|||
if rootdir_cmd_arg is None:
|
||||
rootdir = inipath_.parent
|
||||
else:
|
||||
ancestor = get_common_ancestor(dirs)
|
||||
rootdir, inipath, inicfg = locate_config([ancestor])
|
||||
ancestor = get_common_ancestor(invocation_dir, dirs)
|
||||
rootdir, inipath, inicfg = locate_config(invocation_dir, [ancestor])
|
||||
if rootdir is None and rootdir_cmd_arg is None:
|
||||
for possible_rootdir in (ancestor, *ancestor.parents):
|
||||
if (possible_rootdir / "setup.py").is_file():
|
||||
|
@ -198,22 +201,18 @@ def determine_setup(
|
|||
break
|
||||
else:
|
||||
if dirs != [ancestor]:
|
||||
rootdir, inipath, inicfg = locate_config(dirs)
|
||||
rootdir, inipath, inicfg = locate_config(invocation_dir, dirs)
|
||||
if rootdir is None:
|
||||
if invocation_dir is not None:
|
||||
cwd = invocation_dir
|
||||
else:
|
||||
cwd = Path.cwd()
|
||||
rootdir = get_common_ancestor([cwd, ancestor])
|
||||
rootdir = get_common_ancestor(
|
||||
invocation_dir, [invocation_dir, ancestor]
|
||||
)
|
||||
if is_fs_root(rootdir):
|
||||
rootdir = ancestor
|
||||
if rootdir_cmd_arg:
|
||||
rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg))
|
||||
if not rootdir.is_dir():
|
||||
raise UsageError(
|
||||
"Directory '{}' not found. Check your '--rootdir' option.".format(
|
||||
rootdir
|
||||
)
|
||||
f"Directory '{rootdir}' not found. Check your '--rootdir' option."
|
||||
)
|
||||
assert rootdir is not None
|
||||
return rootdir, inipath, inicfg or {}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Interactive debugging with PDB, the Python Debugger."""
|
||||
import argparse
|
||||
import functools
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
|
@ -13,6 +13,7 @@ from typing import Tuple
|
|||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import unittest
|
||||
|
||||
from _pytest import outcomes
|
||||
from _pytest._code import ExceptionInfo
|
||||
|
@ -25,6 +26,7 @@ from _pytest.config.exceptions import UsageError
|
|||
from _pytest.nodes import Node
|
||||
from _pytest.reports import BaseReport
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.capture import CaptureManager
|
||||
from _pytest.runner import CallInfo
|
||||
|
@ -263,8 +265,7 @@ class pytestPDB:
|
|||
elif capturing:
|
||||
tw.sep(
|
||||
">",
|
||||
"PDB %s (IO-capturing turned off for %s)"
|
||||
% (method, capturing),
|
||||
f"PDB {method} (IO-capturing turned off for {capturing})",
|
||||
)
|
||||
else:
|
||||
tw.sep(">", f"PDB {method}")
|
||||
|
|
|
@ -8,12 +8,14 @@ All constants defined in this module should be either instances of
|
|||
:class:`PytestWarning`, or :class:`UnformattedWarning`
|
||||
in case of warnings which need to format their messages.
|
||||
"""
|
||||
|
||||
from warnings import warn
|
||||
|
||||
from _pytest.warning_types import PytestDeprecationWarning
|
||||
from _pytest.warning_types import PytestRemovedIn9Warning
|
||||
from _pytest.warning_types import UnformattedWarning
|
||||
|
||||
|
||||
# set of plugins which have been integrated into the core; we use this list to ignore
|
||||
# them during registration to avoid conflicts
|
||||
DEPRECATED_EXTERNAL_PLUGINS = {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Discover and run doctests in modules and test files."""
|
||||
import bdb
|
||||
from contextlib import contextmanager
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
|
@ -23,6 +23,7 @@ from typing import Tuple
|
|||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
from _pytest import outcomes
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
|
@ -39,11 +40,11 @@ from _pytest.nodes import Item
|
|||
from _pytest.outcomes import OutcomeException
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
from _pytest.pathlib import import_path
|
||||
from _pytest.python import Module
|
||||
from _pytest.python_api import approx
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import doctest
|
||||
|
||||
|
@ -105,7 +106,7 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
"--doctest-ignore-import-errors",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Ignore doctest ImportErrors",
|
||||
help="Ignore doctest collection errors",
|
||||
dest="doctest_ignore_import_errors",
|
||||
)
|
||||
group.addoption(
|
||||
|
@ -485,9 +486,9 @@ def _patch_unwrap_mock_aware() -> Generator[None, None, None]:
|
|||
return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func))
|
||||
except Exception as e:
|
||||
warnings.warn(
|
||||
"Got %r when unwrapping %r. This is usually caused "
|
||||
f"Got {e!r} when unwrapping {func!r}. This is usually caused "
|
||||
"by a violation of Python's object protocol; see e.g. "
|
||||
"https://github.com/pytest-dev/pytest/issues/5080" % (e, func),
|
||||
"https://github.com/pytest-dev/pytest/issues/5080",
|
||||
PytestWarning,
|
||||
)
|
||||
raise
|
||||
|
@ -559,17 +560,17 @@ class DoctestModule(Module):
|
|||
pass
|
||||
|
||||
try:
|
||||
module = import_path(
|
||||
self.path,
|
||||
root=self.config.rootpath,
|
||||
mode=self.config.getoption("importmode"),
|
||||
)
|
||||
except ImportError:
|
||||
module = self.obj
|
||||
except Collector.CollectError:
|
||||
if self.config.getvalue("doctest_ignore_import_errors"):
|
||||
skip("unable to import module %r" % self.path)
|
||||
else:
|
||||
raise
|
||||
|
||||
# While doctests currently don't support fixtures directly, we still
|
||||
# need to pick up autouse fixtures.
|
||||
self.session._fixturemanager.parsefactories(self)
|
||||
|
||||
# Uses internal doctest module parsing mechanism.
|
||||
finder = MockAwareDocTestFinder()
|
||||
optionflags = get_optionflags(self.config)
|
||||
|
|
|
@ -2,11 +2,11 @@ import os
|
|||
import sys
|
||||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
from _pytest.config import Config
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.nodes import Item
|
||||
from _pytest.stash import StashKey
|
||||
import pytest
|
||||
|
||||
|
||||
fault_handler_original_stderr_fd_key = StashKey[int]()
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# mypy: allow-untyped-defs
|
||||
import abc
|
||||
from collections import defaultdict
|
||||
from collections import deque
|
||||
from contextlib import suppress
|
||||
import dataclasses
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from collections import deque
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
from typing import AbstractSet
|
||||
from typing import Any
|
||||
|
@ -30,6 +30,7 @@ from typing import Tuple
|
|||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import _pytest
|
||||
from _pytest import nodes
|
||||
|
@ -602,13 +603,9 @@ class FixtureRequest(abc.ABC):
|
|||
fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
|
||||
if has_params and fixtures_not_supported:
|
||||
msg = (
|
||||
"{name} does not support fixtures, maybe unittest.TestCase subclass?\n"
|
||||
"Node id: {nodeid}\n"
|
||||
"Function type: {typename}"
|
||||
).format(
|
||||
name=funcitem.name,
|
||||
nodeid=funcitem.nodeid,
|
||||
typename=type(funcitem).__name__,
|
||||
f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n"
|
||||
f"Node id: {funcitem.nodeid}\n"
|
||||
f"Function type: {type(funcitem).__name__}"
|
||||
)
|
||||
fail(msg, pytrace=False)
|
||||
if has_params:
|
||||
|
@ -738,9 +735,7 @@ class SubRequest(FixtureRequest):
|
|||
if node is None and scope is Scope.Class:
|
||||
# Fallback to function item itself.
|
||||
node = self._pyfuncitem
|
||||
assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(
|
||||
scope, self._pyfuncitem
|
||||
)
|
||||
assert node, f'Could not obtain a node for scope "{scope}" for function {self._pyfuncitem!r}'
|
||||
return node
|
||||
|
||||
def _check_scope(
|
||||
|
@ -843,8 +838,8 @@ class FixtureLookupError(LookupError):
|
|||
if faclist:
|
||||
available.add(name)
|
||||
if self.argname in available:
|
||||
msg = " recursive dependency involving fixture '{}' detected".format(
|
||||
self.argname
|
||||
msg = (
|
||||
f" recursive dependency involving fixture '{self.argname}' detected"
|
||||
)
|
||||
else:
|
||||
msg = f"fixture '{self.argname}' not found"
|
||||
|
@ -938,15 +933,13 @@ def _eval_scope_callable(
|
|||
result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg]
|
||||
except Exception as e:
|
||||
raise TypeError(
|
||||
"Error evaluating {} while defining fixture '{}'.\n"
|
||||
"Expected a function with the signature (*, fixture_name, config)".format(
|
||||
scope_callable, fixture_name
|
||||
)
|
||||
f"Error evaluating {scope_callable} while defining fixture '{fixture_name}'.\n"
|
||||
"Expected a function with the signature (*, fixture_name, config)"
|
||||
) from e
|
||||
if not isinstance(result, str):
|
||||
fail(
|
||||
"Expected {} to return a 'str' while defining fixture '{}', but it returned:\n"
|
||||
"{!r}".format(scope_callable, fixture_name, result),
|
||||
f"Expected {scope_callable} to return a 'str' while defining fixture '{fixture_name}', but it returned:\n"
|
||||
f"{result!r}",
|
||||
pytrace=False,
|
||||
)
|
||||
return result
|
||||
|
@ -1099,9 +1092,7 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
return request.param_index if not hasattr(request, "param") else request.param
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<FixtureDef argname={!r} scope={!r} baseid={!r}>".format(
|
||||
self.argname, self.scope, self.baseid
|
||||
)
|
||||
return f"<FixtureDef argname={self.argname!r} scope={self.scope!r} baseid={self.baseid!r}>"
|
||||
|
||||
|
||||
def resolve_fixture_function(
|
||||
|
@ -1122,7 +1113,8 @@ def resolve_fixture_function(
|
|||
# Handle the case where fixture is defined not in a test class, but some other class
|
||||
# (for example a plugin class with a fixture), see #2270.
|
||||
if hasattr(fixturefunc, "__self__") and not isinstance(
|
||||
request.instance, fixturefunc.__self__.__class__ # type: ignore[union-attr]
|
||||
request.instance,
|
||||
fixturefunc.__self__.__class__, # type: ignore[union-attr]
|
||||
):
|
||||
return fixturefunc
|
||||
fixturefunc = getimfunc(fixturedef.func)
|
||||
|
@ -1205,7 +1197,7 @@ class FixtureFunctionMarker:
|
|||
|
||||
if getattr(function, "_pytestfixturefunction", False):
|
||||
raise ValueError(
|
||||
"fixture is being applied more than once to the same function"
|
||||
f"@pytest.fixture is being applied more than once to the same function {function.__name__!r}"
|
||||
)
|
||||
|
||||
if hasattr(function, "pytestmark"):
|
||||
|
@ -1217,9 +1209,7 @@ class FixtureFunctionMarker:
|
|||
if name == "request":
|
||||
location = getlocation(function)
|
||||
fail(
|
||||
"'request' is a reserved word for fixtures, use another name:\n {}".format(
|
||||
location
|
||||
),
|
||||
f"'request' is a reserved word for fixtures, use another name:\n {location}",
|
||||
pytrace=False,
|
||||
)
|
||||
|
||||
|
@ -1244,7 +1234,7 @@ def fixture(
|
|||
|
||||
|
||||
@overload
|
||||
def fixture( # noqa: F811
|
||||
def fixture(
|
||||
fixture_function: None = ...,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
|
||||
|
@ -1258,7 +1248,7 @@ def fixture( # noqa: F811
|
|||
...
|
||||
|
||||
|
||||
def fixture( # noqa: F811
|
||||
def fixture(
|
||||
fixture_function: Optional[FixtureFunction] = None,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function",
|
||||
|
@ -1692,7 +1682,7 @@ class FixtureManager:
|
|||
raise NotImplementedError()
|
||||
|
||||
@overload
|
||||
def parsefactories( # noqa: F811
|
||||
def parsefactories(
|
||||
self,
|
||||
node_or_obj: object,
|
||||
nodeid: Optional[str],
|
||||
|
@ -1701,7 +1691,7 @@ class FixtureManager:
|
|||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def parsefactories( # noqa: F811
|
||||
def parsefactories(
|
||||
self,
|
||||
node_or_obj: Union[nodes.Node, object],
|
||||
nodeid: Union[str, NotSetType, None] = NOTSET,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Provides a function to report all internal modules for using freezing
|
||||
tools."""
|
||||
|
||||
import types
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Version info, help messages, tracing configuration."""
|
||||
from argparse import Action
|
||||
import os
|
||||
import sys
|
||||
from argparse import Action
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
from _pytest.config import PrintHelp
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.terminal import TerminalReporter
|
||||
import pytest
|
||||
|
||||
|
||||
class HelpAction(Action):
|
||||
|
@ -108,11 +109,11 @@ def pytest_cmdline_parse() -> Generator[None, Config, Config]:
|
|||
path = config.option.debug
|
||||
debugfile = open(path, "w", encoding="utf-8")
|
||||
debugfile.write(
|
||||
"versions pytest-%s, "
|
||||
"python-%s\ncwd=%s\nargs=%s\n\n"
|
||||
% (
|
||||
"versions pytest-{}, "
|
||||
"python-{}\ninvocation_dir={}\ncwd={}\nargs={}\n\n".format(
|
||||
pytest.__version__,
|
||||
".".join(map(str, sys.version_info)),
|
||||
config.invocation_params.dir,
|
||||
os.getcwd(),
|
||||
config.invocation_params.args,
|
||||
)
|
||||
|
@ -135,9 +136,7 @@ def pytest_cmdline_parse() -> Generator[None, Config, Config]:
|
|||
def showversion(config: Config) -> None:
|
||||
if config.option.version > 1:
|
||||
sys.stdout.write(
|
||||
"This is pytest version {}, imported from {}\n".format(
|
||||
pytest.__version__, pytest.__file__
|
||||
)
|
||||
f"This is pytest version {pytest.__version__}, imported from {pytest.__file__}\n"
|
||||
)
|
||||
plugininfo = getpluginversioninfo(config)
|
||||
if plugininfo:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Hook specifications for pytest plugins which are invoked by pytest itself
|
||||
and by builtin plugins."""
|
||||
from pathlib import Path
|
||||
|
@ -13,17 +14,18 @@ from typing import Union
|
|||
|
||||
from pluggy import HookspecMarker
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import pdb
|
||||
import warnings
|
||||
from typing import Literal
|
||||
import warnings
|
||||
|
||||
from _pytest._code.code import ExceptionRepr
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ExceptionRepr
|
||||
from _pytest.config import _PluggyPlugin
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
from _pytest.config import PytestPluginManager
|
||||
from _pytest.config import _PluggyPlugin
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.fixtures import FixtureDef
|
||||
from _pytest.fixtures import SubRequest
|
||||
|
@ -58,6 +60,12 @@ def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None:
|
|||
|
||||
.. note::
|
||||
This hook is incompatible with hook wrappers.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
If a conftest plugin implements this hook, it will be called immediately
|
||||
when the conftest is registered.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -75,6 +83,14 @@ def pytest_plugin_registered(
|
|||
|
||||
.. note::
|
||||
This hook is incompatible with hook wrappers.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
If a conftest plugin implements this hook, it will be called immediately
|
||||
when the conftest is registered, once for each plugin registered thus far
|
||||
(including itself!), and for all plugins thereafter when they are
|
||||
registered.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -83,12 +99,6 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") ->
|
|||
"""Register argparse-style options and ini-style config values,
|
||||
called once at the beginning of a test run.
|
||||
|
||||
.. note::
|
||||
|
||||
This function should be implemented only in plugins or ``conftest.py``
|
||||
files situated at the tests root directory due to how pytest
|
||||
:ref:`discovers plugins during startup <pluginorder>`.
|
||||
|
||||
:param parser:
|
||||
To add command line options, call
|
||||
:py:func:`parser.addoption(...) <pytest.Parser.addoption>`.
|
||||
|
@ -114,6 +124,14 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") ->
|
|||
|
||||
.. note::
|
||||
This hook is incompatible with hook wrappers.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
If a conftest plugin implements this hook, it will be called immediately
|
||||
when the conftest is registered.
|
||||
|
||||
This hook is only called for :ref:`initial conftests <pluginorder>`.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -121,16 +139,17 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") ->
|
|||
def pytest_configure(config: "Config") -> None:
|
||||
"""Allow plugins and conftest files to perform initial configuration.
|
||||
|
||||
This hook is called for every plugin and initial conftest file
|
||||
after command line options have been parsed.
|
||||
|
||||
After that, the hook is called for other conftest files as they are
|
||||
imported.
|
||||
|
||||
.. note::
|
||||
This hook is incompatible with hook wrappers.
|
||||
|
||||
:param config: The pytest config object.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
This hook is called for every :ref:`initial conftest <pluginorder>` file
|
||||
after command line options have been parsed. After that, the hook is called
|
||||
for other conftest files as they are registered.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -149,28 +168,35 @@ def pytest_cmdline_parse(
|
|||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
.. note::
|
||||
This hook will only be called for plugin classes passed to the
|
||||
This hook is only called for plugin classes passed to the
|
||||
``plugins`` arg when using `pytest.main`_ to perform an in-process
|
||||
test run.
|
||||
|
||||
:param pluginmanager: The pytest plugin manager.
|
||||
:param args: List of arguments passed on the command line.
|
||||
:returns: A pytest config object.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
This hook is not called for conftest files.
|
||||
"""
|
||||
|
||||
|
||||
def pytest_load_initial_conftests(
|
||||
early_config: "Config", parser: "Parser", args: List[str]
|
||||
) -> None:
|
||||
"""Called to implement the loading of initial conftest files ahead
|
||||
of command line option parsing.
|
||||
|
||||
.. note::
|
||||
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
|
||||
"""Called to implement the loading of :ref:`initial conftest files
|
||||
<pluginorder>` ahead of command line option parsing.
|
||||
|
||||
:param early_config: The pytest config object.
|
||||
:param args: Arguments passed on the command line.
|
||||
:param parser: To add command line options.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
This hook is not called for conftest files.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -185,6 +211,11 @@ def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]:
|
|||
|
||||
:param config: The pytest config object.
|
||||
:returns: The exit code.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
This hook is only called for :ref:`initial conftests <pluginorder>`.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -227,6 +258,11 @@ def pytest_collection(session: "Session") -> Optional[object]:
|
|||
counter (and returns `None`).
|
||||
|
||||
:param session: The pytest session object.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
This hook is only called for :ref:`initial conftests <pluginorder>`.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -239,6 +275,11 @@ def pytest_collection_modifyitems(
|
|||
:param session: The pytest session object.
|
||||
:param config: The pytest config object.
|
||||
:param items: List of item objects.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest plugin can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -246,6 +287,11 @@ def pytest_collection_finish(session: "Session") -> None:
|
|||
"""Called after collection has been performed and modified.
|
||||
|
||||
:param session: The pytest session object.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest plugin can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -268,6 +314,14 @@ def pytest_ignore_collect(collection_path: Path, config: "Config") -> Optional[b
|
|||
|
||||
.. versionchanged:: 8.0.0
|
||||
The ``path`` parameter has been removed.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given collection path, only
|
||||
conftest files in parent directories of the collection path are consulted
|
||||
(if the path is a directory, its own conftest file is *not* consulted - a
|
||||
directory cannot ignore itself!).
|
||||
"""
|
||||
|
||||
|
||||
|
@ -289,6 +343,14 @@ def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Colle
|
|||
|
||||
See :ref:`custom directory collectors` for a simple example of use of this
|
||||
hook.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given collection path, only
|
||||
conftest files in parent directories of the collection path are consulted
|
||||
(if the path is a directory, its own conftest file is *not* consulted - a
|
||||
directory cannot collect itself!).
|
||||
"""
|
||||
|
||||
|
||||
|
@ -309,6 +371,12 @@ def pytest_collect_file(file_path: Path, parent: "Collector") -> "Optional[Colle
|
|||
|
||||
.. versionchanged:: 8.0.0
|
||||
The ``path`` parameter was removed.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given file path, only
|
||||
conftest files in parent directories of the file path are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -320,6 +388,13 @@ def pytest_collectstart(collector: "Collector") -> None:
|
|||
|
||||
:param collector:
|
||||
The collector.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given collector, only
|
||||
conftest files in the collector's directory and its parent directories are
|
||||
consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -328,6 +403,12 @@ def pytest_itemcollected(item: "Item") -> None:
|
|||
|
||||
:param item:
|
||||
The item.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -336,6 +417,13 @@ def pytest_collectreport(report: "CollectReport") -> None:
|
|||
|
||||
:param report:
|
||||
The collect report.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given collector, only
|
||||
conftest files in the collector's directory and its parent directories are
|
||||
consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -346,6 +434,11 @@ def pytest_deselected(items: Sequence["Item"]) -> None:
|
|||
|
||||
:param items:
|
||||
The items.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -358,6 +451,13 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor
|
|||
|
||||
:param collector:
|
||||
The collector.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given collector, only
|
||||
conftest files in the collector's directory and its parent directories are
|
||||
consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -385,6 +485,13 @@ def pytest_pycollect_makemodule(module_path: Path, parent) -> Optional["Module"]
|
|||
|
||||
.. versionchanged:: 8.0.0
|
||||
The ``path`` parameter has been removed in favor of ``module_path``.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given parent collector,
|
||||
only conftest files in the collector's directory and its parent directories
|
||||
are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -404,6 +511,13 @@ def pytest_pycollect_makeitem(
|
|||
The object.
|
||||
:returns:
|
||||
The created items/collectors.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given collector, only
|
||||
conftest files in the collector's directory and its parent directories
|
||||
are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -415,6 +529,13 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
|
|||
|
||||
:param pyfuncitem:
|
||||
The function item.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only
|
||||
conftest files in the item's directory and its parent directories
|
||||
are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -423,6 +544,13 @@ def pytest_generate_tests(metafunc: "Metafunc") -> None:
|
|||
|
||||
:param metafunc:
|
||||
The :class:`~pytest.Metafunc` helper for the test function.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given function definition,
|
||||
only conftest files in the functions's directory and its parent directories
|
||||
are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -441,6 +569,11 @@ def pytest_make_parametrize_id(
|
|||
:param config: The pytest config object.
|
||||
:param val: The parametrized value.
|
||||
:param argname: The automatic parameter name produced by pytest.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -467,6 +600,11 @@ def pytest_runtestloop(session: "Session") -> Optional[object]:
|
|||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
The return value is not used, but only stops further processing.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -505,6 +643,11 @@ def pytest_runtest_protocol(
|
|||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
The return value is not used, but only stops further processing.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -519,6 +662,12 @@ def pytest_runtest_logstart(
|
|||
:param location: A tuple of ``(filename, lineno, testname)``
|
||||
where ``filename`` is a file path relative to ``config.rootpath``
|
||||
and ``lineno`` is 0-based.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -533,6 +682,12 @@ def pytest_runtest_logfinish(
|
|||
:param location: A tuple of ``(filename, lineno, testname)``
|
||||
where ``filename`` is a file path relative to ``config.rootpath``
|
||||
and ``lineno`` is 0-based.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -546,6 +701,12 @@ def pytest_runtest_setup(item: "Item") -> None:
|
|||
|
||||
:param item:
|
||||
The item.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -556,6 +717,12 @@ def pytest_runtest_call(item: "Item") -> None:
|
|||
|
||||
:param item:
|
||||
The item.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -574,6 +741,12 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None:
|
|||
scheduled). This argument is used to perform exact teardowns, i.e.
|
||||
calling just enough finalizers so that nextitem only needs to call
|
||||
setup functions.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -590,6 +763,12 @@ def pytest_runtest_makereport(
|
|||
:param call: The :class:`~pytest.CallInfo` for the phase.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -598,6 +777,12 @@ def pytest_runtest_logreport(report: "TestReport") -> None:
|
|||
of the setup, call and teardown runtest phases of an item.
|
||||
|
||||
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -611,6 +796,12 @@ def pytest_report_to_serializable(
|
|||
|
||||
:param config: The pytest config object.
|
||||
:param report: The report.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. The exact details may depend
|
||||
on the plugin which calls the hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -623,6 +814,12 @@ def pytest_report_from_serializable(
|
|||
:hook:`pytest_report_to_serializable`.
|
||||
|
||||
:param config: The pytest config object.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. The exact details may depend
|
||||
on the plugin which calls the hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -650,6 +847,13 @@ def pytest_fixture_setup(
|
|||
If the fixture function returns None, other implementations of
|
||||
this hook function will continue to be called, according to the
|
||||
behavior of the :ref:`firstresult` option.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given fixture, only
|
||||
conftest files in the fixture scope's directory and its parent directories
|
||||
are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -664,6 +868,13 @@ def pytest_fixture_post_finalizer(
|
|||
The fixture definition object.
|
||||
:param request:
|
||||
The fixture request object.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given fixture, only
|
||||
conftest files in the fixture scope's directory and its parent directories
|
||||
are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -677,6 +888,11 @@ def pytest_sessionstart(session: "Session") -> None:
|
|||
and entering the run test loop.
|
||||
|
||||
:param session: The pytest session object.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
This hook is only called for :ref:`initial conftests <pluginorder>`.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -688,6 +904,11 @@ def pytest_sessionfinish(
|
|||
|
||||
:param session: The pytest session object.
|
||||
:param exitstatus: The status which pytest will return to the system.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -695,6 +916,11 @@ def pytest_unconfigure(config: "Config") -> None:
|
|||
"""Called before test process is exited.
|
||||
|
||||
:param config: The pytest config object.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -717,6 +943,12 @@ def pytest_assertrepr_compare(
|
|||
:param op: The operator, e.g. `"=="`, `"!="`, `"not in"`.
|
||||
:param left: The left operand.
|
||||
:param right: The right operand.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -745,6 +977,12 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
|
|||
:param lineno: Line number of the assert statement.
|
||||
:param orig: String with the original assertion.
|
||||
:param expl: String with the assert explanation.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in the item's directory and its parent directories are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -769,18 +1007,17 @@ def pytest_report_header( # type:ignore[empty-body]
|
|||
If you want to have your line(s) displayed first, use
|
||||
:ref:`trylast=True <plugin-hookorder>`.
|
||||
|
||||
.. note::
|
||||
|
||||
This function should be implemented only in plugins or ``conftest.py``
|
||||
files situated at the tests root directory due to how pytest
|
||||
:ref:`discovers plugins during startup <pluginorder>`.
|
||||
|
||||
.. versionchanged:: 7.0.0
|
||||
The ``start_path`` parameter was added as a :class:`pathlib.Path`
|
||||
equivalent of the ``startdir`` parameter.
|
||||
|
||||
.. versionchanged:: 8.0.0
|
||||
The ``startdir`` parameter has been removed.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
This hook is only called for :ref:`initial conftests <pluginorder>`.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -814,6 +1051,11 @@ def pytest_report_collectionfinish( # type:ignore[empty-body]
|
|||
|
||||
.. versionchanged:: 8.0.0
|
||||
The ``startdir`` parameter has been removed.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest plugin can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -842,6 +1084,11 @@ def pytest_report_teststatus( # type:ignore[empty-body]
|
|||
:returns: The test status.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest plugin can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -858,6 +1105,11 @@ def pytest_terminal_summary(
|
|||
|
||||
.. versionadded:: 4.2
|
||||
The ``config`` parameter.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest plugin can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -882,7 +1134,8 @@ def pytest_warning_recorded(
|
|||
* ``"runtest"``: during test execution.
|
||||
|
||||
:param nodeid:
|
||||
Full id of the item.
|
||||
Full id of the item. Empty string for warnings that are not specific to
|
||||
a particular node.
|
||||
|
||||
:param location:
|
||||
When available, holds information about the execution context of the captured
|
||||
|
@ -890,6 +1143,13 @@ def pytest_warning_recorded(
|
|||
when the execution context is at the module level.
|
||||
|
||||
.. versionadded:: 6.0
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. If the warning is specific to a
|
||||
particular node, only conftest files in parent directories of the node are
|
||||
consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -913,6 +1173,12 @@ def pytest_markeval_namespace( # type:ignore[empty-body]
|
|||
|
||||
:param config: The pytest config object.
|
||||
:returns: A dictionary of additional globals to add.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given item, only conftest
|
||||
files in parent directories of the item are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -932,6 +1198,11 @@ def pytest_internalerror(
|
|||
|
||||
:param excrepr: The exception repr object.
|
||||
:param excinfo: The exception info.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest plugin can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -941,6 +1212,11 @@ def pytest_keyboard_interrupt(
|
|||
"""Called for keyboard interrupt.
|
||||
|
||||
:param excinfo: The exception info.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest plugin can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -967,6 +1243,12 @@ def pytest_exception_interact(
|
|||
The call information. Contains the exception.
|
||||
:param report:
|
||||
The collection or test report.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest file can implement this hook. For a given node, only conftest
|
||||
files in parent directories of the node are consulted.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -978,6 +1260,11 @@ def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None:
|
|||
|
||||
:param config: The pytest config object.
|
||||
:param pdb: The Pdb instance.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest plugin can implement this hook.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -989,4 +1276,9 @@ def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None:
|
|||
|
||||
:param config: The pytest config object.
|
||||
:param pdb: The Pdb instance.
|
||||
|
||||
Use in conftest plugins
|
||||
=======================
|
||||
|
||||
Any conftest plugin can implement this hook.
|
||||
"""
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Report test results in JUnit-XML format, for use with Jenkins and build
|
||||
integration servers.
|
||||
|
||||
|
@ -6,12 +7,11 @@ Based on initial code from Ross Lawley.
|
|||
Output conforms to
|
||||
https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
|
||||
"""
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
@ -19,8 +19,8 @@ from typing import Match
|
|||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import pytest
|
||||
from _pytest import nodes
|
||||
from _pytest import timing
|
||||
from _pytest._code.code import ExceptionRepr
|
||||
|
@ -32,6 +32,7 @@ from _pytest.fixtures import FixtureRequest
|
|||
from _pytest.reports import TestReport
|
||||
from _pytest.stash import StashKey
|
||||
from _pytest.terminal import TerminalReporter
|
||||
import pytest
|
||||
|
||||
|
||||
xml_key = StashKey["LogXML"]()
|
||||
|
@ -248,7 +249,9 @@ class _NodeReporter:
|
|||
skipreason = skipreason[9:]
|
||||
details = f"{filename}:{lineno}: {skipreason}"
|
||||
|
||||
skipped = ET.Element("skipped", type="pytest.skip", message=skipreason)
|
||||
skipped = ET.Element(
|
||||
"skipped", type="pytest.skip", message=bin_xml_escape(skipreason)
|
||||
)
|
||||
skipped.text = bin_xml_escape(details)
|
||||
self.append(skipped)
|
||||
self.write_captured_output(report)
|
||||
|
@ -271,9 +274,7 @@ def _warn_incompatibility_with_xunit2(
|
|||
if xml is not None and xml.family not in ("xunit1", "legacy"):
|
||||
request.node.warn(
|
||||
PytestWarning(
|
||||
"{fixture_name} is incompatible with junit_family '{family}' (use 'legacy' or 'xunit1')".format(
|
||||
fixture_name=fixture_name, family=xml.family
|
||||
)
|
||||
f"{fixture_name} is incompatible with junit_family '{xml.family}' (use 'legacy' or 'xunit1')"
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -365,7 +366,6 @@ def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object]
|
|||
`pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See
|
||||
:issue:`7767` for details.
|
||||
"""
|
||||
|
||||
__tracebackhide__ = True
|
||||
|
||||
def record_func(name: str, value: object) -> None:
|
||||
|
@ -375,7 +375,7 @@ def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object]
|
|||
|
||||
xml = request.config.stash.get(xml_key, None)
|
||||
if xml is not None:
|
||||
record_func = xml.add_global_property # noqa
|
||||
record_func = xml.add_global_property
|
||||
return record_func
|
||||
|
||||
|
||||
|
@ -624,7 +624,7 @@ class LogXML:
|
|||
def update_testcase_duration(self, report: TestReport) -> None:
|
||||
"""Accumulate total duration for nodeid from given report and update
|
||||
the Junit.testcase with the new total if already created."""
|
||||
if self.report_duration == "total" or report.when == self.report_duration:
|
||||
if self.report_duration in {"total", report.when}:
|
||||
reporter = self.node_reporter(report)
|
||||
reporter.duration += getattr(report, "duration", 0.0)
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Add backward compatibility support for the legacy py path type."""
|
||||
import dataclasses
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
from typing import final
|
||||
from typing import List
|
||||
|
@ -14,6 +15,7 @@ from typing import Union
|
|||
from iniconfig import SectionWrapper
|
||||
|
||||
import py
|
||||
|
||||
from _pytest.cacheprovider import Cache
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import hookimpl
|
||||
|
@ -32,6 +34,7 @@ from _pytest.pytester import RunResult
|
|||
from _pytest.terminal import TerminalReporter
|
||||
from _pytest.tmpdir import TempPathFactory
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import pexpect
|
||||
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Access and control log capturing."""
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
from contextlib import nullcontext
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from datetime import timezone
|
||||
import io
|
||||
from io import StringIO
|
||||
import logging
|
||||
from logging import LogRecord
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
from types import TracebackType
|
||||
from typing import AbstractSet
|
||||
from typing import Dict
|
||||
|
@ -43,6 +44,7 @@ from _pytest.main import Session
|
|||
from _pytest.stash import StashKey
|
||||
from _pytest.terminal import TerminalReporter
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
logging_StreamHandler = logging.StreamHandler[StringIO]
|
||||
else:
|
||||
|
@ -71,7 +73,8 @@ class DatetimeFormatter(logging.Formatter):
|
|||
tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone)
|
||||
# Construct `datetime.datetime` object from `struct_time`
|
||||
# and msecs information from `record`
|
||||
dt = datetime(*ct[0:6], microsecond=round(record.msecs * 1000), tzinfo=tz)
|
||||
# Using int() instead of round() to avoid it exceeding 1_000_000 and causing a ValueError (#11861).
|
||||
dt = datetime(*ct[0:6], microsecond=int(record.msecs * 1000), tzinfo=tz)
|
||||
return dt.strftime(datefmt)
|
||||
# Use `logging.Formatter` for non-microsecond formats
|
||||
return super().formatTime(record, datefmt)
|
||||
|
@ -114,7 +117,6 @@ class ColoredLevelFormatter(DatetimeFormatter):
|
|||
.. warning::
|
||||
This is an experimental API.
|
||||
"""
|
||||
|
||||
assert self._fmt is not None
|
||||
levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
|
||||
if not levelname_fmt_match:
|
||||
|
@ -181,7 +183,6 @@ class PercentStyleMultiline(logging.PercentStyle):
|
|||
0 (auto-indent turned off) or
|
||||
>0 (explicitly set indentation position).
|
||||
"""
|
||||
|
||||
if auto_indent_option is None:
|
||||
return 0
|
||||
elif isinstance(auto_indent_option, bool):
|
||||
|
@ -623,9 +624,9 @@ def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[i
|
|||
except ValueError as e:
|
||||
# Python logging does not recognise this as a logging level
|
||||
raise UsageError(
|
||||
"'{}' is not recognized as a logging level name for "
|
||||
"'{}'. Please consider passing the "
|
||||
"logging level num instead.".format(log_level, setting_name)
|
||||
f"'{log_level}' is not recognized as a logging level name for "
|
||||
f"'{setting_name}'. Please consider passing the "
|
||||
"logging level num instead."
|
||||
) from e
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
"""Core implementation of the testing process: init, session, runtest loop."""
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import fnmatch
|
||||
import functools
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from typing import AbstractSet
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
|
@ -22,11 +22,12 @@ from typing import overload
|
|||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
|
||||
import _pytest._code
|
||||
from _pytest import nodes
|
||||
import _pytest._code
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import directory_arg
|
||||
from _pytest.config import ExitCode
|
||||
|
@ -725,12 +726,12 @@ class Session(nodes.Collector):
|
|||
...
|
||||
|
||||
@overload
|
||||
def perform_collect( # noqa: F811
|
||||
def perform_collect(
|
||||
self, args: Optional[Sequence[str]] = ..., genitems: bool = ...
|
||||
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
|
||||
...
|
||||
|
||||
def perform_collect( # noqa: F811
|
||||
def perform_collect(
|
||||
self, args: Optional[Sequence[str]] = None, genitems: bool = True
|
||||
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
|
||||
"""Perform the collection phase for this session.
|
||||
|
@ -896,7 +897,7 @@ class Session(nodes.Collector):
|
|||
|
||||
# Prune this level.
|
||||
any_matched_in_collector = False
|
||||
for node in subnodes:
|
||||
for node in reversed(subnodes):
|
||||
# Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`.
|
||||
if isinstance(matchparts[0], Path):
|
||||
is_match = node.path == matchparts[0]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Generic mechanism for marking and selecting python functions."""
|
||||
|
||||
import dataclasses
|
||||
from typing import AbstractSet
|
||||
from typing import Collection
|
||||
|
@ -23,6 +24,7 @@ from _pytest.config import UsageError
|
|||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.stash import StashKey
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.nodes import Item
|
||||
|
||||
|
@ -267,8 +269,8 @@ def pytest_configure(config: Config) -> None:
|
|||
|
||||
if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""):
|
||||
raise UsageError(
|
||||
"{!s} must be one of skip, xfail or fail_at_collect"
|
||||
" but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)
|
||||
f"{EMPTY_PARAMETERSET_OPTION!s} must be one of skip, xfail or fail_at_collect"
|
||||
f" but it is {empty_parameterset!r}"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ The semantics are:
|
|||
- ident evaluates to True of False according to a provided matcher function.
|
||||
- or/and/not evaluate according to the usual boolean semantics.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import dataclasses
|
||||
import enum
|
||||
|
@ -26,6 +27,7 @@ from typing import NoReturn
|
|||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Expression",
|
||||
"ParseError",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# mypy: allow-untyped-defs
|
||||
import collections.abc
|
||||
import dataclasses
|
||||
import inspect
|
||||
import warnings
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Collection
|
||||
|
@ -21,6 +21,7 @@ from typing import Type
|
|||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
from .._code import getfslineno
|
||||
from ..compat import ascii_escaped
|
||||
|
@ -32,6 +33,7 @@ from _pytest.deprecated import MARKED_FIXTURE
|
|||
from _pytest.outcomes import fail
|
||||
from _pytest.warning_types import PytestUnknownMarkWarning
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..nodes import Node
|
||||
|
||||
|
@ -111,7 +113,6 @@ class ParameterSet(NamedTuple):
|
|||
Enforce tuple wrapping so single argument tuple values
|
||||
don't get decomposed and break tests.
|
||||
"""
|
||||
|
||||
if isinstance(parameterset, cls):
|
||||
return parameterset
|
||||
if force_tuple:
|
||||
|
@ -271,8 +272,8 @@ class MarkDecorator:
|
|||
|
||||
``MarkDecorators`` are created with ``pytest.mark``::
|
||||
|
||||
mark1 = pytest.mark.NAME # Simple MarkDecorator
|
||||
mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator
|
||||
mark1 = pytest.mark.NAME # Simple MarkDecorator
|
||||
mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator
|
||||
|
||||
and can then be applied as decorators to test functions::
|
||||
|
||||
|
@ -393,7 +394,7 @@ def get_unpacked_marks(
|
|||
|
||||
|
||||
def normalize_mark_list(
|
||||
mark_list: Iterable[Union[Mark, MarkDecorator]]
|
||||
mark_list: Iterable[Union[Mark, MarkDecorator]],
|
||||
) -> Iterable[Mark]:
|
||||
"""
|
||||
Normalize an iterable of Mark or MarkDecorator objects into a list of marks
|
||||
|
@ -405,7 +406,7 @@ def normalize_mark_list(
|
|||
for mark in mark_list:
|
||||
mark_obj = getattr(mark, "mark", mark)
|
||||
if not isinstance(mark_obj, Mark):
|
||||
raise TypeError(f"got {repr(mark_obj)} instead of Mark")
|
||||
raise TypeError(f"got {mark_obj!r} instead of Mark")
|
||||
yield mark_obj
|
||||
|
||||
|
||||
|
@ -503,9 +504,10 @@ class MarkGenerator:
|
|||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.slowtest
|
||||
def test_function():
|
||||
pass
|
||||
pass
|
||||
|
||||
applies a 'slowtest' :class:`Mark` on ``test_function``.
|
||||
"""
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Monkeypatching and mocking functionality."""
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from typing import Any
|
||||
from typing import final
|
||||
from typing import Generator
|
||||
|
@ -15,10 +15,12 @@ from typing import overload
|
|||
from typing import Tuple
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
from _pytest.fixtures import fixture
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
|
||||
RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$")
|
||||
|
||||
|
||||
|
@ -89,9 +91,7 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object:
|
|||
obj = getattr(obj, name)
|
||||
except AttributeError as e:
|
||||
raise AttributeError(
|
||||
"{!r} object at {} has no attribute {!r}".format(
|
||||
type(obj).__name__, ann, name
|
||||
)
|
||||
f"{type(obj).__name__!r} object at {ann} has no attribute {name!r}"
|
||||
) from e
|
||||
return obj
|
||||
|
||||
|
@ -141,7 +141,6 @@ class MonkeyPatch:
|
|||
which undoes any patching done inside the ``with`` block upon exit.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import functools
|
||||
|
@ -321,10 +320,8 @@ class MonkeyPatch:
|
|||
if not isinstance(value, str):
|
||||
warnings.warn( # type: ignore[unreachable]
|
||||
PytestWarning(
|
||||
"Value of environment variable {name} type should be str, but got "
|
||||
"{value!r} (type: {type}); converted to str implicitly".format(
|
||||
name=name, value=value, type=type(value).__name__
|
||||
)
|
||||
f"Value of environment variable {name} type should be str, but got "
|
||||
f"{value!r} (type: {type(value).__name__}); converted to str implicitly"
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
|
@ -344,7 +341,6 @@ class MonkeyPatch:
|
|||
|
||||
def syspath_prepend(self, path) -> None:
|
||||
"""Prepend ``path`` to ``sys.path`` list of import locations."""
|
||||
|
||||
if self._savesyspath is None:
|
||||
self._savesyspath = sys.path[:]
|
||||
sys.path.insert(0, str(path))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# mypy: allow-untyped-defs
|
||||
import abc
|
||||
import os
|
||||
import warnings
|
||||
from functools import cached_property
|
||||
from inspect import signature
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
|
@ -19,6 +19,7 @@ from typing import Type
|
|||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
|
||||
|
@ -38,10 +39,11 @@ from _pytest.pathlib import commonpath
|
|||
from _pytest.stash import Stash
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Imported here due to circular import.
|
||||
from _pytest.main import Session
|
||||
from _pytest._code.code import _TracebackStyle
|
||||
from _pytest.main import Session
|
||||
|
||||
|
||||
SEP = "/"
|
||||
|
@ -103,6 +105,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
``Collector``\'s are the internal nodes of the tree, and ``Item``\'s are the
|
||||
leaf nodes.
|
||||
"""
|
||||
|
||||
# Use __slots__ to make attribute access faster.
|
||||
# Note that __dict__ is still available.
|
||||
__slots__ = (
|
||||
|
@ -227,9 +230,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
# enforce type checks here to avoid getting a generic type error later otherwise.
|
||||
if not isinstance(warning, Warning):
|
||||
raise ValueError(
|
||||
"warning must be an instance of Warning or subclass, got {!r}".format(
|
||||
warning
|
||||
)
|
||||
f"warning must be an instance of Warning or subclass, got {warning!r}"
|
||||
)
|
||||
path, lineno = get_fslocation_from_item(self)
|
||||
assert lineno is not None
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Exception classes and constants handling test outcomes as well as
|
||||
functions creating them."""
|
||||
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
|
@ -238,8 +239,7 @@ def importorskip(
|
|||
|
||||
if verattr is None or Version(verattr) < Version(minversion):
|
||||
raise Skipped(
|
||||
"module %r has __version__ %r, required is: %r"
|
||||
% (modname, verattr, minversion),
|
||||
f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}",
|
||||
allow_module_level=True,
|
||||
)
|
||||
return mod
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Submit failure or test session information to a pastebin service."""
|
||||
import tempfile
|
||||
from io import StringIO
|
||||
import tempfile
|
||||
from typing import IO
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import create_terminal_writer
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.stash import StashKey
|
||||
from _pytest.terminal import TerminalReporter
|
||||
import pytest
|
||||
|
||||
|
||||
pastebinfile_key = StashKey[IO[bytes]]()
|
||||
|
@ -73,8 +74,8 @@ def create_new_paste(contents: Union[str, bytes]) -> str:
|
|||
:returns: URL to the pasted contents, or an error message.
|
||||
"""
|
||||
import re
|
||||
from urllib.request import urlopen
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import urlopen
|
||||
|
||||
params = {"code": contents, "lexer": "text", "expiry": "1week"}
|
||||
url = "https://bpa.st"
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
# mypy: allow-untyped-defs
|
||||
import atexit
|
||||
import contextlib
|
||||
import fnmatch
|
||||
import importlib.util
|
||||
import itertools
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import types
|
||||
import uuid
|
||||
import warnings
|
||||
from enum import Enum
|
||||
from errno import EBADF
|
||||
from errno import ELOOP
|
||||
from errno import ENOENT
|
||||
from errno import ENOTDIR
|
||||
import fnmatch
|
||||
from functools import partial
|
||||
import importlib.util
|
||||
import itertools
|
||||
import os
|
||||
from os.path import expanduser
|
||||
from os.path import expandvars
|
||||
from os.path import isabs
|
||||
|
@ -22,6 +18,9 @@ from os.path import sep
|
|||
from pathlib import Path
|
||||
from pathlib import PurePath
|
||||
from posixpath import sep as posix_sep
|
||||
import shutil
|
||||
import sys
|
||||
import types
|
||||
from types import ModuleType
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
|
@ -34,11 +33,14 @@ from typing import Tuple
|
|||
from typing import Type
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import uuid
|
||||
import warnings
|
||||
|
||||
from _pytest.compat import assert_never
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
|
||||
LOCK_TIMEOUT = 60 * 60 * 24 * 3
|
||||
|
||||
|
||||
|
@ -101,9 +103,7 @@ def on_rm_rf_error(
|
|||
if func not in (os.open,):
|
||||
warnings.warn(
|
||||
PytestWarning(
|
||||
"(rm_rf) unknown function {} when removing {}:\n{}: {}".format(
|
||||
func, path, type(exc), exc
|
||||
)
|
||||
f"(rm_rf) unknown function {func} when removing {path}:\n{type(exc)}: {exc}"
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
@ -171,23 +171,23 @@ def rm_rf(path: Path) -> None:
|
|||
shutil.rmtree(str(path), onerror=onerror)
|
||||
|
||||
|
||||
def find_prefixed(root: Path, prefix: str) -> Iterator[Path]:
|
||||
def find_prefixed(root: Path, prefix: str) -> Iterator["os.DirEntry[str]"]:
|
||||
"""Find all elements in root that begin with the prefix, case insensitive."""
|
||||
l_prefix = prefix.lower()
|
||||
for x in root.iterdir():
|
||||
for x in os.scandir(root):
|
||||
if x.name.lower().startswith(l_prefix):
|
||||
yield x
|
||||
|
||||
|
||||
def extract_suffixes(iter: Iterable[PurePath], prefix: str) -> Iterator[str]:
|
||||
def extract_suffixes(iter: Iterable["os.DirEntry[str]"], prefix: str) -> Iterator[str]:
|
||||
"""Return the parts of the paths following the prefix.
|
||||
|
||||
:param iter: Iterator over path names.
|
||||
:param prefix: Expected prefix of the path names.
|
||||
"""
|
||||
p_len = len(prefix)
|
||||
for p in iter:
|
||||
yield p.name[p_len:]
|
||||
for entry in iter:
|
||||
yield entry.name[p_len:]
|
||||
|
||||
|
||||
def find_suffixes(root: Path, prefix: str) -> Iterator[str]:
|
||||
|
@ -242,7 +242,7 @@ def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> Path:
|
|||
else:
|
||||
raise OSError(
|
||||
"could not create numbered dir with prefix "
|
||||
"{prefix} in {root} after 10 tries".format(prefix=prefix, root=root)
|
||||
f"{prefix} in {root} after 10 tries"
|
||||
)
|
||||
|
||||
|
||||
|
@ -346,12 +346,12 @@ def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]:
|
|||
"""List candidates for numbered directories to be removed - follows py.path."""
|
||||
max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1)
|
||||
max_delete = max_existing - keep
|
||||
paths = find_prefixed(root, prefix)
|
||||
paths, paths2 = itertools.tee(paths)
|
||||
numbers = map(parse_num, extract_suffixes(paths2, prefix))
|
||||
for path, number in zip(paths, numbers):
|
||||
entries = find_prefixed(root, prefix)
|
||||
entries, entries2 = itertools.tee(entries)
|
||||
numbers = map(parse_num, extract_suffixes(entries2, prefix))
|
||||
for entry, number in zip(entries, numbers):
|
||||
if number <= max_delete:
|
||||
yield path
|
||||
yield Path(entry)
|
||||
|
||||
|
||||
def cleanup_dead_symlinks(root: Path):
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""(Disabled by default) support for testing pytest and pytest plugins.
|
||||
|
||||
PYTEST_DONT_REWRITE
|
||||
"""
|
||||
import collections.abc
|
||||
import contextlib
|
||||
from fnmatch import fnmatch
|
||||
import gc
|
||||
import importlib
|
||||
from io import StringIO
|
||||
import locale
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
from fnmatch import fnmatch
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
|
@ -69,6 +70,7 @@ from _pytest.reports import TestReport
|
|||
from _pytest.tmpdir import TempPathFactory
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import pexpect
|
||||
|
||||
|
@ -186,7 +188,7 @@ class LsofFdLeakChecker:
|
|||
"*** After:",
|
||||
*(str(f) for f in lines2),
|
||||
"***** %s FD leakage detected" % len(leaked_files),
|
||||
"*** function %s:%s: %s " % item.location,
|
||||
"*** function {}:{}: {} ".format(*item.location),
|
||||
"See issue #2366",
|
||||
]
|
||||
item.warn(PytestWarning("\n".join(error)))
|
||||
|
@ -375,14 +377,12 @@ class HookRecorder:
|
|||
values.append(rep)
|
||||
if not values:
|
||||
raise ValueError(
|
||||
"could not find test report matching %r: "
|
||||
"no test reports at all!" % (inamepart,)
|
||||
f"could not find test report matching {inamepart!r}: "
|
||||
"no test reports at all!"
|
||||
)
|
||||
if len(values) > 1:
|
||||
raise ValueError(
|
||||
"found 2 or more testreports matching {!r}: {}".format(
|
||||
inamepart, values
|
||||
)
|
||||
f"found 2 or more testreports matching {inamepart!r}: {values}"
|
||||
)
|
||||
return values[0]
|
||||
|
||||
|
@ -807,7 +807,6 @@ class Pytester:
|
|||
The first created file.
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytester.makefile(".txt", "line1", "line2")
|
||||
|
@ -861,7 +860,6 @@ class Pytester:
|
|||
existing files.
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_something(pytester):
|
||||
|
@ -881,7 +879,6 @@ class Pytester:
|
|||
existing files.
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_something(pytester):
|
||||
|
@ -1064,7 +1061,7 @@ class Pytester:
|
|||
:param cmdlineargs: Any extra command line arguments to use.
|
||||
"""
|
||||
p = self.makepyfile(source)
|
||||
values = list(cmdlineargs) + [p]
|
||||
values = [*list(cmdlineargs), p]
|
||||
return self.inline_run(*values)
|
||||
|
||||
def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]:
|
||||
|
@ -1271,9 +1268,7 @@ class Pytester:
|
|||
for item in items:
|
||||
if item.name == funcname:
|
||||
return item
|
||||
assert 0, "{!r} item not found in module:\n{}\nitems: {}".format(
|
||||
funcname, source, items
|
||||
)
|
||||
assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}"
|
||||
|
||||
def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]:
|
||||
"""Return all test items collected from the module.
|
||||
|
@ -1432,10 +1427,7 @@ class Pytester:
|
|||
def handle_timeout() -> None:
|
||||
__tracebackhide__ = True
|
||||
|
||||
timeout_message = (
|
||||
"{seconds} second timeout expired running:"
|
||||
" {command}".format(seconds=timeout, command=cmdargs)
|
||||
)
|
||||
timeout_message = f"{timeout} second timeout expired running: {cmdargs}"
|
||||
|
||||
popen.kill()
|
||||
popen.wait()
|
||||
|
@ -1499,10 +1491,10 @@ class Pytester:
|
|||
"""
|
||||
__tracebackhide__ = True
|
||||
p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700)
|
||||
args = ("--basetemp=%s" % p,) + args
|
||||
args = ("--basetemp=%s" % p, *args)
|
||||
plugins = [x for x in self.plugins if isinstance(x, str)]
|
||||
if plugins:
|
||||
args = ("-p", plugins[0]) + args
|
||||
args = ("-p", plugins[0], *args)
|
||||
args = self._getpytestargs() + args
|
||||
return self.run(*args, timeout=timeout)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Helper plugin for pytester; should not be loaded on its own."""
|
||||
|
||||
# This plugin contains assertions used by pytester. pytester cannot
|
||||
# contain them itself, since it is imported by the `pytest` module,
|
||||
# hence cannot be subject to assertion rewriting, which requires a
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Python test discovery, setup and run of test functions."""
|
||||
import abc
|
||||
from collections import Counter
|
||||
from collections import defaultdict
|
||||
import dataclasses
|
||||
import enum
|
||||
import fnmatch
|
||||
from functools import partial
|
||||
import inspect
|
||||
import itertools
|
||||
import os
|
||||
import types
|
||||
import warnings
|
||||
from collections import Counter
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
import types
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
|
@ -28,6 +28,7 @@ from typing import Sequence
|
|||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import _pytest
|
||||
from _pytest import fixtures
|
||||
|
@ -49,7 +50,6 @@ from _pytest.compat import is_generator
|
|||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.compat import safe_isclass
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
from _pytest.config import hookimpl
|
||||
|
@ -263,8 +263,8 @@ def pytest_pycollect_makeitem(
|
|||
elif getattr(obj, "__test__", True):
|
||||
if is_generator(obj):
|
||||
res: Function = Function.from_parent(collector, name=name)
|
||||
reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
|
||||
name=name
|
||||
reason = (
|
||||
f"yield tests were removed in pytest 4.0 - {name} will be ignored"
|
||||
)
|
||||
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
|
||||
res.warn(PytestCollectionWarning(reason))
|
||||
|
@ -357,7 +357,7 @@ class PyobjMixin(nodes.Node):
|
|||
# hook is not called for them.
|
||||
# fmt: off
|
||||
class _EmptyClass: pass # noqa: E701
|
||||
IGNORED_ATTRIBUTES = frozenset.union( # noqa: E305
|
||||
IGNORED_ATTRIBUTES = frozenset.union(
|
||||
frozenset(),
|
||||
# Module.
|
||||
dir(types.ModuleType("empty_module")),
|
||||
|
@ -524,12 +524,12 @@ def importtestmodule(
|
|||
except ImportPathMismatchError as e:
|
||||
raise nodes.Collector.CollectError(
|
||||
"import file mismatch:\n"
|
||||
"imported module %r has this __file__ attribute:\n"
|
||||
" %s\n"
|
||||
"imported module {!r} has this __file__ attribute:\n"
|
||||
" {}\n"
|
||||
"which is not the same as the test file we want to collect:\n"
|
||||
" %s\n"
|
||||
" {}\n"
|
||||
"HINT: remove __pycache__ / .pyc files and/or use a "
|
||||
"unique basename for your test file modules" % e.args
|
||||
"unique basename for your test file modules".format(*e.args)
|
||||
) from e
|
||||
except ImportError as e:
|
||||
exc_info = ExceptionInfo.from_current()
|
||||
|
@ -542,10 +542,10 @@ def importtestmodule(
|
|||
)
|
||||
formatted_tb = str(exc_repr)
|
||||
raise nodes.Collector.CollectError(
|
||||
"ImportError while importing test module '{path}'.\n"
|
||||
f"ImportError while importing test module '{path}'.\n"
|
||||
"Hint: make sure your test modules/packages have valid Python names.\n"
|
||||
"Traceback:\n"
|
||||
"{traceback}".format(path=path, traceback=formatted_tb)
|
||||
f"{formatted_tb}"
|
||||
) from e
|
||||
except skip.Exception as e:
|
||||
if e.allow_module_level:
|
||||
|
@ -765,9 +765,8 @@ class Class(PyCollector):
|
|||
assert self.parent is not None
|
||||
self.warn(
|
||||
PytestCollectionWarning(
|
||||
"cannot collect test class %r because it has a "
|
||||
"__init__ constructor (from: %s)"
|
||||
% (self.obj.__name__, self.parent.nodeid)
|
||||
f"cannot collect test class {self.obj.__name__!r} because it has a "
|
||||
f"__init__ constructor (from: {self.parent.nodeid})"
|
||||
)
|
||||
)
|
||||
return []
|
||||
|
@ -775,9 +774,8 @@ class Class(PyCollector):
|
|||
assert self.parent is not None
|
||||
self.warn(
|
||||
PytestCollectionWarning(
|
||||
"cannot collect test class %r because it has a "
|
||||
"__new__ constructor (from: %s)"
|
||||
% (self.obj.__name__, self.parent.nodeid)
|
||||
f"cannot collect test class {self.obj.__name__!r} because it has a "
|
||||
f"__new__ constructor (from: {self.parent.nodeid})"
|
||||
)
|
||||
)
|
||||
return []
|
||||
|
@ -999,7 +997,7 @@ class IdMaker:
|
|||
def _idval_from_value(self, val: object) -> Optional[str]:
|
||||
"""Try to make an ID for a parameter in a ParameterSet from its value,
|
||||
if the value type is supported."""
|
||||
if isinstance(val, STRING_TYPES):
|
||||
if isinstance(val, (str, bytes)):
|
||||
return _ascii_escaped_by_config(val, self.config)
|
||||
elif val is None or isinstance(val, (float, int, bool, complex)):
|
||||
return str(val)
|
||||
|
@ -1432,17 +1430,14 @@ class Metafunc:
|
|||
for arg in indirect:
|
||||
if arg not in argnames:
|
||||
fail(
|
||||
"In {}: indirect fixture '{}' doesn't exist".format(
|
||||
self.function.__name__, arg
|
||||
),
|
||||
f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist",
|
||||
pytrace=False,
|
||||
)
|
||||
arg_directness[arg] = "indirect"
|
||||
else:
|
||||
fail(
|
||||
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
|
||||
type=type(indirect).__name__, func=self.function.__name__
|
||||
),
|
||||
f"In {self.function.__name__}: expected Sequence or boolean"
|
||||
f" for indirect, got {type(indirect).__name__}",
|
||||
pytrace=False,
|
||||
)
|
||||
return arg_directness
|
||||
|
@ -1464,9 +1459,7 @@ class Metafunc:
|
|||
if arg not in self.fixturenames:
|
||||
if arg in default_arg_names:
|
||||
fail(
|
||||
"In {}: function already takes an argument '{}' with a default value".format(
|
||||
func_name, arg
|
||||
),
|
||||
f"In {func_name}: function already takes an argument '{arg}' with a default value",
|
||||
pytrace=False,
|
||||
)
|
||||
else:
|
||||
|
@ -1525,14 +1518,13 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -
|
|||
return val if escape_option else ascii_escaped(val) # type: ignore
|
||||
|
||||
|
||||
def _pretty_fixture_path(func) -> str:
|
||||
cwd = Path.cwd()
|
||||
loc = Path(getlocation(func, str(cwd)))
|
||||
def _pretty_fixture_path(invocation_dir: Path, func) -> str:
|
||||
loc = Path(getlocation(func, invocation_dir))
|
||||
prefix = Path("...", "_pytest")
|
||||
try:
|
||||
return str(prefix / loc.relative_to(_PYTEST_DIR))
|
||||
except ValueError:
|
||||
return bestrelpath(cwd, loc)
|
||||
return bestrelpath(invocation_dir, loc)
|
||||
|
||||
|
||||
def show_fixtures_per_test(config):
|
||||
|
@ -1545,19 +1537,19 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None:
|
|||
import _pytest.config
|
||||
|
||||
session.perform_collect()
|
||||
curdir = Path.cwd()
|
||||
invocation_dir = config.invocation_params.dir
|
||||
tw = _pytest.config.create_terminal_writer(config)
|
||||
verbose = config.getvalue("verbose")
|
||||
|
||||
def get_best_relpath(func) -> str:
|
||||
loc = getlocation(func, str(curdir))
|
||||
return bestrelpath(curdir, Path(loc))
|
||||
loc = getlocation(func, invocation_dir)
|
||||
return bestrelpath(invocation_dir, Path(loc))
|
||||
|
||||
def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
|
||||
argname = fixture_def.argname
|
||||
if verbose <= 0 and argname.startswith("_"):
|
||||
return
|
||||
prettypath = _pretty_fixture_path(fixture_def.func)
|
||||
prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func)
|
||||
tw.write(f"{argname}", green=True)
|
||||
tw.write(f" -- {prettypath}", yellow=True)
|
||||
tw.write("\n")
|
||||
|
@ -1601,7 +1593,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
|
|||
import _pytest.config
|
||||
|
||||
session.perform_collect()
|
||||
curdir = Path.cwd()
|
||||
invocation_dir = config.invocation_params.dir
|
||||
tw = _pytest.config.create_terminal_writer(config)
|
||||
verbose = config.getvalue("verbose")
|
||||
|
||||
|
@ -1615,7 +1607,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
|
|||
if not fixturedefs:
|
||||
continue
|
||||
for fixturedef in fixturedefs:
|
||||
loc = getlocation(fixturedef.func, str(curdir))
|
||||
loc = getlocation(fixturedef.func, invocation_dir)
|
||||
if (fixturedef.argname, loc) in seen:
|
||||
continue
|
||||
seen.add((fixturedef.argname, loc))
|
||||
|
@ -1623,7 +1615,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
|
|||
(
|
||||
len(fixturedef.baseid),
|
||||
fixturedef.func.__module__,
|
||||
_pretty_fixture_path(fixturedef.func),
|
||||
_pretty_fixture_path(invocation_dir, fixturedef.func),
|
||||
fixturedef.argname,
|
||||
fixturedef,
|
||||
)
|
||||
|
@ -1793,10 +1785,11 @@ class Function(PyobjMixin, nodes.Item):
|
|||
if self.config.getoption("tbstyle", "auto") == "auto":
|
||||
if len(ntraceback) > 2:
|
||||
ntraceback = Traceback(
|
||||
entry
|
||||
if i == 0 or i == len(ntraceback) - 1
|
||||
else entry.with_repr_style("short")
|
||||
for i, entry in enumerate(ntraceback)
|
||||
(
|
||||
ntraceback[0],
|
||||
*(t.with_repr_style("short") for t in ntraceback[1:-1]),
|
||||
ntraceback[-1],
|
||||
)
|
||||
)
|
||||
|
||||
return ntraceback
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import math
|
||||
import pprint
|
||||
# mypy: allow-untyped-defs
|
||||
from collections.abc import Collection
|
||||
from collections.abc import Sized
|
||||
from decimal import Decimal
|
||||
import math
|
||||
from numbers import Complex
|
||||
import pprint
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
|
@ -23,9 +24,9 @@ from typing import TypeVar
|
|||
from typing import Union
|
||||
|
||||
import _pytest._code
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from numpy import ndarray
|
||||
|
||||
|
@ -237,9 +238,7 @@ class ApproxMapping(ApproxBase):
|
|||
with numeric values (the keys can be anything)."""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "approx({!r})".format(
|
||||
{k: self._approx_scalar(v) for k, v in self.expected.items()}
|
||||
)
|
||||
return f"approx({({k: self._approx_scalar(v) for k, v in self.expected.items()})!r})"
|
||||
|
||||
def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]:
|
||||
import math
|
||||
|
@ -314,9 +313,7 @@ class ApproxSequenceLike(ApproxBase):
|
|||
seq_type = type(self.expected)
|
||||
if seq_type not in (tuple, list):
|
||||
seq_type = list
|
||||
return "approx({!r})".format(
|
||||
seq_type(self._approx_scalar(x) for x in self.expected)
|
||||
)
|
||||
return f"approx({seq_type(self._approx_scalar(x) for x in self.expected)!r})"
|
||||
|
||||
def _repr_compare(self, other_side: Sequence[float]) -> List[str]:
|
||||
import math
|
||||
|
@ -696,7 +693,6 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
|
|||
``approx`` falls back to strict equality for nonnumeric types instead
|
||||
of raising ``TypeError``.
|
||||
"""
|
||||
|
||||
# Delegate the comparison to a class that knows how to deal with the type
|
||||
# of the expected value (e.g. int, float, list, dict, numpy.array, etc).
|
||||
#
|
||||
|
@ -724,16 +720,11 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
|
|||
elif (
|
||||
hasattr(expected, "__getitem__")
|
||||
and isinstance(expected, Sized)
|
||||
# Type ignored because the error is wrong -- not unreachable.
|
||||
and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable]
|
||||
and not isinstance(expected, (str, bytes))
|
||||
):
|
||||
cls = ApproxSequenceLike
|
||||
elif (
|
||||
isinstance(expected, Collection)
|
||||
# Type ignored because the error is wrong -- not unreachable.
|
||||
and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable]
|
||||
):
|
||||
msg = f"pytest.approx() only supports ordered sequences, but got: {repr(expected)}"
|
||||
elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)):
|
||||
msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}"
|
||||
raise TypeError(msg)
|
||||
else:
|
||||
cls = ApproxScalar
|
||||
|
@ -783,7 +774,7 @@ def raises(
|
|||
|
||||
|
||||
@overload
|
||||
def raises( # noqa: F811
|
||||
def raises(
|
||||
expected_exception: Union[Type[E], Tuple[Type[E], ...]],
|
||||
func: Callable[..., Any],
|
||||
*args: Any,
|
||||
|
@ -792,7 +783,7 @@ def raises( # noqa: F811
|
|||
...
|
||||
|
||||
|
||||
def raises( # noqa: F811
|
||||
def raises(
|
||||
expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any
|
||||
) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]:
|
||||
r"""Assert that a code block/function call raises an exception type, or one of its subclasses.
|
||||
|
@ -838,10 +829,10 @@ def raises( # noqa: F811
|
|||
The ``match`` argument searches the formatted exception string, which includes any
|
||||
`PEP-678 <https://peps.python.org/pep-0678/>`__ ``__notes__``:
|
||||
|
||||
>>> with pytest.raises(ValueError, match=r'had a note added'): # doctest: +SKIP
|
||||
... e = ValueError("value must be 42")
|
||||
... e.add_note("had a note added")
|
||||
... raise e
|
||||
>>> with pytest.raises(ValueError, match=r"had a note added"): # doctest: +SKIP
|
||||
... e = ValueError("value must be 42")
|
||||
... e.add_note("had a note added")
|
||||
... raise e
|
||||
|
||||
The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
|
||||
details of the captured exception::
|
||||
|
@ -856,7 +847,7 @@ def raises( # noqa: F811
|
|||
Given that ``pytest.raises`` matches subclasses, be wary of using it to match :class:`Exception` like this::
|
||||
|
||||
with pytest.raises(Exception): # Careful, this will catch ANY exception raised.
|
||||
some_function()
|
||||
some_function()
|
||||
|
||||
Because :class:`Exception` is the base class of almost all exceptions, it is easy for this to hide
|
||||
real bugs, where the user wrote this expecting a specific exception, but some other exception is being
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Record warnings during test function execution."""
|
||||
import re
|
||||
import warnings
|
||||
from pprint import pformat
|
||||
import re
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
|
@ -16,6 +16,7 @@ from typing import Tuple
|
|||
from typing import Type
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
from _pytest.deprecated import check_ispytest
|
||||
from _pytest.fixtures import fixture
|
||||
|
@ -46,13 +47,11 @@ def deprecated_call(
|
|||
|
||||
|
||||
@overload
|
||||
def deprecated_call( # noqa: F811
|
||||
func: Callable[..., T], *args: Any, **kwargs: Any
|
||||
) -> T:
|
||||
def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
|
||||
...
|
||||
|
||||
|
||||
def deprecated_call( # noqa: F811
|
||||
def deprecated_call(
|
||||
func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any
|
||||
) -> Union["WarningsRecorder", Any]:
|
||||
"""Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``.
|
||||
|
@ -80,7 +79,7 @@ def deprecated_call( # noqa: F811
|
|||
"""
|
||||
__tracebackhide__ = True
|
||||
if func is not None:
|
||||
args = (func,) + args
|
||||
args = (func, *args)
|
||||
return warns(
|
||||
(DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs
|
||||
)
|
||||
|
@ -96,7 +95,7 @@ def warns(
|
|||
|
||||
|
||||
@overload
|
||||
def warns( # noqa: F811
|
||||
def warns(
|
||||
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]],
|
||||
func: Callable[..., T],
|
||||
*args: Any,
|
||||
|
@ -105,7 +104,7 @@ def warns( # noqa: F811
|
|||
...
|
||||
|
||||
|
||||
def warns( # noqa: F811
|
||||
def warns(
|
||||
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning,
|
||||
*args: Any,
|
||||
match: Optional[Union[str, Pattern[str]]] = None,
|
||||
|
@ -330,3 +329,14 @@ class WarningsChecker(WarningsRecorder):
|
|||
module=w.__module__,
|
||||
source=w.source,
|
||||
)
|
||||
# Check warnings has valid argument type (#10865).
|
||||
wrn: warnings.WarningMessage
|
||||
for wrn in self:
|
||||
self._validate_message(wrn)
|
||||
|
||||
@staticmethod
|
||||
def _validate_message(wrn: Any) -> None:
|
||||
if not isinstance(msg := wrn.message.args[0], str):
|
||||
raise TypeError(
|
||||
f"Warning message must be str, got {msg!r} (type {type(msg).__name__})"
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# mypy: allow-untyped-defs
|
||||
import dataclasses
|
||||
import os
|
||||
from io import StringIO
|
||||
import os
|
||||
from pprint import pprint
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
|
@ -36,6 +37,7 @@ from _pytest.nodes import Collector
|
|||
from _pytest.nodes import Item
|
||||
from _pytest.outcomes import skip
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.runner import CallInfo
|
||||
|
||||
|
@ -45,7 +47,7 @@ def getworkerinfoline(node):
|
|||
return node._workerinfocache
|
||||
except AttributeError:
|
||||
d = node.workerinfo
|
||||
ver = "%s.%s.%s" % d["version_info"][:3]
|
||||
ver = "{}.{}.{}".format(*d["version_info"][:3])
|
||||
node._workerinfocache = s = "[{}] {} -- Python {} {}".format(
|
||||
d["id"], d["sysplatform"], ver, d["executable"]
|
||||
)
|
||||
|
@ -313,9 +315,7 @@ class TestReport(BaseReport):
|
|||
self.__dict__.update(extra)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<{} {!r} when={!r} outcome={!r}>".format(
|
||||
self.__class__.__name__, self.nodeid, self.when, self.outcome
|
||||
)
|
||||
return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>"
|
||||
|
||||
@classmethod
|
||||
def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport":
|
||||
|
@ -430,9 +430,7 @@ class CollectReport(BaseReport):
|
|||
return (self.fspath, None, self.fspath)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<CollectReport {!r} lenresult={} outcome={!r}>".format(
|
||||
self.nodeid, len(self.result), self.outcome
|
||||
)
|
||||
return f"<CollectReport {self.nodeid!r} lenresult={len(self.result)} outcome={self.outcome!r}>"
|
||||
|
||||
|
||||
class CollectErrorRepr(TerminalRepr):
|
||||
|
@ -444,7 +442,7 @@ class CollectErrorRepr(TerminalRepr):
|
|||
|
||||
|
||||
def pytest_report_to_serializable(
|
||||
report: Union[CollectReport, TestReport]
|
||||
report: Union[CollectReport, TestReport],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
if isinstance(report, (TestReport, CollectReport)):
|
||||
data = report._to_json()
|
||||
|
@ -476,7 +474,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
|||
"""
|
||||
|
||||
def serialize_repr_entry(
|
||||
entry: Union[ReprEntry, ReprEntryNative]
|
||||
entry: Union[ReprEntry, ReprEntryNative],
|
||||
) -> Dict[str, Any]:
|
||||
data = dataclasses.asdict(entry)
|
||||
for key, value in data.items():
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Basic collect and runtest protocol implementations."""
|
||||
import bdb
|
||||
import dataclasses
|
||||
|
@ -36,6 +37,7 @@ from _pytest.outcomes import OutcomeException
|
|||
from _pytest.outcomes import Skipped
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
||||
|
||||
if sys.version_info[:2] < (3, 11):
|
||||
from exceptiongroup import BaseExceptionGroup
|
||||
|
||||
|
@ -93,8 +95,7 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None:
|
|||
if verbose < 2 and rep.duration < durations_min:
|
||||
tr.write_line("")
|
||||
tr.write_line(
|
||||
"(%s durations < %gs hidden. Use -vv to show these durations.)"
|
||||
% (len(dlist) - i, durations_min)
|
||||
f"({len(dlist) - i} durations < {durations_min:g}s hidden. Use -vv to show these durations.)"
|
||||
)
|
||||
break
|
||||
tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}")
|
||||
|
@ -223,13 +224,26 @@ def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str
|
|||
def call_and_report(
|
||||
item: Item, when: Literal["setup", "call", "teardown"], log: bool = True, **kwds
|
||||
) -> TestReport:
|
||||
call = call_runtest_hook(item, when, **kwds)
|
||||
hook = item.ihook
|
||||
report: TestReport = hook.pytest_runtest_makereport(item=item, call=call)
|
||||
ihook = item.ihook
|
||||
if when == "setup":
|
||||
runtest_hook: Callable[..., None] = ihook.pytest_runtest_setup
|
||||
elif when == "call":
|
||||
runtest_hook = ihook.pytest_runtest_call
|
||||
elif when == "teardown":
|
||||
runtest_hook = ihook.pytest_runtest_teardown
|
||||
else:
|
||||
assert False, f"Unhandled runtest hook case: {when}"
|
||||
reraise: Tuple[Type[BaseException], ...] = (Exit,)
|
||||
if not item.config.getoption("usepdb", False):
|
||||
reraise += (KeyboardInterrupt,)
|
||||
call = CallInfo.from_call(
|
||||
lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
|
||||
)
|
||||
report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call)
|
||||
if log:
|
||||
hook.pytest_runtest_logreport(report=report)
|
||||
ihook.pytest_runtest_logreport(report=report)
|
||||
if check_interactive_exception(call, report):
|
||||
hook.pytest_exception_interact(node=item, call=call, report=report)
|
||||
ihook.pytest_exception_interact(node=item, call=call, report=report)
|
||||
return report
|
||||
|
||||
|
||||
|
@ -248,25 +262,6 @@ def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) ->
|
|||
return True
|
||||
|
||||
|
||||
def call_runtest_hook(
|
||||
item: Item, when: Literal["setup", "call", "teardown"], **kwds
|
||||
) -> "CallInfo[None]":
|
||||
if when == "setup":
|
||||
ihook: Callable[..., None] = item.ihook.pytest_runtest_setup
|
||||
elif when == "call":
|
||||
ihook = item.ihook.pytest_runtest_call
|
||||
elif when == "teardown":
|
||||
ihook = item.ihook.pytest_runtest_teardown
|
||||
else:
|
||||
assert False, f"Unhandled runtest hook case: {when}"
|
||||
reraise: Tuple[Type[BaseException], ...] = (Exit,)
|
||||
if not item.config.getoption("usepdb", False):
|
||||
reraise += (KeyboardInterrupt,)
|
||||
return CallInfo.from_call(
|
||||
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
|
||||
)
|
||||
|
||||
|
||||
TResult = TypeVar("TResult", covariant=True)
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ would cause circular references.
|
|||
|
||||
Also this makes the module light to import, as it should.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from functools import total_ordering
|
||||
from typing import Literal
|
||||
|
|
|
@ -2,7 +2,6 @@ from typing import Generator
|
|||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
|
@ -10,6 +9,7 @@ from _pytest.config.argparsing import Parser
|
|||
from _pytest.fixtures import FixtureDef
|
||||
from _pytest.fixtures import SubRequest
|
||||
from _pytest.scope import Scope
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
|
@ -74,7 +74,7 @@ def _show_fixture_action(
|
|||
scope_indent = list(reversed(Scope)).index(fixturedef._scope)
|
||||
tw.write(" " * 2 * scope_indent)
|
||||
tw.write(
|
||||
"{step} {scope} {fixture}".format(
|
||||
"{step} {scope} {fixture}".format( # noqa: UP032 (Readability)
|
||||
step=msg.ljust(8), # align the output to TEARDOWN
|
||||
scope=fixturedef.scope[0].upper(),
|
||||
fixture=fixturedef.argname,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.fixtures import FixtureDef
|
||||
from _pytest.fixtures import SubRequest
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Support for skip/xfail functions and markers."""
|
||||
from collections.abc import Mapping
|
||||
import dataclasses
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
from collections.abc import Mapping
|
||||
from typing import Generator
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
@ -104,9 +105,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
|
|||
):
|
||||
if not isinstance(dictionary, Mapping):
|
||||
raise ValueError(
|
||||
"pytest_markeval_namespace() needs to return a dict, got {!r}".format(
|
||||
dictionary
|
||||
)
|
||||
f"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}"
|
||||
)
|
||||
globals_.update(dictionary)
|
||||
if hasattr(item, "obj"):
|
||||
|
|
|
@ -2,12 +2,13 @@ from typing import List
|
|||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from _pytest import nodes
|
||||
from _pytest.config import Config
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.main import Session
|
||||
from _pytest.reports import TestReport
|
||||
import pytest
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.cacheprovider import Cache
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
# mypy: allow-untyped-defs
|
||||
"""Terminal reporting of the full testing process.
|
||||
|
||||
This is a good source for looking at the various reporting hooks.
|
||||
"""
|
||||
import argparse
|
||||
from collections import Counter
|
||||
import dataclasses
|
||||
import datetime
|
||||
from functools import partial
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import sys
|
||||
import textwrap
|
||||
import warnings
|
||||
from collections import Counter
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import ClassVar
|
||||
|
@ -30,16 +30,17 @@ from typing import TextIO
|
|||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import pluggy
|
||||
|
||||
import _pytest._version
|
||||
from _pytest import nodes
|
||||
from _pytest import timing
|
||||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code.code import ExceptionRepr
|
||||
from _pytest._io import TerminalWriter
|
||||
from _pytest._io.wcwidth import wcswidth
|
||||
import _pytest._version
|
||||
from _pytest.assertion.util import running_on_ci
|
||||
from _pytest.config import _PluggyPlugin
|
||||
from _pytest.config import Config
|
||||
|
@ -54,6 +55,7 @@ from _pytest.reports import BaseReport
|
|||
from _pytest.reports import CollectReport
|
||||
from _pytest.reports import TestReport
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.main import Session
|
||||
|
||||
|
@ -379,7 +381,7 @@ class TerminalReporter:
|
|||
if self.config.getoption("setupshow", False):
|
||||
return False
|
||||
cfg: str = self.config.getini("console_output_style")
|
||||
if cfg == "progress" or cfg == "progress-even-when-capture-no":
|
||||
if cfg in {"progress", "progress-even-when-capture-no"}:
|
||||
return "progress"
|
||||
elif cfg == "count":
|
||||
return "count"
|
||||
|
@ -670,8 +672,8 @@ class TerminalReporter:
|
|||
return f" [ {collected} / {collected} ]"
|
||||
else:
|
||||
if collected:
|
||||
return " [{:3d}%]".format(
|
||||
len(self._progress_nodeids_reported) * 100 // collected
|
||||
return (
|
||||
f" [{len(self._progress_nodeids_reported) * 100 // collected:3d}%]"
|
||||
)
|
||||
return " [100%]"
|
||||
|
||||
|
@ -756,9 +758,7 @@ class TerminalReporter:
|
|||
if pypy_version_info:
|
||||
verinfo = ".".join(map(str, pypy_version_info[:3]))
|
||||
msg += f"[pypy-{verinfo}-{pypy_version_info[3]}]"
|
||||
msg += ", pytest-{}, pluggy-{}".format(
|
||||
_pytest._version.version, pluggy.__version__
|
||||
)
|
||||
msg += f", pytest-{_pytest._version.version}, pluggy-{pluggy.__version__}"
|
||||
if (
|
||||
self.verbosity > 0
|
||||
or self.config.option.debug
|
||||
|
@ -1463,7 +1463,7 @@ def _plugin_nameversions(plugininfo) -> List[str]:
|
|||
values: List[str] = []
|
||||
for plugin, dist in plugininfo:
|
||||
# Gets us name and version!
|
||||
name = "{dist.project_name}-{dist.version}".format(dist=dist)
|
||||
name = f"{dist.project_name}-{dist.version}"
|
||||
# Questionable convenience, but it keeps things short.
|
||||
if name.startswith("pytest-"):
|
||||
name = name[7:]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue