Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b6157e979 |
@@ -66,7 +66,7 @@ jobs:
|
||||
- name: "windows-py37-pluggy"
|
||||
python: "3.7"
|
||||
os: windows-latest
|
||||
tox_env: "py37-pluggymain-pylib-xdist"
|
||||
tox_env: "py37-pluggymain-xdist"
|
||||
- name: "windows-py38"
|
||||
python: "3.8"
|
||||
os: windows-latest
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
- name: "ubuntu-py37-pluggy"
|
||||
python: "3.7"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py37-pluggymain-pylib-xdist"
|
||||
tox_env: "py37-pluggymain-xdist"
|
||||
- name: "ubuntu-py37-freeze"
|
||||
python: "3.7"
|
||||
os: ubuntu-latest
|
||||
@@ -114,7 +114,6 @@ jobs:
|
||||
python: "3.11-dev"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py311"
|
||||
use_coverage: true
|
||||
- name: "ubuntu-pypy3"
|
||||
python: "pypy-3.7"
|
||||
os: ubuntu-latest
|
||||
@@ -154,13 +153,13 @@ jobs:
|
||||
use_coverage: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python ${{ matrix.python }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
@@ -183,8 +182,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: "matrix.use_coverage"
|
||||
uses: codecov/codecov-action@v3
|
||||
continue-on-error: true
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
files: ./coverage.xml
|
||||
|
||||
@@ -50,7 +50,6 @@ coverage.xml
|
||||
.project
|
||||
.settings
|
||||
.vscode
|
||||
__pycache__/
|
||||
|
||||
# generated by pip
|
||||
pip-wheel-metadata/
|
||||
|
||||
+10
-14
@@ -1,8 +1,6 @@
|
||||
default_language_version:
|
||||
python: "3.10"
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
rev: 22.6.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
@@ -22,8 +20,8 @@ repos:
|
||||
- id: debug-statements
|
||||
exclude: _pytest/(debugging|hookspec).py
|
||||
language_version: python3
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v1.7.6
|
||||
- repo: https://github.com/myint/autoflake
|
||||
rev: v1.4
|
||||
hooks:
|
||||
- id: autoflake
|
||||
name: autoflake
|
||||
@@ -31,7 +29,7 @@ repos:
|
||||
language: python
|
||||
files: \.py$
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
rev: 4.0.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
@@ -39,39 +37,37 @@ repos:
|
||||
- flake8-typing-imports==1.12.0
|
||||
- flake8-docstrings==1.5.0
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v3.8.5
|
||||
rev: v3.8.1
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src', --py37-plus]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.1.0
|
||||
rev: v2.37.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py37-plus]
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.1.0
|
||||
rev: v1.20.1
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: ["--max-py-version=3.10", "--include-version-classifiers"]
|
||||
args: [--max-py-version=3.10]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.9.0
|
||||
hooks:
|
||||
- id: python-use-type-annotations
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v0.982
|
||||
rev: v0.961
|
||||
hooks:
|
||||
- id: mypy
|
||||
files: ^(src/|testing/)
|
||||
args: []
|
||||
additional_dependencies:
|
||||
- iniconfig>=1.1.0
|
||||
- py>=1.8.2
|
||||
- attrs>=19.2.0
|
||||
- packaging
|
||||
- tomli
|
||||
- types-pkg_resources
|
||||
# for mypy running on python>=3.11 since exceptiongroup is only a dependency
|
||||
# on <3.11
|
||||
- exceptiongroup>=1.0.0rc8
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rst
|
||||
|
||||
@@ -129,7 +129,6 @@ Feng Ma
|
||||
Florian Bruhin
|
||||
Florian Dahlitz
|
||||
Floris Bruynooghe
|
||||
Gabriel Landau
|
||||
Gabriel Reis
|
||||
Garvit Shubham
|
||||
Gene Wood
|
||||
@@ -155,7 +154,6 @@ Ian Bicking
|
||||
Ian Lesperance
|
||||
Ilya Konstantinov
|
||||
Ionuț Turturică
|
||||
Itxaso Aizpurua
|
||||
Iwan Briquemont
|
||||
Jaap Broekhuizen
|
||||
Jakob van Santen
|
||||
@@ -170,7 +168,6 @@ Jeff Rackauckas
|
||||
Jeff Widman
|
||||
Jenni Rinker
|
||||
John Eddie Ayson
|
||||
John Litborn
|
||||
John Towler
|
||||
Jon Parise
|
||||
Jon Sonesen
|
||||
@@ -316,7 +313,6 @@ Seth Junot
|
||||
Shantanu Jain
|
||||
Shubham Adep
|
||||
Simon Gomizelj
|
||||
Simon Holesch
|
||||
Simon Kerr
|
||||
Skylar Downes
|
||||
Srinivas Reddy Thatiparthy
|
||||
@@ -358,7 +354,6 @@ Victor Uriarte
|
||||
Vidar T. Fauske
|
||||
Virgil Dupras
|
||||
Vitaly Lashmanov
|
||||
Vivaan Verma
|
||||
Vlad Dragos
|
||||
Vlad Radziuk
|
||||
Vladyslav Rachek
|
||||
|
||||
+3
-8
@@ -223,7 +223,7 @@ changes you want to review and merge. Pull requests are stored on
|
||||
Once you send a pull request, we can discuss its potential modifications and
|
||||
even add more commits to it later on. There's an excellent tutorial on how Pull
|
||||
Requests work in the
|
||||
`GitHub Help Center <https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests>`_.
|
||||
`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_.
|
||||
|
||||
Here is a simple overview, with pytest-specific bits:
|
||||
|
||||
@@ -244,11 +244,6 @@ Here is a simple overview, with pytest-specific bits:
|
||||
be released in micro releases whereas features will be released in
|
||||
minor releases and incompatible changes in major releases.
|
||||
|
||||
You will need the tags to test locally, so be sure you have the tags from the main repository. If you suspect you don't, set the main repository as upstream and fetch the tags::
|
||||
|
||||
$ git remote add upstream https://github.com/pytest-dev/pytest
|
||||
$ git fetch upstream --tags
|
||||
|
||||
If you need some help with Git, follow this quick start
|
||||
guide: https://git.wiki.kernel.org/index.php/QuickStart
|
||||
|
||||
@@ -385,7 +380,7 @@ them.
|
||||
Backporting bug fixes for the next patch release
|
||||
------------------------------------------------
|
||||
|
||||
Pytest makes a feature release every few weeks or months. In between, patch releases
|
||||
Pytest makes feature release every few weeks or months. In between, patch releases
|
||||
are made to the previous feature release, containing bug fixes only. The bug fixes
|
||||
usually fix regressions, but may be any change that should reach users before the
|
||||
next feature release.
|
||||
@@ -394,7 +389,7 @@ Suppose for example that the latest release was 1.2.3, and you want to include
|
||||
a bug fix in 1.2.4 (check https://github.com/pytest-dev/pytest/releases for the
|
||||
actual latest release). The procedure for this is:
|
||||
|
||||
#. First, make sure the bug is fixed in the ``main`` branch, with a regular pull
|
||||
#. First, make sure the bug is fixed the ``main`` branch, with a regular pull
|
||||
request, as described above. An exception to this is if the bug fix is not
|
||||
applicable to ``main`` anymore.
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
When running with ``--pdb``, ``TestCase.tearDown`` is no longer called for tests when the *class* has been skipped via ``unittest.skip`` or ``pytest.mark.skip``.
|
||||
@@ -0,0 +1 @@
|
||||
Replace `atomicwrites <https://github.com/untitaker/python-atomicwrites>`__ dependency on windows with `os.replace`.
|
||||
@@ -0,0 +1 @@
|
||||
Doctests now respect the ``--import-mode`` flag.
|
||||
@@ -0,0 +1 @@
|
||||
A warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`.
|
||||
@@ -0,0 +1,2 @@
|
||||
Introduce multiline display for warning matching via :py:func:`pytest.warns` and
|
||||
enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`.
|
||||
@@ -0,0 +1,2 @@
|
||||
Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing
|
||||
error. We now raise immediately with a more helpful message.
|
||||
@@ -0,0 +1 @@
|
||||
Type-annotate ``FixtureRequest.param`` as ``Any`` as a stop gap measure until :issue:`8073` is fixed.
|
||||
@@ -0,0 +1,3 @@
|
||||
On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML.
|
||||
|
||||
:mod:`tomli`` is no longer a dependency on Python 3.11.
|
||||
@@ -0,0 +1 @@
|
||||
Display assertion message without escaped newline characters with ``-vv``.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a path handling code in ``rewrite.py`` that seems to work fine, but was incorrect and fails in some systems.
|
||||
@@ -0,0 +1 @@
|
||||
Improved error message that is shown when no collector is found for a given file.
|
||||
@@ -0,0 +1 @@
|
||||
Some coloring has been added to the short test summary.
|
||||
@@ -0,0 +1 @@
|
||||
Ensure ``caplog.get_records(when)`` returns current/correct data after invoking ``caplog.clear()``.
|
||||
@@ -0,0 +1 @@
|
||||
Normalize the help description of all command-line options.
|
||||
@@ -0,0 +1 @@
|
||||
Added shell-style wildcard support to ``testpaths``.
|
||||
@@ -0,0 +1 @@
|
||||
Made ``_pytest.compat`` re-export ``importlib_metadata`` in the eyes of type checkers.
|
||||
@@ -0,0 +1 @@
|
||||
Fix default encoding warning (``EncodingWarning``) in ``cacheprovider``
|
||||
@@ -0,0 +1 @@
|
||||
Fixed string representation for :func:`pytest.approx` when used to compare tuples.
|
||||
@@ -0,0 +1 @@
|
||||
Display full crash messages in ``short test summary info``, when runng in a CI environment.
|
||||
@@ -0,0 +1 @@
|
||||
Explicit note that :fixture:`tmpdir` fixture is discouraged in favour of :fixture:`tmp_path`.
|
||||
@@ -0,0 +1,4 @@
|
||||
Improve the error message when we attempt to access a fixture that has been
|
||||
torn down.
|
||||
Add an additional sentence to the docstring explaining when it's not a good
|
||||
idea to call getfixturevalue.
|
||||
@@ -0,0 +1 @@
|
||||
Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``.
|
||||
@@ -6,8 +6,6 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-7.2.0
|
||||
release-7.1.3
|
||||
release-7.1.2
|
||||
release-7.1.1
|
||||
release-7.1.0
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
pytest-7.1.3
|
||||
=======================================
|
||||
|
||||
pytest 7.1.3 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Gergely Kalmár
|
||||
* Nipunn Koorapati
|
||||
* Pax
|
||||
* Sviatoslav Sydorenko
|
||||
* Tim Hoffmann
|
||||
* Tony Narlock
|
||||
* Wolfremium
|
||||
* Zach OBrien
|
||||
* aizpurua23a
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,93 +0,0 @@
|
||||
pytest-7.2.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 7.2.0 release!
|
||||
|
||||
This release contains new features, improvements, and bug fixes,
|
||||
the full list of changes is available in the changelog:
|
||||
|
||||
https://docs.pytest.org/en/stable/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/stable/
|
||||
|
||||
As usual, you can upgrade from PyPI via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Aaron Berdy
|
||||
* Adam Turner
|
||||
* Albert Villanova del Moral
|
||||
* Alice Purcell
|
||||
* Anthony Sottile
|
||||
* Anton Yakutovich
|
||||
* Babak Keyvani
|
||||
* Brandon Chinn
|
||||
* Bruno Oliveira
|
||||
* Chanvin Xiao
|
||||
* Cheuk Ting Ho
|
||||
* Chris Wheeler
|
||||
* EmptyRabbit
|
||||
* Ezio Melotti
|
||||
* Florian Best
|
||||
* Florian Bruhin
|
||||
* Fredrik Berndtsson
|
||||
* Gabriel Landau
|
||||
* Gergely Kalmár
|
||||
* Hugo van Kemenade
|
||||
* James Gerity
|
||||
* John Litborn
|
||||
* Jon Parise
|
||||
* Kevin C
|
||||
* Kian Eliasi
|
||||
* MatthewFlamm
|
||||
* Miro Hrončok
|
||||
* Nate Meyvis
|
||||
* Neil Girdhar
|
||||
* Nhieuvu1802
|
||||
* Nipunn Koorapati
|
||||
* Ofek Lev
|
||||
* Paul Müller
|
||||
* Paul Reece
|
||||
* Pax
|
||||
* Pete Baughman
|
||||
* Peyman Salehi
|
||||
* Philipp A
|
||||
* Ran Benita
|
||||
* Robert O'Shea
|
||||
* Ronny Pfannschmidt
|
||||
* Rowin
|
||||
* Ruth Comer
|
||||
* Samuel Colvin
|
||||
* Samuel Gaist
|
||||
* Sandro Tosi
|
||||
* Shantanu
|
||||
* Simon K
|
||||
* Stephen Rosen
|
||||
* Sviatoslav Sydorenko
|
||||
* Tatiana Ovary
|
||||
* Thierry Moisan
|
||||
* Thomas Grainger
|
||||
* Tim Hoffmann
|
||||
* Tobias Diez
|
||||
* Tony Narlock
|
||||
* Vivaan Verma
|
||||
* Wolfremium
|
||||
* Zac Hatfield-Dodds
|
||||
* Zach OBrien
|
||||
* aizpurua23a
|
||||
* gresm
|
||||
* holesch
|
||||
* itxasos23
|
||||
* johnkangw
|
||||
* skhomuti
|
||||
* sommersoft
|
||||
* wodny
|
||||
* zx.qiu
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
+26
-93
@@ -33,93 +33,39 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
|
||||
Values can be any object handled by the json stdlib module.
|
||||
|
||||
capsys -- .../_pytest/capture.py:905
|
||||
capsys -- .../_pytest/capture.py:878
|
||||
Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
|
||||
The captured output is made available via ``capsys.readouterr()`` method
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``text`` objects.
|
||||
|
||||
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_output(capsys):
|
||||
print("hello")
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out == "hello\n"
|
||||
|
||||
capsysbinary -- .../_pytest/capture.py:933
|
||||
capsysbinary -- .../_pytest/capture.py:895
|
||||
Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
|
||||
The captured output is made available via ``capsysbinary.readouterr()``
|
||||
method calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``bytes`` objects.
|
||||
|
||||
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_output(capsysbinary):
|
||||
print("hello")
|
||||
captured = capsysbinary.readouterr()
|
||||
assert captured.out == b"hello\n"
|
||||
|
||||
capfd -- .../_pytest/capture.py:961
|
||||
capfd -- .../_pytest/capture.py:912
|
||||
Enable text capturing of writes to file descriptors ``1`` and ``2``.
|
||||
|
||||
The captured output is made available via ``capfd.readouterr()`` method
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``text`` objects.
|
||||
|
||||
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_system_echo(capfd):
|
||||
os.system('echo "hello"')
|
||||
captured = capfd.readouterr()
|
||||
assert captured.out == "hello\n"
|
||||
|
||||
capfdbinary -- .../_pytest/capture.py:989
|
||||
capfdbinary -- .../_pytest/capture.py:929
|
||||
Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
|
||||
|
||||
The captured output is made available via ``capfd.readouterr()`` method
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``byte`` objects.
|
||||
|
||||
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_system_echo(capfdbinary):
|
||||
os.system('echo "hello"')
|
||||
captured = capfdbinary.readouterr()
|
||||
assert captured.out == b"hello\n"
|
||||
|
||||
doctest_namespace [session scope] -- .../_pytest/doctest.py:738
|
||||
doctest_namespace [session scope] -- .../_pytest/doctest.py:731
|
||||
Fixture that returns a :py:class:`dict` that will be injected into the
|
||||
namespace of doctests.
|
||||
|
||||
Usually this fixture is used in conjunction with another ``autouse`` fixture:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def add_np(doctest_namespace):
|
||||
doctest_namespace["np"] = numpy
|
||||
|
||||
For more details: :ref:`doctest_namespace`.
|
||||
|
||||
pytestconfig [session scope] -- .../_pytest/fixtures.py:1351
|
||||
pytestconfig [session scope] -- .../_pytest/fixtures.py:1334
|
||||
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
||||
object.
|
||||
|
||||
@@ -163,10 +109,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
record_testsuite_property("ARCH", "PPC")
|
||||
record_testsuite_property("STORAGE_TYPE", "CEPH")
|
||||
|
||||
:param name:
|
||||
The property name.
|
||||
:param value:
|
||||
The property value. Will be converted to a string.
|
||||
``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -174,10 +117,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
`pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See
|
||||
:issue:`7767` for details.
|
||||
|
||||
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:302
|
||||
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:295
|
||||
Return a :class:`pytest.TempdirFactory` instance for the test session.
|
||||
|
||||
tmpdir -- .../_pytest/legacypath.py:309
|
||||
tmpdir -- .../_pytest/legacypath.py:302
|
||||
Return a temporary directory path object which is unique to each test
|
||||
function invocation, created as a sub directory of the base temporary
|
||||
directory.
|
||||
@@ -189,14 +132,9 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
|
||||
The returned object is a `legacy_path`_ object.
|
||||
|
||||
.. note::
|
||||
These days, it is preferred to use ``tmp_path``.
|
||||
|
||||
:ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`.
|
||||
|
||||
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
|
||||
|
||||
caplog -- .../_pytest/logging.py:491
|
||||
caplog -- .../_pytest/logging.py:487
|
||||
Access and control log capturing.
|
||||
|
||||
Captured logs are available through the following properties/methods::
|
||||
@@ -210,37 +148,32 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
monkeypatch -- .../_pytest/monkeypatch.py:29
|
||||
A convenient fixture for monkey-patching.
|
||||
|
||||
The fixture provides these methods to modify objects, dictionaries, or
|
||||
:data:`os.environ`:
|
||||
The fixture provides these methods to modify objects, dictionaries or
|
||||
os.environ::
|
||||
|
||||
* :meth:`monkeypatch.setattr(obj, name, value, raising=True) <pytest.MonkeyPatch.setattr>`
|
||||
* :meth:`monkeypatch.delattr(obj, name, raising=True) <pytest.MonkeyPatch.delattr>`
|
||||
* :meth:`monkeypatch.setitem(mapping, name, value) <pytest.MonkeyPatch.setitem>`
|
||||
* :meth:`monkeypatch.delitem(obj, name, raising=True) <pytest.MonkeyPatch.delitem>`
|
||||
* :meth:`monkeypatch.setenv(name, value, prepend=None) <pytest.MonkeyPatch.setenv>`
|
||||
* :meth:`monkeypatch.delenv(name, raising=True) <pytest.MonkeyPatch.delenv>`
|
||||
* :meth:`monkeypatch.syspath_prepend(path) <pytest.MonkeyPatch.syspath_prepend>`
|
||||
* :meth:`monkeypatch.chdir(path) <pytest.MonkeyPatch.chdir>`
|
||||
* :meth:`monkeypatch.context() <pytest.MonkeyPatch.context>`
|
||||
monkeypatch.setattr(obj, name, value, raising=True)
|
||||
monkeypatch.delattr(obj, name, raising=True)
|
||||
monkeypatch.setitem(mapping, name, value)
|
||||
monkeypatch.delitem(obj, name, raising=True)
|
||||
monkeypatch.setenv(name, value, prepend=None)
|
||||
monkeypatch.delenv(name, raising=True)
|
||||
monkeypatch.syspath_prepend(path)
|
||||
monkeypatch.chdir(path)
|
||||
|
||||
All modifications will be undone after the requesting test function or
|
||||
fixture has finished. The ``raising`` parameter determines if a :class:`KeyError`
|
||||
or :class:`AttributeError` will be raised if the set/deletion operation does not have the
|
||||
specified target.
|
||||
fixture has finished. The ``raising`` parameter determines if a KeyError
|
||||
or AttributeError will be raised if the set/deletion operation has no target.
|
||||
|
||||
To undo modifications done by the fixture in a contained scope,
|
||||
use :meth:`context() <pytest.MonkeyPatch.context>`.
|
||||
|
||||
recwarn -- .../_pytest/recwarn.py:30
|
||||
recwarn -- .../_pytest/recwarn.py:29
|
||||
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
|
||||
|
||||
See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information
|
||||
See https://docs.python.org/library/how-to/capture-warnings.html for information
|
||||
on warning categories.
|
||||
|
||||
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:188
|
||||
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:184
|
||||
Return a :class:`pytest.TempPathFactory` instance for the test session.
|
||||
|
||||
tmp_path -- .../_pytest/tmpdir.py:203
|
||||
tmp_path -- .../_pytest/tmpdir.py:199
|
||||
Return a temporary directory path object which is unique to each test
|
||||
function invocation, created as a sub directory of the base temporary
|
||||
directory.
|
||||
|
||||
+2
-191
@@ -28,195 +28,6 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 7.2.0 (2022-10-23)
|
||||
=========================
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
- `#10012 <https://github.com/pytest-dev/pytest/issues/10012>`_: Update :class:`pytest.PytestUnhandledCoroutineWarning` to a deprecation; it will raise an error in pytest 8.
|
||||
|
||||
|
||||
- `#10396 <https://github.com/pytest-dev/pytest/issues/10396>`_: pytest no longer depends on the ``py`` library. ``pytest`` provides a vendored copy of ``py.error`` and ``py.path`` modules but will use the ``py`` library if it is installed. If you need other ``py.*`` modules, continue to install the deprecated ``py`` library separately, otherwise it can usually be removed as a dependency.
|
||||
|
||||
|
||||
- `#4562 <https://github.com/pytest-dev/pytest/issues/4562>`_: Deprecate configuring hook specs/impls using attributes/marks.
|
||||
|
||||
Instead use :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec`.
|
||||
For more details, see the :ref:`docs <legacy-path-hooks-deprecated>`.
|
||||
|
||||
|
||||
- `#9886 <https://github.com/pytest-dev/pytest/issues/9886>`_: The functionality for running tests written for ``nose`` has been officially deprecated.
|
||||
|
||||
This includes:
|
||||
|
||||
* Plain ``setup`` and ``teardown`` functions and methods: this might catch users by surprise, as ``setup()`` and ``teardown()`` are not pytest idioms, but part of the ``nose`` support.
|
||||
* Setup/teardown using the `@with_setup <with-setup-nose>`_ decorator.
|
||||
|
||||
For more details, consult the :ref:`deprecation docs <nose-deprecation>`.
|
||||
|
||||
.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#9897 <https://github.com/pytest-dev/pytest/issues/9897>`_: Added shell-style wildcard support to ``testpaths``.
|
||||
|
||||
|
||||
|
||||
Improvements
|
||||
------------
|
||||
|
||||
- `#10218 <https://github.com/pytest-dev/pytest/issues/10218>`_: ``@pytest.mark.parametrize()`` (and similar functions) now accepts any ``Sequence[str]`` for the argument names,
|
||||
instead of just ``list[str]`` and ``tuple[str, ...]``.
|
||||
|
||||
(Note that ``str``, which is itself a ``Sequence[str]``, is still treated as a
|
||||
comma-delimited name list, as before).
|
||||
|
||||
|
||||
- `#10381 <https://github.com/pytest-dev/pytest/issues/10381>`_: The ``--no-showlocals`` flag has been added. This can be passed directly to tests to override ``--showlocals`` declared through ``addopts``.
|
||||
|
||||
|
||||
- `#3426 <https://github.com/pytest-dev/pytest/issues/3426>`_: Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expresed instead.
|
||||
|
||||
|
||||
- `#7337 <https://github.com/pytest-dev/pytest/issues/7337>`_: A warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`.
|
||||
|
||||
|
||||
- `#8508 <https://github.com/pytest-dev/pytest/issues/8508>`_: Introduce multiline display for warning matching via :py:func:`pytest.warns` and
|
||||
enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`.
|
||||
|
||||
|
||||
- `#8646 <https://github.com/pytest-dev/pytest/issues/8646>`_: Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing
|
||||
error. We now raise immediately with a more helpful message.
|
||||
|
||||
|
||||
- `#9741 <https://github.com/pytest-dev/pytest/issues/9741>`_: On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML.
|
||||
|
||||
:mod:`tomli`` is no longer a dependency on Python 3.11.
|
||||
|
||||
|
||||
- `#9742 <https://github.com/pytest-dev/pytest/issues/9742>`_: Display assertion message without escaped newline characters with ``-vv``.
|
||||
|
||||
|
||||
- `#9823 <https://github.com/pytest-dev/pytest/issues/9823>`_: Improved error message that is shown when no collector is found for a given file.
|
||||
|
||||
|
||||
- `#9873 <https://github.com/pytest-dev/pytest/issues/9873>`_: Some coloring has been added to the short test summary.
|
||||
|
||||
|
||||
- `#9883 <https://github.com/pytest-dev/pytest/issues/9883>`_: Normalize the help description of all command-line options.
|
||||
|
||||
|
||||
- `#9920 <https://github.com/pytest-dev/pytest/issues/9920>`_: Display full crash messages in ``short test summary info``, when runng in a CI environment.
|
||||
|
||||
|
||||
- `#9987 <https://github.com/pytest-dev/pytest/issues/9987>`_: Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#10150 <https://github.com/pytest-dev/pytest/issues/10150>`_: :data:`sys.stdin` now contains all expected methods of a file-like object when capture is enabled.
|
||||
|
||||
|
||||
- `#10382 <https://github.com/pytest-dev/pytest/issues/10382>`_: Do not break into pdb when ``raise unittest.SkipTest()`` appears top-level in a file.
|
||||
|
||||
|
||||
- `#7792 <https://github.com/pytest-dev/pytest/issues/7792>`_: Marks are now inherited according to the full MRO in test classes. Previously, if a test class inherited from two or more classes, only marks from the first super-class would apply.
|
||||
|
||||
When inheriting marks from super-classes, marks from the sub-classes are now ordered before marks from the super-classes, in MRO order. Previously it was the reverse.
|
||||
|
||||
When inheriting marks from super-classes, the `pytestmark` attribute of the sub-class now only contains the marks directly applied to it. Previously, it also contained marks from its super-classes. Please note that this attribute should not normally be accessed directly; use :func:`pytest.Node.iter_markers` instead.
|
||||
|
||||
|
||||
- `#9159 <https://github.com/pytest-dev/pytest/issues/9159>`_: Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``.
|
||||
|
||||
|
||||
- `#9877 <https://github.com/pytest-dev/pytest/issues/9877>`_: Ensure ``caplog.get_records(when)`` returns current/correct data after invoking ``caplog.clear()``.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#10344 <https://github.com/pytest-dev/pytest/issues/10344>`_: Update information on writing plugins to use ``pyproject.toml`` instead of ``setup.py``.
|
||||
|
||||
|
||||
- `#9248 <https://github.com/pytest-dev/pytest/issues/9248>`_: The documentation is now built using Sphinx 5.x (up from 3.x previously).
|
||||
|
||||
|
||||
- `#9291 <https://github.com/pytest-dev/pytest/issues/9291>`_: Update documentation on how :func:`pytest.warns` affects :class:`DeprecationWarning`.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#10196 <https://github.com/pytest-dev/pytest/issues/10196>`_: :class:`~pytest.PytestReturnNotNoneWarning` is now a subclass of :class:`~pytest.PytestRemovedIn8Warning`: the plan is to make returning non-``None`` from tests an error in the future.
|
||||
|
||||
|
||||
- `#10313 <https://github.com/pytest-dev/pytest/issues/10313>`_: Made ``_pytest.doctest.DoctestItem`` export ``pytest.DoctestItem`` for
|
||||
type check and runtime purposes. Made `_pytest.doctest` use internal APIs
|
||||
to avoid circular imports.
|
||||
|
||||
|
||||
- `#9906 <https://github.com/pytest-dev/pytest/issues/9906>`_: Made ``_pytest.compat`` re-export ``importlib_metadata`` in the eyes of type checkers.
|
||||
|
||||
|
||||
- `#9910 <https://github.com/pytest-dev/pytest/issues/9910>`_: Fix default encoding warning (``EncodingWarning``) in ``cacheprovider``
|
||||
|
||||
|
||||
- `#9984 <https://github.com/pytest-dev/pytest/issues/9984>`_: Improve the error message when we attempt to access a fixture that has been
|
||||
torn down.
|
||||
Add an additional sentence to the docstring explaining when it's not a good
|
||||
idea to call getfixturevalue.
|
||||
|
||||
|
||||
pytest 7.1.3 (2022-08-31)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#10060 <https://github.com/pytest-dev/pytest/issues/10060>`_: When running with ``--pdb``, ``TestCase.tearDown`` is no longer called for tests when the *class* has been skipped via ``unittest.skip`` or ``pytest.mark.skip``.
|
||||
|
||||
|
||||
- `#10190 <https://github.com/pytest-dev/pytest/issues/10190>`_: Invalid XML characters in setup or teardown error messages are now properly escaped for JUnit XML reports.
|
||||
|
||||
|
||||
- `#10230 <https://github.com/pytest-dev/pytest/issues/10230>`_: Ignore ``.py`` files created by ``pyproject.toml``-based editable builds introduced in `pip 21.3 <https://pip.pypa.io/en/stable/news/#v21-3>`__.
|
||||
|
||||
|
||||
- `#3396 <https://github.com/pytest-dev/pytest/issues/3396>`_: Doctests now respect the ``--import-mode`` flag.
|
||||
|
||||
|
||||
- `#9514 <https://github.com/pytest-dev/pytest/issues/9514>`_: Type-annotate ``FixtureRequest.param`` as ``Any`` as a stop gap measure until :issue:`8073` is fixed.
|
||||
|
||||
|
||||
- `#9791 <https://github.com/pytest-dev/pytest/issues/9791>`_: Fixed a path handling code in ``rewrite.py`` that seems to work fine, but was incorrect and fails in some systems.
|
||||
|
||||
|
||||
- `#9917 <https://github.com/pytest-dev/pytest/issues/9917>`_: Fixed string representation for :func:`pytest.approx` when used to compare tuples.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#9937 <https://github.com/pytest-dev/pytest/issues/9937>`_: Explicit note that :fixture:`tmpdir` fixture is discouraged in favour of :fixture:`tmp_path`.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#10114 <https://github.com/pytest-dev/pytest/issues/10114>`_: Replace `atomicwrites <https://github.com/untitaker/python-atomicwrites>`__ dependency on windows with `os.replace`.
|
||||
|
||||
|
||||
pytest 7.1.2 (2022-04-23)
|
||||
=========================
|
||||
|
||||
@@ -6377,7 +6188,7 @@ Bug Fixes
|
||||
Thanks :user:`adborden` for the report and :user:`nicoddemus` for the PR.
|
||||
|
||||
* Clean up unittest TestCase objects after tests are complete (:issue:`1649`).
|
||||
Thanks :user:`d-b-w` for the report and PR.
|
||||
Thanks :user:`d_b_w` for the report and PR.
|
||||
|
||||
|
||||
3.0.3 (2016-09-28)
|
||||
@@ -6392,7 +6203,7 @@ Bug Fixes
|
||||
Thanks :user:`nicoddemus` for the PR.
|
||||
|
||||
* Fix pkg_resources import error in Jython projects (:issue:`1853`).
|
||||
Thanks :user:`raquelalegre` for the PR.
|
||||
Thanks :user:`raquel-ucl` for the PR.
|
||||
|
||||
* Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception
|
||||
in Python 3 (:issue:`1944`).
|
||||
|
||||
+7
-7
@@ -38,7 +38,6 @@ release = ".".join(version.split(".")[:2])
|
||||
|
||||
autodoc_member_order = "bysource"
|
||||
autodoc_typehints = "description"
|
||||
autodoc_typehints_description_target = "documented"
|
||||
todo_include_todos = 1
|
||||
|
||||
latex_engine = "lualatex"
|
||||
@@ -163,11 +162,11 @@ linkcheck_workers = 5
|
||||
|
||||
_repo = "https://github.com/pytest-dev/pytest"
|
||||
extlinks = {
|
||||
"bpo": ("https://bugs.python.org/issue%s", "bpo-%s"),
|
||||
"pypi": ("https://pypi.org/project/%s/", "%s"),
|
||||
"issue": (f"{_repo}/issues/%s", "issue #%s"),
|
||||
"pull": (f"{_repo}/pull/%s", "pull request #%s"),
|
||||
"user": ("https://github.com/%s", "@%s"),
|
||||
"bpo": ("https://bugs.python.org/issue%s", "bpo-"),
|
||||
"pypi": ("https://pypi.org/project/%s/", ""),
|
||||
"issue": (f"{_repo}/issues/%s", "issue #"),
|
||||
"pull": (f"{_repo}/pull/%s", "pull request #"),
|
||||
"user": ("https://github.com/%s", "@"),
|
||||
}
|
||||
|
||||
|
||||
@@ -393,7 +392,6 @@ intersphinx_mapping = {
|
||||
"tox": ("https://tox.wiki/en/stable", None),
|
||||
"virtualenv": ("https://virtualenv.pypa.io/en/stable", None),
|
||||
"setuptools": ("https://setuptools.pypa.io/en/stable", None),
|
||||
"packaging": ("https://packaging.python.org/en/latest", None),
|
||||
}
|
||||
|
||||
|
||||
@@ -421,6 +419,8 @@ def configure_logging(app: "sphinx.application.Sphinx") -> None:
|
||||
|
||||
|
||||
def setup(app: "sphinx.application.Sphinx") -> None:
|
||||
# from sphinx.ext.autodoc import cut_lines
|
||||
# app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
|
||||
app.add_crossref_type(
|
||||
"fixture",
|
||||
"fixture",
|
||||
|
||||
@@ -18,113 +18,6 @@ Deprecated Features
|
||||
Below is a complete list of all pytest features which are considered deprecated. Using those features will issue
|
||||
:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
|
||||
|
||||
|
||||
.. _nose-deprecation:
|
||||
|
||||
Support for tests written for nose
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.2
|
||||
|
||||
Support for running tests written for `nose <https://nose.readthedocs.io/en/latest/>`__ is now deprecated.
|
||||
|
||||
``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills
|
||||
over the code base (see :issue:`9886` for more details).
|
||||
|
||||
setup/teardown
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
One thing that might catch users by surprise is that plain ``setup`` and ``teardown`` methods are not pytest native,
|
||||
they are in fact part of the ``nose`` support.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Test:
|
||||
def setup(self):
|
||||
self.resource = make_resource()
|
||||
|
||||
def teardown(self):
|
||||
self.resource.close()
|
||||
|
||||
def test_foo(self):
|
||||
...
|
||||
|
||||
def test_bar(self):
|
||||
...
|
||||
|
||||
|
||||
|
||||
Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`xunit-method-setup`), so the above should be changed to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Test:
|
||||
def setup_method(self):
|
||||
self.resource = make_resource()
|
||||
|
||||
def teardown_method(self):
|
||||
self.resource.close()
|
||||
|
||||
def test_foo(self):
|
||||
...
|
||||
|
||||
def test_bar(self):
|
||||
...
|
||||
|
||||
|
||||
This is easy to do in an entire code base by doing a simple find/replace.
|
||||
|
||||
@with_setup
|
||||
^^^^^^^^^^^
|
||||
|
||||
Code using `@with_setup <with-setup-nose>`_ such as this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from nose.tools import with_setup
|
||||
|
||||
|
||||
def setup_some_resource():
|
||||
...
|
||||
|
||||
|
||||
def teardown_some_resource():
|
||||
...
|
||||
|
||||
|
||||
@with_setup(setup_some_resource, teardown_some_resource)
|
||||
def test_foo():
|
||||
...
|
||||
|
||||
Will also need to be ported to a supported pytest style. One way to do it is using a fixture:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def setup_some_resource():
|
||||
...
|
||||
|
||||
|
||||
def teardown_some_resource():
|
||||
...
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def some_resource():
|
||||
setup_some_resource()
|
||||
yield
|
||||
teardown_some_resource()
|
||||
|
||||
|
||||
def test_foo(some_resource):
|
||||
...
|
||||
|
||||
|
||||
.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup
|
||||
|
||||
.. _instance-collector-deprecation:
|
||||
|
||||
The ``pytest.Instance`` collector
|
||||
@@ -185,50 +78,6 @@ no matter what argument was used in the constructor. We expect to deprecate the
|
||||
|
||||
.. _legacy-path-hooks-deprecated:
|
||||
|
||||
Configuring hook specs/impls using markers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before pluggy, pytest's plugin library, was its own package and had a clear API,
|
||||
pytest just used ``pytest.mark`` to configure hooks.
|
||||
|
||||
The :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec` decorators
|
||||
have been available since years and should be used instead.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.tryfirst
|
||||
def pytest_runtest_call():
|
||||
...
|
||||
|
||||
|
||||
# or
|
||||
def pytest_runtest_call():
|
||||
...
|
||||
|
||||
|
||||
pytest_runtest_call.tryfirst = True
|
||||
|
||||
should be changed to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_runtest_call():
|
||||
...
|
||||
|
||||
Changed ``hookimpl`` attributes:
|
||||
|
||||
* ``tryfirst``
|
||||
* ``trylast``
|
||||
* ``optionalhook``
|
||||
* ``hookwrapper``
|
||||
|
||||
Changed ``hookwrapper`` attributes:
|
||||
|
||||
* ``firstresult``
|
||||
* ``historic``
|
||||
|
||||
|
||||
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -246,9 +246,9 @@ You can ask which markers exist for your test suite - the list includes our just
|
||||
|
||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures
|
||||
|
||||
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.
|
||||
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
||||
|
||||
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead.
|
||||
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
|
||||
|
||||
|
||||
For an example on how to add and work with markers from a plugin, see
|
||||
@@ -346,7 +346,7 @@ Custom marker and command line option to control test runs
|
||||
Plugins can provide custom markers and implement specific behaviour
|
||||
based on it. This is a self-contained example which adds a command
|
||||
line option and a parametrized test function marker to run tests
|
||||
specified via named environments:
|
||||
specifies via named environments:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -438,9 +438,9 @@ The ``--markers`` option always gives you a list of available markers:
|
||||
|
||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures
|
||||
|
||||
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.
|
||||
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
||||
|
||||
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead.
|
||||
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
|
||||
|
||||
|
||||
.. _`passing callables to custom markers`:
|
||||
@@ -611,7 +611,7 @@ then you will see two tests skipped and two executed tests as expected:
|
||||
test_plat.py s.s. [100%]
|
||||
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [2] conftest.py:13: cannot run on platform linux
|
||||
SKIPPED [2] conftest.py:12: cannot run on platform linux
|
||||
======================= 2 passed, 2 skipped in 0.12s =======================
|
||||
|
||||
Note that if you specify a platform via the marker-command line option like this:
|
||||
|
||||
@@ -9,7 +9,7 @@ Working with non-python tests
|
||||
A basic example for specifying tests in Yaml files
|
||||
--------------------------------------------------------------
|
||||
|
||||
.. _`pytest-yamlwsgi`: https://pypi.org/project/pytest-yamlwsgi/
|
||||
.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py
|
||||
|
||||
Here is an example ``conftest.py`` (extracted from Ali Afshar's special purpose `pytest-yamlwsgi`_ plugin). This ``conftest.py`` will collect ``test*.yaml`` files and will execute the yaml-formatted content as custom tests:
|
||||
|
||||
|
||||
@@ -661,7 +661,8 @@ If we run this:
|
||||
|
||||
test_step.py:11: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification)
|
||||
XFAIL test_step.py::TestUserHandling::test_deletion
|
||||
reason: previous test failed (test_modification)
|
||||
================== 1 failed, 2 passed, 1 xfailed in 0.12s ==================
|
||||
|
||||
We'll see that ``test_deletion`` was not executed because ``test_modification``
|
||||
|
||||
@@ -94,7 +94,7 @@ Mark Lapierre discusses the `Pros and Cons of Quarantined Tests <https://dev.to/
|
||||
CI tools that rerun on failure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests <https://docs.microsoft.com/en-us/previous-versions/azure/devops/2017/dec-11-vsts?view=tfs-2017#identify-flaky-tests>`_ and rerun failed tests.
|
||||
Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests <https://docs.microsoft.com/en-us/azure/devops/release-notes/2017/dec-11-vsts#identify-flaky-tests>`_ and rerun failed tests.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,26 +12,41 @@ For development, we recommend you use :mod:`venv` for virtual environments and
|
||||
as well as the ``pytest`` package itself.
|
||||
This ensures your code and dependencies are isolated from your system Python installation.
|
||||
|
||||
Create a ``pyproject.toml`` file in the root of your repository as described in
|
||||
:doc:`packaging:tutorials/packaging-projects`.
|
||||
The first few lines should look like this:
|
||||
Next, place a ``pyproject.toml`` file in the root of your package:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["setuptools>=42", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "PACKAGENAME"
|
||||
and a ``setup.cfg`` file containing your package's metadata with the following minimum content:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[metadata]
|
||||
name = PACKAGENAME
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
|
||||
where ``PACKAGENAME`` is the name of your package.
|
||||
|
||||
.. note::
|
||||
|
||||
If your pip version is older than ``21.3``, you'll also need a ``setup.py`` file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
|
||||
You can then install your package in "editable" mode by running from the same directory:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install -e .
|
||||
pip install -e .
|
||||
|
||||
which lets you change your source code (both tests and application) and rerun tests at will.
|
||||
|
||||
@@ -74,11 +89,11 @@ to keep tests separate from actual application code (often a good idea):
|
||||
.. code-block:: text
|
||||
|
||||
pyproject.toml
|
||||
src/
|
||||
mypkg/
|
||||
__init__.py
|
||||
app.py
|
||||
view.py
|
||||
setup.cfg
|
||||
mypkg/
|
||||
__init__.py
|
||||
app.py
|
||||
view.py
|
||||
tests/
|
||||
test_app.py
|
||||
test_view.py
|
||||
@@ -88,57 +103,83 @@ This has the following benefits:
|
||||
|
||||
* Your tests can run against an installed version after executing ``pip install .``.
|
||||
* Your tests can run against the local copy with an editable install after executing ``pip install --editable .``.
|
||||
|
||||
For new projects, we recommend to use ``importlib`` :ref:`import mode <import-modes>`
|
||||
(see which-import-mode_ for a detailed explanation).
|
||||
To this end, add the following to your ``pyproject.toml``:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = [
|
||||
"--import-mode=importlib",
|
||||
]
|
||||
|
||||
.. _src-layout:
|
||||
|
||||
Generally, but especially if you use the default import mode ``prepend``,
|
||||
it is **strongly** suggested to use a ``src`` layout.
|
||||
Here, your application root package resides in a sub-directory of your root,
|
||||
i.e. ``src/mypkg/`` instead of ``mypkg``.
|
||||
|
||||
This layout prevents a lot of common pitfalls and has many benefits,
|
||||
which are better explained in this excellent `blog post`_ by Ionel Cristian Mărieș.
|
||||
|
||||
.. _blog post: https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>
|
||||
* If you don't use an editable install and are relying on the fact that Python by default puts the current
|
||||
directory in ``sys.path`` to import your package, you can execute ``python -m pytest`` to execute the tests against the
|
||||
local copy directly, without using ``pip``.
|
||||
|
||||
.. note::
|
||||
|
||||
If you do not use an editable install and use the ``src`` layout as above you need to extend the Python's
|
||||
search path for module files to execute the tests against the local copy directly. You can do it in an
|
||||
ad-hoc manner by setting the ``PYTHONPATH`` environment variable:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
PYTHONPATH=src pytest
|
||||
|
||||
or in a permanent manner by using the :confval:`pythonpath` configuration variable and adding the
|
||||
following to your ``pyproject.toml``:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = "src"
|
||||
|
||||
.. note::
|
||||
|
||||
If you do not use an editable install and not use the ``src`` layout (``mypkg`` directly in the root
|
||||
directory) you can rely on the fact that Python by default puts the current directory in ``sys.path`` to
|
||||
import your package and run ``python -m pytest`` to execute the tests against the local copy directly.
|
||||
|
||||
See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
|
||||
``python -m pytest``.
|
||||
|
||||
Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode <import-modes>`
|
||||
(which is the default): your test files must have **unique names**, because
|
||||
``pytest`` will import them as *top-level* modules since there are no packages
|
||||
to derive a full package name from. In other words, the test files in the example above will
|
||||
be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to
|
||||
``sys.path``.
|
||||
|
||||
If you need to have test modules with the same name, you might add ``__init__.py`` files to your
|
||||
``tests`` folder and subfolders, changing them to packages:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
pyproject.toml
|
||||
setup.cfg
|
||||
mypkg/
|
||||
...
|
||||
tests/
|
||||
__init__.py
|
||||
foo/
|
||||
__init__.py
|
||||
test_view.py
|
||||
bar/
|
||||
__init__.py
|
||||
test_view.py
|
||||
|
||||
Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing
|
||||
you to have modules with the same name. But now this introduces a subtle problem: in order to load
|
||||
the test modules from the ``tests`` directory, pytest prepends the root of the repository to
|
||||
``sys.path``, which adds the side-effect that now ``mypkg`` is also importable.
|
||||
|
||||
This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment,
|
||||
because you want to test the *installed* version of your package, not the local code from the repository.
|
||||
|
||||
.. _`src-layout`:
|
||||
|
||||
In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
|
||||
sub-directory of your root:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
pyproject.toml
|
||||
setup.cfg
|
||||
src/
|
||||
mypkg/
|
||||
__init__.py
|
||||
app.py
|
||||
view.py
|
||||
tests/
|
||||
__init__.py
|
||||
foo/
|
||||
__init__.py
|
||||
test_view.py
|
||||
bar/
|
||||
__init__.py
|
||||
test_view.py
|
||||
|
||||
|
||||
This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent
|
||||
`blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
|
||||
|
||||
.. note::
|
||||
The ``--import-mode=importlib`` option (see :ref:`import-modes`) does not have
|
||||
any of the drawbacks above because ``sys.path`` is not changed when importing
|
||||
test modules, so users that run into this issue are strongly encouraged to try it.
|
||||
|
||||
The ``src`` directory layout is still strongly recommended however.
|
||||
|
||||
|
||||
Tests as part of application code
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -149,7 +190,8 @@ want to distribute them along with your application:
|
||||
.. code-block:: text
|
||||
|
||||
pyproject.toml
|
||||
[src/]mypkg/
|
||||
setup.cfg
|
||||
mypkg/
|
||||
__init__.py
|
||||
app.py
|
||||
view.py
|
||||
@@ -211,56 +253,6 @@ Note that this layout also works in conjunction with the ``src`` layout mentione
|
||||
much less surprising.
|
||||
|
||||
|
||||
.. _which-import-mode:
|
||||
|
||||
Choosing an import mode
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For historical reasons, pytest defaults to the ``prepend`` :ref:`import mode <import-modes>`
|
||||
instead of the ``importlib`` import mode we recommend for new projects.
|
||||
The reason lies in the way the ``prepend`` mode works:
|
||||
|
||||
Since there are no packages to derive a full package name from,
|
||||
``pytest`` will import your test files as *top-level* modules.
|
||||
The test files in the first example (:ref:`src layout <src-layout>`) would be imported as
|
||||
``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to ``sys.path``.
|
||||
|
||||
This results in a drawback compared to the import mode ``importlib``:
|
||||
your test files must have **unique names**.
|
||||
|
||||
If you need to have test modules with the same name,
|
||||
as a workaround you might add ``__init__.py`` files to your ``tests`` folder and subfolders,
|
||||
changing them to packages:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
pyproject.toml
|
||||
mypkg/
|
||||
...
|
||||
tests/
|
||||
__init__.py
|
||||
foo/
|
||||
__init__.py
|
||||
test_view.py
|
||||
bar/
|
||||
__init__.py
|
||||
test_view.py
|
||||
|
||||
Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``,
|
||||
allowing you to have modules with the same name.
|
||||
But now this introduces a subtle problem:
|
||||
in order to load the test modules from the ``tests`` directory,
|
||||
pytest prepends the root of the repository to ``sys.path``,
|
||||
which adds the side-effect that now ``mypkg`` is also importable.
|
||||
|
||||
This is problematic if you are using a tool like tox_ to test your package in a virtual environment,
|
||||
because you want to test the *installed* version of your package,
|
||||
not the local code from the repository.
|
||||
|
||||
The ``importlib`` import mode does not have any of the drawbacks above,
|
||||
because ``sys.path`` is not changed when importing test modules.
|
||||
|
||||
|
||||
.. _`buildout`: http://www.buildout.org/en/latest/
|
||||
|
||||
.. _`use tox`:
|
||||
|
||||
@@ -22,7 +22,7 @@ Install ``pytest``
|
||||
.. code-block:: bash
|
||||
|
||||
$ pytest --version
|
||||
pytest 7.2.0
|
||||
pytest 7.1.2
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ If you run this command for the first time, you can see the print statement:
|
||||
> assert mydata == 23
|
||||
E assert 42 == 23
|
||||
|
||||
test_caching.py:19: AssertionError
|
||||
test_caching.py:20: AssertionError
|
||||
-------------------------- Captured stdout setup ---------------------------
|
||||
running expensive computation...
|
||||
========================= short test summary info ==========================
|
||||
@@ -256,7 +256,7 @@ the cache and nothing will be printed:
|
||||
> assert mydata == 23
|
||||
E assert 42 == 23
|
||||
|
||||
test_caching.py:19: AssertionError
|
||||
test_caching.py:20: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
FAILED test_caching.py::test_function - assert 42 == 23
|
||||
1 failed in 0.12s
|
||||
|
||||
@@ -42,8 +42,6 @@ Running pytest now produces this output:
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
======================= 1 passed, 1 warning in 0.12s =======================
|
||||
|
||||
.. _`controlling-warnings`:
|
||||
|
||||
Controlling warnings
|
||||
--------------------
|
||||
|
||||
@@ -178,14 +176,11 @@ using an external system.
|
||||
DeprecationWarning and PendingDeprecationWarning
|
||||
------------------------------------------------
|
||||
|
||||
|
||||
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings from
|
||||
user code and third-party libraries, as recommended by :pep:`565`.
|
||||
This helps users keep their code modern and avoid breakages when deprecated warnings are effectively removed.
|
||||
|
||||
However, in the specific case where users capture any type of warnings in their test, either with
|
||||
:func:`pytest.warns`, :func:`pytest.deprecated_call` or using the :ref:`recwarn <recwarn>` fixture,
|
||||
no warning will be displayed at all.
|
||||
|
||||
Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over
|
||||
(such as third-party libraries), in which case you might use the warning filters options (ini or marks) to ignore
|
||||
those warnings.
|
||||
@@ -202,9 +197,6 @@ For example:
|
||||
This will ignore all warnings of type ``DeprecationWarning`` where the start of the message matches
|
||||
the regular expression ``".*U.*mode is deprecated"``.
|
||||
|
||||
See :ref:`@pytest.mark.filterwarnings <filterwarnings>` and
|
||||
:ref:`Controlling warnings <controlling-warnings>` for more examples.
|
||||
|
||||
.. note::
|
||||
|
||||
If warnings are configured at the interpreter level, using
|
||||
@@ -253,10 +245,10 @@ when called with a ``17`` argument.
|
||||
Asserting warnings with the warns function
|
||||
------------------------------------------
|
||||
|
||||
|
||||
|
||||
You can check that code raises a particular warning using :func:`pytest.warns`,
|
||||
which works in a similar manner to :ref:`raises <assertraises>` (except that
|
||||
:ref:`raises <assertraises>` does not capture all exceptions, only the
|
||||
``expected_exception``):
|
||||
which works in a similar manner to :ref:`raises <assertraises>`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -269,8 +261,8 @@ which works in a similar manner to :ref:`raises <assertraises>` (except that
|
||||
with pytest.warns(UserWarning):
|
||||
warnings.warn("my warning", UserWarning)
|
||||
|
||||
The test will fail if the warning in question is not raised. Use the keyword
|
||||
argument ``match`` to assert that the warning matches a text or regex::
|
||||
The test will fail if the warning in question is not raised. The keyword
|
||||
argument ``match`` to assert that the exception matches a text or regex::
|
||||
|
||||
>>> with warns(UserWarning, match='must be 0 or None'):
|
||||
... warnings.warn("value must be 0 or None", UserWarning)
|
||||
@@ -367,32 +359,20 @@ Additional use cases of warnings in tests
|
||||
|
||||
Here are some use cases involving warnings that often come up in tests, and suggestions on how to deal with them:
|
||||
|
||||
- To ensure that **at least one** of the indicated warnings is issued, use:
|
||||
- To ensure that **at least one** warning is emitted, use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_warning():
|
||||
with pytest.warns((RuntimeWarning, UserWarning)):
|
||||
...
|
||||
|
||||
- To ensure that **only** certain warnings are issued, use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_warning(recwarn):
|
||||
with pytest.warns():
|
||||
...
|
||||
assert len(recwarn) == 1
|
||||
user_warning = recwarn.pop(UserWarning)
|
||||
assert issubclass(user_warning.category, UserWarning)
|
||||
|
||||
- To ensure that **no** warnings are emitted, use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_warning():
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
...
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
...
|
||||
|
||||
- To suppress warnings, use:
|
||||
|
||||
|
||||
+14
-87
@@ -738,87 +738,6 @@ does offer some nuances for when you're in a pinch.
|
||||
. [100%]
|
||||
1 passed in 0.12s
|
||||
|
||||
Note on finalizer order
|
||||
""""""""""""""""""""""""
|
||||
|
||||
Finalizers are executed in a first-in-last-out order.
|
||||
For yield fixtures, the first teardown code to run is from the right-most fixture, i.e. the last test parameter.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_finalizers.py
|
||||
import pytest
|
||||
|
||||
|
||||
def test_bar(fix_w_yield1, fix_w_yield2):
|
||||
print("test_bar")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fix_w_yield1():
|
||||
yield
|
||||
print("after_yield_1")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fix_w_yield2():
|
||||
yield
|
||||
print("after_yield_2")
|
||||
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -s test_finalizers.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
test_finalizers.py test_bar
|
||||
.after_yield_2
|
||||
after_yield_1
|
||||
|
||||
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
For finalizers, the first fixture to run is last call to `request.addfinalizer`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_finalizers.py
|
||||
from functools import partial
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fix_w_finalizers(request):
|
||||
request.addfinalizer(partial(print, "finalizer_2"))
|
||||
request.addfinalizer(partial(print, "finalizer_1"))
|
||||
|
||||
|
||||
def test_bar(fix_w_finalizers):
|
||||
print("test_bar")
|
||||
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest -s test_finalizers.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
test_finalizers.py test_bar
|
||||
.finalizer_1
|
||||
finalizer_2
|
||||
|
||||
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
This is so because yield fixtures use `addfinalizer` behind the scenes: when the fixture executes, `addfinalizer` registers a function that resumes the generator, which in turn calls the teardown code.
|
||||
|
||||
|
||||
.. _`safe teardowns`:
|
||||
|
||||
Safe teardowns
|
||||
@@ -1417,15 +1336,13 @@ Running the above tests results in the following test IDs being used:
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 12 items
|
||||
collected 11 items
|
||||
|
||||
<Module test_anothersmtp.py>
|
||||
<Function test_showhelo[smtp.gmail.com]>
|
||||
<Function test_showhelo[mail.python.org]>
|
||||
<Module test_emaillib.py>
|
||||
<Function test_email_received>
|
||||
<Module test_finalizers.py>
|
||||
<Function test_bar>
|
||||
<Module test_ids.py>
|
||||
<Function test_a[spam]>
|
||||
<Function test_a[ham]>
|
||||
@@ -1437,7 +1354,7 @@ Running the above tests results in the following test IDs being used:
|
||||
<Function test_ehlo[mail.python.org]>
|
||||
<Function test_noop[mail.python.org]>
|
||||
|
||||
======================= 12 tests collected in 0.12s ========================
|
||||
======================= 11 tests collected in 0.12s ========================
|
||||
|
||||
.. _`fixture-parametrize-marks`:
|
||||
|
||||
@@ -1772,6 +1689,8 @@ Given the tests file structure is:
|
||||
::
|
||||
|
||||
tests/
|
||||
__init__.py
|
||||
|
||||
conftest.py
|
||||
# content of tests/conftest.py
|
||||
import pytest
|
||||
@@ -1786,6 +1705,8 @@ Given the tests file structure is:
|
||||
assert username == 'username'
|
||||
|
||||
subfolder/
|
||||
__init__.py
|
||||
|
||||
conftest.py
|
||||
# content of tests/subfolder/conftest.py
|
||||
import pytest
|
||||
@@ -1794,8 +1715,8 @@ Given the tests file structure is:
|
||||
def username(username):
|
||||
return 'overridden-' + username
|
||||
|
||||
test_something_else.py
|
||||
# content of tests/subfolder/test_something_else.py
|
||||
test_something.py
|
||||
# content of tests/subfolder/test_something.py
|
||||
def test_username(username):
|
||||
assert username == 'overridden-username'
|
||||
|
||||
@@ -1811,6 +1732,8 @@ Given the tests file structure is:
|
||||
::
|
||||
|
||||
tests/
|
||||
__init__.py
|
||||
|
||||
conftest.py
|
||||
# content of tests/conftest.py
|
||||
import pytest
|
||||
@@ -1852,6 +1775,8 @@ Given the tests file structure is:
|
||||
::
|
||||
|
||||
tests/
|
||||
__init__.py
|
||||
|
||||
conftest.py
|
||||
# content of tests/conftest.py
|
||||
import pytest
|
||||
@@ -1888,6 +1813,8 @@ Given the tests file structure is:
|
||||
::
|
||||
|
||||
tests/
|
||||
__init__.py
|
||||
|
||||
conftest.py
|
||||
# content of tests/conftest.py
|
||||
import pytest
|
||||
|
||||
@@ -14,16 +14,18 @@ environment variable, or to modify ``sys.path`` for importing.
|
||||
The ``monkeypatch`` fixture provides these helper methods for safely patching and mocking
|
||||
functionality in tests:
|
||||
|
||||
* :meth:`monkeypatch.setattr(obj, name, value, raising=True) <pytest.MonkeyPatch.setattr>`
|
||||
* :meth:`monkeypatch.delattr(obj, name, raising=True) <pytest.MonkeyPatch.delattr>`
|
||||
* :meth:`monkeypatch.setitem(mapping, name, value) <pytest.MonkeyPatch.setitem>`
|
||||
* :meth:`monkeypatch.delitem(obj, name, raising=True) <pytest.MonkeyPatch.delitem>`
|
||||
* :meth:`monkeypatch.setenv(name, value, prepend=None) <pytest.MonkeyPatch.setenv>`
|
||||
* :meth:`monkeypatch.delenv(name, raising=True) <pytest.MonkeyPatch.delenv>`
|
||||
* :meth:`monkeypatch.syspath_prepend(path) <pytest.MonkeyPatch.syspath_prepend>`
|
||||
* :meth:`monkeypatch.chdir(path) <pytest.MonkeyPatch.chdir>`
|
||||
* :meth:`monkeypatch.context() <pytest.MonkeyPatch.context>`
|
||||
.. code-block:: python
|
||||
|
||||
monkeypatch.setattr(obj, name, value, raising=True)
|
||||
monkeypatch.setattr("somemodule.obj.name", value, raising=True)
|
||||
monkeypatch.delattr(obj, name, raising=True)
|
||||
monkeypatch.setitem(mapping, name, value)
|
||||
monkeypatch.delitem(obj, name, raising=True)
|
||||
monkeypatch.setenv(name, value, prepend=None)
|
||||
monkeypatch.delenv(name, raising=True)
|
||||
monkeypatch.syspath_prepend(path)
|
||||
monkeypatch.chdir(path)
|
||||
monkeypatch.context()
|
||||
|
||||
All modifications will be undone after the requesting
|
||||
test function or fixture has finished. The ``raising``
|
||||
@@ -62,8 +64,8 @@ and a discussion of its motivation.
|
||||
|
||||
.. _`monkeypatch blog post`: https://tetamap.wordpress.com//2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||
|
||||
Monkeypatching functions
|
||||
------------------------
|
||||
Simple example: monkeypatching functions
|
||||
----------------------------------------
|
||||
|
||||
Consider a scenario where you are working with user directories. In the context of
|
||||
testing, you do not want your test to depend on the running user. ``monkeypatch``
|
||||
|
||||
@@ -5,9 +5,6 @@ How to run tests written for nose
|
||||
|
||||
``pytest`` has basic support for running tests written for nose_.
|
||||
|
||||
.. warning::
|
||||
This functionality has been deprecated and is likely to be removed in ``pytest 8.x``.
|
||||
|
||||
.. _nosestyle:
|
||||
|
||||
Usage
|
||||
@@ -26,8 +23,8 @@ make use of pytest's capabilities.
|
||||
Supported nose Idioms
|
||||
----------------------
|
||||
|
||||
* ``setup()`` and ``teardown()`` at module/class/method level: any function or method called ``setup`` will be called during the setup phase for each test, same for ``teardown``.
|
||||
* ``SkipTest`` exceptions and markers
|
||||
* setup and teardown at module/class/method level
|
||||
* SkipTest exceptions and markers
|
||||
* setup/teardown decorators
|
||||
* ``__test__`` attribute on modules/classes/functions
|
||||
* general usage of nose utilities
|
||||
|
||||
@@ -12,9 +12,8 @@ Examples for modifying traceback printing:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --showlocals # show local variables in tracebacks
|
||||
pytest -l # show local variables (shortcut)
|
||||
pytest --no-showlocals # hide local variables (if addopts enables them)
|
||||
pytest --showlocals # show local variables in tracebacks
|
||||
pytest -l # show local variables (shortcut)
|
||||
|
||||
pytest --tb=auto # (default) 'long' tracebacks for the first and last
|
||||
# entry, but 'short' style for the other entries
|
||||
@@ -349,7 +348,8 @@ Example:
|
||||
test_example.py:14: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] test_example.py:22: skipping this test
|
||||
XFAIL test_example.py::test_xfail - reason: xfailing this test
|
||||
XFAIL test_example.py::test_xfail
|
||||
reason: xfailing this test
|
||||
XPASS test_example.py::test_xpass always xfail
|
||||
ERROR test_example.py::test_error - assert 0
|
||||
FAILED test_example.py::test_fail - assert 0
|
||||
|
||||
@@ -51,9 +51,6 @@ Here is a little annotated list for some popular plugins:
|
||||
* :pypi:`pytest-flakes`:
|
||||
check source code with pyflakes.
|
||||
|
||||
* :pypi:`allure-pytest`:
|
||||
report test results via `allure-framework <https://github.com/allure-framework/>`_.
|
||||
|
||||
To see a complete list of all plugins with their latest testing
|
||||
status against different pytest and Python versions, please visit
|
||||
:ref:`plugin-list`.
|
||||
|
||||
@@ -109,17 +109,6 @@ rather than standard :class:`pathlib.Path` objects.
|
||||
.. note::
|
||||
These days, it is preferred to use ``tmp_path`` and ``tmp_path_factory``.
|
||||
|
||||
In order to help modernize old code bases, one can run pytest with the legacypath
|
||||
plugin disabled:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -p no:legacypath
|
||||
|
||||
This will trigger errors on tests using the legacy paths.
|
||||
It can also be permanently set as part of the :confval:`addopts` parameter in the
|
||||
config file.
|
||||
|
||||
See :fixture:`tmpdir <tmpdir>` :fixture:`tmpdir_factory <tmpdir_factory>`
|
||||
API for details.
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ the ``self.db`` values in the traceback:
|
||||
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
|
||||
E assert 0
|
||||
|
||||
test_unittest_db.py:11: AssertionError
|
||||
test_unittest_db.py:10: AssertionError
|
||||
___________________________ MyTest.test_method2 ____________________________
|
||||
|
||||
self = <test_unittest_db.MyTest testMethod=test_method2>
|
||||
@@ -167,7 +167,7 @@ the ``self.db`` values in the traceback:
|
||||
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
|
||||
E assert 0
|
||||
|
||||
test_unittest_db.py:14: AssertionError
|
||||
test_unittest_db.py:13: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
|
||||
FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
|
||||
|
||||
@@ -147,33 +147,27 @@ Making your plugin installable by others
|
||||
|
||||
If you want to make your plugin externally available, you
|
||||
may define a so-called entry point for your distribution so
|
||||
that ``pytest`` finds your plugin module. Entry points are
|
||||
a feature that is provided by :std:doc:`setuptools <setuptools:index>`.
|
||||
that ``pytest`` finds your plugin module. Entry points are
|
||||
a feature that is provided by :std:doc:`setuptools:index`. pytest looks up
|
||||
the ``pytest11`` entrypoint to discover its
|
||||
plugins and you can thus make your plugin available by defining
|
||||
it in your setuptools-invocation:
|
||||
|
||||
pytest looks up the ``pytest11`` entrypoint to discover its
|
||||
plugins, thus you can make your plugin available by defining
|
||||
it in your ``pyproject.toml`` file.
|
||||
.. sourcecode:: python
|
||||
|
||||
.. sourcecode:: toml
|
||||
# sample ./setup.py file
|
||||
from setuptools import setup
|
||||
|
||||
# sample ./pyproject.toml file
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "myproject"
|
||||
classifiers = [
|
||||
"Framework :: Pytest",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["myproject"]
|
||||
|
||||
[project.entry_points]
|
||||
pytest11 = [
|
||||
"myproject = myproject.pluginmodule",
|
||||
]
|
||||
name_of_plugin = "myproject" # register plugin with this name
|
||||
setup(
|
||||
name="myproject",
|
||||
packages=["myproject"],
|
||||
# the following makes a plugin available to pytest
|
||||
entry_points={"pytest11": [f"{name_of_plugin} = myproject.pluginmodule"]},
|
||||
# custom PyPI classifier for pytest plugins
|
||||
classifiers=["Framework :: Pytest"],
|
||||
)
|
||||
|
||||
If a package is installed this way, ``pytest`` will load
|
||||
``myproject.pluginmodule`` as a plugin which can define
|
||||
|
||||
@@ -63,8 +63,6 @@ and after all test methods of the class are called:
|
||||
setup_class.
|
||||
"""
|
||||
|
||||
.. _xunit-method-setup:
|
||||
|
||||
Method and function level setup/teardown
|
||||
-----------------------------------------------
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
.. sidebar:: Next Open Trainings
|
||||
|
||||
- Professionelles Testen für Python mit pytest, part of `enterPy <https://www.enterpy.de/>`__ (German), `October 28th <https://www.enterpy.de/veranstaltung-15409-se-0-professionelles-testen-fuer-python-mit-pytest.html>`__ (sold out) and `November 4th <https://www.enterpy.de/veranstaltung-15557-se-0-professionelles-testen-fuer-python-mit-pytest-zusatztermin.html>`__, online
|
||||
- `CH Open Workshop-Tage <https://workshoptage.ch/workshops/2022/pytest-professionelles-testen-nicht-nur-fuer-python/>`__ (German), September 8th 2022, Bern, Switzerland
|
||||
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 7th to 9th 2023 (3 day in-depth training), Remote and Leipzig, Germany
|
||||
|
||||
Also see :doc:`previous talks and blogposts <talks>`.
|
||||
|
||||
@@ -90,7 +90,7 @@ and can also be used to hold pytest configuration if they have a ``[pytest]`` se
|
||||
setup.cfg
|
||||
~~~~~~~~~
|
||||
|
||||
``setup.cfg`` files are general purpose configuration files, used originally by :doc:`distutils <python:distutils/configfile>`, and can also be used to hold pytest configuration
|
||||
``setup.cfg`` files are general purpose configuration files, used originally by :doc:`distutils <distutils/configfile>`, and can also be used to hold pytest configuration
|
||||
if they have a ``[tool:pytest]`` section.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
+187
-483
File diff suppressed because it is too large
Load Diff
+148
-151
@@ -102,7 +102,7 @@ pytest.deprecated_call
|
||||
|
||||
**Tutorial**: :ref:`ensuring_function_triggers`
|
||||
|
||||
.. autofunction:: pytest.deprecated_call([match])
|
||||
.. autofunction:: pytest.deprecated_call()
|
||||
:with:
|
||||
|
||||
pytest.register_assert_rewrite
|
||||
@@ -290,7 +290,7 @@ Example for using multiple custom markers:
|
||||
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(...)``.
|
||||
When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers <_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(...)``.
|
||||
|
||||
.. _`fixtures-api`:
|
||||
|
||||
@@ -333,68 +333,6 @@ For more details, consult the full :ref:`fixtures docs <fixture>`.
|
||||
:decorator:
|
||||
|
||||
|
||||
.. fixture:: capfd
|
||||
|
||||
capfd
|
||||
~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capfd()
|
||||
:no-auto-options:
|
||||
|
||||
|
||||
.. fixture:: capfdbinary
|
||||
|
||||
capfdbinary
|
||||
~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capfdbinary()
|
||||
:no-auto-options:
|
||||
|
||||
|
||||
.. fixture:: caplog
|
||||
|
||||
caplog
|
||||
~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`logging`
|
||||
|
||||
.. autofunction:: _pytest.logging.caplog()
|
||||
:no-auto-options:
|
||||
|
||||
Returns a :class:`pytest.LogCaptureFixture` instance.
|
||||
|
||||
.. autoclass:: pytest.LogCaptureFixture()
|
||||
:members:
|
||||
|
||||
|
||||
.. fixture:: capsys
|
||||
|
||||
capsys
|
||||
~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capsys()
|
||||
:no-auto-options:
|
||||
|
||||
.. autoclass:: pytest.CaptureFixture()
|
||||
:members:
|
||||
|
||||
.. fixture:: capsysbinary
|
||||
|
||||
capsysbinary
|
||||
~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capsysbinary()
|
||||
:no-auto-options:
|
||||
|
||||
|
||||
.. fixture:: cache
|
||||
|
||||
config.cache
|
||||
@@ -415,6 +353,51 @@ Under the hood, the cache plugin uses the simple
|
||||
:members:
|
||||
|
||||
|
||||
.. fixture:: capsys
|
||||
|
||||
capsys
|
||||
~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capsys()
|
||||
:no-auto-options:
|
||||
|
||||
.. autoclass:: pytest.CaptureFixture()
|
||||
:members:
|
||||
|
||||
|
||||
.. fixture:: capsysbinary
|
||||
|
||||
capsysbinary
|
||||
~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capsysbinary()
|
||||
:no-auto-options:
|
||||
|
||||
.. fixture:: capfd
|
||||
|
||||
capfd
|
||||
~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capfd()
|
||||
:no-auto-options:
|
||||
|
||||
.. fixture:: capfdbinary
|
||||
|
||||
capfdbinary
|
||||
~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capfdbinary()
|
||||
:no-auto-options:
|
||||
|
||||
|
||||
.. fixture:: doctest_namespace
|
||||
|
||||
doctest_namespace
|
||||
@@ -425,6 +408,63 @@ doctest_namespace
|
||||
.. autofunction:: _pytest.doctest.doctest_namespace()
|
||||
|
||||
|
||||
.. fixture:: request
|
||||
|
||||
request
|
||||
~~~~~~~
|
||||
|
||||
**Example**: :ref:`request example`
|
||||
|
||||
The ``request`` fixture is a special fixture providing information of the requesting test function.
|
||||
|
||||
.. autoclass:: pytest.FixtureRequest()
|
||||
:members:
|
||||
|
||||
|
||||
.. fixture:: pytestconfig
|
||||
|
||||
pytestconfig
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. autofunction:: _pytest.fixtures.pytestconfig()
|
||||
|
||||
|
||||
.. fixture:: record_property
|
||||
|
||||
record_property
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`record_property example`
|
||||
|
||||
.. autofunction:: _pytest.junitxml.record_property()
|
||||
|
||||
|
||||
.. fixture:: record_testsuite_property
|
||||
|
||||
record_testsuite_property
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`record_testsuite_property example`
|
||||
|
||||
.. autofunction:: _pytest.junitxml.record_testsuite_property()
|
||||
|
||||
|
||||
.. fixture:: caplog
|
||||
|
||||
caplog
|
||||
~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`logging`
|
||||
|
||||
.. autofunction:: _pytest.logging.caplog()
|
||||
:no-auto-options:
|
||||
|
||||
Returns a :class:`pytest.LogCaptureFixture` instance.
|
||||
|
||||
.. autoclass:: pytest.LogCaptureFixture()
|
||||
:members:
|
||||
|
||||
|
||||
.. fixture:: monkeypatch
|
||||
|
||||
monkeypatch
|
||||
@@ -441,14 +481,6 @@ monkeypatch
|
||||
:members:
|
||||
|
||||
|
||||
.. fixture:: pytestconfig
|
||||
|
||||
pytestconfig
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. autofunction:: _pytest.fixtures.pytestconfig()
|
||||
|
||||
|
||||
.. fixture:: pytester
|
||||
|
||||
pytester
|
||||
@@ -485,25 +517,18 @@ To use it, include in your topmost ``conftest.py`` file:
|
||||
.. autoclass:: pytest.RecordedHookCall()
|
||||
:members:
|
||||
|
||||
.. fixture:: testdir
|
||||
|
||||
.. fixture:: record_property
|
||||
testdir
|
||||
~~~~~~~
|
||||
|
||||
record_property
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
Identical to :fixture:`pytester`, but provides an instance whose methods return
|
||||
legacy ``py.path.local`` objects instead when applicable.
|
||||
|
||||
**Tutorial**: :ref:`record_property example`
|
||||
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
|
||||
|
||||
.. autofunction:: _pytest.junitxml.record_property()
|
||||
|
||||
|
||||
.. fixture:: record_testsuite_property
|
||||
|
||||
record_testsuite_property
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`record_testsuite_property example`
|
||||
|
||||
.. autofunction:: _pytest.junitxml.record_testsuite_property()
|
||||
.. autoclass:: pytest.Testdir()
|
||||
:members:
|
||||
|
||||
|
||||
.. fixture:: recwarn
|
||||
@@ -520,34 +545,6 @@ recwarn
|
||||
:members:
|
||||
|
||||
|
||||
.. fixture:: request
|
||||
|
||||
request
|
||||
~~~~~~~
|
||||
|
||||
**Example**: :ref:`request example`
|
||||
|
||||
The ``request`` fixture is a special fixture providing information of the requesting test function.
|
||||
|
||||
.. autoclass:: pytest.FixtureRequest()
|
||||
:members:
|
||||
|
||||
|
||||
.. fixture:: testdir
|
||||
|
||||
testdir
|
||||
~~~~~~~
|
||||
|
||||
Identical to :fixture:`pytester`, but provides an instance whose methods return
|
||||
legacy ``py.path.local`` objects instead when applicable.
|
||||
|
||||
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
|
||||
|
||||
.. autoclass:: pytest.Testdir()
|
||||
:members:
|
||||
:noindex: TimeoutExpired
|
||||
|
||||
|
||||
.. fixture:: tmp_path
|
||||
|
||||
tmp_path
|
||||
@@ -1136,9 +1133,6 @@ Custom warnings generated in some situations such as improper usage or deprecate
|
||||
.. autoclass:: pytest.PytestReturnNotNoneWarning
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: pytest.PytestRemovedIn8Warning
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: pytest.PytestUnhandledCoroutineWarning
|
||||
:show-inheritance:
|
||||
|
||||
@@ -1759,12 +1753,12 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
$ pytest --help
|
||||
usage: pytest [options] [file_or_dir] [file_or_dir] [...]
|
||||
|
||||
positional arguments:
|
||||
Positional arguments:
|
||||
file_or_dir
|
||||
|
||||
general:
|
||||
General:
|
||||
-k EXPRESSION Only run tests which match the given substring
|
||||
expression. An expression is a Python evaluatable
|
||||
expression. An expression is a python evaluatable
|
||||
expression where all names are substring-matched
|
||||
against test names and their parent classes.
|
||||
Example: -k 'test_method or test_other' matches all
|
||||
@@ -1778,9 +1772,9 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
'extra_keyword_matches' set, as well as functions
|
||||
which have names assigned directly to them. The
|
||||
matching is case-insensitive.
|
||||
-m MARKEXPR Only run tests matching given mark expression. For
|
||||
example: -m 'mark1 and not mark2'.
|
||||
--markers show markers (builtin, plugin and per-project ones).
|
||||
-m MARKEXPR Only run tests matching given mark expression.
|
||||
For example: -m 'mark1 and not mark2'.
|
||||
--markers Show markers (builtin, plugin and per-project ones)
|
||||
-x, --exitfirst Exit instantly on first error or failed test
|
||||
--fixtures, --funcargs
|
||||
Show available fixtures, sorted by plugin appearance
|
||||
@@ -1790,18 +1784,18 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
KeyboardInterrupt
|
||||
--pdbcls=modulename:classname
|
||||
Specify a custom interactive Python debugger for use
|
||||
with --pdb.For example:
|
||||
with --pdb. For example:
|
||||
--pdbcls=IPython.terminal.debugger:TerminalPdb
|
||||
--trace Immediately break when running each test
|
||||
--capture=method Per-test capturing method: one of fd|sys|no|tee-sys
|
||||
-s Shortcut for --capture=no
|
||||
--capture=method Per-test capturing method: one of fd|sys|no|tee-sys.
|
||||
-s Shortcut for --capture=no.
|
||||
--runxfail Report the results of xfail tests as if they were
|
||||
not marked
|
||||
--lf, --last-failed Rerun only the tests that failed at the last run (or
|
||||
all if none failed)
|
||||
--ff, --failed-first Run all tests, but run the last failures first. This
|
||||
may re-order tests and thus lead to repeated fixture
|
||||
setup/teardown.
|
||||
--ff, --failed-first Run all tests, but run the last failures first
|
||||
This may re-order tests and thus lead to repeated
|
||||
fixture setup/teardown
|
||||
--nf, --new-first Run tests from new files first, then the rest of the
|
||||
tests sorted by file mtime
|
||||
--cache-show=[CACHESHOW]
|
||||
@@ -1815,10 +1809,11 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
test next time
|
||||
--sw-skip, --stepwise-skip
|
||||
Ignore the first failing test but stop on the next
|
||||
failing test. Implicitly enables --stepwise.
|
||||
failing test.
|
||||
implicitly enables --stepwise.
|
||||
|
||||
Reporting:
|
||||
--durations=N Show N slowest setup/test durations (N=0 for all)
|
||||
--durations=N show N slowest setup/test durations (N=0 for all)
|
||||
--durations-min=N Minimal duration in seconds for inclusion in slowest
|
||||
list. Default: 0.005.
|
||||
-v, --verbose Increase verbosity
|
||||
@@ -1835,10 +1830,8 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
--disable-warnings, --disable-pytest-warnings
|
||||
Disable warnings summary
|
||||
-l, --showlocals Show locals in tracebacks (disabled by default)
|
||||
--no-showlocals Hide locals in tracebacks (negate --showlocals
|
||||
passed through addopts)
|
||||
--tb=style Traceback print mode
|
||||
(auto/long/short/line/native/no)
|
||||
(auto/long/short/line/native/no).
|
||||
--show-capture={no,stdout,stderr,log,all}
|
||||
Controls how captured stdout/stderr/log is shown on
|
||||
failed tests. Default: all.
|
||||
@@ -1864,14 +1857,15 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
-c file Load configuration from `file` instead of trying to
|
||||
locate one of the implicit configuration files
|
||||
--continue-on-collection-errors
|
||||
Force test execution even if collection errors occur
|
||||
Force test execution even if collection errors
|
||||
occur
|
||||
--rootdir=ROOTDIR Define root directory for tests. Can be relative
|
||||
path: 'root_dir', './root_dir',
|
||||
'root_dir/another_dir/'; absolute path:
|
||||
'/home/user/root_dir'; path with variables:
|
||||
'$HOME/root_dir'.
|
||||
|
||||
collection:
|
||||
Collection:
|
||||
--collect-only, --co Only collect tests, don't execute them
|
||||
--pyargs Try to interpret all arguments as Python packages
|
||||
--ignore=path Ignore path during collection (multi-allowed)
|
||||
@@ -1899,24 +1893,27 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
For a given doctest, continue to run after the first
|
||||
failure
|
||||
|
||||
test session debugging and configuration:
|
||||
--basetemp=dir Base temporary directory for this test run.
|
||||
(Warning: this directory is removed if it exists.)
|
||||
Test session debugging and configuration:
|
||||
--basetemp=dir Base temporary directory for this test run. (Warning:
|
||||
this directory is removed if it exists.)
|
||||
-V, --version Display pytest version and information about
|
||||
plugins. When given twice, also display information
|
||||
about plugins.
|
||||
-h, --help Show help message and configuration info
|
||||
-p name Early-load given plugin module name or entry point
|
||||
(multi-allowed). To avoid loading of plugins, use
|
||||
the `no:` prefix, e.g. `no:doctest`.
|
||||
(multi-allowed)
|
||||
To avoid loading of plugins, use the `no:` prefix,
|
||||
e.g. `no:doctest`
|
||||
--trace-config Trace considerations of conftest.py files
|
||||
--debug=[DEBUG_FILE_NAME]
|
||||
Store internal tracing debug information in this log
|
||||
file. This file is opened with 'w' and truncated as
|
||||
a result, care advised. Default: pytestdebug.log.
|
||||
file.
|
||||
This file is opened with 'w' and truncated as a
|
||||
result, care advised.
|
||||
Default: pytestdebug.log.
|
||||
-o OVERRIDE_INI, --override-ini=OVERRIDE_INI
|
||||
Override ini option with "option=value" style, e.g.
|
||||
`-o xfail_strict=True -o cache_dir=cache`.
|
||||
`-o xfail_strict=True -o cache_dir=cache`
|
||||
--assert=MODE Control assertion debugging tools.
|
||||
'plain' performs no assertion debugging.
|
||||
'rewrite' (the default) rewrites assert statements
|
||||
@@ -1927,11 +1924,11 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
--setup-plan Show what fixtures and tests would be executed but
|
||||
don't execute anything
|
||||
|
||||
logging:
|
||||
--log-level=LEVEL Level of messages to catch/display. Not set by
|
||||
default, so it depends on the root/parent log
|
||||
handler's effective level, where it is "WARNING" by
|
||||
default.
|
||||
Logging:
|
||||
--log-level=LEVEL Level of messages to catch/display.
|
||||
Not set by default, so it depends on the root/parent
|
||||
log handler's effective level, where it is "WARNING"
|
||||
by default.
|
||||
--log-format=LOG_FORMAT
|
||||
Log format used by the logging module
|
||||
--log-date-format=LOG_DATE_FORMAT
|
||||
@@ -1960,7 +1957,7 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
Default marker for empty parametersets
|
||||
norecursedirs (args): Directory patterns to avoid for recursion
|
||||
testpaths (args): Directories to search for tests when no files or
|
||||
directories are given on the command line
|
||||
directories are given in the command line
|
||||
filterwarnings (linelist):
|
||||
Each line specifies a pattern for
|
||||
warnings.filterwarnings. Processed after
|
||||
|
||||
@@ -2,6 +2,9 @@ pallets-sphinx-themes
|
||||
pluggy>=1.0
|
||||
pygments-pytest>=2.2.0
|
||||
sphinx-removed-in>=0.2.0
|
||||
sphinx>=5,<6
|
||||
sphinx>=3.1,<4
|
||||
sphinxcontrib-trio
|
||||
sphinxcontrib-svg2pdfconverter
|
||||
|
||||
# XXX: sphinx<4 is broken with latest jinja2
|
||||
jinja2<3.1
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import sys
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "sdist" not in sys.argv[1:]:
|
||||
raise ValueError("please use 'pytest' pypi package instead of 'py.test'")
|
||||
setup(
|
||||
name="py.test",
|
||||
version="0.0",
|
||||
description="please use 'pytest' for installation",
|
||||
)
|
||||
+1
-3
@@ -3,6 +3,7 @@ requires = [
|
||||
# sync with setup.py until we discard non-pep-517/518
|
||||
"setuptools>=45.0",
|
||||
"setuptools-scm[toml]>=6.2.3",
|
||||
"wheel",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
@@ -37,9 +38,6 @@ filterwarnings = [
|
||||
# Those are caught/handled by pyupgrade, and not easy to filter with the
|
||||
# module being the filename (with .py removed).
|
||||
"default:invalid escape sequence:DeprecationWarning",
|
||||
# ignore not yet fixed warnings for hook markers
|
||||
"default:.*not marked using pytest.hook.*",
|
||||
"ignore:.*not marked using pytest.hook.*::xdist.*",
|
||||
# ignore use of unregistered marks, because we use many to test the implementation
|
||||
"ignore::_pytest.warning_types.PytestUnknownMarkWarning",
|
||||
# https://github.com/benjaminp/six/issues/341
|
||||
|
||||
@@ -36,19 +36,17 @@ packages =
|
||||
_pytest
|
||||
_pytest._code
|
||||
_pytest._io
|
||||
_pytest._py
|
||||
_pytest.assertion
|
||||
_pytest.config
|
||||
_pytest.mark
|
||||
pytest
|
||||
py_modules = py
|
||||
install_requires =
|
||||
attrs>=19.2.0
|
||||
iniconfig
|
||||
packaging
|
||||
pluggy>=0.12,<2.0
|
||||
py>=1.8.2
|
||||
colorama;sys_platform=="win32"
|
||||
exceptiongroup>=1.0.0rc8;python_version<"3.11"
|
||||
importlib-metadata>=0.12;python_version<"3.8"
|
||||
tomli>=1.0.0;python_version<"3.11"
|
||||
python_requires = >=3.7
|
||||
|
||||
@@ -56,9 +56,6 @@ if TYPE_CHECKING:
|
||||
|
||||
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
||||
|
||||
if sys.version_info[:2] < (3, 11):
|
||||
from exceptiongroup import BaseExceptionGroup
|
||||
|
||||
|
||||
class Code:
|
||||
"""Wrapper around Python code objects."""
|
||||
@@ -927,21 +924,7 @@ class FormattedExcinfo:
|
||||
while e is not None and id(e) not in seen:
|
||||
seen.add(id(e))
|
||||
if excinfo_:
|
||||
# Fall back to native traceback as a temporary workaround until
|
||||
# full support for exception groups added to ExceptionInfo.
|
||||
# See https://github.com/pytest-dev/pytest/issues/9159
|
||||
if isinstance(e, BaseExceptionGroup):
|
||||
reprtraceback: Union[
|
||||
ReprTracebackNative, ReprTraceback
|
||||
] = ReprTracebackNative(
|
||||
traceback.format_exception(
|
||||
type(excinfo_.value),
|
||||
excinfo_.value,
|
||||
excinfo_.traceback[0]._rawentry,
|
||||
)
|
||||
)
|
||||
else:
|
||||
reprtraceback = self.repr_traceback(excinfo_)
|
||||
reprtraceback = self.repr_traceback(excinfo_)
|
||||
reprcrash: Optional[ReprFileLocation] = (
|
||||
excinfo_._getreprcrash() if self.style != "value" else None
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ class SafeRepr(reprlib.Repr):
|
||||
information on exceptions raised during the call.
|
||||
"""
|
||||
|
||||
def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None:
|
||||
def __init__(self, maxsize: Optional[int]) -> None:
|
||||
"""
|
||||
:param maxsize:
|
||||
If not None, will truncate the resulting repr to that specific size, using ellipsis
|
||||
@@ -54,15 +54,10 @@ class SafeRepr(reprlib.Repr):
|
||||
# truncation.
|
||||
self.maxstring = maxsize if maxsize is not None else 1_000_000_000
|
||||
self.maxsize = maxsize
|
||||
self.use_ascii = use_ascii
|
||||
|
||||
def repr(self, x: object) -> str:
|
||||
try:
|
||||
if self.use_ascii:
|
||||
s = ascii(x)
|
||||
else:
|
||||
s = super().repr(x)
|
||||
|
||||
s = super().repr(x)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
@@ -99,9 +94,7 @@ def safeformat(obj: object) -> str:
|
||||
DEFAULT_REPR_MAX_SIZE = 240
|
||||
|
||||
|
||||
def saferepr(
|
||||
obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False
|
||||
) -> str:
|
||||
def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str:
|
||||
"""Return a size-limited safe repr-string for the given object.
|
||||
|
||||
Failing __repr__ functions of user instances will be represented
|
||||
@@ -111,11 +104,10 @@ def saferepr(
|
||||
This function is a wrapper around the Repr/reprlib functionality of the
|
||||
stdlib.
|
||||
"""
|
||||
|
||||
return SafeRepr(maxsize, use_ascii).repr(obj)
|
||||
return SafeRepr(maxsize).repr(obj)
|
||||
|
||||
|
||||
def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str:
|
||||
def saferepr_unlimited(obj: object) -> str:
|
||||
"""Return an unlimited-size safe repr-string for the given object.
|
||||
|
||||
As with saferepr, failing __repr__ functions of user instances
|
||||
@@ -127,8 +119,6 @@ def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str:
|
||||
when maxsize=None, but that might affect some other code.
|
||||
"""
|
||||
try:
|
||||
if use_ascii:
|
||||
return ascii(obj)
|
||||
return repr(obj)
|
||||
except Exception as exc:
|
||||
return _format_repr_exception(exc, obj)
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
"""create errno-specific classes for IO or os calls."""
|
||||
from __future__ import annotations
|
||||
|
||||
import errno
|
||||
import os
|
||||
import sys
|
||||
from typing import Callable
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
P = ParamSpec("P")
|
||||
|
||||
R = TypeVar("R")
|
||||
|
||||
|
||||
class Error(EnvironmentError):
|
||||
def __repr__(self) -> str:
|
||||
return "{}.{} {!r}: {} ".format(
|
||||
self.__class__.__module__,
|
||||
self.__class__.__name__,
|
||||
self.__class__.__doc__,
|
||||
" ".join(map(str, self.args)),
|
||||
# repr(self.args)
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
s = "[{}]: {}".format(
|
||||
self.__class__.__doc__,
|
||||
" ".join(map(str, self.args)),
|
||||
)
|
||||
return s
|
||||
|
||||
|
||||
_winerrnomap = {
|
||||
2: errno.ENOENT,
|
||||
3: errno.ENOENT,
|
||||
17: errno.EEXIST,
|
||||
18: errno.EXDEV,
|
||||
13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable
|
||||
22: errno.ENOTDIR,
|
||||
20: errno.ENOTDIR,
|
||||
267: errno.ENOTDIR,
|
||||
5: errno.EACCES, # anything better?
|
||||
}
|
||||
|
||||
|
||||
class ErrorMaker:
|
||||
"""lazily provides Exception classes for each possible POSIX errno
|
||||
(as defined per the 'errno' module). All such instances
|
||||
subclass EnvironmentError.
|
||||
"""
|
||||
|
||||
_errno2class: dict[int, type[Error]] = {}
|
||||
|
||||
def __getattr__(self, name: str) -> type[Error]:
|
||||
if name[0] == "_":
|
||||
raise AttributeError(name)
|
||||
eno = getattr(errno, name)
|
||||
cls = self._geterrnoclass(eno)
|
||||
setattr(self, name, cls)
|
||||
return cls
|
||||
|
||||
def _geterrnoclass(self, eno: int) -> type[Error]:
|
||||
try:
|
||||
return self._errno2class[eno]
|
||||
except KeyError:
|
||||
clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,))
|
||||
errorcls = type(
|
||||
clsname,
|
||||
(Error,),
|
||||
{"__module__": "py.error", "__doc__": os.strerror(eno)},
|
||||
)
|
||||
self._errno2class[eno] = errorcls
|
||||
return errorcls
|
||||
|
||||
def checked_call(
|
||||
self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs
|
||||
) -> R:
|
||||
"""Call a function and raise an errno-exception if applicable."""
|
||||
__tracebackhide__ = True
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Error:
|
||||
raise
|
||||
except OSError as value:
|
||||
if not hasattr(value, "errno"):
|
||||
raise
|
||||
errno = value.errno
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
cls = self._geterrnoclass(_winerrnomap[errno])
|
||||
except KeyError:
|
||||
raise value
|
||||
else:
|
||||
# we are not on Windows, or we got a proper OSError
|
||||
cls = self._geterrnoclass(errno)
|
||||
|
||||
raise cls(f"{func.__name__}{args!r}")
|
||||
|
||||
|
||||
_error_maker = ErrorMaker()
|
||||
checked_call = _error_maker.checked_call
|
||||
|
||||
|
||||
def __getattr__(attr: str) -> type[Error]:
|
||||
return getattr(_error_maker, attr) # type: ignore[no-any-return]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,7 +53,7 @@ def register_assert_rewrite(*names: str) -> None:
|
||||
actually imported, usually in your __init__.py if you are a plugin
|
||||
using a package.
|
||||
|
||||
:param names: The module names to register.
|
||||
:raises TypeError: If the given module names are not strings.
|
||||
"""
|
||||
for name in names:
|
||||
if not isinstance(name, str):
|
||||
|
||||
@@ -10,7 +10,6 @@ from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from unicodedata import normalize
|
||||
|
||||
import _pytest._code
|
||||
from _pytest import outcomes
|
||||
@@ -157,32 +156,20 @@ def has_default_eq(
|
||||
return True
|
||||
|
||||
|
||||
def assertrepr_compare(
|
||||
config, op: str, left: Any, right: Any, use_ascii: bool = False
|
||||
) -> Optional[List[str]]:
|
||||
def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]:
|
||||
"""Return specialised explanations for some operators/operands."""
|
||||
verbose = config.getoption("verbose")
|
||||
|
||||
# Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier.
|
||||
# See issue #3246.
|
||||
use_ascii = (
|
||||
isinstance(left, str)
|
||||
and isinstance(right, str)
|
||||
and normalize("NFD", left) == normalize("NFD", right)
|
||||
)
|
||||
|
||||
if verbose > 1:
|
||||
left_repr = saferepr_unlimited(left, use_ascii=use_ascii)
|
||||
right_repr = saferepr_unlimited(right, use_ascii=use_ascii)
|
||||
left_repr = saferepr_unlimited(left)
|
||||
right_repr = saferepr_unlimited(right)
|
||||
else:
|
||||
# XXX: "15 chars indentation" is wrong
|
||||
# ("E AssertionError: assert "); should use term width.
|
||||
maxsize = (
|
||||
80 - 15 - len(op) - 2
|
||||
) // 2 # 15 chars indentation, 1 space around op
|
||||
|
||||
left_repr = saferepr(left, maxsize=maxsize, use_ascii=use_ascii)
|
||||
right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii)
|
||||
left_repr = saferepr(left, maxsize=maxsize)
|
||||
right_repr = saferepr(right, maxsize=maxsize)
|
||||
|
||||
summary = f"{left_repr} {op} {right_repr}"
|
||||
|
||||
|
||||
+1
-28
@@ -203,39 +203,12 @@ class DontReadFromInput:
|
||||
def fileno(self) -> int:
|
||||
raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()")
|
||||
|
||||
def flush(self) -> None:
|
||||
raise UnsupportedOperation("redirected stdin is pseudofile, has no flush()")
|
||||
|
||||
def isatty(self) -> bool:
|
||||
return False
|
||||
|
||||
def close(self) -> None:
|
||||
pass
|
||||
|
||||
def readable(self) -> bool:
|
||||
return False
|
||||
|
||||
def seek(self, offset: int) -> int:
|
||||
raise UnsupportedOperation("redirected stdin is pseudofile, has no seek(int)")
|
||||
|
||||
def seekable(self) -> bool:
|
||||
return False
|
||||
|
||||
def tell(self) -> int:
|
||||
raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()")
|
||||
|
||||
def truncate(self, size: int) -> None:
|
||||
raise UnsupportedOperation("cannont truncate stdin")
|
||||
|
||||
def write(self, *args) -> None:
|
||||
raise UnsupportedOperation("cannot write to stdin")
|
||||
|
||||
def writelines(self, *args) -> None:
|
||||
raise UnsupportedOperation("Cannot write to stdin")
|
||||
|
||||
def writable(self) -> bool:
|
||||
return False
|
||||
|
||||
@property
|
||||
def buffer(self):
|
||||
return self
|
||||
@@ -381,7 +354,7 @@ class FDCaptureBinary:
|
||||
self.targetfd_save = os.dup(targetfd)
|
||||
|
||||
if targetfd == 0:
|
||||
self.tmpfile = open(os.devnull, encoding="utf-8")
|
||||
self.tmpfile = open(os.devnull)
|
||||
self.syscapture = SysCapture(targetfd)
|
||||
else:
|
||||
self.tmpfile = EncodedFile(
|
||||
|
||||
+1
-11
@@ -18,19 +18,8 @@ from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
import attr
|
||||
|
||||
import py
|
||||
|
||||
# fmt: off
|
||||
# Workaround for https://github.com/sphinx-doc/sphinx/issues/10351.
|
||||
# If `overload` is imported from `compat` instead of from `typing`,
|
||||
# Sphinx doesn't recognize it as `overload` and the API docs for
|
||||
# overloaded functions look good again. But type checkers handle
|
||||
# it fine.
|
||||
# fmt: on
|
||||
if True:
|
||||
from typing import overload as overload
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Final
|
||||
|
||||
@@ -356,6 +345,7 @@ else:
|
||||
if sys.version_info >= (3, 8):
|
||||
from functools import cached_property as cached_property
|
||||
else:
|
||||
from typing import overload
|
||||
from typing import Type
|
||||
|
||||
class cached_property(Generic[_S, _T]):
|
||||
|
||||
@@ -14,7 +14,6 @@ import warnings
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
from types import FunctionType
|
||||
from types import TracebackType
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
@@ -59,7 +58,6 @@ from _pytest.pathlib import ImportMode
|
||||
from _pytest.pathlib import resolve_package_path
|
||||
from _pytest.stash import Stash
|
||||
from _pytest.warning_types import PytestConfigWarning
|
||||
from _pytest.warning_types import warn_explicit_for
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -343,38 +341,6 @@ def _get_directory(path: Path) -> Path:
|
||||
return path
|
||||
|
||||
|
||||
def _get_legacy_hook_marks(
|
||||
method: Any,
|
||||
hook_type: str,
|
||||
opt_names: Tuple[str, ...],
|
||||
) -> Dict[str, bool]:
|
||||
if TYPE_CHECKING:
|
||||
# abuse typeguard from importlib to avoid massive method type union thats lacking a alias
|
||||
assert inspect.isroutine(method)
|
||||
known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
must_warn: list[str] = []
|
||||
opts: dict[str, bool] = {}
|
||||
for opt_name in opt_names:
|
||||
opt_attr = getattr(method, opt_name, AttributeError)
|
||||
if opt_attr is not AttributeError:
|
||||
must_warn.append(f"{opt_name}={opt_attr}")
|
||||
opts[opt_name] = True
|
||||
elif opt_name in known_marks:
|
||||
must_warn.append(f"{opt_name}=True")
|
||||
opts[opt_name] = True
|
||||
else:
|
||||
opts[opt_name] = False
|
||||
if must_warn:
|
||||
hook_opts = ", ".join(must_warn)
|
||||
message = _pytest.deprecated.HOOK_LEGACY_MARKING.format(
|
||||
type=hook_type,
|
||||
fullname=method.__qualname__,
|
||||
hook_opts=hook_opts,
|
||||
)
|
||||
warn_explicit_for(cast(FunctionType, method), message)
|
||||
return opts
|
||||
|
||||
|
||||
@final
|
||||
class PytestPluginManager(PluginManager):
|
||||
"""A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
|
||||
@@ -448,29 +414,40 @@ class PytestPluginManager(PluginManager):
|
||||
if name == "pytest_plugins":
|
||||
return
|
||||
|
||||
opts = super().parse_hookimpl_opts(plugin, name)
|
||||
if opts is not None:
|
||||
return opts
|
||||
|
||||
method = getattr(plugin, name)
|
||||
opts = super().parse_hookimpl_opts(plugin, name)
|
||||
|
||||
# Consider only actual functions for hooks (#3775).
|
||||
if not inspect.isroutine(method):
|
||||
return
|
||||
|
||||
# Collect unmarked hooks as long as they have the `pytest_' prefix.
|
||||
return _get_legacy_hook_marks(
|
||||
method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
|
||||
)
|
||||
if opts is None and name.startswith("pytest_"):
|
||||
opts = {}
|
||||
if opts is not None:
|
||||
# TODO: DeprecationWarning, people should use hookimpl
|
||||
# https://github.com/pytest-dev/pytest/issues/4562
|
||||
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
|
||||
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
|
||||
opts.setdefault(name, hasattr(method, name) or name in known_marks)
|
||||
return opts
|
||||
|
||||
def parse_hookspec_opts(self, module_or_class, name: str):
|
||||
opts = super().parse_hookspec_opts(module_or_class, name)
|
||||
if opts is None:
|
||||
method = getattr(module_or_class, name)
|
||||
|
||||
if name.startswith("pytest_"):
|
||||
opts = _get_legacy_hook_marks(
|
||||
method,
|
||||
"spec",
|
||||
("firstresult", "historic"),
|
||||
)
|
||||
# todo: deprecate hookspec hacks
|
||||
# https://github.com/pytest-dev/pytest/issues/4562
|
||||
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
opts = {
|
||||
"firstresult": hasattr(method, "firstresult")
|
||||
or "firstresult" in known_marks,
|
||||
"historic": hasattr(method, "historic")
|
||||
or "historic" in known_marks,
|
||||
}
|
||||
return opts
|
||||
|
||||
def register(
|
||||
@@ -512,14 +489,12 @@ class PytestPluginManager(PluginManager):
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"tryfirst: mark a hook implementation function such that the "
|
||||
"plugin machinery will try to call it first/as early as possible. "
|
||||
"DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.",
|
||||
"plugin machinery will try to call it first/as early as possible.",
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"trylast: mark a hook implementation function such that the "
|
||||
"plugin machinery will try to call it last/as late as possible. "
|
||||
"DEPRECATED, use @pytest.hookimpl(trylast=True) instead.",
|
||||
"plugin machinery will try to call it last/as late as possible.",
|
||||
)
|
||||
self._configured = True
|
||||
|
||||
@@ -861,8 +836,7 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
|
||||
if is_simple_module:
|
||||
module_name, _ = os.path.splitext(fn)
|
||||
# we ignore "setup.py" at the root of the distribution
|
||||
# as well as editable installation finder modules made by setuptools
|
||||
if module_name != "setup" and not module_name.startswith("__editable__"):
|
||||
if module_name != "setup":
|
||||
seen_some = True
|
||||
yield module_name
|
||||
elif is_package:
|
||||
|
||||
@@ -66,15 +66,14 @@ class Parser:
|
||||
) -> "OptionGroup":
|
||||
"""Get (or create) a named option Group.
|
||||
|
||||
:param name: Name of the option group.
|
||||
:param description: Long description for --help output.
|
||||
:param after: Name of another group, used for ordering --help output.
|
||||
:returns: The option group.
|
||||
:name: Name of the option group.
|
||||
:description: Long description for --help output.
|
||||
:after: Name of another group, used for ordering --help output.
|
||||
|
||||
The returned group object has an ``addoption`` method with the same
|
||||
signature as :func:`parser.addoption <pytest.Parser.addoption>` but
|
||||
will be shown in the respective group in the output of
|
||||
``pytest --help``.
|
||||
``pytest. --help``.
|
||||
"""
|
||||
for group in self._groups:
|
||||
if group.name == name:
|
||||
@@ -90,11 +89,10 @@ class Parser:
|
||||
def addoption(self, *opts: str, **attrs: Any) -> None:
|
||||
"""Register a command line option.
|
||||
|
||||
:param opts:
|
||||
Option names, can be short or long options.
|
||||
:param attrs:
|
||||
Same attributes as the argparse library's :py:func:`add_argument()
|
||||
<argparse.ArgumentParser.add_argument>` function accepts.
|
||||
:opts: Option names, can be short or long options.
|
||||
:attrs: Same attributes which the ``add_argument()`` function of the
|
||||
`argparse library <https://docs.python.org/library/argparse.html>`_
|
||||
accepts.
|
||||
|
||||
After command line parsing, options are available on the pytest config
|
||||
object via ``config.option.NAME`` where ``NAME`` is usually set
|
||||
@@ -150,10 +148,7 @@ class Parser:
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> argparse.Namespace:
|
||||
"""Parse the known arguments at this point.
|
||||
|
||||
:returns: An argparse namespace object.
|
||||
"""
|
||||
"""Parse and return a namespace object with known arguments at this point."""
|
||||
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
|
||||
|
||||
def parse_known_and_unknown_args(
|
||||
@@ -161,13 +156,8 @@ class Parser:
|
||||
args: Sequence[Union[str, "os.PathLike[str]"]],
|
||||
namespace: Optional[argparse.Namespace] = None,
|
||||
) -> Tuple[argparse.Namespace, List[str]]:
|
||||
"""Parse the known arguments at this point, and also return the
|
||||
remaining unknown arguments.
|
||||
|
||||
:returns:
|
||||
A tuple containing an argparse namespace object for the known
|
||||
arguments, and a list of the unknown arguments.
|
||||
"""
|
||||
"""Parse and return a namespace object with known arguments, and
|
||||
the remaining arguments unknown at this point."""
|
||||
optparser = self._getparser()
|
||||
strargs = [os.fspath(x) for x in args]
|
||||
return optparser.parse_known_args(strargs, namespace=namespace)
|
||||
@@ -179,13 +169,13 @@ class Parser:
|
||||
type: Optional[
|
||||
"Literal['string', 'paths', 'pathlist', 'args', 'linelist', 'bool']"
|
||||
] = None,
|
||||
default: Any = None,
|
||||
default=None,
|
||||
) -> None:
|
||||
"""Register an ini-file option.
|
||||
|
||||
:param name:
|
||||
:name:
|
||||
Name of the ini-variable.
|
||||
:param type:
|
||||
:type:
|
||||
Type of the variable. Can be:
|
||||
|
||||
* ``string``: a string
|
||||
@@ -199,7 +189,7 @@ class Parser:
|
||||
The ``paths`` variable type.
|
||||
|
||||
Defaults to ``string`` if ``None`` or not passed.
|
||||
:param default:
|
||||
:default:
|
||||
Default value if no ini-file option exists but is queried.
|
||||
|
||||
The value of ini-variables can be retrieved via a call to
|
||||
@@ -364,30 +354,24 @@ class OptionGroup:
|
||||
self.options: List[Argument] = []
|
||||
self.parser = parser
|
||||
|
||||
def addoption(self, *opts: str, **attrs: Any) -> None:
|
||||
def addoption(self, *optnames: str, **attrs: Any) -> None:
|
||||
"""Add an option to this group.
|
||||
|
||||
If a shortened version of a long option is specified, it will
|
||||
be suppressed in the help. ``addoption('--twowords', '--two-words')``
|
||||
results in help showing ``--two-words`` only, but ``--twowords`` gets
|
||||
accepted **and** the automatic destination is in ``args.twowords``.
|
||||
|
||||
:param opts:
|
||||
Option names, can be short or long options.
|
||||
:param attrs:
|
||||
Same attributes as the argparse library's :py:func:`add_argument()
|
||||
<argparse.ArgumentParser.add_argument>` function accepts.
|
||||
"""
|
||||
conflict = set(opts).intersection(
|
||||
conflict = set(optnames).intersection(
|
||||
name for opt in self.options for name in opt.names()
|
||||
)
|
||||
if conflict:
|
||||
raise ValueError("option names %s already added" % conflict)
|
||||
option = Argument(*opts, **attrs)
|
||||
option = Argument(*optnames, **attrs)
|
||||
self._addoption_instance(option, shortupper=False)
|
||||
|
||||
def _addoption(self, *opts: str, **attrs: Any) -> None:
|
||||
option = Argument(*opts, **attrs)
|
||||
def _addoption(self, *optnames: str, **attrs: Any) -> None:
|
||||
option = Argument(*optnames, **attrs)
|
||||
self._addoption_instance(option, shortupper=True)
|
||||
|
||||
def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None:
|
||||
|
||||
@@ -3,7 +3,6 @@ import argparse
|
||||
import functools
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
@@ -294,9 +293,7 @@ class PdbInvoke:
|
||||
sys.stdout.write(out)
|
||||
sys.stdout.write(err)
|
||||
assert call.excinfo is not None
|
||||
|
||||
if not isinstance(call.excinfo.value, unittest.SkipTest):
|
||||
_enter_pdb(node, call.excinfo, report)
|
||||
_enter_pdb(node, call.excinfo, report)
|
||||
|
||||
def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None:
|
||||
tb = _postmortem_traceback(excinfo)
|
||||
|
||||
@@ -22,21 +22,6 @@ DEPRECATED_EXTERNAL_PLUGINS = {
|
||||
"pytest_faulthandler",
|
||||
}
|
||||
|
||||
NOSE_SUPPORT = UnformattedWarning(
|
||||
PytestRemovedIn8Warning,
|
||||
"Support for nose tests is deprecated and will be removed in a future release.\n"
|
||||
"{nodeid} is using nose method: `{method}` ({stage})\n"
|
||||
"See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose",
|
||||
)
|
||||
|
||||
NOSE_SUPPORT_METHOD = UnformattedWarning(
|
||||
PytestRemovedIn8Warning,
|
||||
"Support for nose tests is deprecated and will be removed in a future release.\n"
|
||||
"{nodeid} is using nose-specific method: `{method}(self)`\n"
|
||||
"To remove this warning, rename it to `{method}_method(self)`\n"
|
||||
"See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose",
|
||||
)
|
||||
|
||||
|
||||
# This can be* removed pytest 8, but it's harmless and common, so no rush to remove.
|
||||
# * If you're in the future: "could have been".
|
||||
@@ -113,14 +98,6 @@ INSTANCE_COLLECTOR = PytestRemovedIn8Warning(
|
||||
"The pytest.Instance collector type is deprecated and is no longer used. "
|
||||
"See https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector",
|
||||
)
|
||||
HOOK_LEGACY_MARKING = UnformattedWarning(
|
||||
PytestDeprecationWarning,
|
||||
"The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n"
|
||||
"Please use the pytest.hook{type}({hook_opts}) decorator instead\n"
|
||||
" to configure the hooks.\n"
|
||||
" See https://docs.pytest.org/en/latest/deprecations.html"
|
||||
"#configuring-hook-specs-impls-using-markers",
|
||||
)
|
||||
|
||||
# You want to make some `__init__` or function "private".
|
||||
#
|
||||
|
||||
+7
-10
@@ -23,6 +23,7 @@ from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from _pytest import outcomes
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ReprFileLocation
|
||||
@@ -31,15 +32,11 @@ from _pytest._io import TerminalWriter
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.config import Config
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.fixtures import fixture
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
from _pytest.nodes import Collector
|
||||
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
|
||||
|
||||
@@ -249,7 +246,7 @@ def _get_runner(
|
||||
)
|
||||
|
||||
|
||||
class DoctestItem(Item):
|
||||
class DoctestItem(pytest.Item):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
@@ -414,7 +411,7 @@ def _get_continue_on_failure(config):
|
||||
return continue_on_failure
|
||||
|
||||
|
||||
class DoctestTextfile(Module):
|
||||
class DoctestTextfile(pytest.Module):
|
||||
obj = None
|
||||
|
||||
def collect(self) -> Iterable[DoctestItem]:
|
||||
@@ -452,7 +449,7 @@ def _check_all_skipped(test: "doctest.DocTest") -> None:
|
||||
|
||||
all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
|
||||
if all_skipped:
|
||||
skip("all tests skipped by +SKIP option")
|
||||
pytest.skip("all tests skipped by +SKIP option")
|
||||
|
||||
|
||||
def _is_mocked(obj: object) -> bool:
|
||||
@@ -494,7 +491,7 @@ def _patch_unwrap_mock_aware() -> Generator[None, None, None]:
|
||||
inspect.unwrap = real_unwrap
|
||||
|
||||
|
||||
class DoctestModule(Module):
|
||||
class DoctestModule(pytest.Module):
|
||||
def collect(self) -> Iterable[DoctestItem]:
|
||||
import doctest
|
||||
|
||||
@@ -552,7 +549,7 @@ class DoctestModule(Module):
|
||||
)
|
||||
except ImportError:
|
||||
if self.config.getvalue("doctest_ignore_import_errors"):
|
||||
skip("unable to import module %r" % self.path)
|
||||
pytest.skip("unable to import module %r" % self.path)
|
||||
else:
|
||||
raise
|
||||
# Uses internal doctest module parsing mechanism.
|
||||
@@ -734,7 +731,7 @@ def _get_report_choice(key: str) -> int:
|
||||
}[key]
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
@pytest.fixture(scope="session")
|
||||
def doctest_namespace() -> Dict[str, Any]:
|
||||
"""Fixture that returns a :py:class:`dict` that will be injected into the
|
||||
namespace of doctests.
|
||||
|
||||
+10
-15
@@ -20,6 +20,7 @@ from typing import List
|
||||
from typing import MutableMapping
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
@@ -47,7 +48,6 @@ from _pytest.compat import getimfunc
|
||||
from _pytest.compat import getlocation
|
||||
from _pytest.compat import is_generator
|
||||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import overload
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.config import _PluggyPlugin
|
||||
from _pytest.config import Config
|
||||
@@ -513,8 +513,8 @@ class FixtureRequest:
|
||||
return self._pyfuncitem.session # type: ignore[no-any-return]
|
||||
|
||||
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
||||
"""Add finalizer/teardown function to be called without arguments after
|
||||
the last test within the requesting test context finished execution."""
|
||||
"""Add finalizer/teardown function to be called after the last test
|
||||
within the requesting test context finished execution."""
|
||||
# XXX usually this method is shadowed by fixturedef specific ones.
|
||||
self._addfinalizer(finalizer, scope=self.scope)
|
||||
|
||||
@@ -529,16 +529,13 @@ class FixtureRequest:
|
||||
on all function invocations.
|
||||
|
||||
:param marker:
|
||||
An object created by a call to ``pytest.mark.NAME(...)``.
|
||||
A :class:`pytest.MarkDecorator` object created by a call
|
||||
to ``pytest.mark.NAME(...)``.
|
||||
"""
|
||||
self.node.add_marker(marker)
|
||||
|
||||
def raiseerror(self, msg: Optional[str]) -> NoReturn:
|
||||
"""Raise a FixtureLookupError exception.
|
||||
|
||||
:param msg:
|
||||
An optional custom error message.
|
||||
"""
|
||||
"""Raise a FixtureLookupError with the given message."""
|
||||
raise self._fixturemanager.FixtureLookupError(None, self, msg)
|
||||
|
||||
def _fillfixtures(self) -> None:
|
||||
@@ -560,8 +557,6 @@ class FixtureRequest:
|
||||
phase, but during the test teardown phase a fixture's value may not
|
||||
be available.
|
||||
|
||||
:param argname:
|
||||
The fixture name.
|
||||
:raises pytest.FixtureLookupError:
|
||||
If the given fixture could not be found.
|
||||
"""
|
||||
@@ -773,8 +768,8 @@ class SubRequest(FixtureRequest):
|
||||
return f"<SubRequest {self.fixturename!r} for {self._pyfuncitem!r}>"
|
||||
|
||||
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
||||
"""Add finalizer/teardown function to be called without arguments after
|
||||
the last test within the requesting test context finished execution."""
|
||||
"""Add finalizer/teardown function to be called after the last test
|
||||
within the requesting test context finished execution."""
|
||||
self._fixturedef.addfinalizer(finalizer)
|
||||
|
||||
def _schedule_finalizers(
|
||||
@@ -1231,7 +1226,7 @@ def fixture(
|
||||
|
||||
|
||||
@overload
|
||||
def fixture( # noqa: F811
|
||||
def fixture(
|
||||
fixture_function: None = ...,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
|
||||
@@ -1245,7 +1240,7 @@ def fixture( # noqa: F811
|
||||
...
|
||||
|
||||
|
||||
def fixture( # noqa: F811
|
||||
def fixture(
|
||||
fixture_function: Optional[FixtureFunction] = None,
|
||||
*,
|
||||
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function",
|
||||
|
||||
+64
-144
@@ -143,7 +143,7 @@ def pytest_configure(config: "Config") -> None:
|
||||
def pytest_cmdline_parse(
|
||||
pluginmanager: "PytestPluginManager", args: List[str]
|
||||
) -> Optional["Config"]:
|
||||
"""Return an initialized :class:`~pytest.Config`, parsing the specified args.
|
||||
"""Return an initialized config object, parsing the specified args.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
@@ -152,9 +152,8 @@ def pytest_cmdline_parse(
|
||||
``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.
|
||||
:param pytest.PytestPluginManager pluginmanager: The pytest plugin manager.
|
||||
:param List[str] args: List of arguments passed on the command line.
|
||||
"""
|
||||
|
||||
|
||||
@@ -168,8 +167,8 @@ def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None:
|
||||
.. note::
|
||||
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param args: Arguments passed on the command line.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
:param List[str] args: Arguments passed on the command line.
|
||||
"""
|
||||
|
||||
|
||||
@@ -180,8 +179,7 @@ def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]:
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:returns: The exit code.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
"""
|
||||
|
||||
|
||||
@@ -194,9 +192,9 @@ def pytest_load_initial_conftests(
|
||||
.. note::
|
||||
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
|
||||
|
||||
:param early_config: The pytest config object.
|
||||
:param args: Arguments passed on the command line.
|
||||
:param parser: To add command line options.
|
||||
:param pytest.Config early_config: The pytest config object.
|
||||
:param List[str] args: Arguments passed on the command line.
|
||||
:param pytest.Parser parser: To add command line options.
|
||||
"""
|
||||
|
||||
|
||||
@@ -238,7 +236,7 @@ def pytest_collection(session: "Session") -> Optional[object]:
|
||||
for example the terminal plugin uses it to start displaying the collection
|
||||
counter (and returns `None`).
|
||||
|
||||
:param session: The pytest session object.
|
||||
:param pytest.Session session: The pytest session object.
|
||||
"""
|
||||
|
||||
|
||||
@@ -248,16 +246,16 @@ def pytest_collection_modifyitems(
|
||||
"""Called after collection has been performed. May filter or re-order
|
||||
the items in-place.
|
||||
|
||||
:param session: The pytest session object.
|
||||
:param config: The pytest config object.
|
||||
:param items: List of item objects.
|
||||
:param pytest.Session session: The pytest session object.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
:param List[pytest.Item] items: List of item objects.
|
||||
"""
|
||||
|
||||
|
||||
def pytest_collection_finish(session: "Session") -> None:
|
||||
"""Called after collection has been performed and modified.
|
||||
|
||||
:param session: The pytest session object.
|
||||
:param pytest.Session session: The pytest session object.
|
||||
"""
|
||||
|
||||
|
||||
@@ -272,9 +270,9 @@ def pytest_ignore_collect(
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param collection_path: The path to analyze.
|
||||
:param path: The path to analyze (deprecated).
|
||||
:param config: The pytest config object.
|
||||
:param pathlib.Path collection_path : The path to analyze.
|
||||
:param LEGACY_PATH path: The path to analyze (deprecated).
|
||||
:param pytest.Config config: The pytest config object.
|
||||
|
||||
.. versionchanged:: 7.0.0
|
||||
The ``collection_path`` parameter was added as a :class:`pathlib.Path`
|
||||
@@ -286,12 +284,12 @@ def pytest_ignore_collect(
|
||||
def pytest_collect_file(
|
||||
file_path: Path, path: "LEGACY_PATH", parent: "Collector"
|
||||
) -> "Optional[Collector]":
|
||||
"""Create a :class:`~pytest.Collector` for the given path, or None if not relevant.
|
||||
"""Create a Collector for the given path, or None if not relevant.
|
||||
|
||||
The new node needs to have the specified ``parent`` as a parent.
|
||||
|
||||
:param file_path: The path to analyze.
|
||||
:param path: The path to collect (deprecated).
|
||||
:param pathlib.Path file_path: The path to analyze.
|
||||
:param LEGACY_PATH path: The path to collect (deprecated).
|
||||
|
||||
.. versionchanged:: 7.0.0
|
||||
The ``file_path`` parameter was added as a :class:`pathlib.Path`
|
||||
@@ -304,36 +302,21 @@ def pytest_collect_file(
|
||||
|
||||
|
||||
def pytest_collectstart(collector: "Collector") -> None:
|
||||
"""Collector starts collecting.
|
||||
|
||||
:param collector:
|
||||
The collector.
|
||||
"""
|
||||
"""Collector starts collecting."""
|
||||
|
||||
|
||||
def pytest_itemcollected(item: "Item") -> None:
|
||||
"""We just collected a test item.
|
||||
|
||||
:param item:
|
||||
The item.
|
||||
"""
|
||||
"""We just collected a test item."""
|
||||
|
||||
|
||||
def pytest_collectreport(report: "CollectReport") -> None:
|
||||
"""Collector finished collecting.
|
||||
|
||||
:param report:
|
||||
The collect report.
|
||||
"""
|
||||
"""Collector finished collecting."""
|
||||
|
||||
|
||||
def pytest_deselected(items: Sequence["Item"]) -> None:
|
||||
"""Called for deselected test items, e.g. by keyword.
|
||||
|
||||
May be called multiple times.
|
||||
|
||||
:param items:
|
||||
The items.
|
||||
"""
|
||||
|
||||
|
||||
@@ -343,9 +326,6 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor
|
||||
a :class:`~pytest.CollectReport`.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param collector:
|
||||
The collector.
|
||||
"""
|
||||
|
||||
|
||||
@@ -358,16 +338,16 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor
|
||||
def pytest_pycollect_makemodule(
|
||||
module_path: Path, path: "LEGACY_PATH", parent
|
||||
) -> Optional["Module"]:
|
||||
"""Return a :class:`pytest.Module` collector or None for the given path.
|
||||
"""Return a Module collector or None for the given path.
|
||||
|
||||
This hook will be called for each matching test module path.
|
||||
The :hook:`pytest_collect_file` hook needs to be used if you want to
|
||||
The pytest_collect_file hook needs to be used if you want to
|
||||
create test modules for files that do not match as a test module.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param module_path: The path of the module to collect.
|
||||
:param path: The path of the module to collect (deprecated).
|
||||
:param pathlib.Path module_path: The path of the module to collect.
|
||||
:param LEGACY_PATH path: The path of the module to collect (deprecated).
|
||||
|
||||
.. versionchanged:: 7.0.0
|
||||
The ``module_path`` parameter was added as a :class:`pathlib.Path`
|
||||
@@ -384,15 +364,6 @@ def pytest_pycollect_makeitem(
|
||||
"""Return a custom item/collector for a Python object in a module, or None.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param collector:
|
||||
The module/class collector.
|
||||
:param name:
|
||||
The name of the object in the module/class.
|
||||
:param obj:
|
||||
The object.
|
||||
:returns:
|
||||
The created items/collectors.
|
||||
"""
|
||||
|
||||
|
||||
@@ -401,18 +372,11 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
|
||||
"""Call underlying test function.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param pyfuncitem:
|
||||
The function item.
|
||||
"""
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc: "Metafunc") -> None:
|
||||
"""Generate (multiple) parametrized calls to a test function.
|
||||
|
||||
:param metafunc:
|
||||
The :class:`~pytest.Metafunc` helper for the test function.
|
||||
"""
|
||||
"""Generate (multiple) parametrized calls to a test function."""
|
||||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
@@ -427,7 +391,7 @@ def pytest_make_parametrize_id(
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
:param val: The parametrized value.
|
||||
:param str argname: The automatic parameter name produced by pytest.
|
||||
"""
|
||||
@@ -452,7 +416,7 @@ def pytest_runtestloop(session: "Session") -> Optional[object]:
|
||||
If at any point ``session.shouldfail`` or ``session.shouldstop`` are set, the
|
||||
loop is terminated after the runtest protocol for the current item is finished.
|
||||
|
||||
:param session: The pytest session object.
|
||||
:param pytest.Session session: The pytest session object.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
The return value is not used, but only stops further processing.
|
||||
@@ -504,7 +468,7 @@ def pytest_runtest_logstart(
|
||||
|
||||
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
|
||||
|
||||
:param nodeid: Full node ID of the item.
|
||||
:param str nodeid: Full node ID of the item.
|
||||
:param location: A tuple of ``(filename, lineno, testname)``.
|
||||
"""
|
||||
|
||||
@@ -516,7 +480,7 @@ def pytest_runtest_logfinish(
|
||||
|
||||
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
|
||||
|
||||
:param nodeid: Full node ID of the item.
|
||||
:param str nodeid: Full node ID of the item.
|
||||
:param location: A tuple of ``(filename, lineno, testname)``.
|
||||
"""
|
||||
|
||||
@@ -528,9 +492,6 @@ def pytest_runtest_setup(item: "Item") -> None:
|
||||
parents (which haven't been setup yet). This includes obtaining the
|
||||
values of fixtures required by the item (which haven't been obtained
|
||||
yet).
|
||||
|
||||
:param item:
|
||||
The item.
|
||||
"""
|
||||
|
||||
|
||||
@@ -538,9 +499,6 @@ def pytest_runtest_call(item: "Item") -> None:
|
||||
"""Called to run the test for test item (the call phase).
|
||||
|
||||
The default implementation calls ``item.runtest()``.
|
||||
|
||||
:param item:
|
||||
The item.
|
||||
"""
|
||||
|
||||
|
||||
@@ -552,8 +510,6 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None:
|
||||
includes running the teardown phase of fixtures required by the item (if
|
||||
they go out of scope).
|
||||
|
||||
:param item:
|
||||
The item.
|
||||
:param nextitem:
|
||||
The scheduled-to-be-next test item (None if no further test item is
|
||||
scheduled). This argument is used to perform exact teardowns, i.e.
|
||||
@@ -571,7 +527,6 @@ def pytest_runtest_makereport(
|
||||
|
||||
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
|
||||
|
||||
:param item: The item.
|
||||
:param call: The :class:`~pytest.CallInfo` for the phase.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
@@ -592,11 +547,7 @@ def pytest_report_to_serializable(
|
||||
report: Union["CollectReport", "TestReport"],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Serialize the given report object into a data structure suitable for
|
||||
sending over the wire, e.g. converted to JSON.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param report: The report.
|
||||
"""
|
||||
sending over the wire, e.g. converted to JSON."""
|
||||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
@@ -605,10 +556,7 @@ def pytest_report_from_serializable(
|
||||
data: Dict[str, Any],
|
||||
) -> Optional[Union["CollectReport", "TestReport"]]:
|
||||
"""Restore a report object previously serialized with
|
||||
:hook:`pytest_report_to_serializable`.
|
||||
|
||||
:param config: The pytest config object.
|
||||
"""
|
||||
:hook:`pytest_report_to_serializable`."""
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -622,12 +570,7 @@ def pytest_fixture_setup(
|
||||
) -> Optional[object]:
|
||||
"""Perform fixture setup execution.
|
||||
|
||||
:param fixturdef:
|
||||
The fixture definition object.
|
||||
:param request:
|
||||
The fixture request object.
|
||||
:returns:
|
||||
The return value of the call to the fixture function.
|
||||
:returns: The return value of the call to the fixture function.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
|
||||
@@ -643,13 +586,7 @@ def pytest_fixture_post_finalizer(
|
||||
) -> None:
|
||||
"""Called after fixture teardown, but before the cache is cleared, so
|
||||
the fixture result ``fixturedef.cached_result`` is still available (not
|
||||
``None``).
|
||||
|
||||
:param fixturdef:
|
||||
The fixture definition object.
|
||||
:param request:
|
||||
The fixture request object.
|
||||
"""
|
||||
``None``)."""
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -661,7 +598,7 @@ def pytest_sessionstart(session: "Session") -> None:
|
||||
"""Called after the ``Session`` object has been created and before performing collection
|
||||
and entering the run test loop.
|
||||
|
||||
:param session: The pytest session object.
|
||||
:param pytest.Session session: The pytest session object.
|
||||
"""
|
||||
|
||||
|
||||
@@ -671,15 +608,15 @@ def pytest_sessionfinish(
|
||||
) -> None:
|
||||
"""Called after whole test run finished, right before returning the exit status to the system.
|
||||
|
||||
:param session: The pytest session object.
|
||||
:param exitstatus: The status which pytest will return to the system.
|
||||
:param pytest.Session session: The pytest session object.
|
||||
:param int exitstatus: The status which pytest will return to the system.
|
||||
"""
|
||||
|
||||
|
||||
def pytest_unconfigure(config: "Config") -> None:
|
||||
"""Called before test process is exited.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
"""
|
||||
|
||||
|
||||
@@ -698,10 +635,7 @@ def pytest_assertrepr_compare(
|
||||
*in* a string will be escaped. Note that all but the first line will
|
||||
be indented slightly, the intention is for the first line to be a summary.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param op: The operator, e.g. `"=="`, `"!="`, `"not in"`.
|
||||
:param left: The left operand.
|
||||
:param right: The right operand.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
"""
|
||||
|
||||
|
||||
@@ -726,10 +660,10 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
|
||||
You need to **clean the .pyc** files in your project directory and interpreter libraries
|
||||
when enabling this option, as assertions will require to be re-written.
|
||||
|
||||
:param item: pytest item object of current test.
|
||||
:param lineno: Line number of the assert statement.
|
||||
:param orig: String with the original assertion.
|
||||
:param expl: String with the assert explanation.
|
||||
:param pytest.Item item: pytest item object of current test.
|
||||
:param int lineno: Line number of the assert statement.
|
||||
:param str orig: String with the original assertion.
|
||||
:param str expl: String with the assert explanation.
|
||||
"""
|
||||
|
||||
|
||||
@@ -743,9 +677,9 @@ def pytest_report_header(
|
||||
) -> Union[str, List[str]]:
|
||||
"""Return a string or list of strings to be displayed as header info for terminal reporting.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param start_path: The starting dir.
|
||||
:param startdir: The starting dir (deprecated).
|
||||
:param pytest.Config config: The pytest config object.
|
||||
:param Path start_path: The starting dir.
|
||||
:param LEGACY_PATH startdir: The starting dir (deprecated).
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -780,9 +714,9 @@ def pytest_report_collectionfinish(
|
||||
|
||||
.. versionadded:: 3.2
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param start_path: The starting dir.
|
||||
:param startdir: The starting dir (deprecated).
|
||||
:param pytest.Config config: The pytest config object.
|
||||
:param Path start_path: The starting dir.
|
||||
:param LEGACY_PATH startdir: The starting dir (deprecated).
|
||||
:param items: List of pytest items that are going to be executed; this list should not be modified.
|
||||
|
||||
.. note::
|
||||
@@ -821,7 +755,6 @@ def pytest_report_teststatus(
|
||||
|
||||
:param report: The report object whose status is to be returned.
|
||||
:param config: The pytest config object.
|
||||
:returns: The test status.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`.
|
||||
"""
|
||||
@@ -834,9 +767,9 @@ def pytest_terminal_summary(
|
||||
) -> None:
|
||||
"""Add a section to terminal summary reporting.
|
||||
|
||||
:param terminalreporter: The internal terminal reporter object.
|
||||
:param exitstatus: The exit status that will be reported back to the OS.
|
||||
:param config: The pytest config object.
|
||||
:param _pytest.terminal.TerminalReporter terminalreporter: The internal terminal reporter object.
|
||||
:param int exitstatus: The exit status that will be reported back to the OS.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
|
||||
.. versionadded:: 4.2
|
||||
The ``config`` parameter.
|
||||
@@ -852,21 +785,21 @@ def pytest_warning_recorded(
|
||||
) -> None:
|
||||
"""Process a warning captured by the internal pytest warnings plugin.
|
||||
|
||||
:param warning_message:
|
||||
:param warnings.WarningMessage warning_message:
|
||||
The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
|
||||
the same attributes as the parameters of :py:func:`warnings.showwarning`.
|
||||
|
||||
:param when:
|
||||
:param str when:
|
||||
Indicates when the warning was captured. Possible values:
|
||||
|
||||
* ``"config"``: during pytest configuration/initialization stage.
|
||||
* ``"collect"``: during test collection.
|
||||
* ``"runtest"``: during test execution.
|
||||
|
||||
:param nodeid:
|
||||
:param str nodeid:
|
||||
Full id of the item.
|
||||
|
||||
:param location:
|
||||
:param tuple|None location:
|
||||
When available, holds information about the execution context of the captured
|
||||
warning (filename, linenumber, function). ``function`` evaluates to <module>
|
||||
when the execution context is at the module level.
|
||||
@@ -891,7 +824,7 @@ def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]:
|
||||
|
||||
.. versionadded:: 6.2
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
:returns: A dictionary of additional globals to add.
|
||||
"""
|
||||
|
||||
@@ -909,19 +842,13 @@ def pytest_internalerror(
|
||||
|
||||
Return True to suppress the fallback handling of printing an
|
||||
INTERNALERROR message directly to sys.stderr.
|
||||
|
||||
:param excrepr: The exception repr object.
|
||||
:param excinfo: The exception info.
|
||||
"""
|
||||
|
||||
|
||||
def pytest_keyboard_interrupt(
|
||||
excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]",
|
||||
) -> None:
|
||||
"""Called for keyboard interrupt.
|
||||
|
||||
:param excinfo: The exception info.
|
||||
"""
|
||||
"""Called for keyboard interrupt."""
|
||||
|
||||
|
||||
def pytest_exception_interact(
|
||||
@@ -940,13 +867,6 @@ def pytest_exception_interact(
|
||||
|
||||
This hook is not called if the exception that was raised is an internal
|
||||
exception like ``skip.Exception``.
|
||||
|
||||
:param node:
|
||||
The item or collector.
|
||||
:param call:
|
||||
The call information. Contains the exception.
|
||||
:param report:
|
||||
The collection or test report.
|
||||
"""
|
||||
|
||||
|
||||
@@ -956,8 +876,8 @@ def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None:
|
||||
Can be used by plugins to take special action just before the python
|
||||
debugger enters interactive mode.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param pdb: The Pdb instance.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
:param pdb.Pdb pdb: The Pdb instance.
|
||||
"""
|
||||
|
||||
|
||||
@@ -967,6 +887,6 @@ def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None:
|
||||
Can be used by plugins to take special action just after the python
|
||||
debugger leaves interactive mode.
|
||||
|
||||
:param config: The pytest config object.
|
||||
:param pdb: The Pdb instance.
|
||||
:param pytest.Config config: The pytest config object.
|
||||
:param pdb.Pdb pdb: The Pdb instance.
|
||||
"""
|
||||
|
||||
@@ -231,7 +231,7 @@ class _NodeReporter:
|
||||
msg = f'failed on teardown with "{reason}"'
|
||||
else:
|
||||
msg = f'failed on setup with "{reason}"'
|
||||
self._add_simple("error", bin_xml_escape(msg), str(report.longrepr))
|
||||
self._add_simple("error", msg, str(report.longrepr))
|
||||
|
||||
def append_skipped(self, report: TestReport) -> None:
|
||||
if hasattr(report, "wasxfail"):
|
||||
@@ -354,10 +354,7 @@ def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object]
|
||||
record_testsuite_property("ARCH", "PPC")
|
||||
record_testsuite_property("STORAGE_TYPE", "CEPH")
|
||||
|
||||
:param name:
|
||||
The property name.
|
||||
:param value:
|
||||
The property value. Will be converted to a string.
|
||||
``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped.
|
||||
|
||||
.. warning::
|
||||
|
||||
|
||||
+12
-13
@@ -37,8 +37,6 @@ from _pytest.terminal import TerminalReporter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
logging_StreamHandler = logging.StreamHandler[StringIO]
|
||||
|
||||
from typing_extensions import Literal
|
||||
else:
|
||||
logging_StreamHandler = logging.StreamHandler
|
||||
|
||||
@@ -384,19 +382,20 @@ class LogCaptureFixture:
|
||||
|
||||
@property
|
||||
def handler(self) -> LogCaptureHandler:
|
||||
"""Get the logging handler used by the fixture."""
|
||||
"""Get the logging handler used by the fixture.
|
||||
|
||||
:rtype: LogCaptureHandler
|
||||
"""
|
||||
return self._item.stash[caplog_handler_key]
|
||||
|
||||
def get_records(
|
||||
self, when: "Literal['setup', 'call', 'teardown']"
|
||||
) -> List[logging.LogRecord]:
|
||||
def get_records(self, when: str) -> List[logging.LogRecord]:
|
||||
"""Get the logging records for one of the possible test phases.
|
||||
|
||||
:param when:
|
||||
Which test phase to obtain the records from.
|
||||
Valid values are: "setup", "call" and "teardown".
|
||||
:param str when:
|
||||
Which test phase to obtain the records from. Valid values are: "setup", "call" and "teardown".
|
||||
|
||||
:returns: The list of captured records at the given stage.
|
||||
:rtype: List[logging.LogRecord]
|
||||
|
||||
.. versionadded:: 3.4
|
||||
"""
|
||||
@@ -453,8 +452,8 @@ class LogCaptureFixture:
|
||||
The levels of the loggers changed by this function will be
|
||||
restored to their initial values at the end of the test.
|
||||
|
||||
:param level: The level.
|
||||
:param logger: The logger to update. If not given, the root logger.
|
||||
:param int level: The level.
|
||||
:param str logger: The logger to update. If not given, the root logger.
|
||||
"""
|
||||
logger_obj = logging.getLogger(logger)
|
||||
# Save the original log-level to restore it during teardown.
|
||||
@@ -472,8 +471,8 @@ class LogCaptureFixture:
|
||||
the end of the 'with' statement the level is restored to its original
|
||||
value.
|
||||
|
||||
:param level: The level.
|
||||
:param logger: The logger to update. If not given, the root logger.
|
||||
:param int level: The level.
|
||||
:param str logger: The logger to update. If not given, the root logger.
|
||||
"""
|
||||
logger_obj = logging.getLogger(logger)
|
||||
orig_level = logger_obj.level
|
||||
|
||||
+3
-3
@@ -12,6 +12,7 @@ from typing import FrozenSet
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
@@ -24,7 +25,6 @@ import attr
|
||||
import _pytest._code
|
||||
from _pytest import nodes
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import overload
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import directory_arg
|
||||
from _pytest.config import ExitCode
|
||||
@@ -597,12 +597,12 @@ class Session(nodes.FSCollector):
|
||||
...
|
||||
|
||||
@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.
|
||||
|
||||
@@ -62,8 +62,8 @@ def param(
|
||||
assert eval(test_input) == expected
|
||||
|
||||
:param values: Variable args of the values of the parameter set, in order.
|
||||
:param marks: A single mark or a list of marks to be applied to this parameter set.
|
||||
:param id: The id to attribute to this parameter set.
|
||||
:keyword marks: A single mark or a list of marks to be applied to this parameter set.
|
||||
:keyword str id: The id to attribute to this parameter set.
|
||||
"""
|
||||
return ParameterSet.param(*values, marks=marks, id=id)
|
||||
|
||||
|
||||
@@ -126,12 +126,12 @@ class ParameterSet(NamedTuple):
|
||||
|
||||
@staticmethod
|
||||
def _parse_parametrize_args(
|
||||
argnames: Union[str, Sequence[str]],
|
||||
argnames: Union[str, List[str], Tuple[str, ...]],
|
||||
argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> Tuple[Sequence[str], bool]:
|
||||
if isinstance(argnames, str):
|
||||
) -> Tuple[Union[List[str], Tuple[str, ...]], bool]:
|
||||
if not isinstance(argnames, (tuple, list)):
|
||||
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
|
||||
force_tuple = len(argnames) == 1
|
||||
else:
|
||||
@@ -150,12 +150,12 @@ class ParameterSet(NamedTuple):
|
||||
@classmethod
|
||||
def _for_parametrize(
|
||||
cls,
|
||||
argnames: Union[str, Sequence[str]],
|
||||
argnames: Union[str, List[str], Tuple[str, ...]],
|
||||
argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
|
||||
func,
|
||||
config: Config,
|
||||
nodeid: str,
|
||||
) -> Tuple[Sequence[str], List["ParameterSet"]]:
|
||||
) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]:
|
||||
argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues)
|
||||
parameters = cls._parse_parametrize_parameters(argvalues, force_tuple)
|
||||
del argvalues
|
||||
@@ -355,35 +355,12 @@ class MarkDecorator:
|
||||
return self.with_args(*args, **kwargs)
|
||||
|
||||
|
||||
def get_unpacked_marks(
|
||||
obj: Union[object, type],
|
||||
*,
|
||||
consider_mro: bool = True,
|
||||
) -> List[Mark]:
|
||||
"""Obtain the unpacked marks that are stored on an object.
|
||||
|
||||
If obj is a class and consider_mro is true, return marks applied to
|
||||
this class and all of its super-classes in MRO order. If consider_mro
|
||||
is false, only return marks applied directly to this class.
|
||||
"""
|
||||
if isinstance(obj, type):
|
||||
if not consider_mro:
|
||||
mark_lists = [obj.__dict__.get("pytestmark", [])]
|
||||
else:
|
||||
mark_lists = [x.__dict__.get("pytestmark", []) for x in obj.__mro__]
|
||||
mark_list = []
|
||||
for item in mark_lists:
|
||||
if isinstance(item, list):
|
||||
mark_list.extend(item)
|
||||
else:
|
||||
mark_list.append(item)
|
||||
else:
|
||||
mark_attribute = getattr(obj, "pytestmark", [])
|
||||
if isinstance(mark_attribute, list):
|
||||
mark_list = mark_attribute
|
||||
else:
|
||||
mark_list = [mark_attribute]
|
||||
return list(normalize_mark_list(mark_list))
|
||||
def get_unpacked_marks(obj: object) -> Iterable[Mark]:
|
||||
"""Obtain the unpacked marks that are stored on an object."""
|
||||
mark_list = getattr(obj, "pytestmark", [])
|
||||
if not isinstance(mark_list, list):
|
||||
mark_list = [mark_list]
|
||||
return normalize_mark_list(mark_list)
|
||||
|
||||
|
||||
def normalize_mark_list(
|
||||
@@ -411,7 +388,7 @@ def store_mark(obj, mark: Mark) -> None:
|
||||
assert isinstance(mark, Mark), mark
|
||||
# Always reassign name to avoid updating pytestmark in a reference that
|
||||
# was only borrowed.
|
||||
obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark]
|
||||
obj.pytestmark = [*get_unpacked_marks(obj), mark]
|
||||
|
||||
|
||||
# Typing for builtin pytest marks. This is cheating; it gives builtin marks
|
||||
@@ -457,7 +434,7 @@ if TYPE_CHECKING:
|
||||
class _ParametrizeMarkDecorator(MarkDecorator):
|
||||
def __call__( # type: ignore[override]
|
||||
self,
|
||||
argnames: Union[str, Sequence[str]],
|
||||
argnames: Union[str, List[str], Tuple[str, ...]],
|
||||
argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
|
||||
*,
|
||||
indirect: Union[bool, Sequence[str]] = ...,
|
||||
|
||||
+25
-58
@@ -29,26 +29,21 @@ V = TypeVar("V")
|
||||
def monkeypatch() -> Generator["MonkeyPatch", None, None]:
|
||||
"""A convenient fixture for monkey-patching.
|
||||
|
||||
The fixture provides these methods to modify objects, dictionaries, or
|
||||
:data:`os.environ`:
|
||||
The fixture provides these methods to modify objects, dictionaries or
|
||||
os.environ::
|
||||
|
||||
* :meth:`monkeypatch.setattr(obj, name, value, raising=True) <pytest.MonkeyPatch.setattr>`
|
||||
* :meth:`monkeypatch.delattr(obj, name, raising=True) <pytest.MonkeyPatch.delattr>`
|
||||
* :meth:`monkeypatch.setitem(mapping, name, value) <pytest.MonkeyPatch.setitem>`
|
||||
* :meth:`monkeypatch.delitem(obj, name, raising=True) <pytest.MonkeyPatch.delitem>`
|
||||
* :meth:`monkeypatch.setenv(name, value, prepend=None) <pytest.MonkeyPatch.setenv>`
|
||||
* :meth:`monkeypatch.delenv(name, raising=True) <pytest.MonkeyPatch.delenv>`
|
||||
* :meth:`monkeypatch.syspath_prepend(path) <pytest.MonkeyPatch.syspath_prepend>`
|
||||
* :meth:`monkeypatch.chdir(path) <pytest.MonkeyPatch.chdir>`
|
||||
* :meth:`monkeypatch.context() <pytest.MonkeyPatch.context>`
|
||||
monkeypatch.setattr(obj, name, value, raising=True)
|
||||
monkeypatch.delattr(obj, name, raising=True)
|
||||
monkeypatch.setitem(mapping, name, value)
|
||||
monkeypatch.delitem(obj, name, raising=True)
|
||||
monkeypatch.setenv(name, value, prepend=None)
|
||||
monkeypatch.delenv(name, raising=True)
|
||||
monkeypatch.syspath_prepend(path)
|
||||
monkeypatch.chdir(path)
|
||||
|
||||
All modifications will be undone after the requesting test function or
|
||||
fixture has finished. The ``raising`` parameter determines if a :class:`KeyError`
|
||||
or :class:`AttributeError` will be raised if the set/deletion operation does not have the
|
||||
specified target.
|
||||
|
||||
To undo modifications done by the fixture in a contained scope,
|
||||
use :meth:`context() <pytest.MonkeyPatch.context>`.
|
||||
fixture has finished. The ``raising`` parameter determines if a KeyError
|
||||
or AttributeError will be raised if the set/deletion operation has no target.
|
||||
"""
|
||||
mpatch = MonkeyPatch()
|
||||
yield mpatch
|
||||
@@ -120,7 +115,7 @@ class MonkeyPatch:
|
||||
|
||||
Returned by the :fixture:`monkeypatch` fixture.
|
||||
|
||||
.. versionchanged:: 6.2
|
||||
:versionchanged:: 6.2
|
||||
Can now also be used directly as `pytest.MonkeyPatch()`, for when
|
||||
the fixture is not available. In this case, use
|
||||
:meth:`with MonkeyPatch.context() as mp: <context>` or remember to call
|
||||
@@ -187,40 +182,16 @@ class MonkeyPatch:
|
||||
value: object = notset,
|
||||
raising: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
Set attribute value on target, memorizing the old value.
|
||||
"""Set attribute value on target, memorizing the old value.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os
|
||||
|
||||
monkeypatch.setattr(os, "getcwd", lambda: "/")
|
||||
|
||||
The code above replaces the :func:`os.getcwd` function by a ``lambda`` which
|
||||
always returns ``"/"``.
|
||||
|
||||
For convenience, you can specify a string as ``target`` which
|
||||
For convenience you can specify a string as ``target`` which
|
||||
will be interpreted as a dotted import path, with the last part
|
||||
being the attribute name:
|
||||
being the attribute name. For example,
|
||||
``monkeypatch.setattr("os.getcwd", lambda: "/")``
|
||||
would set the ``getcwd`` function of the ``os`` module.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
monkeypatch.setattr("os.getcwd", lambda: "/")
|
||||
|
||||
Raises :class:`AttributeError` if the attribute does not exist, unless
|
||||
Raises AttributeError if the attribute does not exist, unless
|
||||
``raising`` is set to False.
|
||||
|
||||
**Where to patch**
|
||||
|
||||
``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one.
|
||||
There can be many names pointing to any individual object, so for patching to work you must ensure
|
||||
that you patch the name used by the system under test.
|
||||
|
||||
See the section :ref:`Where to patch <python:where-to-patch>` in the :mod:`unittest.mock`
|
||||
docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but
|
||||
applies to ``monkeypatch.setattr`` as well.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
import inspect
|
||||
@@ -367,8 +338,7 @@ class MonkeyPatch:
|
||||
def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None:
|
||||
"""Change the current working directory to the specified path.
|
||||
|
||||
:param path:
|
||||
The path to change into.
|
||||
Path can be a string or a path object.
|
||||
"""
|
||||
if self._cwd is None:
|
||||
self._cwd = os.getcwd()
|
||||
@@ -383,14 +353,11 @@ class MonkeyPatch:
|
||||
There is generally no need to call `undo()`, since it is
|
||||
called automatically during tear-down.
|
||||
|
||||
.. note::
|
||||
The same `monkeypatch` fixture is used across a
|
||||
single test function invocation. If `monkeypatch` is used both by
|
||||
the test function itself and one of the test fixtures,
|
||||
calling `undo()` will undo all of the changes made in
|
||||
both functions.
|
||||
|
||||
Prefer to use :meth:`context() <pytest.MonkeyPatch.context>` instead.
|
||||
Note that the same `monkeypatch` fixture is used across a
|
||||
single test function invocation. If `monkeypatch` is used both by
|
||||
the test function itself and one of the test fixtures,
|
||||
calling `undo()` will undo all of the changes made in
|
||||
both functions.
|
||||
"""
|
||||
for obj, name, value in reversed(self._setattr):
|
||||
if value is not notset:
|
||||
|
||||
+8
-17
@@ -193,7 +193,7 @@ class Node(metaclass=NodeMeta):
|
||||
nodeid: Optional[str] = None,
|
||||
) -> None:
|
||||
#: A unique name within the scope of the parent node.
|
||||
self.name: str = name
|
||||
self.name = name
|
||||
|
||||
#: The parent collector node.
|
||||
self.parent = parent
|
||||
@@ -208,7 +208,7 @@ class Node(metaclass=NodeMeta):
|
||||
|
||||
if session:
|
||||
#: The pytest session this node is part of.
|
||||
self.session: Session = session
|
||||
self.session = session
|
||||
else:
|
||||
if not parent:
|
||||
raise TypeError("session or parent must be provided")
|
||||
@@ -239,7 +239,9 @@ class Node(metaclass=NodeMeta):
|
||||
|
||||
#: A place where plugins can store information on the node for their
|
||||
#: own use.
|
||||
self.stash: Stash = Stash()
|
||||
#:
|
||||
#: :type: Stash
|
||||
self.stash = Stash()
|
||||
# Deprecated alias. Was never public. Can be removed in a few releases.
|
||||
self._store = self.stash
|
||||
|
||||
@@ -324,10 +326,7 @@ class Node(metaclass=NodeMeta):
|
||||
|
||||
def listchain(self) -> List["Node"]:
|
||||
"""Return list of all parent collectors up to self, starting from
|
||||
the root of collection tree.
|
||||
|
||||
:returns: The nodes.
|
||||
"""
|
||||
the root of collection tree."""
|
||||
chain = []
|
||||
item: Optional[Node] = self
|
||||
while item is not None:
|
||||
@@ -341,8 +340,6 @@ class Node(metaclass=NodeMeta):
|
||||
) -> None:
|
||||
"""Dynamically add a marker object to the node.
|
||||
|
||||
:param marker:
|
||||
The marker.
|
||||
:param append:
|
||||
Whether to append the marker, or prepend it.
|
||||
"""
|
||||
@@ -364,7 +361,6 @@ class Node(metaclass=NodeMeta):
|
||||
"""Iterate over all markers of the node.
|
||||
|
||||
:param name: If given, filter the results by the name attribute.
|
||||
:returns: An iterator of the markers of the node.
|
||||
"""
|
||||
return (x[1] for x in self.iter_markers_with_node(name=name))
|
||||
|
||||
@@ -411,8 +407,7 @@ class Node(metaclass=NodeMeta):
|
||||
return [x.name for x in self.listchain()]
|
||||
|
||||
def addfinalizer(self, fin: Callable[[], object]) -> None:
|
||||
"""Register a function to be called without arguments when this node is
|
||||
finalized.
|
||||
"""Register a function to be called when this node is finalized.
|
||||
|
||||
This method can only be called when this node is active
|
||||
in a setup chain, for example during self.setup().
|
||||
@@ -421,11 +416,7 @@ class Node(metaclass=NodeMeta):
|
||||
|
||||
def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]:
|
||||
"""Get the next parent node (including self) which is an instance of
|
||||
the given class.
|
||||
|
||||
:param cls: The node class to search for.
|
||||
:returns: The node, if found.
|
||||
"""
|
||||
the given class."""
|
||||
current: Optional[Node] = self
|
||||
while current and not isinstance(current, cls):
|
||||
current = current.parent
|
||||
|
||||
+3
-11
@@ -1,8 +1,5 @@
|
||||
"""Run testsuites written for nose."""
|
||||
import warnings
|
||||
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.deprecated import NOSE_SUPPORT
|
||||
from _pytest.fixtures import getfixturemarker
|
||||
from _pytest.nodes import Item
|
||||
from _pytest.python import Function
|
||||
@@ -21,8 +18,8 @@ def pytest_runtest_setup(item: Item) -> None:
|
||||
# see https://github.com/python/mypy/issues/2608
|
||||
func = item
|
||||
|
||||
call_optional(func.obj, "setup", func.nodeid)
|
||||
func.addfinalizer(lambda: call_optional(func.obj, "teardown", func.nodeid))
|
||||
call_optional(func.obj, "setup")
|
||||
func.addfinalizer(lambda: call_optional(func.obj, "teardown"))
|
||||
|
||||
# NOTE: Module- and class-level fixtures are handled in python.py
|
||||
# with `pluginmanager.has_plugin("nose")` checks.
|
||||
@@ -30,7 +27,7 @@ def pytest_runtest_setup(item: Item) -> None:
|
||||
# it's not straightforward.
|
||||
|
||||
|
||||
def call_optional(obj: object, name: str, nodeid: str) -> bool:
|
||||
def call_optional(obj: object, name: str) -> bool:
|
||||
method = getattr(obj, name, None)
|
||||
if method is None:
|
||||
return False
|
||||
@@ -39,11 +36,6 @@ def call_optional(obj: object, name: str, nodeid: str) -> bool:
|
||||
return False
|
||||
if not callable(method):
|
||||
return False
|
||||
# Warn about deprecation of this plugin.
|
||||
method_name = getattr(method, "__name__", str(method))
|
||||
warnings.warn(
|
||||
NOSE_SUPPORT.format(nodeid=nodeid, method=method_name, stage=name), stacklevel=2
|
||||
)
|
||||
# If there are any problems allow the exception to raise rather than
|
||||
# silently ignoring it.
|
||||
method()
|
||||
|
||||
@@ -241,9 +241,6 @@ def xfail(reason: str = "") -> NoReturn:
|
||||
|
||||
This function should be called only during testing (setup, call or teardown).
|
||||
|
||||
:param reason:
|
||||
The message to show the user as reason for the xfail.
|
||||
|
||||
.. note::
|
||||
It is better to use the :ref:`pytest.mark.xfail ref` marker when
|
||||
possible to declare a test to be xfailed under certain conditions
|
||||
@@ -259,12 +256,12 @@ def importorskip(
|
||||
"""Import and return the requested module ``modname``, or skip the
|
||||
current test if the module cannot be imported.
|
||||
|
||||
:param modname:
|
||||
:param str modname:
|
||||
The name of the module to import.
|
||||
:param minversion:
|
||||
:param str minversion:
|
||||
If given, the imported module's ``__version__`` attribute must be at
|
||||
least this minimal version, otherwise the test is still skipped.
|
||||
:param reason:
|
||||
:param str reason:
|
||||
If given, this reason is shown as the message when the module cannot
|
||||
be imported.
|
||||
|
||||
|
||||
+38
-75
@@ -661,7 +661,17 @@ class Pytester:
|
||||
against expected output, perfect for black-box testing of pytest plugins.
|
||||
|
||||
It attempts to isolate the test run from external factors as much as possible, modifying
|
||||
the current working directory to :attr:`path` and environment variables during initialization.
|
||||
the current working directory to ``path`` and environment variables during initialization.
|
||||
|
||||
Attributes:
|
||||
|
||||
:ivar Path path: temporary directory path used to create files/run tests from, etc.
|
||||
|
||||
:ivar plugins:
|
||||
A list of plugins to use with :py:meth:`parseconfig` and
|
||||
:py:meth:`runpytest`. Initially this is an empty list but plugins can
|
||||
be added to the list. The type of items to add to the list depends on
|
||||
the method using them so refer to them for details.
|
||||
"""
|
||||
|
||||
__test__ = False
|
||||
@@ -690,10 +700,6 @@ class Pytester:
|
||||
name = request.node.name
|
||||
self._name = name
|
||||
self._path: Path = tmp_path_factory.mktemp(name, numbered=True)
|
||||
#: A list of plugins to use with :py:meth:`parseconfig` and
|
||||
#: :py:meth:`runpytest`. Initially this is an empty list but plugins can
|
||||
#: be added to the list. The type of items to add to the list depends on
|
||||
#: the method using them so refer to them for details.
|
||||
self.plugins: List[Union[str, _PluggyPlugin]] = []
|
||||
self._cwd_snapshot = CwdSnapshot()
|
||||
self._sys_path_snapshot = SysPathsSnapshot()
|
||||
@@ -718,7 +724,7 @@ class Pytester:
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
"""Temporary directory path used to create files/run tests from, etc."""
|
||||
"""Temporary directory where files are created and pytest is executed."""
|
||||
return self._path
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -749,7 +755,7 @@ class Pytester:
|
||||
return SysModulesSnapshot(preserve=preserve_module)
|
||||
|
||||
def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder:
|
||||
"""Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`."""
|
||||
"""Create a new :py:class:`HookRecorder` for a PluginManager."""
|
||||
pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True)
|
||||
self._request.addfinalizer(reprec.finish_recording)
|
||||
return reprec
|
||||
@@ -798,7 +804,7 @@ class Pytester:
|
||||
def makefile(self, ext: str, *args: str, **kwargs: str) -> Path:
|
||||
r"""Create new text file(s) in the test directory.
|
||||
|
||||
:param ext:
|
||||
:param str ext:
|
||||
The extension the file(s) should use, including the dot, e.g. `.py`.
|
||||
:param args:
|
||||
All args are treated as strings and joined using newlines.
|
||||
@@ -807,8 +813,6 @@ class Pytester:
|
||||
:param kwargs:
|
||||
Each keyword is the name of a file, while the value of it will
|
||||
be written as contents of the file.
|
||||
:returns:
|
||||
The first created file.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -828,19 +832,11 @@ class Pytester:
|
||||
return self._makefile(ext, args, kwargs)
|
||||
|
||||
def makeconftest(self, source: str) -> Path:
|
||||
"""Write a contest.py file.
|
||||
|
||||
:param source: The contents.
|
||||
:returns: The conftest.py file.
|
||||
"""
|
||||
"""Write a conftest.py file with 'source' as contents."""
|
||||
return self.makepyfile(conftest=source)
|
||||
|
||||
def makeini(self, source: str) -> Path:
|
||||
"""Write a tox.ini file.
|
||||
|
||||
:param source: The contents.
|
||||
:returns: The tox.ini file.
|
||||
"""
|
||||
"""Write a tox.ini file with 'source' as contents."""
|
||||
return self.makefile(".ini", tox=source)
|
||||
|
||||
def getinicfg(self, source: str) -> SectionWrapper:
|
||||
@@ -849,10 +845,7 @@ class Pytester:
|
||||
return IniConfig(str(p))["pytest"]
|
||||
|
||||
def makepyprojecttoml(self, source: str) -> Path:
|
||||
"""Write a pyproject.toml file.
|
||||
|
||||
:param source: The contents.
|
||||
:returns: The pyproject.ini file.
|
||||
"""Write a pyproject.toml file with 'source' as contents.
|
||||
|
||||
.. versionadded:: 6.0
|
||||
"""
|
||||
@@ -905,9 +898,6 @@ class Pytester:
|
||||
|
||||
This is undone automatically when this object dies at the end of each
|
||||
test.
|
||||
|
||||
:param path:
|
||||
The path.
|
||||
"""
|
||||
if path is None:
|
||||
path = self.path
|
||||
@@ -915,13 +905,7 @@ class Pytester:
|
||||
self._monkeypatch.syspath_prepend(str(path))
|
||||
|
||||
def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path:
|
||||
"""Create a new (sub)directory.
|
||||
|
||||
:param name:
|
||||
The name of the directory, relative to the pytester path.
|
||||
:returns:
|
||||
The created directory.
|
||||
"""
|
||||
"""Create a new (sub)directory."""
|
||||
p = self.path / name
|
||||
p.mkdir()
|
||||
return p
|
||||
@@ -940,15 +924,14 @@ class Pytester:
|
||||
def copy_example(self, name: Optional[str] = None) -> Path:
|
||||
"""Copy file from project's directory into the testdir.
|
||||
|
||||
:param name:
|
||||
The name of the file to copy.
|
||||
:return:
|
||||
Path to the copied directory (inside ``self.path``).
|
||||
:param str name: The name of the file to copy.
|
||||
:return: path to the copied directory (inside ``self.path``).
|
||||
|
||||
"""
|
||||
example_dir_ = self._request.config.getini("pytester_example_dir")
|
||||
if example_dir_ is None:
|
||||
example_dir = self._request.config.getini("pytester_example_dir")
|
||||
if example_dir is None:
|
||||
raise ValueError("pytester_example_dir is unset, can't copy examples")
|
||||
example_dir: Path = self._request.config.rootpath / example_dir_
|
||||
example_dir = self._request.config.rootpath / example_dir
|
||||
|
||||
for extra_element in self._request.node.iter_markers("pytester_example_path"):
|
||||
assert extra_element.args
|
||||
@@ -984,16 +967,14 @@ class Pytester:
|
||||
|
||||
def getnode(
|
||||
self, config: Config, arg: Union[str, "os.PathLike[str]"]
|
||||
) -> Union[Collector, Item]:
|
||||
"""Get the collection node of a file.
|
||||
) -> Optional[Union[Collector, Item]]:
|
||||
"""Return the collection node of a file.
|
||||
|
||||
:param config:
|
||||
:param pytest.Config config:
|
||||
A pytest config.
|
||||
See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it.
|
||||
:param arg:
|
||||
:param os.PathLike[str] arg:
|
||||
Path to the file.
|
||||
:returns:
|
||||
The node.
|
||||
"""
|
||||
session = Session.from_config(config)
|
||||
assert "::" not in str(arg)
|
||||
@@ -1003,18 +984,13 @@ class Pytester:
|
||||
config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
|
||||
return res
|
||||
|
||||
def getpathnode(
|
||||
self, path: Union[str, "os.PathLike[str]"]
|
||||
) -> Union[Collector, Item]:
|
||||
def getpathnode(self, path: Union[str, "os.PathLike[str]"]):
|
||||
"""Return the collection node of a file.
|
||||
|
||||
This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
|
||||
create the (configured) pytest Config instance.
|
||||
|
||||
:param path:
|
||||
Path to the file.
|
||||
:returns:
|
||||
The node.
|
||||
:param os.PathLike[str] path: Path to the file.
|
||||
"""
|
||||
path = Path(path)
|
||||
config = self.parseconfigure(path)
|
||||
@@ -1030,11 +1006,6 @@ class Pytester:
|
||||
|
||||
This recurses into the collection node and returns a list of all the
|
||||
test items contained within.
|
||||
|
||||
:param colitems:
|
||||
The collection nodes.
|
||||
:returns:
|
||||
The collected items.
|
||||
"""
|
||||
session = colitems[0].session
|
||||
result: List[Item] = []
|
||||
@@ -1221,16 +1192,15 @@ class Pytester:
|
||||
return new_args
|
||||
|
||||
def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config:
|
||||
"""Return a new pytest :class:`pytest.Config` instance from given
|
||||
commandline args.
|
||||
"""Return a new pytest Config instance from given commandline args.
|
||||
|
||||
This invokes the pytest bootstrapping code in _pytest.config to create a
|
||||
new :py:class:`pytest.PytestPluginManager` and call the
|
||||
:hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config`
|
||||
instance.
|
||||
This invokes the pytest bootstrapping code in _pytest.config to create
|
||||
a new :py:class:`_pytest.core.PluginManager` and call the
|
||||
pytest_cmdline_parse hook to create a new
|
||||
:py:class:`pytest.Config` instance.
|
||||
|
||||
If :attr:`plugins` has been populated they should be plugin modules
|
||||
to be registered with the plugin manager.
|
||||
If :py:attr:`plugins` has been populated they should be plugin modules
|
||||
to be registered with the PluginManager.
|
||||
"""
|
||||
import _pytest.config
|
||||
|
||||
@@ -1248,8 +1218,7 @@ class Pytester:
|
||||
"""Return a new pytest configured Config instance.
|
||||
|
||||
Returns a new :py:class:`pytest.Config` instance like
|
||||
:py:meth:`parseconfig`, but also calls the :hook:`pytest_configure`
|
||||
hook.
|
||||
:py:meth:`parseconfig`, but also calls the pytest_configure hook.
|
||||
"""
|
||||
config = self.parseconfig(*args)
|
||||
config._do_configure()
|
||||
@@ -1268,8 +1237,6 @@ class Pytester:
|
||||
The module source.
|
||||
:param funcname:
|
||||
The name of the test function for which to return a test item.
|
||||
:returns:
|
||||
The test item.
|
||||
"""
|
||||
items = self.getitems(source)
|
||||
for item in items:
|
||||
@@ -1410,8 +1377,6 @@ class Pytester:
|
||||
- Otherwise, it is passed through to :py:class:`subprocess.Popen`.
|
||||
For further information in this case, consult the document of the
|
||||
``stdin`` parameter in :py:class:`subprocess.Popen`.
|
||||
:returns:
|
||||
The result.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
|
||||
@@ -1498,8 +1463,6 @@ class Pytester:
|
||||
:param timeout:
|
||||
The period in seconds after which to timeout and raise
|
||||
:py:class:`Pytester.TimeoutExpired`.
|
||||
:returns:
|
||||
The result.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700)
|
||||
|
||||
+2
-21
@@ -59,7 +59,6 @@ from _pytest.config.argparsing import Parser
|
||||
from _pytest.deprecated import check_ispytest
|
||||
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
|
||||
from _pytest.deprecated import INSTANCE_COLLECTOR
|
||||
from _pytest.deprecated import NOSE_SUPPORT_METHOD
|
||||
from _pytest.fixtures import FuncFixtureInfo
|
||||
from _pytest.main import Session
|
||||
from _pytest.mark import MARK_GEN
|
||||
@@ -873,23 +872,19 @@ class Class(PyCollector):
|
||||
"""Inject a hidden autouse, function scoped fixture into the collected class object
|
||||
that invokes setup_method/teardown_method if either or both are available.
|
||||
|
||||
Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with
|
||||
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
|
||||
other fixtures (#517).
|
||||
"""
|
||||
has_nose = self.config.pluginmanager.has_plugin("nose")
|
||||
setup_name = "setup_method"
|
||||
setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
|
||||
emit_nose_setup_warning = False
|
||||
if setup_method is None and has_nose:
|
||||
setup_name = "setup"
|
||||
emit_nose_setup_warning = True
|
||||
setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
|
||||
teardown_name = "teardown_method"
|
||||
teardown_method = getattr(self.obj, teardown_name, None)
|
||||
emit_nose_teardown_warning = False
|
||||
if teardown_method is None and has_nose:
|
||||
teardown_name = "teardown"
|
||||
emit_nose_teardown_warning = True
|
||||
teardown_method = getattr(self.obj, teardown_name, None)
|
||||
if setup_method is None and teardown_method is None:
|
||||
return
|
||||
@@ -905,24 +900,10 @@ class Class(PyCollector):
|
||||
if setup_method is not None:
|
||||
func = getattr(self, setup_name)
|
||||
_call_with_optional_argument(func, method)
|
||||
if emit_nose_setup_warning:
|
||||
warnings.warn(
|
||||
NOSE_SUPPORT_METHOD.format(
|
||||
nodeid=request.node.nodeid, method="setup"
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
yield
|
||||
if teardown_method is not None:
|
||||
func = getattr(self, teardown_name)
|
||||
_call_with_optional_argument(func, method)
|
||||
if emit_nose_teardown_warning:
|
||||
warnings.warn(
|
||||
NOSE_SUPPORT_METHOD.format(
|
||||
nodeid=request.node.nodeid, method="teardown"
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
self.obj.__pytest_setup_method = xunit_setup_method_fixture
|
||||
|
||||
@@ -1226,7 +1207,7 @@ class Metafunc:
|
||||
|
||||
def parametrize(
|
||||
self,
|
||||
argnames: Union[str, Sequence[str]],
|
||||
argnames: Union[str, List[str], Tuple[str, ...]],
|
||||
argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
|
||||
indirect: Union[bool, Sequence[str]] = False,
|
||||
ids: Optional[
|
||||
|
||||
+10
-13
@@ -12,6 +12,7 @@ from typing import Generic
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
@@ -27,7 +28,6 @@ if TYPE_CHECKING:
|
||||
import _pytest._code
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.compat import overload
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
||||
@@ -521,7 +521,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
|
||||
"""Assert that two numbers (or two ordered sequences of numbers) are equal to each other
|
||||
within some tolerance.
|
||||
|
||||
Due to the :doc:`python:tutorial/floatingpoint`, numbers that we
|
||||
Due to the :std:doc:`tutorial/floatingpoint`, numbers that we
|
||||
would intuitively expect to be equal are not always so::
|
||||
|
||||
>>> 0.1 + 0.2 == 0.3
|
||||
@@ -786,7 +786,7 @@ def raises(
|
||||
|
||||
|
||||
@overload
|
||||
def raises( # noqa: F811
|
||||
def raises(
|
||||
expected_exception: Union[Type[E], Tuple[Type[E], ...]],
|
||||
func: Callable[..., Any],
|
||||
*args: Any,
|
||||
@@ -795,21 +795,18 @@ 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.
|
||||
r"""Assert that a code block/function call raises ``expected_exception``
|
||||
or raise a failure exception otherwise.
|
||||
|
||||
:param typing.Type[E] | typing.Tuple[typing.Type[E], ...] expected_exception:
|
||||
The excpected exception type, or a tuple if one of multiple possible
|
||||
exception types are excepted.
|
||||
:kwparam str | typing.Pattern[str] | None match:
|
||||
:kwparam match:
|
||||
If specified, a string containing a regular expression,
|
||||
or a regular expression object, that is tested against the string
|
||||
representation of the exception using :func:`re.search`.
|
||||
|
||||
To match a literal string that may contain :ref:`special characters
|
||||
<re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
|
||||
representation of the exception using :py:func:`re.search`. To match a literal
|
||||
string that may contain :std:ref:`special characters <re-syntax>`, the pattern can
|
||||
first be escaped with :py:func:`re.escape`.
|
||||
|
||||
(This is only used when :py:func:`pytest.raises` is used as a context manager,
|
||||
and passed through to the function otherwise.
|
||||
|
||||
+14
-24
@@ -9,6 +9,7 @@ from typing import Generator
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Pattern
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
@@ -16,7 +17,6 @@ from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import overload
|
||||
from _pytest.deprecated import check_ispytest
|
||||
from _pytest.deprecated import WARNS_NONE_ARG
|
||||
from _pytest.fixtures import fixture
|
||||
@@ -30,7 +30,7 @@ T = TypeVar("T")
|
||||
def recwarn() -> Generator["WarningsRecorder", None, None]:
|
||||
"""Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
|
||||
|
||||
See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information
|
||||
See https://docs.python.org/library/how-to/capture-warnings.html for information
|
||||
on warning categories.
|
||||
"""
|
||||
wrec = WarningsRecorder(_ispytest=True)
|
||||
@@ -47,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``.
|
||||
@@ -95,7 +93,7 @@ def warns(
|
||||
|
||||
|
||||
@overload
|
||||
def warns( # noqa: F811
|
||||
def warns(
|
||||
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]],
|
||||
func: Callable[..., T],
|
||||
*args: Any,
|
||||
@@ -104,7 +102,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,
|
||||
@@ -112,15 +110,15 @@ def warns( # noqa: F811
|
||||
) -> Union["WarningsChecker", Any]:
|
||||
r"""Assert that code raises a particular class of warning.
|
||||
|
||||
Specifically, the parameter ``expected_warning`` can be a warning class or sequence
|
||||
of warning classes, and the code inside the ``with`` block must issue at least one
|
||||
warning of that class or classes.
|
||||
Specifically, the parameter ``expected_warning`` can be a warning class or
|
||||
sequence of warning classes, and the code inside the ``with`` block must issue a warning of that class or
|
||||
classes.
|
||||
|
||||
This helper produces a list of :class:`warnings.WarningMessage` objects, one for
|
||||
each warning raised (regardless of whether it is an ``expected_warning`` or not).
|
||||
This helper produces a list of :class:`warnings.WarningMessage` objects,
|
||||
one for each warning raised.
|
||||
|
||||
This function can be used as a context manager, which will capture all the raised
|
||||
warnings inside it::
|
||||
This function can be used as a context manager, or any of the other ways
|
||||
:func:`pytest.raises` can be used::
|
||||
|
||||
>>> import pytest
|
||||
>>> with pytest.warns(RuntimeWarning):
|
||||
@@ -141,14 +139,6 @@ def warns( # noqa: F811
|
||||
...
|
||||
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
|
||||
|
||||
**Using with** ``pytest.mark.parametrize``
|
||||
|
||||
When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests
|
||||
such that some runs raise a warning and others do not.
|
||||
|
||||
This could be achieved in the same way as with exceptions, see
|
||||
:ref:`parametrizing_conditional_raising` for an example.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
if not args:
|
||||
@@ -167,7 +157,7 @@ def warns( # noqa: F811
|
||||
return func(*args[1:], **kwargs)
|
||||
|
||||
|
||||
class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
||||
class WarningsRecorder(warnings.catch_warnings):
|
||||
"""A context manager to record raised warnings.
|
||||
|
||||
Each recorded warning is an instance of :class:`warnings.WarningMessage`.
|
||||
|
||||
@@ -276,7 +276,7 @@ class TestReport(BaseReport):
|
||||
|
||||
#: A name -> value dictionary containing all keywords and
|
||||
#: markers associated with a test invocation.
|
||||
self.keywords: Mapping[str, Any] = keywords
|
||||
self.keywords = keywords
|
||||
|
||||
#: Test outcome, always one of "passed", "failed", "skipped".
|
||||
self.outcome = outcome
|
||||
@@ -298,7 +298,7 @@ class TestReport(BaseReport):
|
||||
self.sections = list(sections)
|
||||
|
||||
#: Time it took to run just the test.
|
||||
self.duration: float = duration
|
||||
self.duration = duration
|
||||
|
||||
self.__dict__.update(extra)
|
||||
|
||||
@@ -309,11 +309,7 @@ class TestReport(BaseReport):
|
||||
|
||||
@classmethod
|
||||
def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport":
|
||||
"""Create and fill a TestReport with standard item and call info.
|
||||
|
||||
:param item: The item.
|
||||
:param call: The call info.
|
||||
"""
|
||||
"""Create and fill a TestReport with standard item and call info."""
|
||||
when = call.when
|
||||
# Remove "collect" from the Literal type -- only for collection calls.
|
||||
assert when != "collect"
|
||||
|
||||
@@ -178,12 +178,6 @@ def pytest_addoption(parser: Parser) -> None:
|
||||
default=False,
|
||||
help="Show locals in tracebacks (disabled by default)",
|
||||
)
|
||||
group._addoption(
|
||||
"--no-showlocals",
|
||||
action="store_false",
|
||||
dest="showlocals",
|
||||
help="Hide locals in tracebacks (negate --showlocals passed through addopts)",
|
||||
)
|
||||
group._addoption(
|
||||
"--tb",
|
||||
metavar="style",
|
||||
|
||||
@@ -100,11 +100,7 @@ class TempPathFactory:
|
||||
return p
|
||||
|
||||
def getbasetemp(self) -> Path:
|
||||
"""Return the base temporary directory, creating it if needed.
|
||||
|
||||
:returns:
|
||||
The base temporary directory.
|
||||
"""
|
||||
"""Return the base temporary directory, creating it if needed."""
|
||||
if self._basetemp is not None:
|
||||
return self._basetemp
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import inspect
|
||||
import warnings
|
||||
from types import FunctionType
|
||||
from typing import Any
|
||||
from typing import Generic
|
||||
from typing import Type
|
||||
@@ -51,13 +48,15 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
|
||||
__module__ = "pytest"
|
||||
|
||||
|
||||
@final
|
||||
class PytestRemovedIn8Warning(PytestDeprecationWarning):
|
||||
"""Warning class for features that will be removed in pytest 8."""
|
||||
|
||||
__module__ = "pytest"
|
||||
|
||||
|
||||
class PytestReturnNotNoneWarning(PytestRemovedIn8Warning):
|
||||
@final
|
||||
class PytestReturnNotNoneWarning(PytestDeprecationWarning):
|
||||
"""Warning emitted when a test function is returning value other than None."""
|
||||
|
||||
__module__ = "pytest"
|
||||
@@ -83,7 +82,7 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
|
||||
|
||||
|
||||
@final
|
||||
class PytestUnhandledCoroutineWarning(PytestReturnNotNoneWarning):
|
||||
class PytestUnhandledCoroutineWarning(PytestWarning):
|
||||
"""Warning emitted for an unhandled coroutine.
|
||||
|
||||
A coroutine was encountered when collecting test functions, but was not
|
||||
@@ -144,28 +143,3 @@ class UnformattedWarning(Generic[_W]):
|
||||
def format(self, **kwargs: Any) -> _W:
|
||||
"""Return an instance of the warning category, formatted with given kwargs."""
|
||||
return self.category(self.template.format(**kwargs))
|
||||
|
||||
|
||||
def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None:
|
||||
"""
|
||||
Issue the warning :param:`message` for the definition of the given :param:`method`
|
||||
|
||||
this helps to log warnigns for functions defined prior to finding an issue with them
|
||||
(like hook wrappers being marked in a legacy mechanism)
|
||||
"""
|
||||
lineno = method.__code__.co_firstlineno
|
||||
filename = inspect.getfile(method)
|
||||
module = method.__module__
|
||||
mod_globals = method.__globals__
|
||||
try:
|
||||
warnings.warn_explicit(
|
||||
message,
|
||||
type(message),
|
||||
filename=filename,
|
||||
module=module,
|
||||
registry=mod_globals.setdefault("__warningregistry__", {}),
|
||||
lineno=lineno,
|
||||
)
|
||||
except Warning as w:
|
||||
# If warnings are errors (e.g. -Werror), location information gets lost, so we add it to the message.
|
||||
raise type(w)(f"{w}\n at {filename}:{lineno}") from None
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# shim for pylib going away
|
||||
# if pylib is installed this file will get skipped
|
||||
# (`py/__init__.py` has higher precedence)
|
||||
import sys
|
||||
|
||||
import _pytest._py.error as error
|
||||
import _pytest._py.path as path
|
||||
|
||||
sys.modules["py.error"] = error
|
||||
sys.modules["py.path"] = path
|
||||
@@ -18,7 +18,6 @@ from _pytest.config import UsageError
|
||||
from _pytest.config.argparsing import OptionGroup
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.debugging import pytestPDB as __pytestPDB
|
||||
from _pytest.doctest import DoctestItem
|
||||
from _pytest.fixtures import fixture
|
||||
from _pytest.fixtures import FixtureLookupError
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
@@ -93,7 +92,6 @@ __all__ = [
|
||||
"Config",
|
||||
"console_main",
|
||||
"deprecated_call",
|
||||
"DoctestItem",
|
||||
"exit",
|
||||
"ExceptionInfo",
|
||||
"ExitCode",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1470,90 +1470,3 @@ def test_no_recursion_index_on_recursion_error():
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
RecursionDepthError().trigger
|
||||
assert "maximum recursion" in str(excinfo.getrepr())
|
||||
|
||||
|
||||
def _exceptiongroup_common(
|
||||
pytester: Pytester,
|
||||
outer_chain: str,
|
||||
inner_chain: str,
|
||||
native: bool,
|
||||
) -> None:
|
||||
pre_raise = "exceptiongroup." if not native else ""
|
||||
pre_catch = pre_raise if sys.version_info < (3, 11) else ""
|
||||
filestr = f"""
|
||||
{"import exceptiongroup" if not native else ""}
|
||||
import pytest
|
||||
|
||||
def f(): raise ValueError("From f()")
|
||||
def g(): raise BaseException("From g()")
|
||||
|
||||
def inner(inner_chain):
|
||||
excs = []
|
||||
for callback in [f, g]:
|
||||
try:
|
||||
callback()
|
||||
except BaseException as err:
|
||||
excs.append(err)
|
||||
if excs:
|
||||
if inner_chain == "none":
|
||||
raise {pre_raise}BaseExceptionGroup("Oops", excs)
|
||||
try:
|
||||
raise SyntaxError()
|
||||
except SyntaxError as e:
|
||||
if inner_chain == "from":
|
||||
raise {pre_raise}BaseExceptionGroup("Oops", excs) from e
|
||||
else:
|
||||
raise {pre_raise}BaseExceptionGroup("Oops", excs)
|
||||
|
||||
def outer(outer_chain, inner_chain):
|
||||
try:
|
||||
inner(inner_chain)
|
||||
except {pre_catch}BaseExceptionGroup as e:
|
||||
if outer_chain == "none":
|
||||
raise
|
||||
if outer_chain == "from":
|
||||
raise IndexError() from e
|
||||
else:
|
||||
raise IndexError()
|
||||
|
||||
|
||||
def test():
|
||||
outer("{outer_chain}", "{inner_chain}")
|
||||
"""
|
||||
pytester.makepyfile(test_excgroup=filestr)
|
||||
result = pytester.runpytest()
|
||||
match_lines = []
|
||||
if inner_chain in ("another", "from"):
|
||||
match_lines.append(r"SyntaxError: <no detail available>")
|
||||
|
||||
match_lines += [
|
||||
r" + Exception Group Traceback (most recent call last):",
|
||||
rf" \| {pre_catch}BaseExceptionGroup: Oops \(2 sub-exceptions\)",
|
||||
r" \| ValueError: From f\(\)",
|
||||
r" \| BaseException: From g\(\)",
|
||||
r"=* short test summary info =*",
|
||||
]
|
||||
if outer_chain in ("another", "from"):
|
||||
match_lines.append(r"FAILED test_excgroup.py::test - IndexError")
|
||||
else:
|
||||
match_lines.append(
|
||||
rf"FAILED test_excgroup.py::test - {pre_catch}BaseExceptionGroup: Oops \(2.*"
|
||||
)
|
||||
result.stdout.re_match_lines(match_lines)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented"
|
||||
)
|
||||
@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
|
||||
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
|
||||
def test_native_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
|
||||
_exceptiongroup_common(pytester, outer_chain, inner_chain, native=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
|
||||
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
|
||||
def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
|
||||
# with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it
|
||||
pytest.importorskip("exceptiongroup")
|
||||
_exceptiongroup_common(pytester, outer_chain, inner_chain, native=False)
|
||||
|
||||
@@ -20,54 +20,6 @@ def test_external_plugins_integrated(pytester: Pytester, plugin) -> None:
|
||||
pytester.parseconfig("-p", plugin)
|
||||
|
||||
|
||||
def test_hookspec_via_function_attributes_are_deprecated():
|
||||
from _pytest.config import PytestPluginManager
|
||||
|
||||
pm = PytestPluginManager()
|
||||
|
||||
class DeprecatedHookMarkerSpec:
|
||||
def pytest_bad_hook(self):
|
||||
pass
|
||||
|
||||
pytest_bad_hook.historic = False # type: ignore[attr-defined]
|
||||
|
||||
with pytest.warns(
|
||||
PytestDeprecationWarning,
|
||||
match=r"Please use the pytest\.hookspec\(historic=False\) decorator",
|
||||
) as recorder:
|
||||
pm.add_hookspecs(DeprecatedHookMarkerSpec)
|
||||
(record,) = recorder
|
||||
assert (
|
||||
record.lineno
|
||||
== DeprecatedHookMarkerSpec.pytest_bad_hook.__code__.co_firstlineno
|
||||
)
|
||||
assert record.filename == __file__
|
||||
|
||||
|
||||
def test_hookimpl_via_function_attributes_are_deprecated():
|
||||
from _pytest.config import PytestPluginManager
|
||||
|
||||
pm = PytestPluginManager()
|
||||
|
||||
class DeprecatedMarkImplPlugin:
|
||||
def pytest_runtest_call(self):
|
||||
pass
|
||||
|
||||
pytest_runtest_call.tryfirst = True # type: ignore[attr-defined]
|
||||
|
||||
with pytest.warns(
|
||||
PytestDeprecationWarning,
|
||||
match=r"Please use the pytest.hookimpl\(tryfirst=True\)",
|
||||
) as recorder:
|
||||
pm.register(DeprecatedMarkImplPlugin())
|
||||
(record,) = recorder
|
||||
assert (
|
||||
record.lineno
|
||||
== DeprecatedMarkImplPlugin.pytest_runtest_call.__code__.co_firstlineno
|
||||
)
|
||||
assert record.filename == __file__
|
||||
|
||||
|
||||
def test_fscollector_gethookproxy_isinitpath(pytester: Pytester) -> None:
|
||||
module = pytester.getmodulecol(
|
||||
"""
|
||||
@@ -279,62 +231,3 @@ def test_importing_instance_is_deprecated(pytester: Pytester) -> None:
|
||||
match=re.escape("The pytest.Instance collector type is deprecated"),
|
||||
):
|
||||
from _pytest.python import Instance # noqa: F401
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_nose_deprecated_with_setup(pytester: Pytester) -> None:
|
||||
pytest.importorskip("nose")
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
from nose.tools import with_setup
|
||||
|
||||
def setup_fn_no_op():
|
||||
...
|
||||
|
||||
def teardown_fn_no_op():
|
||||
...
|
||||
|
||||
@with_setup(setup_fn_no_op, teardown_fn_no_op)
|
||||
def test_omits_warnings():
|
||||
...
|
||||
"""
|
||||
)
|
||||
output = pytester.runpytest()
|
||||
message = [
|
||||
"*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
|
||||
"*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)",
|
||||
"*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
|
||||
"*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `teardown_fn_no_op` (teardown)",
|
||||
]
|
||||
output.stdout.fnmatch_lines(message)
|
||||
output.assert_outcomes(passed=1)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_nose_deprecated_setup_teardown(pytester: Pytester) -> None:
|
||||
pytest.importorskip("nose")
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
class Test:
|
||||
|
||||
def setup(self):
|
||||
...
|
||||
|
||||
def teardown(self):
|
||||
...
|
||||
|
||||
def test(self):
|
||||
...
|
||||
"""
|
||||
)
|
||||
output = pytester.runpytest()
|
||||
message = [
|
||||
"*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
|
||||
"*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `setup(self)`",
|
||||
"*To remove this warning, rename it to `setup_method(self)`",
|
||||
"*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
|
||||
"*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `teardown(self)`",
|
||||
"*To remove this warning, rename it to `teardown_method(self)`",
|
||||
]
|
||||
output.stdout.fnmatch_lines(message)
|
||||
output.assert_outcomes(passed=1)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
anyio[curio,trio]==3.6.1
|
||||
django==4.1.2
|
||||
pytest-asyncio==0.19.0
|
||||
django==4.0.6
|
||||
pytest-asyncio==0.18.3
|
||||
pytest-bdd==6.0.1
|
||||
pytest-cov==4.0.0
|
||||
pytest-cov==3.0.0
|
||||
pytest-django==4.5.2
|
||||
pytest-flakes==4.0.5
|
||||
pytest-html==3.1.1
|
||||
pytest-mock==3.10.0
|
||||
pytest-mock==3.8.2
|
||||
pytest-rerunfailures==10.2
|
||||
pytest-sugar==0.9.5
|
||||
pytest-trio==0.7.0
|
||||
pytest-twisted==1.14.0
|
||||
twisted==22.8.0
|
||||
pytest-twisted==1.13.4
|
||||
twisted==22.4.0
|
||||
pytest-xvfb==2.0.0
|
||||
|
||||
@@ -776,24 +776,6 @@ class TestAssert_reprcompare:
|
||||
msg = "\n".join(expl)
|
||||
assert msg
|
||||
|
||||
def test_nfc_nfd_same_string(self) -> None:
|
||||
# issue 3426
|
||||
left = "hyv\xe4"
|
||||
right = "hyva\u0308"
|
||||
expl = callequal(left, right)
|
||||
assert expl == [
|
||||
r"'hyv\xe4' == 'hyva\u0308'",
|
||||
f"- {str(right)}",
|
||||
f"+ {str(left)}",
|
||||
]
|
||||
|
||||
expl = callequal(left, right, verbose=2)
|
||||
assert expl == [
|
||||
r"'hyv\xe4' == 'hyva\u0308'",
|
||||
f"- {str(right)}",
|
||||
f"+ {str(left)}",
|
||||
]
|
||||
|
||||
|
||||
class TestAssert_reprcompare_dataclass:
|
||||
def test_dataclasses(self, pytester: Pytester) -> None:
|
||||
|
||||
@@ -897,15 +897,6 @@ def test_dontreadfrominput() -> None:
|
||||
iter_f = iter(f)
|
||||
pytest.raises(OSError, next, iter_f)
|
||||
pytest.raises(UnsupportedOperation, f.fileno)
|
||||
pytest.raises(UnsupportedOperation, f.flush)
|
||||
assert not f.readable()
|
||||
pytest.raises(UnsupportedOperation, f.seek, 0)
|
||||
assert not f.seekable()
|
||||
pytest.raises(UnsupportedOperation, f.tell)
|
||||
pytest.raises(UnsupportedOperation, f.truncate, 0)
|
||||
pytest.raises(UnsupportedOperation, f.write, b"")
|
||||
pytest.raises(UnsupportedOperation, f.writelines, [])
|
||||
assert not f.writable()
|
||||
f.close() # just for completeness
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user